Skip to content

Consolidate PredictionSnapshotStore into the unified per-day DailyView format - #490

Open
johanzander wants to merge 9 commits into
mainfrom
feat/issue-409-prediction-snapshot-consolidation
Open

Consolidate PredictionSnapshotStore into the unified per-day DailyView format#490
johanzander wants to merge 9 commits into
mainfrom
feat/issue-409-prediction-snapshot-consolidation

Conversation

@johanzander

@johanzander johanzander commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Root cause

Per #409 (follow-up to #408's HistoricalDataStore persistence work): the "today" store landscape was still fragmented across three persistence formats/locations. PredictionSnapshotStore had its own file, discarded at midnight, separate from the DailyView schema HistoricalDataStore/DailyViewStore already share.

Design

Full design doc: docs/superpowers/specs/2026-08-06-prediction-snapshot-consolidation-design.md. Key points:

  • Shared _load_container/_write_container helpers in daily_view_store.py do an atomic (temp-file + os.replace) read-modify-write, so DailyViewStore (touches only "view") and PredictionSnapshotStore (touches only "snapshots") never clobber each other.
  • Deserialization helpers (_daily_view_from_dict, _period_data_from_dict) relocated from prediction_snapshot.py to daily_view_builder.py, which already owns the dataclasses they construct.
  • Legacy flat-file fallback: files written before this change (no "view"/"snapshots" wrapper) still load correctly.
  • Caught in final review, fixed before this PR: the original design's premise — "a new calendar day is naturally a new file, so the explicit midnight clear() call is redundant" — only held for the on-disk side. PredictionSnapshotStore is a long-lived process singleton; without a rollover mechanism in the in-memory cache itself, snapshots would have accumulated forever (unbounded growth, stale cross-day data leaking into API responses). Fixed with date-aware lazy rollover (_ensure_current_day(), checked before every state-touching method) instead of relying on an external clear() call.
  • Also caught in final review: two writers now sharing one file needed a real lock (not just atomic replace) plus unique temp filenames, to prevent a corruption/lost-update race between concurrent writers (quarterly scheduler tick vs. FastAPI request handler, different threads).

Test plan

  • ./scripts/quality-check.sh / .venv/bin/pytest -m "not slow" passes locally (1537 passed, 13 skipped)
  • .venv/bin/pytest -m slow passes locally (391 passed, 3 skipped)
  • New test coverage: test_prediction_snapshot_store.py (round-trip persistence, ordering, nearest-match, day-rollover with a single long-lived instance across a date change, concurrent-write locking with a real threading.Barrier-forced interleaving), extended test_daily_view_store.py (wrapped container shape, legacy-format fallback), regression test in test_bsm_settings_and_lifecycle.py proving the redundant clear() call is gone.
  • Local E2E verification (mock-HA + real backend via docker-compose.ci.yml, ci-growatt-vpp scenario, faketime-pinned to the scenario's date): ran a real optimization cycle, confirmed /data/daily_views/{date}.json contains both "view" and "snapshots" keys with real data (96 periods, 2 captured snapshots), confirmed GET /api/prediction-analysis/snapshots and /timeline return the expected shape, confirmed no legacy /data/bess_prediction_snapshots.json file is ever created.

Part of #409 — this PR covers PredictionSnapshotStore only. ScheduleStore consolidation is tracked as a follow-up PR against the same issue.

johanzander and others added 8 commits August 7, 2026 09:10
First of two planned PRs folding PredictionSnapshotStore and ScheduleStore
into the DailyView per-day file format, split by risk since ScheduleStore
sits on the hot path for inverter writes. This covers the lower-risk store.
… file (#409)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses final whole-branch review findings on the prediction snapshot
consolidation.

- PredictionSnapshotStore now tracks the calendar day its in-memory list
  belongs to and re-anchors on every public read/write. The store is
  created once per process, so folding persistence into the per-day file
  rolled the *file* over at midnight but left self._snapshots growing
  across days (unbounded rewrite churn plus cross-day leakage into the
  prediction-analysis API). clear() stays as an optional manual reset.
- Serialize the shared {date}.json load-mutate-write cycle behind a
  process-wide lock exported from daily_view_store (container_transaction
  / read_container), reused by both stores, and give each write a unique
  temp filename so concurrent writers cannot share a temp path.
- Best-effort cleanup of orphaned *.tmp siblings before each write; they
  are invisible to the *.json housekeeping globs.
- Refresh stale docstrings in both modules.

Tests: day-rollover regression on a single long-lived store with a moving
mocked date, and a threaded interleaved-write test asserting no lost
update. Both fail against the pre-fix code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Plan doc served its purpose driving subagent-driven implementation;
dropping it now that the code and tests are the source of truth. Spec
stays as the design record.
@johanzander
johanzander marked this pull request as ready for review August 7, 2026 15:16
…er race

container_lock only serialized the shared file's read-modify-write cycle,
not self._current_date/self._snapshots. A scheduler-thread call straddling
midnight could have its snapshot appended to the wrong day's in-memory list
after a concurrent request-thread call rolled the store over, silently
misfiling or losing it. Add an instance-level lock around each public
method's full ensure-day/mutate/save critical section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant