feat(tariff): whole-window package pricing mode (packageMinor)
A windowed card can now charge ONE total for any presence in its window —
the real night rate ("20:00–07:00 = 400, leave earlier and it's still
400"), which the per-increment flatMinor could not express (park-buzi's
"night 400" card billed 400/HOUR). Engine charges once per contiguous run
of increments the card wins, tracked across rolling-day segments so a
night crossing the 24h boundary charges once; out-of-window increments
price by the base card as usual.
Operator decisions (2026-07-05): per-occurrence repeat (two nights = two
charges), any-touch-pays-full, windowed cards only (a base "price per
day" is a 1-row up-to table). Validator: mutually exclusive with
flat/blocks/steps, no per-card cap, forbidden on the defaultCard.
flatMinor docs clarified as PER INCREMENT. 6 new engine tests.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
type: concept
|
||||
tags: [parking, domain, business, pricing, design]
|
||||
sources: [parksql2017-legacy-schema]
|
||||
updated: 2026-06-18
|
||||
updated: 2026-07-05
|
||||
status: settled
|
||||
---
|
||||
|
||||
@@ -140,7 +140,22 @@ case stays one rate card; tiers are opt-in.
|
||||
`DEFAULT_VEHICLE_CATEGORY` in `@parking/shared`). Per-relay capture (a "bus lane") is the future
|
||||
seam, mirroring per-relay direction.
|
||||
- **Flat rate** is a first-class card body (`flatMinor`, mutually exclusive with `blocks`). A flat V1
|
||||
is published as a single open-ended block (V1 has no flat field).
|
||||
is published as a single open-ended block (V1 has no flat field). ⚠ `flatMinor` is **per billing
|
||||
increment** (an hourly flat rate at increment 60) — NOT a whole-stay/whole-window price. This was
|
||||
misread in the field (park-buzi published a "night 400" believing it covered the night; it billed
|
||||
400/h, 2026-07-05) — the UI now labels it "Flat price / hour" and the whole-window need got its own
|
||||
mode:
|
||||
- **WINDOW PACKAGE (`packageMinor`, 2026-07-05 — windowed cards only).** "Any presence in this
|
||||
window = ONE total" (the real night rate: 20:00–07:00 = 400, leave earlier and it's still 400).
|
||||
Decisions (operator, 2026-07-05): charged **once per occurrence** (a stay touching two nights pays
|
||||
twice); **any touch pays full** (an 06:30 arrival before the 07:00 close pays the whole package —
|
||||
package pricing's accepted sharp edge); **not offered on the base card** (a base "one price per
|
||||
day" is a 1-row up-to table — no duplicate concept). Engine: one charge per **contiguous run of
|
||||
increments the card wins**, tracked across rolling-day segments so a night crossing the 24h
|
||||
boundary charges once; out-of-window increments price by the base rate as usual; the charge lands
|
||||
in the day segment where the occurrence starts (that day's default-card cap applies). Mutually
|
||||
exclusive with flat/blocks/steps + no per-card cap (the package IS the window's total); validator
|
||||
enforces both and the composer offers the mode only on tier cards.
|
||||
- **UI** (`TariffComposer.tsx`): default card **front-and-centre** (flat/ladder toggle + cap); tiers
|
||||
under a collapsed **"Advanced: time & seasonal tiers"** disclosure (window builder — dow checkboxes,
|
||||
optional date range, optional hour range with an overnight hint; category; priority; flat/ladder
|
||||
|
||||
Reference in New Issue
Block a user