refactor(regime): collapse the monitor page, fix the OAS rebuild window

The page had twelve stacked blocks, several of them different views of the
same numbers. The quadrant plot and the score-history chart drew the same two
series from the same query key, which read as two datasets; they are now one
card with a Time | Path toggle. The two pillar disclosures become one grouped
table, and three prose blocks (data quality, basket, coverage) become one
provenance chip strip. Page text is now limited to what changes how the reader
interprets today's number; the rest moved to the methodology doc.

Removes three stale-threshold bugs of one class. The quadrant fell back to v2's
60/60 dividers when quadrant_config was absent -- the real values are 50/40 and
they feed alert_service, so the chart could disagree with what actually fires.
The gauge fell back to v2's 30/60/80 band ticks, and drew a divider line that
always landed on its own "elevated" tick. The time series' reference lines were
at 30/60/80, which correspond to nothing in v3; they are now per-axis dashed
lines read from the same quadrant_config. Rendering also surfaced a live
clipping bug inherited from the old chart: margin.left -18 against YAxis
width 28 left ~10px for a 3-digit label, so every Y tick was cut off.

HY_OAS_WINDOW_DAYS was 400 *calendar* days while a rebuild replays
REBUILD_SESSIONS = 400 *trading* sessions (~579 calendar days), so the oldest
~180 days of any rebuild got no OAS at all and both credit sensors returned
None. State then lands at 80% coverage and Warning at exactly MIN_COVERAGE, so
both still publish bands -- a series that looks homogeneous while its oldest
rows were scored without credit. Widened to 700. This needs no methodology
bump: C1 reads [-1] and W3 reads [-21], both from the end, so widening only
prepends and every live score is bit-identical. Sequenced deliberately, since
acting on the open findings below bumps METHODOLOGY and fires the rebuild.

A just-collected fundamental observation was hidden until its effective date --
one day, three over a weekend -- because the live reading called the
point-in-time function, so refreshing appeared to do nothing. That was the
opposite of what the doc claimed. fundamental_overlay stays the gated record
(it runs for every replayed date during a rebuild); current_observation is the
live reading and reports the effective date instead of blanking the content.
Nothing in the overlay is scored, so showing it early cannot reach a published
number.

Documents four calculation findings. Three are not implemented, since each
changes a published score and so requires a v4 cut: State's top band is a
credit-event band (credit returns 0.0 rather than None below the 3.5 anchor, so
it is pinned at zero at weight 20 -- with everything else pegged State computes
to exactly 80.0, the breaking threshold); V1 saturates at VIX 30; and the
deliberate max(P1,P2,P3) defeats P3's anchoring because P1 is binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 17:53:45 +02:00
co-authored by Claude Opus 5
parent 23de8c9540
commit 46ace501a2
8 changed files with 646 additions and 434 deletions
+90 -3
View File
@@ -140,13 +140,34 @@ The fundamental overlay keeps its effective date (normally the next session afte
collection) and is never replayed backward, so a rebuild cannot stamp today's
observation onto historical snapshots. Because the observation is stored in a
single slot, a refresh replaces the previously effective record: the snapshot
therefore reports the overlay as `pending` until the new effective date, and the
live reading additionally carries `fundamental_context` so a just-collected
observation is visible immediately rather than appearing to have done nothing.
therefore reports the overlay as `pending` until the new effective date.
Two functions, deliberately: `fundamental_overlay` is the **record** and keeps
the gate — it runs for every replayed date during a rebuild, so it must never
grow a bypass flag. `current_observation` is the **live reading** behind
`fundamental_context`, and *reports* the effective date instead of blanking the
content.
Until 2026-08-07 the live reading called the gated function, so a just-collected
observation stayed hidden until the next weekday — three days over a weekend —
and refreshing appeared to do nothing. That was the opposite of what this section
already claimed. Showing it early cannot leak into a published number, because
nothing in the overlay is scored (see "Fundamentals left the score").
Each snapshot stores the fixed basket symbols, hash, and freeze date.
Reconstructed history before that freeze date is retrospective/exploratory.
## Presentation
The page is deliberately thin: two gauges, one chart card, one pillar table, the
overlay, and a provenance strip. Time and Path are two projections of the same
snapshot series and share one card and one query key — they were previously two
panels, which read as two datasets. Methodology rationale lives in this document,
not on the page; page text is limited to what changes how the reader interprets
today's number. The quadrant dividers rendered in Path view come from
`quadrant_config` and are the same constants the alert path consumes
(`alert_service`), so the chart cannot drift from what actually fires.
## Warning study
The study calls the outcome a **10% correction**, not a regime break. The first
@@ -194,6 +215,72 @@ coverage bias for a regime-selection bias. The honest position is that the
threshold is hypersensitive to window choice at this sample size; the report
states its limits rather than pretending to a precision it does not have.
## Open calibration questions
Raised 2026-08-07 during the page refactor. **None are implemented.** Each one
changes a published score, so acting on any of them means cutting `METHODOLOGY`
to v4 — which reseeds 400 sessions and discards the cached event study. They are
recorded here rather than hand-patched into v3.
**1. State's top band is a credit-event band.** `f2_credit_spreads` returns
`0.0` — not `None` — for any OAS below the 3.5 mild anchor, so credit stays
*available* at weight 20 and is not renormalized out. It is simply pinned at
zero. Verified: with price, breadth and volatility all pegged at 100 and OAS at
the cutover's 2.77, State computes to exactly **80.0** at 100% coverage — the
"breaking" threshold to the decimal. So the top State band requires either a
credit event or all three remaining pillars simultaneously at maximum. A pure
AI/Tech drawdown with calm credit — the scenario this monitor exists to
measure — cannot print it with anything to spare. Anchors-only credit was
nonzero on 27 of 408 calibration sessions, so that 20-point weight sits at zero
roughly 93% of the time. This is structurally the same defect v3 corrected on
the Warning axis ("the upper half of the Warning axis was unreachable"), and it
means the State bands were fit against a v2 credit distribution that v3 no
longer produces.
**2. V1 saturates at VIX 30.** `(vix - 15) / 15 * 100` reaches 100 at VIX 30 and
has no resolution above it: VIX 30, 50 and 82 all score identically. That is the
same failure mode, at a similar percentile, as the `dd_pct * 5` formula this
version replaced for pegging at a 20% drawdown. If addressed, it should get an
anchor table in the P3 style rather than a rescaled slope.
**3. `max(P1, P2, P3)` defeats P3's anchoring.** The `max` is deliberate ("one
capped vote for correlated reads"), but `_under_200` is binary, so P1 prints 100
whenever SMH and QQQ are both below their 200-DMA. P3's anchor ladder therefore
only resolves anything while price is *above* the 200-DMA — that is, before the
drawdown it measures is underway. Note also that "P3's realized share of State
falls from 65% to 40%" is argmax-share accounting, which is a slippery statistic
under `max()`.
## Fixed 2026-08-07: the OAS fetch window did not cover a rebuild
`HY_OAS_WINDOW_DAYS` was 400 **calendar** days, but a rebuild replays
`leader_series[-REBUILD_SESSIONS:]` — 400 **trading** sessions, about 579
calendar days. The oldest ~180 calendar days of any rebuild therefore got no OAS
data at all, so `f2_credit_spreads` and `w3_credit_impulse` both returned `None`.
Verified: State then lands at 80% coverage and Warning at exactly 75.0% —
`MIN_COVERAGE` — so **both still publish bands**. The rebuilt series would look
homogeneous while its oldest rows had been scored without credit, the tell being
a null `data_quality.credit_history_days` on exactly those rows.
The window is now 700 days: it must cover the oldest replayed date (~579) plus
W3's lookback and slack, while staying under ICE's ~3-year cap so FRED still
honours the request. This required **no methodology bump** — C1 reads
`oas_values[-1]` and W3 reads `oas_values[-21]`, both indexed from the end, so
widening only prepends older observations and every live score is bit-identical.
Confirmed by evaluating both windows against a varying synthetic series: today's
C1/W3 match exactly, while the oldest rebuild row goes from `None`/`None` to real
values.
Expect `credit_history_days` on new snapshots to rise from ~400 to ~700. That is
the widened request, not new upstream history — and it makes the chip a better
truncation canary, since a 700-day request returning ~1095 days' worth is now
the visible ceiling.
The fix was sequenced deliberately: acting on items 13 above bumps
`METHODOLOGY`, which fires `rebuilding`, which would have baked the credit-less
rows into the fresh series. Fixing the window afterwards would mean reseeding
twice.
## Operator rule
Quadrant alerts default off for new/reset configurations. When enabled they