ADR-011: Input Rule Snapshotting on PriceMatrix β
Status: β Approved β 2026-04-10 Origin: Kalkulations-Engine ProtocolImpacts:
schema-backoffice.mdΒ§price_matrices,PRODUCT_domain-model.mdΒ§PriceMatrix Data Structure
Context β
The PriceMatrix already snapshots margin_config and resolves prices into variants[] JSONB, making each version self-contained for cost and pricing output. However, the input rules that drove variant generation β demographic pricing rules, season tiers, early-bird discounts β had no representation on the PriceMatrix.
This creates an audit gap: an operator or auditor examining a PriceMatrix from 6 months ago can see what prices the system generated. They cannot determine which rules produced them if the TourTemplate's configuration has since changed. The immutable append versioning guarantees output stability, but without input snapshots, the derivation chain breaks.
Current versioning coverage (before this ADR) β
| Data | Versioned? | Location |
|---|---|---|
| Margin rules | β | PriceMatrix.margin_config |
| Resolved prices | β | PriceMatrix.variants[] |
| Variable cost per variant | β | PriceVariant.variable_cost_snapshot |
| PricingRules (demographics) | β | Only on TourTemplate (mutable) |
| Season/early-bird config | β | Only on TourTemplate (mutable) |
| Cost base | β | CostingSheet via FK |
Decision β
1. Add Two Snapshot Columns to price_matrices β
| Column | Type | Constraints | Description |
|---|---|---|---|
pricing_rules_snapshot | JSONB | NOT NULL | Deep copy of PricingRule[] active at generation time. Immutable. |
pricing_config_snapshot | JSONB | NOT NULL | Deep copy of PricingConfig (room surcharge, accommodation flag, season tiers, early-bird tiers) active at generation time. Immutable. |
2. Snapshot Semantics β
- Deep copy at generation time. The engine serializes the TourTemplate's current
pricing_rulesandpricing_configinto these columns when creating a new PriceMatrix version. - Immutable after creation. These columns never change. If the operator changes rules on the TourTemplate, the engine generates a new PriceMatrix version with fresh snapshots β the existing version's snapshots remain untouched.
NOT NULLwith empty-array default. A PriceMatrix with no configured pricing rules stores[], notnull. This ensures consistent querying and prevents null-check ambiguity.
3. Self-Containment Guarantee β
After this change, every PriceMatrix version contains its complete derivation context:
| Data | Versioned? | Location |
|---|---|---|
| Margin rules | β | PriceMatrix.margin_config |
| Resolved prices | β | PriceMatrix.variants[] |
| Variable cost per variant | β | PriceVariant.variable_cost_snapshot |
| PricingRules (demographics) | β | PriceMatrix.pricing_rules_snapshot |
| Season/early-bird config | β | PriceMatrix.pricing_config_snapshot |
| Cost base | β | CostingSheet via FK |
An auditor can fully reconstruct the pricing derivation chain from a single PriceMatrix row β no joins to mutable template data required.
Consequences β
Positive:
- Complete audit trail: input rules + output prices co-locate as immutable data
- Enables historical comparison: "Why did v3 cost more than v2?" is answerable by diffing snapshots
- GoBD alignment: all data needed for Nachvollziehbarkeit (traceability) is self-contained
- Eliminates stale-rule confusion when an operator updates the TourTemplate
Negative:
- JSONB storage overhead (~1β3 KB per matrix version, bounded by the number of rules)
- Schema migration required: two new
NOT NULLcolumns with a default of'[]'::jsonb
Neutral:
- No API surface change β snapshots are internal audit data, not exposed to Commerce