← all branches

feat/rpu-history

needs attentionviewing older commit
c45865d · fullPR #324reviewed 2026-07-22 19:15 UTC0H · 3M · 4L · 4I
The branch
Purpose
Complete the RPU-agreements UX arc: surface per-chapter regime history, fix per-bill tariff display, and refresh stale titular data from newer bill evidence
Goal
§3 of rpu-agreements-model.md — rate-tag chip, per-bill regime in Historial, RPU-union bill count, regime-timeline drawer, evidence display refresh
Sub-goals
  • SG-1: Rate-tag chip (tariff_code/utility_service_code columns on UCO + chip component)
  • SG-2: Per-bill regime (extractBillOwnTariff: TARIFA_REG wins over contract tariff)
  • SG-3: RPU-union bill count (COUNT across all chapters sharing contract_number)
  • SG-4: Regime-timeline drawer (ContractDetailDrawer + GET /chapters endpoint)
  • SG-5: RPU-complete exports — already correct, no change
  • SG-6: Evidence display refresh (UseExistingContract.refresh + OCC updateWithVersion)
The changes (whole branch)
What
37 files, +15817/−112. Migration 0063 adds tariff_code/utility_service_code to utility_contract_overview and rewrites bill_count to the RPU union. New chapters endpoint. New ContractDetailDrawer. extractBillOwnTariff pure function. Display refresh in EnsureContractFromBills shell.
Why
Closes the last branch of the RPU-agreements arc (#314/#319/#321/#323 already on main). Fixes the stale-titular bug and surfaces seasonal tariff oscillation (DB1↔DB2) per bill row.
Areas
domains/utility/src/utility-contract+35620domains/utility/src/utility-contract-overview+2690domains/utility/src/bill+12414apps/platform/src/app/[locale]/(dashboard)/bills+50795apps/platform/src/api+1350packages/api/src+690packages/database/drizzle+5160packages/database/src/schema+80docs/development+3240
Blast
37 files, +15817/−112. Touches utility domain decisions/shells/queries, UCO read model (migration + triggers), platform UI (Contratos table, Historial page, new drawer), API contract layer, and DB schema.
migration-0063 trigger-fan-out occ-best-effort
CI· No CI check results visible at time of reviewCodeRabbit· No .coderabbit.yaml in repo

Findings · 11

correctness3

info

OCC race on display refresh defers silently — by design, documented in PR

domains/utility/src/utility-contract/utility-contract.shells.ts

When updateWithVersion returns null (lost OCC race), the shell returns the pre-refresh contract with action='found' and logs 'deferred'. The refresh only re-fires when the next bill has a strictly-newer periodEnd. The log says 'deferred' but nothing actively re-queues — it is naturally re-triggered on the next ingest cycle. Documented as intentional best-effort behaviour; no action required.

info

conceptName literals 'tariff'/'regulatedTariff' confirmed valid — correctness finding retracted

domains/utility/src/bill/bill.decisions.ts

The review panel initially flagged extractBillOwnTariff's use of camelCase conceptName literals. Verified: EnrichedLineItem carries a conceptName field populated from the bill_concepts_catalog, and 'tariff'/'regulatedTariff' are confirmed catalog conceptName values. The function is correct.

info

Trigger fan-out is atomic — TOCTOU concern retracted

packages/database/drizzle/0063_youthful_martin_li.sql

The uco_on_bills_* triggers call utility_contract_overview_refresh() per affected (org, contract), which internally calls derive() — a full atomic re-derive + upsert. bill_count is recomputed as a LATERAL COUNT(*) subquery, not a read-then-increment. No race possible.

security1

low

chapters query uses bare database after org-gate — no org-id predicate on chapter rows

apps/platform/src/api/handlers/utility-contracts.handler.ts

After the RLS-enforced overview lookup establishes org ownership, the subsequent utility_contracts query runs against plain `database` filtering only on contract_number + deleted_at. A contract_number collision between two orgs (data-entry error, multi-tenant edge case) would return the other org's chapters. Adding `.where(eq(utilityContracts.orgId, orgId))` (or scoping through the rlsDb transaction) eliminates the risk structurally.

conventions2

low

listContractChaptersHandler throws instead of returning discriminated error

apps/platform/src/api/handlers/utility-contracts.handler.ts

throw new Error('Request object is required') deviates from the FCIS convention of never throwing; it would surface as an unhandled 500 if the request object is ever absent in a test harness or future call site. This is the established file-wide pattern for all handlers here, so it is consistent within the file — but the file-level pattern is itself a deviation.

info

ContractChapterItem.status typed as string rather than CONTRACT_STATUSES enum

packages/api/src/schemas/utility-contract.schemas.ts

ContractChapterItemSchema uses z.string() for status, consistent with UtilityContractResponse.status elsewhere in this file (widened at API boundary). A z.enum(CONTRACT_STATUSES) would give callers type-safe discrimination. Low risk since isCurrent is pre-computed by the handler.

tests3

medium

chapters endpoint (GET /utility-contracts/:publicId/chapters) has zero test coverage

apps/platform/src/api/handlers/utility-contracts.handler.ts

listContractChaptersHandler has no handler-level or integration test. The UCO integration suite covers the underlying DB query but never exercises this HTTP route or its response shape. New billing-visible endpoints warrant at least a happy-path smoke test.

medium

getBillPeriodRangesByContracts has no tests

domains/utility/src/bill/bill.queries.ts

New query function with COUNT/MIN/MAX aggregation across the chapter union — zero test coverage (confirmed by grep). Warrants an integration test covering the union semantics (bills on sibling chapters counted together).

low

RateTagChip and ContractDetailDrawer have no component tests

apps/platform/src/app/[locale]/(dashboard)/bills/_components/RateTagChip.tsx

Two new user-facing components (RateTagChip, ContractDetailDrawer) have no render or snapshot tests. At minimum, a test covering the null/empty tariff code path and a basic render of the drawer with one active chapter would guard against regressions.

improvement2

medium

LEGACY_TARIFF_DISPLAY_MAP duplicated from xml-parser — silent drift risk

domains/utility/src/bill/bill.decisions.ts

The comment explicitly says 'Mirror of the xml-parser's LEGACY_TARIFF_MAP — keep in sync'. Two hand-maintained copies will diverge silently when a new legacy code is added. Extracting to a shared package (e.g. @batu/api or a utility-bill-concepts constants file) or importing from the xml-parser's constants would eliminate this.

low

window.history.replaceState less idiomatic than useRouter().replace() in App Router

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/ContractDetailDrawer.tsx

Direct replaceState bypasses Next.js App Router's internal state (prefetch cache, scroll restoration, transition state). useRouter().replace(url, { scroll: false }) achieves the same shallow URL update while keeping the router in sync. Low impact today but may cause subtle issues if searchParams-keyed data fetching is added later.

History · 3 commits

  1. 33484d7needs attentionincremental0H · 2M · 3L2026-07-22 21:00
  2. a4117daneeds attentionincremental0H · 2M · 3L2026-07-22 20:40
  3. c45865dneeds attentionfull0H · 3M · 4L2026-07-22 19:15current