claude/energy-management-features-4fdefc
needs attention4f59fe5 · fullpre-PRreviewed 2026-07-11 06:14 UTC2H · 6M · 5L · 4I- Purpose
- Backtest CFE bill-audit detectors against the real historical corpus before productizing; add Energía module JTBD map and design documentation
- Goal
- Validate or kill each candidate detector (estimated_reading, fp_recompute, fp_regime_change, inactive_service, vat, multiplier, component_sum, rate_wrong_month, rate_wrong_zone, demanda_facturada) against 122k real XML bills before any customer-facing exposure
- Sub-goals
- SG-1: Foundation bill_facts TEMP TABLE + corpus census (00-01)
- SG-2: Operational detectors — demand overage/oversized/tariff-mismatch, FP streaks, DAP/porteo (10-21)
- SG-3: Estimated readings, multiplier check, period anomalies, YoY kWh, inactive-service, IVA anomaly, FP-recompute (30-36)
- SG-4: Arithmetic recompute + rate verification + component calibration + demanda facturada (40-43)
- SG-5: Energía JTBD map + design principles + detector verdict ledger + module spec (docs)
- What
- Added scripts/corpus-study/ (run.mjs runner + 20 SQL detector files), docs/design/ (JTBD map, design principles, detector ledger), docs/specs/ (Energía module spec PR1/PR2), .gitignore update, and .mcp.json new supabase production server entry
- Why
- Disciplined pre-ship validation: five candidate CFE-error detectors were killed by this study (multiplier, component_sum, rate_wrong_month, rate_wrong_zone, demanda_facturada). Only what survives XML-level verification ships to customers.
- Areas
- scripts/corpus-study+5617−0docs/design+604−0docs/specs+423−0.mcp.json+5−1.gitignore+3−0
- Blast
- 30 files, +6652/-1 lines. No production code changed — entirely exploratory tooling and documentation. .mcp.json change affects all Claude Code sessions that load the project config.
Findings · 17
correctness5
90-prize-rollup.sql: comment in discoverFiles() misidentifies which guard covers it
scripts/corpus-study/run.mjs:361
The comment reads '// e.g. 90-prize-rollup.sql — harmless template' next to 'if (!prefix) return true'. But 90-prize-rollup.sql has prefix '90' (numeric), so it never hits this guard — it's filtered by wanted.has('90') when --only is used. The comment actively misleads maintainers about how the file is included.
00-bill-facts.sql header says 'CREATE TEMP VIEW' but creates TEMP TABLE
scripts/corpus-study/sql/00-bill-facts.sql:4
The top-level header (line 4) states 'Only SELECT / CREATE TEMP VIEW.' The file actually issues CREATE TEMP TABLE. The internal comment at ~line 107 correctly documents the discrepancy, but the header is the first thing a contributor reads and states the wrong DDL type.
statementKind() does not recognize CREATE TEMP VIEW — would be refused as 'other'
scripts/corpus-study/run.mjs:290
The temp-ddl regex matches only CREATE TEMP|TEMPORARY TABLE. A CREATE TEMP VIEW in a foundation file would be classified 'other' and refused, halting that file. No current file uses TEMP VIEW, but the comment mismatch in 00-bill-facts.sql header creates a trap for future contributors.
41-rate-verification: RETIRED detectors (rate_wrong_month, rate_wrong_zone) still emit rows into findings.csv
scripts/corpus-study/sql/41-rate-verification.sql:8
Both rate_wrong_month and rate_wrong_zone are documented RETIRED/DISPROVEN in the file header. Their SELECTs still match the findings contract, so run.mjs aggregates their rows into findings.csv and summary.md. A reader of those outputs would see these retired finding types without knowing they're invalid.
stripLeadingComments() uses indexOf('*/') without depth tracking — inconsistent with splitStatements
scripts/corpus-study/run.mjs:268
splitStatements correctly tracks nested block comment depth; stripLeadingComments uses a plain indexOf('*/'), stopping at the first */ regardless of nesting. A leading nested block comment would leave residue classified as 'other' and refused. No current SQL file triggers this; consequence is conservative (refusal not misexecution).
security4
supabase MCP entry has no project_ref — implicitly connects to auth-token default (likely production)
.mcp.json
The new 'supabase' entry uses 'https://mcp.supabase.com/mcp?read_only=true' with no project_ref, unlike supabase-staging which pins to project_ref=xrgglyfbginnqgqoqirz. Without a project_ref the MCP server resolves to whichever project the authenticated user's token defaults to — for a founder's account that is production. Any AI agent with this MCP server loaded can issue queries against the production database. Should be pinned to an explicit project_ref.
read_only=true is an unverified URL parameter — not a documented Supabase MCP enforcement mechanism
.mcp.json
There is no evidence that mcp.supabase.com recognizes or enforces a read_only query parameter. If silently ignored, the 'supabase' MCP entry grants full read/write access to the production project. The only safe way to enforce read-only access is a Postgres role with GRANT SELECT only at the DB level, not a URL hint.
No runtime check that DATABASE_URL is a read-only credential
scripts/corpus-study/run.mjs
The phased safety model switches to default_transaction_read_only=on after foundation files, but if a write-capable credential is passed (e.g. service_role), the 00-foundation phase runs without read-only enforcement. A startup check (e.g. verify current_role is not service_role/postgres) would catch accidental write-capable connections before any SQL runs.
Output files contain customer PII and financial data — no handling guidance
scripts/corpus-study/run.mjs
findings.csv embeds org_name, rpu, financial amounts, and JSONB bill details. The directory is gitignored but the README gives no data-handling guidance (retention, deletion, sharing restrictions). For a multi-analyst team, ad-hoc CSV files of customer financial data represent a GDPR/data-governance risk.
conventions2
scope.md describes a completely different feature (feat/mod-gates)
.branch/scope.md
scope.md describes the soft-launch entitlement gate / ORG_MODULES widening. This branch adds corpus study tooling and Energía documentation with no overlap. Reviewers using scope.md to understand intent will be completely misled. Should be replaced with content describing this branch's actual purpose.
.mcp.json missing trailing newline
.mcp.json
The diff shows '\ No newline at end of file'. POSIX text file convention requires a trailing newline. All other JSON files in the repo end with a newline.
tests1
splitStatements(): E'...' escape-string syntax would be misparsed
scripts/corpus-study/run.mjs:120
The tokenizer handles '' doubling but not PostgreSQL's E'...' backslash-escape syntax (E'it\'s tricky'). In squote state, a backslash is treated as a literal, so the following ' closes the string early. No current SQL file uses this syntax, but adding E'...' to any future detector SQL would cause a silent statement split and a REFUSED error. Fix: detect E/B/U& prefixes before entering squote state.
improvement5
01-census is always-included (discovery) but NOT foundation (safety allowlist) — silent asymmetry
scripts/corpus-study/run.mjs:360
discoverFiles() always keeps prefix '00' AND '01' as setup dependencies. But isFoundation only matches '00-'. A future '01-' file needing CREATE TEMP TABLE would be REFUSED with 'outside foundation files' despite the code comment grouping them together. Either expand isFoundation to include '01-', or document the intentional asymmetry.
90-prize-rollup.sql runs unconditionally and produces confusing '0 statement(s)' output
scripts/corpus-study/run.mjs:361
The file is pure SQL comments, so it passes through with '0 executable statements' in the log. The null-prefix guard (the intended mechanism) never fires for it since its prefix is '90'. Options: rename with a non-numeric prefix (template-prize-rollup.sql), exclude it explicitly, or suppress '0 statement' files from the progress log.
sql.unsafe() usage lacks an inline explanation
scripts/corpus-study/run.mjs:396
sql.unsafe() is alarming to readers unfamiliar with postgres-js — it actually means 'no parameterization', safe here because every string is a static literal from a hard-coded file. A one-line comment at the first usage clarifying this would prevent future readers from second-guessing the choice.
No --help flag; CLI is undiscoverable without reading README
scripts/corpus-study/run.mjs:56
parseArgs() silently ignores --help. A simple --help branch printing the header usage block would make the script self-documenting for ad-hoc analysts.
energia-module-pr1-pr2.md hardcodes source-file line numbers that will drift
docs/specs/energia-module-pr1-pr2.md:29
The implementation checklist references specific line numbers in existing files. These will drift as the codebase evolves. Prefer context strings or anchor comments over line numbers in specs meant to be auditable by an independent agent.