Test plan: Billing Reports reconciled to invoice source (#912) โ
Change under test: apps/admin/src/admin/billing/BillingReports.jsx no longer rebuilds the headline Total and the vendor table from the daily line-item stream (fact_cost_attributed). It uses the server's invoice-reconciled numbers (report.kpis.total, report.vendorBreakdown), which come from billing_marts.invoice_reconciliation. fact_cost_attributed is kept only for the daily chart and the By-Service / By-SKU donuts, which are relabelled as line-item detail. Reports is historical throughout; the current-month headline is an estimate (Overview owns the realtime month-to-date view). See #914, #915 for the two related out-of-scope findings.
Confirmed reference data (BigQuery, lantern-app-dev, queried 2026-08-18) โ
July 2026, the reported period:
| Vendor | invoice_reconciliation effective | fact_cost_attributed gross | net |
|---|---|---|---|
| anthropic | $200.00 (invoiced) | none (no line items) | none |
| github | $48.85 (invoiced; $59.15 estimated) | $59.15 | $59.15 |
| gcp | $15.83 | $15.83 | $15.83 |
| railway | $5.00 (invoiced) | $6.36 | $5.00 (โ$1.36 credit) |
| cloudflare | $0.00 | $0.00 | $0.00 |
| total | $269.68 | $81.33 (gross) | $79.97 (net) |
Per-vendor cells are rounded to 2 dp, so they cross-foot to $81.34 gross / $79.98 net; the totals are computed from the full-precision BigQuery aggregates ($81.3343 gross, $79.9742 net), which is why the last cent differs. Trust the totals row, not the sum of the rounded cells.
Pre-fix Reports showed $79.97 (Summary, net line-item) and $81.33/$81.34 (donuts / vendor rows, gross line-item), and listed 3 vendors, no Anthropic. All three were wrong; the correct July spend is $269.68.
Scenario 1: July 2026 headline is the reconciled total โ
Steps: Admin portal โบ Billing โบ Reports. Set period to Monthly, step to July 2026. Expected: Summary Total = $269.68. Three separately-wrong things are now right and can regress independently: (a) the Total is $269.68, not $79.97; (b) Anthropic is present at $200.00 (74%); (c) GitHub reads its invoiced $48.85, not the $59.15 line-item estimate. Vendor breakdown lists 4 vendors with spend (Cloudflare netted $0 and is excluded), sorted by spend, and the rows sum to the Summary Total. The subtitle reads 4 vendors, not 3. Result: (fill in)
Scenario 2: the self-disagreement is gone (Railway credit) โ
Steps: Same screen, July 2026. Compare Summary Total, the By-Vendor donut centre, and the sum of the vendor rows. Expected: All three agree (all are the reconciled $269.68). Railway's row reads $5.00 (its invoiced net), not $6.36. No screen shows the credit netted in one place and grossed in another. Result: (fill in)
Scenario 3: service / SKU donuts are labelled line-item detail โ
Steps: Same screen, read the Distribution section. Expected: "By vendor" ties to the Total and includes Anthropic. "By service (line items)" and "By SKU (line items)" carry the line-item qualifier, and the section subtitle states they cover only part of the Total (invoice-only vendors aren't itemised). Their centre totals (~$81.33) legitimately differ from the headline; the page says why. Result: (fill in)
Scenario 4: GitHub expand shows line-item detail, not a contradiction โ
Steps: Expand the GitHub row. Expected: GitHub's row spend is the invoiced $48.85; its service detail is line-item usage (~$59.15) and carries a caption noting it may differ from the invoiced spend above. It does not read as a silent contradiction. Result: (fill in)
Scenario 5: current month is labelled "month to date" โ
Steps: Set period to the current month. Expected: Summary Total hint reads "โฆ ยท month to date" (NOT "estimate"). The number is month-to-date actuals from the reconciled source, not a full-month projection: fact_cost_attributed has no future-dated rows. The label describes the WINDOW, not completeness: an invoice-only vendor whose bill has not posted (Anthropic's $200 for the live month) is still excluded, which stays tracked as #915, not solved by the label. Avg/day divides by ELAPSED days, and vs-Previous compares against the previous month prorated to the same elapsed days (no fake collapse). Result: (fill in)
Scenario 6: a closed prior month with only metered vendors still ties out โ
Steps: Step to a month with no invoice-only vendor (e.g. a month before Anthropic onboarded). Expected: Summary Total, By-Vendor donut, and vendor rows all agree. No regression for the common case. Result: (fill in)
Scenario 7: Reports and Overview can differ for the current month, BY DESIGN โ
A likely false bug report. It is expected, not a bug.
Steps: Open Billing โบ Overview, note the "Monthly / current month" figure. Open Billing โบ Reports, set the period to the current month, note the Summary Total. Expected: The two can legitimately differ. Both are month-to-date, but from different tables: Overview sums fact_cost_attributed to today; Reports' current month uses invoice_reconciliation (estimated), which covers a slightly different vendor set (e.g. a vendor with metered line items but no reconciliation row yet appears on Overview and not Reports). Reports' "month to date" label signals it is partial. If a future reader sees the two differ and is tempted to re-file #912, this scenario is the answer: the old code made them agree by cloning Overview's math into Reports, and that clone is exactly what dropped Anthropic and mis-netted the credit. Result: (fill in)
Scenario 8: sub-month ranges tell the truth about their grain (finding 1) โ
Steps: Set a custom range or a "Last 7/30/90 days" preset (any weekly/custom window). Expected: These ranges are served from fact_cost_attributed (net line items), NOT invoice_reconciliation. The Summary FYI reads "* Line-item totals, net of credits" and the tooltip says invoice-only vendors and reconciled totals are not reflected. The copy does NOT claim reconciliation, and the Total may be the #912-class line-item number (that is correct for this grain, and now labelled as such rather than mislabelled). Result: (fill in)
Scenario 9: a partial current month does not render a collapse (finding 3) โ
Steps: On the current (partial) month, read "vs Previous" and "Avg / day". Expected: vs-Previous does NOT show a large fake negative from comparing month-to-date against a full prior month; it compares against the previous month prorated to the elapsed days (tooltip states this). Avg/day uses elapsed days, not the full 31. Example (Aug 18): month-to-date $39.51 vs a prorated July, not the raw -85% vs July's full $269.68. Result: (fill in)
Verification method โ
Emulators do not carry the BigQuery billing marts, so the numbers are verified against BigQuery directly (queries above) plus a running-app browser pass on the deployed admin portal after merge (App Check blocks the client-Firestore/BQ-proxy reads headless pre-deploy). Unit tests cover the client transform (server numbers in โ rendered rows/donuts out) with a fixture built from the table above.