feat/billing-improve
needs attentionf406509 · fullpre-PRreviewed 2026-07-30 23:22 UTC3H · 7M · 5L · 9I- Purpose
- Eliminate the redundant Tinybird read on bill-PDF generation and add a batch SFN pipeline for fleet-wide bill rendering.
- Goal
- Read-once bill compute: derive both charts from the same demand series. Shared mapper so both paths render identical PDFs.
- Sub-goals
- SG-1: deriveBillChartsFromSeries — pure function replacing resolveBillChartsShell
- SG-2: bill-pdf-view-model.ts — shared ComputeBillResult→BillPdfViewModel mapper
- SG-3: BillPdfGeneratorSfnStack — CDK SFN Map + per-bill Lambda
- SG-4: generate-bill-pdf.handler.ts — Lambda batch worker (service-role)
- SG-5: react-pdf parity (BAT-284): Poppins, stacked TOU bars, grey table, dashed demand lines
- What
- Deleted resolveBillChartsShell + platform bill-pdf.mapper.ts. Added pure deriveBillChartsFromSeries + shared bill-pdf-view-model.ts. Added Lambda + CDK SFN stack. Added 9-test suite. bill-document.tsx rewritten.
- Why
- Prior implementation made two Tinybird reads per bill. New reads once, enables SFN fan-out.
- Areas
- domains/cross-domain+333−92packages/bill-pdf+260−131infra/cdk+201−2services/billing+119−0apps/platform/src/api+23−126
- Blast
- 27 files, +1102/-351. cross-domain (bills), bill-pdf renderer, CDK billing stack, new billing Lambda, platform handler simplified.
Findings · 15
correctness3
tCO2e formula changed from kWh-based to MXN-based — inflates ~5×
domains/cross-domain/src/bill-pdf-view-model.ts:85
Old: consKwh × 0.000435 (correct, 0.435 tCO2e/MWh). New: subtotal × 0.000435 (wrong units). 10k kWh @ 50k MXN → old=4.35t, new=21.75t. Revert to kWh-based.
TOU classify() silently drops points at schedule date gaps — bars∑≠bill kWh
domains/cross-domain/src/bill-charts-from-series.ts:111
if(band) skips unclassified points. Season-boundary schedule gap → bar undercount. Test fixture masks this with a full-year schedule.
SFN taskTimeout (3 min) > Lambda timeout (2 min) — unreachable dead code
infra/cdk/src/stacks/services/billing/bill-pdf-generator-sfn.stack.ts:120
security3
SSRF: brand logo URLs passed to react-pdf Image without allowlist validation
services/billing/src/handlers/generate-bill-pdf.handler.ts:96
react-pdf fetches external URLs during render. Lambda has unrestricted outbound. Risk scoped to IAM holders who can invoke the SFN.
LogLevel.ALL + includeExecutionData logs full bill payload to CloudWatch
infra/cdk/src/stacks/services/billing/bill-pdf-generator-sfn.stack.ts:157
sitePublicId, yearMonth, brand URLs written to CloudWatch. 1-week retention. Consider includeExecutionData: false.
Lambda event dates not validated for Invalid Date or from >= to
services/billing/src/handlers/generate-bill-pdf.handler.ts:61
conventions4
cross-domain → @batu/bill-pdf new dependency edge — renderer coupling in domain layer
domains/cross-domain/package.json
No cycle (bill-pdf is a renderer leaf). Imports only from JSX-free subpath. Consider moving BillPdfViewModel to @batu/api/schemas.
SFN $.results heterogeneous — document the output contract
infra/cdk/src/stacks/services/billing/bill-pdf-generator-sfn.stack.ts
SiteMetricsWithSeries public barrel export — internal type leaks
domains/cross-domain/src/index.ts
.iterator() deprecated; consistent with existing convention; migrate both stacks together
infra/cdk/src/stacks/services/billing/bill-pdf-generator-sfn.stack.ts:143
tests5
computeBillShell({ includeSeries: true }) never tested — core feature unverified
domains/cross-domain/src/bill-compute.shells.ts
The includeSeries flag and inputSeries propagation have no integration test. Pure derivation is well-tested; the pipeline glue is not.
toBillPdfViewModel / buildBillPdfRichMeta have no unit tests
domains/cross-domain/src/bill-pdf-view-model.ts
Shared mapper is SSOT for both bill-PDF paths. No test guards zone precedence, due-date, tCO2e formula, line sorting.
TOU fixture identical day-types — selectScheduleForDate day-type selector untested
domains/cross-domain/src/__tests__/bill-charts-from-series.test.ts
calendarMonthly guard branch untested
domains/cross-domain/src/bill-charts-from-series.ts
Tijuana DST fall-back (Nov 1 2026) not tested — only spring-forward covered