← all branches

feat/demand-det

needs attentionviewing older commit
fef1896 · fullPR #341reviewed 2026-07-23 04:27 UTC4H · 6M · 6L · 4I
The branch
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)
The changes (whole branch)
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+793198domains/utility/src/consumption-series+8550docs/energia+1830
Blast
13 files, +2561/-376 across stack. Pure read-path; no schema migrations. Draft.
DRAFT — retarget to main once #337→#340 land D2b/D3 cohort min=15 makes them dormant until cross-org cohorts (v2)
typecheck· node_modules not installed on runnertests· node_modules not installed on runnerCI· GraphQL token lacks statusCheckRollupCodeRabbit· no .coderabbit.yaml

Findings · 20

correctness4

medium

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.

low

puntaKwh=0 emitted for winter bills with zero-value punta concept

domains/utility/src/consumption-series/consumption-series.queries.ts:83

info

D1/D2b dedupeKey changes monthly — accumulating rather than updating findings

info

D2b/D3 cohort min=15 within org makes them dormant for most customers in v1

security3

medium

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.

low

Internal billId UUID exposed in InvalidBillPeriodError struct

info

findByPublicId and findByContract unscoped in FCIS namespace

conventions5

high

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>>.

medium

consumption-series entity missing type-check.ts

domains/utility/src/consumption-series/

low

toNumberOrNull duplicated across two query files

low

BillFactRow, PerPeriodDemandMonth, PeerBenchmarkContractMeta belong in finding.type.ts

info

Shell read-outside-transaction is documented intentional FCIS deviation

tests3

high

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.

high

No integration tests for 3 new SQL queries

fetchConsumptionBillInputsForOrg, fetchPerPeriodDemandForOrg, fetchContractRegionsForOrg - complex raw SQL with CTEs and LATERAL joins.

high

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

medium

3 near-identical groupBy helpers will compound with each new detector

domains/utility/src/finding/finding.decisions.ts

medium

Trustworthy-month filter duplicated between YoY-spike and peer-benchmark

domains/utility/src/finding/finding.decisions.ts

medium

computeMedian redundant — percentile(sorted, 0.5) already exists

domains/utility/src/finding/finding.decisions.ts

low

Org-scoping CTE copy-pasted across 3 SQL queries

low

powerFactor! then redundant null filter in detectFpRegimeChange

History · 2 commits

  1. ad71b23safeincremental0H · 1M · 3L2026-07-23 04:53
  2. fef1896needs attentionfull4H · 6M · 6L2026-07-23 04:27current