feat/demand-det
needs attentionviewing older commitfef1896 · fullPR #341reviewed 2026-07-23 04:27 UTC4H · 6M · 6L · 4I- Purpose
- Demand-based anomaly detector suite for GDMTH tariff contracts
- Goal
- D1 (punta_management) + D2b (overnight_load_factor) using per-period TOU demand from bill historicData
- Sub-goals
- D1 — buildings that can shed punta-window demand (own best month = floor) but do so inconsistently
- D2b — GDMTH contracts with overnight base-period utilization far above same-tariff+region cohort median
- New SQL: fetchPerPeriodDemandForOrg pulling tipKw/baseKw from historicData LATERAL JSONB + baseKwh by conceptId
- Validated live on Planet Fitness (627 rows, 28 clubs, 100% per-period coverage)
- What
- fef1896a on feat/peer-bench: adds detectPuntaManagement, detectPerPeriodLoadFactor, PerPeriodDemandMonth type, fetchPerPeriodDemandForOrg, 9 unit tests. Full stack (PRs 1-4): consumption-series entity + clean kWh series + peer-benchmark + D1/D2b.
- Why
- Demand-based signals complement consumption-based detectors. Per-period historicData from bills is richer than the aggregate series.
- Areas
- domains/utility/src/finding+793−198domains/utility/src/consumption-series+855−0docs/energia+183−0
- Blast
- 13 files, +2561/-376 across stack. Pure read-path; no schema migrations. Draft.
Findings · 20
correctness4
ESTIMACION NULL-in-list silently drops real bills from D1/D2b
domains/utility/src/finding/finding.queries.ts:725
WHERE base.estimacion IN ('0', '5') evaluates NULL as FALSE. Bills without ESTIMACION concept excluded from D1/D2b but included in D3 via clean series. Fix: add OR base.estimacion IS NULL.
puntaKwh=0 emitted for winter bills with zero-value punta concept
domains/utility/src/consumption-series/consumption-series.queries.ts:83
D1/D2b dedupeKey changes monthly — accumulating rather than updating findings
D2b/D3 cohort min=15 within org makes them dormant for most customers in v1
security3
Hardcoded bill-concept UUID for baseKwh bypasses catalog join
domains/utility/src/finding/finding.queries.ts:699
11ef27af-74ba-4fa2-bef9-885928b066b5 not referenced elsewhere. Silent null if UUID drifts.
Internal billId UUID exposed in InvalidBillPeriodError struct
findByPublicId and findByContract unscoped in FCIS namespace
conventions5
throw in insert() violates no-throw rule; shell has no try-catch
domains/utility/src/finding/finding.queries.ts:358
Introduced in 281e2807 (peer-benchmark PR). Shell has no try-catch; rejected Promise propagates out. Fix: return Promise<Result<Finding, FindingError>>.
consumption-series entity missing type-check.ts
domains/utility/src/consumption-series/
toNumberOrNull duplicated across two query files
BillFactRow, PerPeriodDemandMonth, PeerBenchmarkContractMeta belong in finding.type.ts
Shell read-outside-transaction is documented intentional FCIS deviation
tests3
Warning-severity path untested for D1, D2b, and D3
All 3 detectors only test critical path. Add warning-range tests: $50k-$400k D1, 1.3x-1.5x D2b, +30pp-+50pp D3.
No integration tests for 3 new SQL queries
fetchConsumptionBillInputsForOrg, fetchPerPeriodDemandForOrg, fetchContractRegionsForOrg - complex raw SQL with CTEs and LATERAL joins.
Overlap hole 2 and unparseable date error path untested
domains/utility/src/consumption-series/__tests__/consumption-series.decisions.test.ts
Holes 1 and 3 tested; hole 2 (recency tie-break) missing. Unparseable date (parseYmdUtc null) path also not covered.
improvement5
3 near-identical groupBy helpers will compound with each new detector
domains/utility/src/finding/finding.decisions.ts
Trustworthy-month filter duplicated between YoY-spike and peer-benchmark
domains/utility/src/finding/finding.decisions.ts
computeMedian redundant — percentile(sorted, 0.5) already exists
domains/utility/src/finding/finding.decisions.ts
Org-scoping CTE copy-pasted across 3 SQL queries
powerFactor! then redundant null filter in detectFpRegimeChange