Add provider-neutral multi-utility bill auditing - #1
Conversation
📝 WalkthroughWalkthroughWattProof now supports provider-neutral utility documents, rendered-page evidence extraction, deterministic reconciliation, exact PG&E/3CE tariff verification, multi-utility fixtures, generalized APIs and CLI output, household browser state, screenshot evidence, and extensive validation coverage. ChangesProvider-neutral audit pipeline
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Remove the duplicate operator Responses path and OpenAI SDK dependency while preserving Chadwick's provider-neutral implementation. Make large Poppler fixtures CI-portable, credit the team contribution, and align architecture, privacy, and submission notes with the single official device-flow path.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
wattproof/codex.py (1)
36-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffConsolidate the duplicated bounded exact-JSON-number parsers.
wattproof/codex.pyandwattproof/app.pyeach define the same policy (length-cappedparse_float→RawJSONDecimal,parse_int→int, and aparse_constantrejecter) with slightly divergent details (app.py guardsRawJSONDecimalwith aDecimalException→ValueErrorconversion; codex.py does not). Extracting a single shared helper (e.g. inwattproof/numeric.py) removes the duplication and prevents the two copies from drifting further.
wattproof/codex.py#L36-L52: replaceMAX_CODEX_JSON_NUMBER_CHARACTERSand the three local parsers with the shared helper.wattproof/app.py#L40-L64: replaceMAX_AUDIT_JSON_NUMBER_CHARACTERSand_parse_json_decimal/_parse_json_integer/_reject_json_constantwith the same shared helper.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wattproof/codex.py` around lines 36 - 52, The bounded JSON-number parsing policy is duplicated and differs between Codex and audit parsing. Add one shared helper in the numeric module that enforces the length cap, converts floats to RawJSONDecimal with DecimalException translated to ValueError, converts integers with int, and rejects constants; replace MAX_CODEX_JSON_NUMBER_CHARACTERS and the local parsers in wattproof/codex.py#L36-52 and MAX_AUDIT_JSON_NUMBER_CHARACTERS plus _parse_json_decimal, _parse_json_integer, and _reject_json_constant in wattproof/app.py#L40-64 with that helper.wattproof/adapters.py (1)
616-644: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
_issue_detailhelper_utility_issue_detailis the helper wired into the request builders, and_issue_detailappears to be dead code. Removing it would trim the module without changing behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wattproof/adapters.py` around lines 616 - 644, Remove the unused _issue_detail helper from adapters.py; retain _utility_issue_detail and all request-builder wiring unchanged.tests/test_utility_fixtures.py (1)
204-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNit: make the regex
match=pattern a raw string.The pattern uses
.*metacharacters but isn't a raw string (RuffRUF043). Prefixing withrdocuments intent and silences the lint.Proposed change
match=( - "Unsupported utility sample 'solar'.*" - "duke.*centerpoint.*bloomington" + r"Unsupported utility sample 'solar'.*" + r"duke.*centerpoint.*bloomington" ),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_utility_fixtures.py` around lines 204 - 211, Update the match pattern in the pytest.raises call around load_utility_sample to use a raw string literal, preserving the existing regex text and exception assertion behavior.Source: Linters/SAST tools
wattproof/static/app.css (1)
28-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeprecated
clipproperty flagged as a stylelint error at two changed sites. Stylelint (property-no-deprecated) reportsclipas deprecated; if this rule runs as an error in CI, the build will fail on these hunks.clipremains the widely supported visually-hidden idiom, so pair it withclip-path: inset(50%)(the modern replacement) rather than dropping it.
wattproof/static/app.css#L28-L37: in.visually-hidden, addclip-path: inset(50%);alongside the existingclip: rect(0 0 0 0);.wattproof/static/app.css#L712-L712: apply the sameclip-path: inset(50%);addition to the.audit-table theadclipping rule.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wattproof/static/app.css` around lines 28 - 37, Add clip-path: inset(50%) alongside the existing deprecated clip declarations in .visually-hidden (wattproof/static/app.css, lines 28-37) and .audit-table thead (wattproof/static/app.css, line 712), preserving the existing clip rules for compatibility.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wattproof/templates/index.html`:
- Line 116: Update the `#review-warnings` live-region semantics by replacing the
contradictory role="alert" and aria-live="polite" combination with
role="status", preserving the existing aria-atomic="true" and non-urgent warning
behavior.
---
Nitpick comments:
In `@tests/test_utility_fixtures.py`:
- Around line 204-211: Update the match pattern in the pytest.raises call around
load_utility_sample to use a raw string literal, preserving the existing regex
text and exception assertion behavior.
In `@wattproof/adapters.py`:
- Around line 616-644: Remove the unused _issue_detail helper from adapters.py;
retain _utility_issue_detail and all request-builder wiring unchanged.
In `@wattproof/codex.py`:
- Around line 36-52: The bounded JSON-number parsing policy is duplicated and
differs between Codex and audit parsing. Add one shared helper in the numeric
module that enforces the length cap, converts floats to RawJSONDecimal with
DecimalException translated to ValueError, converts integers with int, and
rejects constants; replace MAX_CODEX_JSON_NUMBER_CHARACTERS and the local
parsers in wattproof/codex.py#L36-52 and MAX_AUDIT_JSON_NUMBER_CHARACTERS plus
_parse_json_decimal, _parse_json_integer, and _reject_json_constant in
wattproof/app.py#L40-64 with that helper.
In `@wattproof/static/app.css`:
- Around line 28-37: Add clip-path: inset(50%) alongside the existing deprecated
clip declarations in .visually-hidden (wattproof/static/app.css, lines 28-37)
and .audit-table thead (wattproof/static/app.css, line 712), preserving the
existing clip rules for compatibility.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6011e955-a4a4-41aa-a417-bac729dd00fa
⛔ Files ignored due to path filters (13)
docs/screenshots/anchored-no-file-error-desktop.pngis excluded by!**/*.pngdocs/screenshots/centerpoint-gas-desktop.pngis excluded by!**/*.pngdocs/screenshots/duke-internal-reconciliation-desktop.pngis excluded by!**/*.pngdocs/screenshots/household-bundle-desktop.pngis excluded by!**/*.pngdocs/screenshots/household-result-mobile.pngis excluded by!**/*.pngdocs/screenshots/multi-utility-upload-desktop.pngis excluded by!**/*.pngdocs/screenshots/pge-tariff-verified-desktop.pngis excluded by!**/*.pngdocs/screenshots/water-review-mobile.pngis excluded by!**/*.pngoutput/playwright/audit-synthetic-desktop.pngis excluded by!**/*.pngoutput/playwright/audit-synthetic-mobile.pngis excluded by!**/*.pngoutput/playwright/compare-desktop.pngis excluded by!**/*.pngoutput/playwright/review-desktop.pngis excluded by!**/*.pngoutput/playwright/upload-desktop.pngis excluded by!**/*.png
📒 Files selected for processing (41)
ARCHITECTURE.mdCODEX_LOG.mdGROUND_TRUTH.mdPLAN.mdREADME.mdSUBMISSION.mdTODO.mddocs/screenshots/README.mddocs/superpowers/plans/2026-07-21-provider-neutral-multi-utility.mddocs/superpowers/specs/2026-07-21-provider-neutral-multi-utility-design.mdfixtures/authentic-extraction.jsonscripts/fetch-public-samples.shtests/test_audit_service.pytests/test_backend_acceptance_boundaries.pytests/test_exact_json_and_context.pytests/test_multi_utility_web.pytests/test_numeric_domain.pytests/test_reconcile.pytests/test_rendered_extraction.pytests/test_utility_api_validation.pytests/test_utility_fixtures.pytests/test_utility_models.pytests/test_wattproof.pywattproof/adapters.pywattproof/app.pywattproof/audit.pywattproof/audit_service.pywattproof/cli.pywattproof/codex.pywattproof/extract.pywattproof/fixtures.pywattproof/legacy.pywattproof/models.pywattproof/numeric.pywattproof/reconcile.pywattproof/static/app.csswattproof/static/app.jswattproof/tariffs.pywattproof/templates/index.htmlwattproof/utility_fixtures.pywattproof/utility_models.py
| </details> | ||
| <div class="form-actions"><button class="button text" type="button" data-back="1">← Start over</button><button class="button primary" type="submit">Confirm & run audit <span aria-hidden="true">→</span></button></div> | ||
| <div class="facts-header"><div><span class="card-kicker">Evidence ledger</span><h2>Facts by service</h2></div><span id="fact-count" class="fact-count"></span></div> | ||
| <div id="review-warnings" class="review-warnings" role="alert" aria-live="polite" aria-atomic="true" hidden></div> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Contradictory live-region semantics on #review-warnings.
role="alert" already implies aria-live="assertive"; pairing it with aria-live="polite" is contradictory and behaves inconsistently across screen readers. Since these warnings are surfaced non-urgently after extraction, prefer role="status" (implicitly polite) or drop the explicit aria-live.
♿ Proposed fix
- <div id="review-warnings" class="review-warnings" role="alert" aria-live="polite" aria-atomic="true" hidden></div>
+ <div id="review-warnings" class="review-warnings" role="status" aria-live="polite" aria-atomic="true" hidden></div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div id="review-warnings" class="review-warnings" role="alert" aria-live="polite" aria-atomic="true" hidden></div> | |
| <div id="review-warnings" class="review-warnings" role="status" aria-live="polite" aria-atomic="true" hidden></div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wattproof/templates/index.html` at line 116, Update the `#review-warnings`
live-region semantics by replacing the contradictory role="alert" and
aria-live="polite" combination with role="status", preserving the existing
aria-atomic="true" and non-urgent warning behavior.
What changed
WattProof now supports a provider-neutral, sequential household review for electric, gas, water, wastewater, stormwater, and sanitation bills.
main's official Codex device-login/session lifecycle and source-anchored error UI into the provider-neutral schema 2.0 flow.Why
The original PG&E-focused flow did not represent households that receive separate electric, gas, and municipal water/sanitation bills. This broadens the product without overstating what any bill proves.
The result language intentionally distinguishes:
Only supported PG&E/3CE lines receive published-tariff verification. Duke, CenterPoint, and Bloomington samples remain clearly labeled internal reconciliation.
Extraction and privacy boundaries
UtilityDocumentschema 2.0 output.Verification
make verify: 579 passed, 2 skippedReview evidence
Summary by CodeRabbit