Busflow Docs

Internal documentation portal

Skip to content

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) ​

DataVersioned?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 ​

ColumnTypeConstraintsDescription
pricing_rules_snapshotJSONBNOT NULLDeep copy of PricingRule[] active at generation time. Immutable.
pricing_config_snapshotJSONBNOT NULLDeep 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_rules and pricing_config into 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 NULL with empty-array default. A PriceMatrix with no configured pricing rules stores [], not null. This ensures consistent querying and prevents null-check ambiguity.

3. Self-Containment Guarantee ​

After this change, every PriceMatrix version contains its complete derivation context:

DataVersioned?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 NULL columns with a default of '[]'::jsonb

Neutral:

  • No API surface change β€” snapshots are internal audit data, not exposed to Commerce

Internal documentation β€” Busflow