feat(tariff): V2 — legacy-parity pricing (time-of-day, category, seasonal, flat)
Bring the legacy ParkSQL2017 pricing BREADTH onto our engine while keeping
integer-minor-unit money + immutable signed versions (rejecting legacy's
float money / mutable rows). TariffStructure becomes a discriminated union:
V1 = the original bare ladder (UNCHANGED, verbatim algorithm, golden-
regression-tested against the live version); V2 = {version:2, tz, shared
knobs, defaultCard, windowedCards[]} where each card is flat OR a block
ladder and may be scoped by wall-clock hour window / day-of-week / date
range / vehicle category.
computeFeeV2 prices by stepping one increment at a time, advancing the
ladder by ELAPSED minutes (continuous) while selecting the active card by
WALL-CLOCK time in the version's FROZEN tz. Decisions: tz is a per-site
setting (site_config.timezone, default Europe/Tirane) stamped server-side
into each version on publish — never the host clock (reproducibility);
default-card cap governs a mixed day; precedence = specificity
(date>dow>hour) -> priority -> name (total, order-independent), validation
rejects ambiguous ties; category = a card FIELD, frozen in the signed
vehicle_entry payload (site_config.default_vehicle_category default), read
at both pricing call-sites.
Composer: default card front-and-centre (flat/ladder toggle), tiers under
an "Advanced" disclosure; emits BARE V1 when no tiers (back-compat). DB:
migrations 0005 (timezone) + 0006 (default_vehicle_category). Stood up
vitest in @parking/shared (was zero tests on the ledger-feeding fee fn);
36 tests incl. golden V1 regression, happy-hour/overnight/dow/flat/category/
cap edges, precedence shuffle-invariance, Europe/Tirane DST determinism,
validation matrix — all green. No event-chain change.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -217,15 +217,17 @@ Unlike the event log, tariff data is **mutable master data** in the sense that n
|
||||
on the network — [[offline-first]]), a base currency, and a rounding policy. Deferred to
|
||||
[[open-questions]].
|
||||
|
||||
## Extensions under design
|
||||
## Extensions
|
||||
|
||||
Two operator asks extend this engine; both have design pages (not yet built), grounded in
|
||||
[[parksql2017-legacy-schema|the legacy schema]] + external research:
|
||||
Grounded in [[parksql2017-legacy-schema|the legacy schema]] + external research:
|
||||
|
||||
- **Time-of-day / weekday / seasonal tiers** (happy hour, off-peak, weekend, vehicle category) —
|
||||
see [[tariff-time-tiers]]. Chosen shape: **time-windowed rate cards** selected by wall-clock window,
|
||||
layered additively on this structure (a bare default card = today's behaviour). The hard part is
|
||||
slicing a stay at window boundaries while keeping the block ladder + daily cap continuous.
|
||||
- **Time-of-day / weekday / seasonal tiers + vehicle category + flat rate** — **BUILT 2026-06-18**
|
||||
as the **V2 tariff** (the "V2" arm of `TariffStructure`). A `defaultCard` plus optional windowed
|
||||
cards selected by wall-clock window / day-of-week / date / category, each flat or laddered; a stay
|
||||
is sliced at window boundaries while the block ladder + daily cap stay continuous (elapsed-
|
||||
continuous). A bare V1 structure (no `defaultCard`) is unchanged. The wall-clock tz is **frozen in
|
||||
the version** (from site config) for reproducibility. Full as-built decisions in
|
||||
[[tariff-time-tiers]].
|
||||
- **Validation & sponsorship** (merchant comps, coupons, **postpaid B2B** "enter/exit free, bill the
|
||||
business monthly") — see [[validation-sponsorship]]. A validation is a **typed modifier applied as a
|
||||
signed event** on a transient session, distinct from a [[subscription]]; postpaid sponsors accrue a
|
||||
|
||||
Reference in New Issue
Block a user