Skip to content

Add provider-neutral multi-utility bill auditing - #1

Merged
3clyp50 merged 57 commits into
mainfrom
codex/provider-neutral-multi-utility
Jul 21, 2026
Merged

Add provider-neutral multi-utility bill auditing#1
3clyp50 merged 57 commits into
mainfrom
codex/provider-neutral-multi-utility

Conversation

@TerminallyLazy

@TerminallyLazy TerminallyLazy commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What changed

WattProof now supports a provider-neutral, sequential household review for electric, gas, water, wastewater, stormwater, and sanitation bills.

  • Adds exact Decimal-based multi-utility models, reconciliation, evidence provenance, dependency-aware findings, API, and CLI support.
  • Adds deterministic public fixtures for Duke Energy electric, CenterPoint gas, and Bloomington water/sanitation while preserving the PG&E/3CE regression path.
  • Reworks the interface into Upload → Review → Verify → Household → Next steps, with temporary page-memory-only bundle state.
  • Generates provider-specific review requests without storing raw household documents.
  • Integrates current main's official Codex device-login/session lifecycle and source-anchored error UI into the provider-neutral schema 2.0 flow.
  • Includes reproducible desktop/mobile maintainer evidence.

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:

  • Evidence extracted — supported by rendered bill pages.
  • Internally reconciled — printed arithmetic reproduces exactly.
  • Tariff verified — an exact provider, jurisdiction, schedule, period, and archived-source adapter matched.

Only supported PG&E/3CE lines receive published-tariff verification. Duke, CenterPoint, and Bloomington samples remain clearly labeled internal reconciliation.

Extraction and privacy boundaries

  • Known public fixtures remain deterministic and keyless.
  • Personal unknown PDFs are rendered first. Codex receives ordered rendered page images plus a separately labeled untrusted native-text locator hint and must return strict UtilityDocument schema 2.0 output.
  • The server overwrites digest, page count, and source metadata; there is no native-text-only fallback.
  • The operator-managed OpenAI visual path remains available only when explicitly configured.
  • Household summaries remain in page memory; raw uploads and previews are released after processing.

Verification

  • make verify: 579 passed, 2 skipped
  • Ruff, strict MyPy across 28 source files, and compileall: clean
  • Real Chromium five-sample and sequential-household flow: passed
  • Focused extraction/API/Codex, exactness, tariff-boundary, dependency, timeout, privacy, and accessibility suites: passed
  • Linux Actions regression fixed by replacing an oversized test-only argv payload; production extraction behavior is unchanged
  • No private bills, populated environment files, device codes, or downloaded public samples are tracked

Review evidence

Provider-neutral upload

Source-anchored local validation

Sequential household bundle

Summary by CodeRabbit

  • New Features
    • Added provider-neutral bill reviews for electricity, gas, and water statements.
    • Introduced evidence-first extraction using rendered bill pages and clear verification levels.
    • Added calculation ledgers, discrepancy root causes, provider review drafts, and household summaries for multiple bills.
    • Added deterministic public samples and a guided personal-PDF connection flow.
  • Improvements
    • Expanded mobile accessibility, error handling, privacy controls, and responsive review screens.
    • Added strict, fail-closed tariff verification where supporting evidence is available.
  • Documentation
    • Updated architecture, usage guidance, verification instructions, and reproducible screenshot evidence.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

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

Changes

Provider-neutral audit pipeline

Layer / File(s) Summary
Contracts and architecture
ARCHITECTURE.md, README.md, docs/superpowers/*, GROUND_TRUTH.md
Documentation defines rendered evidence as authoritative, native text as an untrusted hint, provider-neutral verification levels, scoped tariff support, household state, and deployment boundaries.
Numeric and document models
wattproof/numeric.py, wattproof/models.py, wattproof/utility_models.py, wattproof/legacy.py, wattproof/utility_fixtures.py
Adds strict numeric validation, schema 2.0 utility documents, legacy translation, evidence/provenance rules, deterministic fixtures, and dependency-aware audit result models.
Extraction and audit services
wattproof/extract.py, wattproof/codex.py, wattproof/app.py, wattproof/audit_service.py, wattproof/cli.py
Routes known documents through fixtures, renders unknown PDFs before Codex extraction, accepts both schemas, and exposes provider-neutral audit and verification output.
Reconciliation and tariff adapter
wattproof/reconcile.py, wattproof/adapters.py, wattproof/audit.py, wattproof/tariffs.py
Computes exact arithmetic, rollups, root causes, review requests, and fail-closed PG&E/3CE tariff verification.
Browser workflow
wattproof/static/app.js, wattproof/static/app.css, wattproof/templates/index.html
Adds provider-neutral review/results screens, abortable async state, correction provenance, household memory, provider drafts, accessibility behavior, and responsive layouts.
Validation and evidence
tests/*, scripts/fetch-public-samples.sh, docs/screenshots/README.md
Adds extensive model, extraction, API, reconciliation, browser, screenshot, integrity, and numeric-context regression coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: 3clyp50

Poem

I’m a rabbit with receipts in my nest,
Rendered pages put truth to the test.
Decimal carrots roll up just right,
Tariff gates close when proof’s not in sight.
Bills join the household, tidy and bright—
Hop, verify, and sleep well tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: provider-neutral auditing across multiple utility bill types.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/provider-neutral-multi-utility

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TerminallyLazy
TerminallyLazy marked this pull request as ready for review July 21, 2026 23:05
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.
@3clyp50
3clyp50 merged commit 0eb82e7 into main Jul 21, 2026
6 of 7 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
wattproof/codex.py (1)

36-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consolidate the duplicated bounded exact-JSON-number parsers. wattproof/codex.py and wattproof/app.py each define the same policy (length-capped parse_floatRawJSONDecimal, parse_intint, and a parse_constant rejecter) with slightly divergent details (app.py guards RawJSONDecimal with a DecimalExceptionValueError conversion; codex.py does not). Extracting a single shared helper (e.g. in wattproof/numeric.py) removes the duplication and prevents the two copies from drifting further.

  • wattproof/codex.py#L36-L52: replace MAX_CODEX_JSON_NUMBER_CHARACTERS and the three local parsers with the shared helper.
  • wattproof/app.py#L40-L64: replace MAX_AUDIT_JSON_NUMBER_CHARACTERS and _parse_json_decimal/_parse_json_integer/_reject_json_constant with 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 value

Remove the unused _issue_detail helper _utility_issue_detail is the helper wired into the request builders, and _issue_detail appears 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 value

Nit: make the regex match= pattern a raw string.

The pattern uses .* metacharacters but isn't a raw string (Ruff RUF043). Prefixing with r documents 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 win

Deprecated clip property flagged as a stylelint error at two changed sites. Stylelint (property-no-deprecated) reports clip as deprecated; if this rule runs as an error in CI, the build will fail on these hunks. clip remains the widely supported visually-hidden idiom, so pair it with clip-path: inset(50%) (the modern replacement) rather than dropping it.

  • wattproof/static/app.css#L28-L37: in .visually-hidden, add clip-path: inset(50%); alongside the existing clip: rect(0 0 0 0);.
  • wattproof/static/app.css#L712-L712: apply the same clip-path: inset(50%); addition to the .audit-table thead clipping 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd5c6d and 15d59a1.

⛔ Files ignored due to path filters (13)
  • docs/screenshots/anchored-no-file-error-desktop.png is excluded by !**/*.png
  • docs/screenshots/centerpoint-gas-desktop.png is excluded by !**/*.png
  • docs/screenshots/duke-internal-reconciliation-desktop.png is excluded by !**/*.png
  • docs/screenshots/household-bundle-desktop.png is excluded by !**/*.png
  • docs/screenshots/household-result-mobile.png is excluded by !**/*.png
  • docs/screenshots/multi-utility-upload-desktop.png is excluded by !**/*.png
  • docs/screenshots/pge-tariff-verified-desktop.png is excluded by !**/*.png
  • docs/screenshots/water-review-mobile.png is excluded by !**/*.png
  • output/playwright/audit-synthetic-desktop.png is excluded by !**/*.png
  • output/playwright/audit-synthetic-mobile.png is excluded by !**/*.png
  • output/playwright/compare-desktop.png is excluded by !**/*.png
  • output/playwright/review-desktop.png is excluded by !**/*.png
  • output/playwright/upload-desktop.png is excluded by !**/*.png
📒 Files selected for processing (41)
  • ARCHITECTURE.md
  • CODEX_LOG.md
  • GROUND_TRUTH.md
  • PLAN.md
  • README.md
  • SUBMISSION.md
  • TODO.md
  • docs/screenshots/README.md
  • docs/superpowers/plans/2026-07-21-provider-neutral-multi-utility.md
  • docs/superpowers/specs/2026-07-21-provider-neutral-multi-utility-design.md
  • fixtures/authentic-extraction.json
  • scripts/fetch-public-samples.sh
  • tests/test_audit_service.py
  • tests/test_backend_acceptance_boundaries.py
  • tests/test_exact_json_and_context.py
  • tests/test_multi_utility_web.py
  • tests/test_numeric_domain.py
  • tests/test_reconcile.py
  • tests/test_rendered_extraction.py
  • tests/test_utility_api_validation.py
  • tests/test_utility_fixtures.py
  • tests/test_utility_models.py
  • tests/test_wattproof.py
  • wattproof/adapters.py
  • wattproof/app.py
  • wattproof/audit.py
  • wattproof/audit_service.py
  • wattproof/cli.py
  • wattproof/codex.py
  • wattproof/extract.py
  • wattproof/fixtures.py
  • wattproof/legacy.py
  • wattproof/models.py
  • wattproof/numeric.py
  • wattproof/reconcile.py
  • wattproof/static/app.css
  • wattproof/static/app.js
  • wattproof/tariffs.py
  • wattproof/templates/index.html
  • wattproof/utility_fixtures.py
  • wattproof/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 &amp; 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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants