MAINT-20 part 1: price_history.py comments cut; FCST-04 note gets the store details
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
c08eb124c8
commit
a3f725e8a4
@@ -188,3 +188,64 @@ still raises; so does the same published point emitted twice; and a carried
|
||||
point sharing an hour with a real print now raises too, which the count never
|
||||
looked at. Each of those three is driven by its own mutation in
|
||||
`test_price_forecast.py`.
|
||||
|
||||
## 5. Store details (moved here from `app/price_history.py` by MAINT-20)
|
||||
|
||||
**Why a file, and which one.** FCST-03 shipped the fill with no caller, so no
|
||||
series ever contained a carried point. Without a yesterday that survives a
|
||||
restart, the mornings after an operator touched something spend the whole
|
||||
window on the truncated series. The cache is `/data/price_history.json`, not
|
||||
`/data/state.json`: `Store.set()` rewrites the whole of state.json on each
|
||||
write, and the refresh runs every `TARIFF_REFRESH_S` (1800 s), so 48 extra
|
||||
rewrites a day would go through the file whose corruption reads as "no
|
||||
maintenance ever ran". Separate file, separate blast radius. The directory is
|
||||
not created (`tariff.PeakStore`'s rule): `/data` is always mounted, so a
|
||||
`makedirs` could only succeed where the durable volume is missing and build a
|
||||
cache on the ephemeral layer. "Cannot persist" is one question asked after the
|
||||
attempt: see [[FCST-06]]; `self._days` never holds a point the file does not.
|
||||
|
||||
**Brussels date, not UTC.** 23:30 UTC in July is already tomorrow in Belgium.
|
||||
`_previous_day()` subtracts on the local `date`, not the instant: the old
|
||||
`brussels(stamp) - timedelta(days=1)` took 24 absolute hours and, at
|
||||
2026-03-30T00:30+02:00, returned 2026-03-28 (a two-day-old profile flagged as
|
||||
yesterday's). Rows are keyed by instant, not local hour, so the two 02:00s of
|
||||
the 25-hour October day both survive and `fill_from_yesterday()`'s own rule is
|
||||
the only collapsing rule. The merge key is the validated instant; the old
|
||||
string key merged by spelling and sorted the `+01:00` 02:00 before the `+02:00`.
|
||||
`KEEP_DAYS` is a sort-order cap, not a horizon: nothing is deleted by arithmetic
|
||||
against the clock (a jumped clock, WITNESS-03, would otherwise delete everything
|
||||
on the one morning the fill is for), and the two dates `yesterday()` can be
|
||||
asked for are pinned on top of the cap, which can only retain.
|
||||
|
||||
**One validator (FCST-05, MEDIUM).** The read path was defensive about every
|
||||
field and the write path trusted `q["start"]` to be hashable and sortable. An
|
||||
int, null, dict or list `start` raised `TypeError` out of `carry()` on the day
|
||||
being written; `run_tariff_refresh` caught it, so the add-on stayed up and every
|
||||
refresh failed for good (the plan retires after `TARIFF_MAX_AGE_S`, arbitrage and
|
||||
the cheap window go off, and `_remember()` raised before `_save()`, so no
|
||||
self-heal). `_point_of()` is now the one standard for both paths and returns
|
||||
`None` rather than raising.
|
||||
|
||||
**A broken cache is a short horizon, never a controller that will not start**
|
||||
(6: absent machinery must not disable the loop). `PriceHistory(...)` and `.words`
|
||||
are unguarded in `Controller.__init__`. Shipped defects, all fixed:
|
||||
|
||||
- `_load()` caught `json.JSONDecodeError` and `OSError` only; one non-UTF-8 byte
|
||||
raised `UnicodeDecodeError` (a `ValueError`, not an `OSError`) and the add-on
|
||||
did not start (FCST-05, HIGH). The handler names the `ValueError` family. The
|
||||
schema version is compared with its type (`1.0 == 1` in Python).
|
||||
- `{"days": {"2026-01-14": "oops"}}` loaded as healthy and raised
|
||||
`AttributeError` out of `words`, `yesterday()` and `resolve_tariff()`.
|
||||
`_usable_days()` drops such records, and counts (not drops) malformed rows
|
||||
inside a kept record so the store stops reporting health it lacks.
|
||||
- `words` printed `f"{d} ({_n_points(rec)} h)"`: points under the hour's name
|
||||
(`'2026-01-13 (96 h)'` for a 24-hour quarter-hourly day, FCST-05 LOW). Both
|
||||
numbers are printed now; only rows that are points are counted. `words` must
|
||||
never raise, `_held()` is total.
|
||||
- A failed merge is undone from a deep copy (a one-level copy shares the day
|
||||
records the merge rebinds).
|
||||
|
||||
`NO_HISTORY` is a name, not `None`, so `history=` can be required: `None`
|
||||
meaning "skip the fill" is how a caller silently gets the truncated horizon.
|
||||
`carry()` records first, then fills, so the filled series exists only as a
|
||||
return value that the store never sees again (rule 1).
|
||||
|
||||
Reference in New Issue
Block a user