Server-side calculations are computed in the container timezone, shifting all TOU windows #22

Open
opened 2026-07-17 03:06:20 +00:00 by claude-bot · 1 comment
Contributor

The shared calculation runtime derives season, weekday, holiday, and TOU hour from local-time Date methods (src/lib/simulation.js:316, src/lib/rates.js:135-141). Usage timestamps travel to the backend as UTC instants (src/lib/homePersistence.js:210, timestamptz column in 001_initial.sql:87), and the backend re-runs the same runtime (backend/src/calculationRunner.js:30) in whatever timezone the container has — which is UTC for node:20-alpine, and nothing in the repo (Dockerfile, backend/Dockerfile, either compose file, .env.example) sets TZ.

Result: server-side calculations — the authoritative path since the server-calculation milestone — bucket every hour 5–6 hours off for a Missouri home. On-peak/off-peak assignment, weekend/holiday detection, month grouping, and season edges are all wrong, and server results silently diverge from the in-browser fallback. Backend tests can't see it because expected values are computed in the same (shifted) process.

Fix in two steps:

  1. Stopgap: add TZ=America/Chicago to the backend image/compose environment.
  2. Real fix: make hour/day/month extraction timezone-explicit in the shared runtime — resolve timestamps with Intl.DateTimeFormat against a per-home IANA zone (default America/Chicago on the home profile) instead of Date#getHours() etc. Add a regression test that runs the simulation with process.env.TZ='UTC' and asserts identical output to a Central-time run.

After deploying either step, force-recompute stored calculation results and re-generate report snapshots — existing cached outputs were computed with shifted buckets.

Acceptance criteria

  • Backend deployment pins or ignores host timezone (stopgap TZ env committed)
  • Shared runtime produces byte-identical output under TZ=UTC and TZ=America/Chicago (test exists and runs in CI)
  • Existing calculation results/report snapshots recomputed after the fix
  • README/deploy docs note the timezone contract

Relations: relates to #8 (deployment config), #16 (cached results may need invalidation after fix)

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

The shared calculation runtime derives season, weekday, holiday, and TOU hour from local-time `Date` methods (`src/lib/simulation.js:316`, `src/lib/rates.js:135-141`). Usage timestamps travel to the backend as UTC instants (`src/lib/homePersistence.js:210`, `timestamptz` column in `001_initial.sql:87`), and the backend re-runs the same runtime (`backend/src/calculationRunner.js:30`) in whatever timezone the container has — which is UTC for node:20-alpine, and nothing in the repo (`Dockerfile`, `backend/Dockerfile`, either compose file, `.env.example`) sets `TZ`. Result: server-side calculations — the authoritative path since the server-calculation milestone — bucket every hour 5–6 hours off for a Missouri home. On-peak/off-peak assignment, weekend/holiday detection, month grouping, and season edges are all wrong, and server results silently diverge from the in-browser fallback. Backend tests can't see it because expected values are computed in the same (shifted) process. Fix in two steps: 1. Stopgap: add `TZ=America/Chicago` to the backend image/compose environment. 2. Real fix: make hour/day/month extraction timezone-explicit in the shared runtime — resolve timestamps with `Intl.DateTimeFormat` against a per-home IANA zone (default `America/Chicago` on the home profile) instead of `Date#getHours()` etc. Add a regression test that runs the simulation with `process.env.TZ='UTC'` and asserts identical output to a Central-time run. After deploying either step, force-recompute stored calculation results and re-generate report snapshots — existing cached outputs were computed with shifted buckets. **Acceptance criteria** - [ ] Backend deployment pins or ignores host timezone (stopgap TZ env committed) - [ ] Shared runtime produces byte-identical output under TZ=UTC and TZ=America/Chicago (test exists and runs in CI) - [ ] Existing calculation results/report snapshots recomputed after the fix - [ ] README/deploy docs note the timezone contract **Relations:** relates to #8 (deployment config), #16 (cached results may need invalidation after fix) *(Audit finding F-01, 2026-07-16 — Severity: Critical, Confidence: High)*
Author
Contributor

Sequencing (from the 2026-07-16 audit roadmap assessment): this issue leads the v1.1.1 Calculation Correctness slice, which should land before or alongside the remaining v1.1.0 items (#7, #8, #9).

Recommended order:

  1. This issue's step 1 (the one-line TZ=America/Chicago stopgap) — ship immediately, ahead of everything else.
  2. Then the rest of v1.1.1: #23 (annualization), #24 (billing-period alignment), #37 (value-level regression tests — write these against the fixed behavior).
  3. The full timezone-explicit fix (step 2 here) can land with or after #37 so the TZ=UTC regression test pins it.

Rationale: every other number in the app depends on the authoritative engine bucketing hours correctly; fixing anything downstream first means re-verifying it later.

**Sequencing (from the 2026-07-16 audit roadmap assessment):** this issue leads the v1.1.1 Calculation Correctness slice, which should land **before or alongside the remaining v1.1.0 items** (#7, #8, #9). Recommended order: 1. **This issue's step 1** (the one-line `TZ=America/Chicago` stopgap) — ship immediately, ahead of everything else. 2. Then the rest of v1.1.1: #23 (annualization), #24 (billing-period alignment), #37 (value-level regression tests — write these against the fixed behavior). 3. The full timezone-explicit fix (step 2 here) can land with or after #37 so the TZ=UTC regression test pins it. Rationale: every other number in the app depends on the authoritative engine bucketing hours correctly; fixing anything downstream first means re-verifying it later.
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#22
No description provided.