← all branches

fix/tariff-cov

needs attentionviewing older commit
76bcc76 · incrementalpre-PRreviewed 2026-08-06 00:51 UTC0H · 1M · 5L · 4I
The branch
Purpose
Fix silent data gaps in CFE tariff coverage — the TOU schedule seed was writing SIN horario to only Noroeste (DB), leaving 14 of 15 SIN zones with no TOU bands, causing the TOU classifier to output 0 baseline energy and negative savings for those zones.
Goal
Seed TOU schedules for all 15 SIN zones (and ensure the CFE rate engine is robust end-to-end)
Sub-goals
  • SG-1: Stop silent data loss in the CFE rate pipeline
  • SG-2: Make the CFE rate engine reach any month and fail loudly on missing data
  • SG-3: CI budget fix for cold cache
  • SG-4: Pair each Valle de México rate table with its own zone
  • SG-5: Seed TOU schedules for all 15 SIN zones
The changes (whole branch)
What
seed-tou-schedules.ts fan-out: SIN now maps to all 15 zone codes instead of just DB. resolveTariffZone (returns null|single) replaced by resolveTariffZones (returns array). Main loop iterates over all resolved zone targets. The branch also added Lambda/SFN stack fixes, watchdog SQL+CI workflow, rate-pages.json update, and 421 lines of new tests.
Why
Valle de México (DL/DM/DN) — the densest zones in the fleet — had no TOU schedules, producing negative savings figures. Root cause: SIN was treated as a 1:1 map to Noroeste rather than a 1:15 fan-out over the whole national interconnected system.
Areas
services/utility/tariffs/cfe+795146infra/cdk/src/stacks/services/utility/tariffs/cfe+246101packages/database/src+7535domains/utility/src/tariff-job+10814.github/workflows+1445scripts/db+1040apps/platform/src/api+130.claude/rules+950
Blast
20 files, +1580/−301 lines across CFE tariff service, CDK stacks, DB seed, domain logic, CI, and platform API. No customer-facing routes changed. Blast radius is scoped to the CFE tariff pipeline and its operational tooling.
seed-script-change data-fix zone-fan-out no-migration-required
github-ci· No GitHub auth on runner — CI status unavailablecoderabbit· No .coderabbit.yaml in repo

Findings · 10

correctness3

medium

Partial zone resolution is silent — missing SIN zones produce no warning

packages/database/src/seed-tou-schedules.ts:111

A row is added to `skipped` only when `targets.length === 0`. If the tariff resolves fine but some of the 15 SIN zones are missing from the DB (pricing_zone rows not yet seeded), those zones are silently dropped: `pricingZoneId` returns null, the zone is excluded from `out`, and the parent row proceeds with the remaining zones. The final `upserted` count will be lower than expected with no warning — exactly the class of silent data gap this branch fixes. Fix: collect unresolved zone codes per row and emit a warning, e.g. `console.warn('partial: ${r.rate}/${r.region} missing zones: [DU, DV]')`.

low

Comment names BC system 'BCA' but map key and seed data use 'BC'

packages/database/src/seed-tou-schedules.ts:35

The JSDoc reads '…three (BCA, BCS, and the SIN…)' but the map key is `BC`. The official interconnected-system abbreviation is BCA; seed data uses `BC`. No code impact, but a future maintainer adding a new region could add `BCA` instead of `BC` and get zero matches. Change to '(BC, BCS, and the SIN…)' or add a parenthetical.

info

Stale comment row-count (~97 rows) no longer matches actual output (~699 DB rows after fan-out)

packages/database/src/seed-tou-schedules.ts:88

The pre-existing '97 rows over 9 rate×region combos' count correctly describes the CSV input but no longer the output. SIN's 43 rows × 15 zones = 645, plus 54 BC/BCS = 699 total upserts. An operator expecting ~97 might flag 699 as a bug. Update the comment to state the fan-out range.

conventions1

info

'fourteen' vs 'fifteen' inconsistency in JSDoc comment

packages/database/src/seed-tou-schedules.ts:40

Line 36 says 'all fifteen non-Baja divisions' but line 40 says 'the other fourteen SIN zones'. Both are defensible (SIN has 15 zones total; 14 were missing before this fix), but read as contradictory at a glance. Suggest: 'left fourteen of the fifteen SIN zones with NO schedule at all.'

tests3

low

REGION_TO_ZONE_CODES constant has no test pinning its zone count or membership

packages/database/src/seed-tou-schedules.ts:45

The invariant that SIN maps to exactly 15 zone codes is not asserted anywhere. The original bug (SIN: 'DB' — one zone instead of 15) would have passed every existing test. A simple assertion on the constant's shape (`.length === 15`, or set-equality vs the authoritative DIVISION_TO_ZONE registry) would pin this against regression.

low

REGION_TO_ZONE_CODES not cross-checked against authoritative DIVISION_TO_ZONE registry

packages/database/src/seed-tou-schedules.ts:45

DIVISION_TO_ZONE in tariff-rate.scrape.ts lists all 17 CFE pricing zones. A test asserting that the union of all REGION_TO_ZONE_CODES arrays equals the set of all DIVISION_TO_ZONE values would catch any future zone added to CFE pricing that is not seeded into TOU schedules. config-integrity.test.ts already guards the scraper side — the seed side is the missing half.

info

Scraper fan-out is pinned in tests; seed fan-out is not — asymmetric coverage

services/utility/tariffs/cfe/__tests__/collector.test.ts:182

collector.test.ts pins the scraper's VALLE DE MEXICO → DL/DM/DN fan-out. The seed script's REGION_TO_ZONE_CODES fan-out is not pinned. Both feed the same downstream TOU classifier. The coverage is asymmetric given that the original bug was precisely in the seed path.

improvement3

low

zoneCode returned in resolve type but never consumed at call site

packages/database/src/seed-tou-schedules.ts:96

`resolveTariffZones` returns `Array<{ tariffId, pricingZoneId, zoneCode }>`. The call site reads only `tz.tariffId` and `tz.pricingZoneId` for the insert — `zoneCode` is never used. If it was intended for partial-resolution logging (see correctness finding), wire it up. Otherwise drop the field from the return type.

low

699 serial DB round-trips where a batch insert would suffice

packages/database/src/seed-tou-schedules.ts:124

43 SIN rows × 15 zones + 54 BC/BCS rows = 699 sequential `await database.insert(...)` calls. Drizzle's `.insert().values([...])` accepts an array and `.onConflictDoUpdate` works on batch inserts too. Grouping inserts per CSV row (15 values per call) would reduce wall-clock time by ~14× on the SIN rows. For a one-off seed this is acceptable, but on a cloud Supabase at 10–15 ms/RTT it's a ~7–10 s run vs ~1 s batched.

info

generateULID called once per zone per row — this is correct behavior on conflict

packages/database/src/seed-tou-schedules.ts:128

On re-runs, the conflict target is (tariffId, pricingZoneId, startDate) so the generated publicId in the values() clause is discarded on conflict and the existing row's publicId is preserved (publicId is not in the onConflictDoUpdate set — correct). No action needed; noted so a batch-insert refactor doesn't accidentally share one ID across rows.

History · 14 commits

  1. 1ed035eneeds attentionincremental0H · 2M · 5L2026-08-11 02:03
  2. fcbe80dblockedfull6H · 12M · 14L2026-08-10 22:02
  3. 92353bdblockedincremental1H · 9M · 7L2026-08-10 19:32
  4. 5f2213eneeds attentionincremental1H · 6M · 10L2026-08-09 05:35
  5. c30da44needs attentionincremental1H · 4M · 2L2026-08-09 04:54
  6. 5940f56needs attentionincremental0H · 3M · 7L2026-08-07 19:15
  7. f222512needs attentionfull2H · 9M · 15L2026-08-07 18:58
  8. eec3b04needs attentionincremental2H · 1M · 4L2026-08-07 18:27
  9. 0b43396needs attentionincremental0H · 2M · 6L2026-08-07 01:39
  10. 3eb9789needs attentionincremental0H · 3M · 5L2026-08-06 18:30
  11. 87fc06aneeds attentionincremental0H · 4M · 9L2026-08-06 01:21
  12. 76bcc76needs attentionincremental0H · 1M · 5L2026-08-06 00:51current
  13. 8948608needs attentionincremental0H · 3M · 4L2026-08-05 23:49
  14. e44f6bbneeds attentionfull3H · 6M · 9L2026-08-05 19:29