feat(reports): occupancy curve, hour×dow heatmap, stay histogram, fraud KPIs

The dashboard had generic BI views but nothing parking-shaped. Added:

- Occupancy step-area over the range with the configured capacity as a
  red reference line. occupancyStart folds the ENTIRE prior ledger
  (voided entries excluded, clamped ≥0); each series point carries
  occupancyEnd. Answers "when are we near full".
- Entries heatmap hour × day-of-week (7×24, row 0 = Monday, site tz) as
  a pure CSS-grid intensity map — weekday-vs-weekend at a glance, the
  direct evidence for tariff windows. Replaces the flat hour histogram
  (strictly contains it).
- Stay-duration histogram at tariff-shaped edges (30m/1h/2h/4h/8h/24h/
  tail): where ladder/up-to breakpoints should sit.
- Voids + anomalies KPIs (accented when >0) — the look-closer counters
  the signed chain exists for; peak-occupancy KPI (peak / capacity).
- Revenue bars stacked cash vs card (the drawer's money vs the bank's);
  CSV export gains cash, card, occupancy_end columns.

Internals: localParts caches its Intl formatter per tz (was one new
formatter per ledger row); @parking/db re-exports lt/gt. 5 new tests.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-07-06 12:35:54 +02:00
parent a2e102f3dd
commit 7ef332999e
9 changed files with 334 additions and 32 deletions
+25 -1
View File
@@ -2,7 +2,7 @@
type: concept
tags: [parking, domain, business, reporting]
sources: []
updated: 2026-06-22
updated: 2026-07-05
status: open
---
@@ -73,6 +73,30 @@ disputes ("I was charged for a car that left earlier"), lost-ticket lookup, and
- **Export** for [[reconciliation]] / accounting (CSV/PDF) — the periodic external-authority path
([[open-questions]] #4).
## As-built additions (2026-07-05) — the parking-shaped graphics
Operator ask: "check /reports for improvements and meaningful graphics." The dashboard (Recharts,
ledger-first aggregation in `apps/server/src/reports.ts`) gained the three views that are
parking-specific rather than generic BI, plus fraud counters:
- **Occupancy curve** — cars-inside step-area over the range with the configured capacity as a
red reference line. `occupancyStart` folds the ENTIRE prior ledger (voided entries excluded,
clamped ≥0); each series point carries `occupancyEnd`. The at-a-glance answer to "when are we
near full" — the input for capacity and dynamic-window decisions.
- **Entries heatmap (hour × day-of-week)** — 7×24 matrix (`entriesByDowHour`, row 0 = Monday,
site-tz), rendered as a pure CSS-grid amber-intensity map. Shows weekday-vs-weekend and
morning/evening patterns — the direct evidence for tariff windows (night rates, weekend cards,
early-bird — see [[tariff-industry-survey]]). Replaces the flat entries-by-hour bar (strictly
contains it).
- **Stay-duration histogram** — closed sessions bucketed at 30m/1h/2h/4h/8h/24h/tail
(`stayHistogram`): where the ladder/up-to breakpoints should sit.
- **Look-closer counters** — voids + anomalies in range as KPI cards (accented when >0): a spike
is exactly what the signed chain exists to surface (the operator is the threat model).
- Revenue bars now **stack cash vs card** per bucket (`cashMinor`/`cardMinor` on each point —
the drawer's money vs the bank's); peak-occupancy KPI (`peak / capacity`); CSV export gained
cash, card, occupancy_end columns. `localParts` now caches its Intl formatter per tz (was one
`new Intl.DateTimeFormat` per ledger row).
## Open
- Which reports matter at launch vs. later; the export format/cadence.