feat(tariff): stepped ("up-to") pricing mode — total-by-duration

Owners often state rates as a total-by-duration matrix (0-1h=200, 0-3h=500,
0-6h=800, 0-9h=900, 0-12h=1000) that the marginal hourly ladder can't express
(the ladder sums per-increment rates; this is cumulative totals at thresholds).
Add STEPPED as a third pricing mode alongside the ladder and flat.

- @parking/shared: TariffStep {uptoMin, totalMinor} + a `steps[]` field on V1
  structures and V2 cards (mutually exclusive with blocks/flatMinor). steppedFee():
  smallest tier with uptoMin >= duration wins (INCLUSIVE boundary), the top tier
  repeats as a per-day cap; wired into computeFeeV1 + computeFeeV2 (V2 default card
  only — a whole-stay total can't be sliced per-increment by a windowed card).
  Validation: ascending uptoMin, non-negative totals, no daily-cap-with-steps,
  steps-only-on-default. priceSession/quote/booth/Lab price it via the shared core.
- Composer UI: a "By duration (up-to)" mode with an up-to/total table (base card
  only). i18n modeStepped/steppedHint/stepUpTo/stepTotal/addStep (sq+en).
- 8 new unit tests incl. the exact owner matrix, multi-day repeat, overstay, and
  validation (53 pass). Verified end-to-end via the UI: authored + published the
  matrix, Tariff Lab prices it exactly (3h->500, 6h->800, 12h->1000, 2d->2000).

Wiki: tariff (three pricing modes + stepped semantics), log.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-20 12:33:27 +02:00
parent 3d02134711
commit cc507f490f
8 changed files with 391 additions and 23 deletions
+18
View File
@@ -1022,3 +1022,21 @@ incl. the ticket-1245791632490 overstay-not-zero regression (40 tests pass). Ver
live via the real UI: a 3h stay → ALL 3,000, curve shows the daily cap flattening at
6h and multi-day stepping; ticket-load returned a real session. Build+lint green.
Updated [[tariff]] + [[booth-exit-flow]].
## [2026-06-20] feat | Stepped ("up-to") tariff mode — total-by-duration pricing
The owner needed a total-by-duration matrix (0-1h=200, 0-3h=500, 0-6h=800, 0-9h=900,
0-12h=1000) the marginal hourly ladder CANNOT express (ladder sums per-increment
rates; this is cumulative totals at thresholds). Added STEPPED pricing as a third
mode alongside ladder + flat. New TariffStep{uptoMin,totalMinor} + steps[] on V1
structures and V2 cards (mutually exclusive w/ blocks/flatMinor). Engine steppedFee():
smallest tier with uptoMin>=duration wins (INCLUSIVE <=), top tier repeats as per-day
cap; wired into computeFeeV1 + computeFeeV2 (V2 defaultCard only — a whole-stay total
can't be sliced by a windowed card). Validation: ascending uptoMin, non-neg totals, no
dailyCap-with-steps, steps-only-on-default. priceSession/quote/booth/Lab all price it
via the shared core (no extra wiring). Composer UI: "By duration (up-to)" radio +
up-to/total table (base card only); i18n modeStepped/steppedHint/stepUpTo/stepTotal/
addStep (sq+en). 8 new unit tests incl. the exact owner matrix + multi-day + overstay
+ validation (53 pass). VERIFIED end-to-end via the real UI: authored the matrix in the
composer, published, Tariff Lab priced it exactly (3h->500, 6h->800, 12h->1000,
2d->2000). Build+lint green. Updated [[tariff]].