Merge pull request 'TEST-17: grade the status page's peak row at a stated month, not at today's' (#26) from TEST-17 into release/1.0
This commit was merged in pull request #26.
This commit is contained in:
@@ -2,6 +2,41 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
**`release/1.0` went red at midnight on a commit nobody had touched (TEST-17).**
|
||||
`Controller.checks()` read the capacity-tariff peak with `self.peak.peak()` - no
|
||||
argument - so `tariff.py` resolved the month from `datetime.now()`, while
|
||||
`test_tariff.py` section 10 banks its quarter at the fixed fixture
|
||||
`2026-08-30`. From 1 September the page was correctly asked for September,
|
||||
correctly answered `NO_RECORD`, and the row read *"no completed quarter recorded
|
||||
yet"* - a string containing no `"peak"`, which the section's filter required.
|
||||
Four checks failed. The suite had been a latent time bomb since TARIFF-01
|
||||
merged on 2026-08-30 and every PR that ran it ran in August.
|
||||
|
||||
Isolated by holding the commit and the image constant and moving only the clock
|
||||
under `libfaketime`: green at 2026-08-30, four failures at 2026-09-01, identical
|
||||
at `f4914a1`, `04292c4~1` and `04292c4`. The calendar, not a regression.
|
||||
|
||||
**The product was right and the test was wrong**, so the fix does not change what
|
||||
the live page says. `checks()` takes `when: datetime | None = None` and threads
|
||||
it into `peak()` and `warning_frac()` - the convention every other peak query in
|
||||
`tariff.py` already follows. The single production caller passes nothing and
|
||||
still means today. Section 10 now grades at its own fixture's month.
|
||||
|
||||
Three new checks make the defect detectable rather than seasonal: the same
|
||||
controller read at two different reference months must produce two different
|
||||
rows, which no clock can satisfy; the later month must read `NO_RECORD` and stay
|
||||
distinguishable from both an observed peak and an unreadable store; and calling
|
||||
`checks()` with no argument must still mean today. Verified by mutation - a
|
||||
`checks()` that drops `when` is now red at every clock tried, where the old
|
||||
section went green in August. Full suite green, 15 files, at the real clock and
|
||||
at faked clocks in four other months.
|
||||
|
||||
One thing left alone deliberately: only the UNREADABLE branch prefixes its row
|
||||
with `capacity-tariff peak:`. The `NO_RECORD` row carries neither that phrase
|
||||
nor the word `peak`, which is what made the original filter brittle and what
|
||||
broke the first draft of the new checks too. It is product text with other
|
||||
suites reading it, so it is noted here rather than changed.
|
||||
|
||||
**Thirty days of the real meter, and the acceptance criterion three cards
|
||||
deferred is now a number (DATA-01).** TARIFF-01 and SAFETY-07 both merged with
|
||||
AC 3 - *"monthly peak held under the soft limit in >= 95 % of intervals over a
|
||||
|
||||
@@ -1270,7 +1270,20 @@ class Controller:
|
||||
self.mqtt.close()
|
||||
|
||||
# -- UI ------------------------------------------------------------------
|
||||
def checks(self) -> list:
|
||||
def checks(self, when: datetime | None = None) -> list:
|
||||
"""The status rows. `when` is the reference time for anything billed by
|
||||
the calendar - today's month when it is None, which is what the live
|
||||
page means; `status_payload()` calls this with no argument.
|
||||
|
||||
⚠️ IT EXISTS SO THE PEAK ROW CAN BE GRADED (TEST-17). Every other peak
|
||||
query already takes it - `PeakStore.peak()`, `PeakTracker.peak()`,
|
||||
`headroom_w()`, `warning_frac()` - and the row built from them was the
|
||||
one thing that could only be read against `datetime.now()`. A suite
|
||||
that banks an August quarter then asks this page what the peak is goes
|
||||
green in August and red in September, which is what happened at 00:00
|
||||
on 2026-09-01: TARIFF-01's own section 10, red on a calendar boundary,
|
||||
on a commit nobody had touched for a day.
|
||||
"""
|
||||
o = self.o
|
||||
out = []
|
||||
if self.p1_enabled:
|
||||
@@ -1331,14 +1344,14 @@ class Controller:
|
||||
# a normal state on the 1st; "unreadable" is a broken instrument, and
|
||||
# a status page that spelled them the same way would let a 0 W read
|
||||
# look like a quiet month right up until SAFETY-07 shaved nothing.
|
||||
_pk = self.peak.peak()
|
||||
_pk = self.peak.peak(when)
|
||||
if _pk.is_unreadable:
|
||||
out.append({"ok": False, "warn": True,
|
||||
"text": "capacity-tariff peak: " + _pk.explain()})
|
||||
else:
|
||||
# §9.1's "warning entity at 80 %", as a status row rather than
|
||||
# a second entity - see the constructor on not doubling up.
|
||||
_frac = self.peak.warning_frac() or 0.0
|
||||
_frac = self.peak.warning_frac(when) or 0.0
|
||||
_ok = _frac < 0.8
|
||||
out.append({"ok": _ok, "warn": not _ok,
|
||||
"text": _pk.explain()
|
||||
|
||||
@@ -863,7 +863,12 @@ check("...and syncing the same quarter again banks nothing new, so the count "
|
||||
check("...and the peak is unchanged by the repeat",
|
||||
near(c.peak.peak(B).peak_w, 4998.0, 1e-6))
|
||||
|
||||
rows = [r for r in c.checks() if "capacity-tariff" in r["text"] or "peak" in r["text"]]
|
||||
# ⚠️ `when=B`, AND THAT ARGUMENT IS THE TICKET (TEST-17). Without it `checks()`
|
||||
# reads the peak for `datetime.now()`'s month while this fixture banked its
|
||||
# quarter in August, so every check below passed in August and failed from
|
||||
# 2026-09-01 - on a commit nobody had touched. The suite must grade the month
|
||||
# it created, not the month the machine happens to be running in.
|
||||
rows = [r for r in c.checks(when=B) if "capacity-tariff" in r["text"] or "peak" in r["text"]]
|
||||
peak_rows = [r for r in rows if "peak" in r["text"] and "quarter(s) banked" in r["text"]]
|
||||
print(" status row: " + (peak_rows[0]["text"] if peak_rows else "(none)"))
|
||||
check("the status page carries exactly one capacity-tariff peak row",
|
||||
@@ -879,7 +884,7 @@ check("...and it is FLAGGED, because 4998 W is over §9.1's 80 % warning point "
|
||||
|
||||
# The half that matters: the same page with a store that cannot answer.
|
||||
c2 = built(TMP / "no-such-dir" / "nope.db")
|
||||
u_rows = [r for r in c2.checks() if "capacity-tariff peak" in r["text"]]
|
||||
u_rows = [r for r in c2.checks(when=B) if "capacity-tariff peak" in r["text"]]
|
||||
print(" status row (unreadable store): "
|
||||
+ (u_rows[0]["text"] if u_rows else "(none)"))
|
||||
check("an unreadable store gets its OWN row, saying UNREADABLE", len(u_rows) == 1)
|
||||
@@ -891,6 +896,58 @@ check("...and the healthy row and the unreadable row are different text",
|
||||
bool(u_rows) and bool(peak_rows)
|
||||
and u_rows[0]["text"] != peak_rows[0]["text"])
|
||||
|
||||
# --- TEST-17: the page must be graded at a stated time, and prove it ----------
|
||||
# Three ways this can regress, so three checks. The first two would both go
|
||||
# green again if `checks()` quietly went back to reading the clock, which is
|
||||
# exactly the mutation this section could not previously detect.
|
||||
|
||||
# ⚠️ SELECTED ON "quarter(s) banked", NOT ON "peak". The NO_RECORD row reads
|
||||
# "2026-10: no completed quarter recorded yet - ..." and contains neither the
|
||||
# word "peak" nor "capacity-tariff" - only the UNREADABLE branch prefixes the
|
||||
# family name. Filtering these rows the way the checks above do would drop the
|
||||
# row and then grade its absence, which is the very mistake this ticket fixes.
|
||||
# (That asymmetry in the row's own text is worth a look on its own; it is what
|
||||
# made the original filter brittle. Not changed here - it is product text with
|
||||
# other suites reading it.)
|
||||
_peak_rows = lambda rows: [r for r in rows if "quarter(s) banked" in r["text"]]
|
||||
|
||||
_later = B + timedelta(days=45) # same fixture, a later billing month
|
||||
_other_peak = _peak_rows(c.checks(when=_later))
|
||||
print(f" the SAME controller read at {_later.date()}: "
|
||||
+ (_other_peak[0]["text"] if _other_peak else "(no peak row)"))
|
||||
check("asked for a LATER month, the same controller reports that month's "
|
||||
"absence of a quarter, not August's 4998 W - the row follows `when`",
|
||||
bool(_other_peak) and "4998 W" not in _other_peak[0]["text"]
|
||||
and "no completed quarter recorded yet" in _other_peak[0]["text"])
|
||||
check("...and that is NO_RECORD, distinguishable from BOTH an observed peak and "
|
||||
"an unreadable store - a month with no quarter is not a broken instrument",
|
||||
bool(_other_peak) and bool(peak_rows)
|
||||
and "UNREADABLE" not in _other_peak[0]["text"]
|
||||
and _other_peak[0]["text"] != peak_rows[0]["text"])
|
||||
check("...and it is not FLAGGED, because an empty month is not an exceedance",
|
||||
bool(_other_peak) and _other_peak[0]["ok"] and not _other_peak[0]["warn"])
|
||||
|
||||
# The mutation guard. `checks()` ignoring `when` is the whole defect, and it is
|
||||
# invisible to every check above whenever the machine's clock happens to sit in
|
||||
# the fixture's own month - which is how this shipped. Grading two DIFFERENT
|
||||
# reference months against each other cannot be satisfied by a clock at all.
|
||||
check("the peak row differs between two reference months, so a `checks()` that "
|
||||
"dropped `when` and read datetime.now() would fail HERE regardless of "
|
||||
"what day the suite is run on",
|
||||
bool(peak_rows) and bool(_other_peak)
|
||||
and peak_rows[0]["text"] != _other_peak[0]["text"])
|
||||
|
||||
# And the production meaning is unchanged: no argument still means today.
|
||||
_now_peak = _peak_rows(c.checks())
|
||||
_today_is_fixture_month = month_key(datetime.now(timezone.utc)) == month_key(B)
|
||||
check("calling checks() with NO argument still reads the month being billed "
|
||||
"today - the live status page's meaning is untouched",
|
||||
bool(_now_peak) and (bool(peak_rows)
|
||||
and _now_peak[0]["text"] == peak_rows[0]["text"]
|
||||
if _today_is_fixture_month
|
||||
else "no completed quarter recorded yet"
|
||||
in _now_peak[0]["text"]))
|
||||
|
||||
# THE MEASUREMENT IS OF DELIVERED POWER (SAFETY-20). Graded on the parsed
|
||||
# SYNTAX, not on the text: the module docstring argues this at length and names
|
||||
# every forbidden word while doing so, so a grep over the source would be
|
||||
|
||||
Reference in New Issue
Block a user