← all branches

feat/tax-regime

needs attentionviewing older commit
7f40d56 · incrementalPR #302reviewed 2026-07-13 16:38 UTC1H · 4M · 5L · 1I
The branch
Purpose
Fix over-taxation of IVA Frontera Norte sites in calculated bills — computeBillShell hardcoded IVA at 16%, incorrectly taxing northern-border sites that owe only 8%.
Goal
Wire per-contract tax_regime (iva/iva_frontera) into the calculated-bill path so THOR, CFE baseline, and grid bills use the correct IVA rate, validated to the cent against WE LOVE BURGERS (16%) and PETCO (8%) management-account bills.
Sub-goals
  • SG-1: tax_regime column on utility_contracts + TAX_REGIMES / TaxRegime domain types + mapper/decisions
  • SG-2: resolveSiteTariffContext returns taxRegime; computeBillShell applies it (dropping MX_IVA constant); bill-for-contract shell resolves it automatically
  • SG-3: Move IVA rates from TAX_REGIME_RATES code constant to data-driven tax_rates catalog table (this commit)
The changes (whole branch)
What
This commit (SG-3) introduces the tax_rates catalog table (migration 0059), a TaxRateFCIS query module (findRateByCode / findAllRates), a seed script, and updates computeBillShell to look up the rate from the DB first with fallback to the code constant then to 0.16. Prior commits wired the tax_regime field through the utility-contract type, mapper, decisions, and billing shells.
Why
Moving rates to the DB makes new regimes (and future period-keyed rates) data-driven — seed rows, not code edits. Also adds the defensive triple-fallback so a missing/unseeded DB row never propagates NaN into a bill.
Areas
domains/cross-domain/src+364domains/utility/src+662packages/api/src+31packages/database+1070
Blast
23 files (excluding drizzle meta snapshots), +212/−7 net. Additive schema change (new table + existing-table column added with default in 0058). Billing path change touches computeBillShell and bill-for-contract shell — all calculated bills, not scraped CFE recibos.
billing-critical schema-change seed-required
ci· gh pr checks unavailable via personal access tokencoderabbit· no .coderabbit.yaml in repotypecheck· PR description: typecheck green across @batu/database, @batu/utility-domain, @batu/cross-domain, @batu/platformrls-check· PR description: db:check-rls greenmigration· 0059 is purely additive — new table only, no column changes on existing tables

Findings · 10

correctness2

high

NaN from Number() silently bypasses ?? fallback chain

domains/utility/src/tax-rate/tax-rate.queries.ts:17

If `rate` ever holds a non-numeric string (empty string, bad seed, direct SQL), `Number(row.rate)` returns `NaN`. `NaN ?? TAX_REGIME_RATES[code]` evaluates to `NaN` — not null/undefined — so both the code-constant fallback and the 0.16 hard-stop are silently skipped, producing `NaN × subtotal = NaN` in the bill. Fix: `const n = Number(row.rate); return Number.isFinite(n) ? n : null;` so a corrupt row falls through correctly. The `numeric(6,4) NOT NULL` column constraint makes this unlikely, but the stated invariant ('must never yield NaN') is not upheld.

medium

Per-bill DB lookup when savings-report prices 3 contracts sequentially

domains/cross-domain/src/bill-compute.shells.ts

`computeBillShell` calls `TaxRateFCIS.findRateByCode` once per bill. The savings-report shell calls `persistBillForContract` (→ `computeBillShell`) for baseline + PPA + grid — 3 round-trips per report just for the tax rate, all fetching the same 2-row table with the same code. `findAllRates` was exported specifically 'for callers that price many bills' but is not consumed anywhere. Either prefetch once at the savings-report caller and pass the resolved rate via the existing `overrides` mechanism, or remove `findAllRates` until it's needed (YAGNI).

security1

low

Silent rate substitution masks future missing catalog entries

domains/cross-domain/src/bill-compute.shells.ts:190

If a future `tax_regime` value is added to the enum but its seed row is omitted from `tax_rates`, bills silently compute at 16% with no logged warning or Result error. Consider emitting a warning log when the DB lookup returns null and the code-constant fallback is used, so unseeded environments surface the gap rather than going unnoticed until a customer dispute.

conventions3

medium

Multi-line JSDoc block on index.ts violates one-short-line-max comment rule

domains/utility/src/tax-rate/index.ts:1

The 3-line `/** Tax-rate FCIS module — the tax_rates catalog (regime code → IVA rate). */` block is a multi-line docstring. Project rule: 'Do NOT add multi-line docstrings or multi-line comment blocks — one short line max.' Collapse to a single `//` line or remove entirely.

medium

File-level and per-function JSDoc in tax-rate.queries.ts explains WHAT, not WHY

domains/utility/src/tax-rate/tax-rate.queries.ts:1

The 4-line file-level block and the two per-function JSDoc comments (`/** The IVA rate… */`, `/** All regime→rate rows… */`) both explain what the code does — already communicated by function names and return types. Rule: 'Never explain WHAT the code does.' The only non-obvious fact worth keeping is the `numeric`→string→`Number()` boundary conversion, as a one-line inline comment at line 17.

low

Missing tax-rate.type.ts breaks canonical FCIS type-flow for a named FCIS module

domains/utility/src/tax-rate/index.ts

The module is exported as `TaxRateFCIS` — a first-class FCIS entity — but has no `tax-rate.type.ts` for the domain type. Canonical form: Drizzle schema → {entity}.type.ts → queries (the SSOT). Peer entity `tariff-rate/` has `tariff-rate.type.ts`. For a catalog-only entity, the type file is minimal (just `TaxRate { code: string; rate: number; description: string | null; … }`) but it closes the type-flow gap.

tests2

medium

No integration test for findRateByCode — the primary lookup in the fallback chain

domains/utility/src/tax-rate/tax-rate.queries.ts

`tax_rates` is now the authoritative rate source. The fallback chain is correct only if `findRateByCode` returns `null` (not NaN, not 0) for an unknown code. This contract is not exercised by any test. A `tax-rate.integration.test.ts` should assert: (1) seeded codes return the correct numeric rate; (2) an unknown code returns `null`; (3) `findAllRates` returns a complete map. See `pricing-zone.integration.test.ts` for the established pattern.

low

Triple-fallback legs 2 and 3 have no test coverage

domains/cross-domain/src/bill-compute.shells.ts:187

The DB→constant→0.16 chain's second leg (unseeded-env fallback to `TAX_REGIME_RATES[code]`) is the designed path for preview/CI environments and is exercised by no test. A unit test with a mocked `findRateByCode` returning null would pin the contract and catch a future refactor that reorders or drops a leg.

improvement2

low

findAllRates is dead code — no caller in this or prior commits

domains/utility/src/tax-rate/tax-rate.queries.ts:20

`findAllRates` is exported and re-exported from the barrel but nothing calls it. Its docstring anticipates a looping caller that doesn't exist yet. Per CLAUDE.md: don't add features beyond what the task requires. Remove it and add it in the PR that adds the looping caller (savings-report or batch-billing path).

info

updatedAt column has no auto-refresh trigger — benign for seed-only catalog

packages/database/src/schema/tax-rates.ts

No `BEFORE UPDATE` trigger exists to auto-refresh `updated_at` on row mutation (none in this codebase). The seed script covers its own `ON CONFLICT DO UPDATE` correctly. For a catalog table only written by seeds this is fine, but a bare `UPDATE tax_rates SET rate = …` would leave `updated_at` stale. Consistent with other catalog tables in the repo; note the invariant in a migration comment if rates ever become admin-editable.

History · 6 commits

  1. a1f0898needs attentionincremental1H · 1M · 4L2026-07-13 20:37
  2. a72b534safeincremental0H · 0M · 0L2026-07-13 19:28
  3. 92e5afaneeds attentionincremental1H · 1M · 8L2026-07-13 16:47
  4. 7f40d56needs attentionincremental1H · 4M · 5L2026-07-13 16:38current
  5. 5088e00needs attentionincremental3H · 4M · 4L2026-07-13 02:47
  6. 1bda877blockedfull12H · 12M · 9L2026-07-13 02:30