Annual bill extrapolation misuses cross-season and partial-month data #23

Open
opened 2026-07-17 03:06:26 +00:00 by claude-bot · 0 comments
Contributor

representativeMonthValue (src/lib/simulation.js:361-375) fills each missing calendar month by: same month → same season → any complete rows → all rows. With a single-season dataset (e.g. summer-only export), missing winter months resolve to the summer average — summer rates and AC load ×12 becomes the annual bill. Separately, monthCoverage (simulation.js:377-381) treats ≥90% coverage as complete but never scales the partial energy sum, and months below the threshold are used raw when no complete month exists.

Fix: (1) scale partial-month energy values by expectedHours/hours before use; (2) when a season has zero representative data, fall back to the parametric seasonal estimate for that season instead of cross-season row averages; (3) push a warning into the calculation output (warnings array already exists) when any month was synthesized, and surface it beside annual/payback figures. calculateDataConfidence in src/lib/analytics.js already computes the season-gap signal — consume it.

Test: dataset with only June–September rows must produce winter months near the parametric winter estimate, never near the summer average; a 27-day month must scale to ~30/27 of its raw sum.

Acceptance criteria

  • Partial months scaled by coverage before annualization
  • Zero-coverage seasons fall back to parametric seasonal estimates, not cross-season averages
  • Warning emitted and visible in UI when annual figures include synthesized months
  • Regression tests for summer-only and sparse-month datasets

Relations: relates to #11 (both touch simulation outputs)

(Audit finding F-02, 2026-07-16 — Severity: High, Confidence: High)

`representativeMonthValue` (`src/lib/simulation.js:361-375`) fills each missing calendar month by: same month → same season → any complete rows → all rows. With a single-season dataset (e.g. summer-only export), missing winter months resolve to the *summer* average — summer rates and AC load ×12 becomes the annual bill. Separately, `monthCoverage` (`simulation.js:377-381`) treats ≥90% coverage as complete but never scales the partial energy sum, and months below the threshold are used raw when no complete month exists. Fix: (1) scale partial-month energy values by `expectedHours/hours` before use; (2) when a season has zero representative data, fall back to the parametric seasonal estimate for that season instead of cross-season row averages; (3) push a warning into the calculation output (`warnings` array already exists) when any month was synthesized, and surface it beside annual/payback figures. `calculateDataConfidence` in `src/lib/analytics.js` already computes the season-gap signal — consume it. Test: dataset with only June–September rows must produce winter months near the parametric winter estimate, never near the summer average; a 27-day month must scale to ~30/27 of its raw sum. **Acceptance criteria** - [ ] Partial months scaled by coverage before annualization - [ ] Zero-coverage seasons fall back to parametric seasonal estimates, not cross-season averages - [ ] Warning emitted and visible in UI when annual figures include synthesized months - [ ] Regression tests for summer-only and sparse-month datasets **Relations:** relates to #11 (both touch simulation outputs) *(Audit finding F-02, 2026-07-16 — Severity: High, Confidence: High)*
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/BatteryStorageCalculator#23
No description provided.