Skip to content

fix(dashboard): a CPR level must not run across a day it has no band for - #180

Merged
DoRmAmMu1997 merged 1 commit into
mainfrom
fix/cpr-day-ladder-gap
Sep 22, 2026
Merged

DoRmAmMu1997 merged 1 commit into
mainfrom
fix/cpr-day-ladder-gap

Conversation

@DoRmAmMu1997

Copy link
Copy Markdown
Owner

What

One set of CPR levels ran flat from 2026-09-16 all the way to 2026-09-21 on the 1m and 5m charts, instead of each day carrying its own. Two independent faults met.

Those days had no band. The ladder is built once, at startup, from the history CSV — whose last row was 2026-09-16 15:29. The candles beside it come from the runner's store instead: INTRADAY_LOOKBACK_DAYS=7 seeds it over REST and DASHBOARD_CHART_BARS=2200 keeps ~5.9 sessions. Nothing server-side ever extended the ladder from that store; cpr_segments had exactly one call site and publish_history is called once.

And the renderer ignored the span. A band covers its own session and nothing else — dashboard_history guarantees it and test_a_band_spans_only_its_own_session says so in as many words. A stepped line cannot express that on its own: it runs flat from one point to the next, so a band with no successor stretches over every day in between.

Fault two is the one that misleads — it draws the 16th's pivot over the 21st's session as if it were that session's level. Fault one is why there was a gap to bleed across. Both are fixed.

Fill what the data supports

The live payload now carries cpr_days: bands for the completed sessions the store still holds, built by dashboard_history.day_segments — the same shift-and-aggregate the CSV ladder uses, extracted so there is still exactly one CPR implementation rather than two that agree today.

Two exclusions, both deliberate:

  • a leading partial session, whose high and low describe only the part of the day the store reaches back to. Nothing marks a number as partial once it is a number, and the very next band would take its levels from it. The CSV path needs no such guard because a downloaded session is whole.
  • today, whose band the page already draws from the live cpr block — the one its caption describes.

A stored band wins on a date both hold. Memoized on the frame's session set, so it rebuilds about once a day, and it runs where _refresh_chart_cpr already has the frame — no extra store copy, and the "never reaches the broker or a mutating gate" assertion still holds.

Make the remainder honest

cprRuns cuts the levels into runs of adjacent periods, one series each, each ending on the last bar its final band owns. Consecutive days stay in one series so the vertical step between them is drawn as before; a run break is where the line simply stops.

A break is by period — the session on the minute timeframes, the month on Daily — and never by "any bar in between". The store's copy of a session can hold a bar the CSV's copy does not (a 15:30 print; here it was exactly one bar), and the two ladders meet precisely there. Splitting on that put a second set of price labels down the axis for a seam nobody can see.

Only the newest run keeps its axis labels, and both lastValueVisible and title have to go. The library draws two axis views per series and gates the second on "" !== title || seriesLastValueMode === 0 — lastValueVisible is nowhere in that test — so an older run with its title kept still printed R1 (chart) on the axis with no number beside it.

Rejected: one series per band (hundreds rebuilt on every scroll); computing the missing levels in JS (a second copy of the CPR algebra, the cost ADR-0017 exists to bound).

Verification

Scratch server on port 8799 in the operator's real shape — CSV ending 09-16, store covering 09-17 through today, each stand-in session a different day so every band's levels differ and a bleed would be visible rather than accidentally flat.

check result
per-day levels, 1m and 5m six blocks, each ~233 columns wide, 0.5 px of vertical travel (the broken build had one 458-column track drifting 43–66 px)
cpr_days in the payload 09-17, 09-18, 09-21, each with its own pivot; equal to dashboard_history.cpr_segments on the same frame
axis labels 9, for the 9 enabled levels, across 6 runs
honest gap with the store trimmed so 09-17 has no computable band, that day draws no CPR at all and its neighbours stop rather than reach across
Daily monthly bands over five years, caption matches the drawn pivot, VWAP still hidden
toggles follow immediately, both directions

Gates: 611 + 28 unittest, 1,582 pytest, coverage policy + 74.2 % locally, ruff, mypy ×2, compileall, bandit as CI runs it, pre-commit, node --check.

Worth knowing

There is now a test that EXECUTES the page's logic. test_a_cpr_level_never_runs_across_a_day_that_has_no_band lifts firstBarAtOrAfter, dayKey and cprRuns out of the asset by brace-matching and runs them under node -e, asserting that a gap day splits the runs, contiguous days do not, a bandless band neither draws nor splits, and the CSV/live seam does not split. Source assertions have let two rendering bugs through in a row — this is the first check that would have caught either. It pytest.skips where node is absent.

The practical remedy alone would not have been enough. python algo.py fetch-data brings the CSV up to date and the symptom goes away — until tomorrow. The chart has to be right with a stale CSV, because by the end of any week it will be.

🤖 Generated with Claude Code

On the 5m chart one set of levels ran flat from 2026-09-16 all the way to
2026-09-21 instead of each day carrying its own. Two faults met.

The days had no band. The ladder is built ONCE from the history CSV, whose
last row was 09-16, while the candles beside it come from the runner's store:
INTRADAY_LOOKBACK_DAYS seeds it with several days of REST history and
DASHBOARD_CHART_BARS=2200 keeps ~5.9 sessions. Nothing server-side ever
extended the ladder from that store.

And the renderer ignored the span. A band covers its own session -- the
server guarantees it and a test there says so -- but a stepped line runs flat
from one point to the next, so a band with no successor stretches over every
day in between.

Both are fixed.

The live payload now carries `cpr_days`: bands for the completed sessions in
the store, built by `dashboard_history.day_segments`, which is the same
shift-and-aggregate the CSV ladder uses rather than a second copy of it. A
stored band wins on a date both hold, because the CSV's sessions are complete
downloads while the store's oldest is only whatever its window reaches back
to; a store session that does not start at 09:15 is dropped rather than
believed, since nothing marks a high as partial once it is a number. Memoized
on the frame's session set, so it rebuilds about once a day.

`cprRuns` cuts the levels into runs of adjacent periods, one series each,
ending on the last bar the run's final band owns. A break is by PERIOD -- the
session on minutes, the MONTH on Daily -- never by any bar in between: the
store's copy of a session can hold a bar the CSV's copy does not, and the two
ladders meet exactly there. Days no band can be computed for now show no CPR,
which is the honest answer.

Only the newest run keeps its axis labels, and BOTH `lastValueVisible` and
`title` have to go: the library gates its second axis view on
`"" !== title || seriesLastValueMode === 0`, with `lastValueVisible` nowhere
in that test, so an older run still printed "R1 (chart)" with no number.

Verified on a scratch server in the operator's real shape -- CSV ending
09-16, store covering 09-17 through today. Six per-day blocks, each ~233
columns wide with 0.5px of vertical travel (the broken build had one
458-column track drifting 43-66px), nine axis labels for nine enabled levels,
and with the store trimmed so 09-17 has no computable band that day draws no
CPR at all. Daily still shows monthly bands against a matching caption.

`test_a_cpr_level_never_runs_across_a_day_that_has_no_band` lifts the pure
geometry out of the asset and runs it under node -- the first test here that
executes the page's logic rather than reading it, because source assertions
have now let two rendering bugs through. It skips where node is absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DoRmAmMu1997
DoRmAmMu1997 merged commit 5683853 into main Sep 22, 2026
7 of 8 checks passed
@DoRmAmMu1997
DoRmAmMu1997 deleted the fix/cpr-day-ladder-gap branch September 22, 2026 10:31
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