Repository navigation
SPC: the sampled chart drawn, and a click opens the five bottles behind the dot - #147
Merged
Merged
Conversation
|
Thanks for the first pull request. Checks: every commit signed off ( |
…point
The SPC screen drew individuals only: a characteristic inspected five at a
time got a sentence saying so and no picture. It now draws both kinds from
one layout, chosen by the envelope's `kind` (decision 0040), and a click on
a sample opens the five readings behind it.
- `services/spc.py`: a `range_chart` block on a sampled chart, in the same
place and the same shape as `moving_range` is on an individuals one -
points, centre, limits, `stable` and its own verdict sentence. It carries
no `signals` key and says why: rule 5 on a sampled chart judges one
sample, so it is indexed by the same samples as rules 1 to 4 and arrives
in `signals` beside them. A second copy would report every firing twice.
- `services/spc_point.py`: `sample_dossier`, the same six questions asked
of a sample. The n readings with each one's distance from the sample mean
and the furthest named; both halves of the chart; the gauge; the machine;
the telemetry over the window the n readings span plus the minutes before,
with every one of them marked. Four helpers now take a value rather than a
check so one set of blocks serves both panels.
- `GET /quality/spc/{material}/{characteristic}/sample/{sample_id}`.
- `web/spc.js`: one `draw` for both kinds; the R half under the X-bar half
on the same x scale and in the same <svg>, so one export takes both; dots
carrying `data-sample`; the panel opening on a sample with `data-sample`
written last. The heading, legend, explainer, hint, panel title and idle
text follow `kind` with or without points. The characteristic list says
how many pieces at a time each one is inspected.
Signed-off-by: Scott Kalwei <scottkalwei@gmail.com>
Signed-off-by: scottkalw <scottkalwei@gmail.com>
The five tests that asserted the screen cannot draw an X-bar and R chart are now the tests that it draws one: fifteen dots on each half of one <svg>, the same sample under the same x, X̿ above and R̄ below, a verdict for each half, and a button that says what it opens. Two are new: one holds every word around the chart that is about which chart it is, and one holds those same words on a sampled characteristic with no samples yet - the state the lab's bottling plant was in on 2026-10-06 when it drew a fill-height chart under the heading INDIVIDUALS AND MOVING RANGE. And a file for the sample dossier, keyed on identity throughout: which sample is flagged, which bottle is furthest from its sample's mean, which stop the machine had just come out of. The ones worth naming: - the furthest bottle is the 147.0 mm one and the other four are not; five equal readings name none; two tied name both. - rule 1 fires about the average and the range chart stays quiet, and a sample spread wider than the rest is flagged on the lower half instead - both read from `spc.chart` rather than worked out a second time. - the window is the four minutes the five readings span plus the ten before, and every one of the five is marked on every trend. - a sample left off the chart says so rather than claiming no rule fired. One fix fell out of writing them: the readings table said nothing when every reading in a sample was identical, because its "none of them pulled it" sentence was conditioned on a list that is empty in exactly that case. Signed-off-by: Scott Kalwei <scottkalwei@gmail.com> Signed-off-by: scottkalw <scottkalwei@gmail.com>
Playwright over the sampled chart in the style of #143's: the dossier fetch held back a second and a bit through a route intercept, every assertion waiting on the panel's own data-sample rather than on the box that will hold it, and the plant underneath shaped like the bottling floor - fill height five bottles at a time, a filler that came back from a changeover running high, a stop nobody labelled, a nozzle pressure that stops partway through the window. Keyed on identity: one dot is flagged and it is the sample after the changeover, and the bottle that pulled its average is the 147.0 mm one. Signed-off-by: scottkalw <scottkalwei@gmail.com>
The sampled chart's entry said the screen did not draw it yet; it does. The control-chart page gains the paragraph and the screenshot, a section on what a dot means when it is an average, and the sample read beside the point read in the API block. The design note in spc.js named a lab plant by its registry name, which is the one thing src/ may not contain. Signed-off-by: scottkalw <scottkalwei@gmail.com>
Taken on a scratch bottling plant from the lab pack at 60x: sixteen samples of five bottles, the means above with their limits and the spread inside each sample below, and the panel open on one sample's five readings. Signed-off-by: scottkalw <scottkalwei@gmail.com>
…hat the stylesheet shouts Signed-off-by: scottkalw <scottkalwei@gmail.com>
kalwei
force-pushed
the
feat/spc-xbar-chart-and-panel
branch
from
October 6, 2026 17:06
eecb325 to
9263d76
Compare
…ey are wrong Both red cells on #147 were pre-existing tests that measure a small signal against a noise floor of the same size. Neither is in a file this change touches; both block a strict main, so they are fixed here rather than re-run until they pass. `test_the_heights_after_a_changeover_sit_half_a_millimetre_high` asked one sample of five either side of a changeover to see the pack's 0.5 mm offset. Five bottles at one weight stand 0.36 mm apart, so the standard error of that difference is about 0.23 mm and the assertion's whole tolerance was 0.35 mm - and more at night, where the pack widens the bench's spread from 0.10 mm to 0.25 mm, which is why it failed repeatably on any run the clock made a night shift. It now splits the means on the changeover over the run, which is what the pack promises in its own words, with the tolerance worked out from the pack's spreads rather than chosen: twelve samples a side and four standard errors of the difference. The shift it looks for is six standard errors out, so an offset that went missing is still caught. `test_a_login_answers_while_the_floor_screen_is_computing` asserted a one-second wall clock on a shared runner. Two things were wrong with the number. The refresh it was supposedly racing was never signed in, so it was a 401 that opened no transaction and held nothing - the test has been measuring a sign-in with nothing behind it since #80. And `TestClient` runs the app in this process, so a refresh that does compute starves the sign-in through the GIL and the number tracks how slow the runner is. Both clients now sign in, the screen is held open inside its own read transaction by a `time.sleep` that hands the GIL back, and the sign-in is measured against the screen's own time: it must answer before the screen is half way through. On a slower runner both numbers move together. Tests only; no behaviour changes. Signed-off-by: scottkalw <scottkalwei@gmail.com>
kalwei
added a commit
that referenced
this pull request
Oct 7, 2026
… window #143 and #147 both found the same hole in the same words: "the panel answers what happened at this station, and the planted cause is a station upstream." Four of the planted causes are findable at the station; the washer's stop and the line's changeover are not, because they happened on a machine beside it. So a `line` block on both dossiers: the other work units under the same parent, each with its stops and its changeovers in this window, read from the same timeline and split by the same `_stops` the station's own block uses. Nothing is inferred. Two things are recorded and both are used: every work unit has a parent, and a stretch of not running is an interval with a reason on it. There is no upstream and no downstream in it - bottling's own routing runs Inspect before Fill while the bottles come off the filler to the bench, so a step number from the routing orders the block and claims nothing else, and the block names the routing it read. Bounded the way the stops table and the analogs already are: the stations with something in the window are read in full to a ceiling, the rest are counted, and the ones that recorded nothing come back by name with the sentence saying that is a statement about records and not about the machine. Signed-off-by: scottkalw <scottkalwei@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Quality → SPC draws a sampled characteristic, and a click on a dot opens the
readings behind it.
Until now the screen drew one kind of control chart. A characteristic with a
sampling plan (#145) had its arithmetic, its samples and its verdict, and a
sentence saying the screen could not draw it. It draws it now:
X̿andX̿ ± A2·R̄,the spread inside each sample below with
R̄,D4·R̄andD3·R̄, thespecification limits where there are any, one dot per sample on each half.
It is the IMR layout from SPC: the moving-range half of the chart, drawn — an IMR chart #144, not a third one: the same two halves in one
<svg>, so one export takes both; the same selected-dot ring; the samepromise that the chart does not move when the panel opens.
reading and the panel opens that reading. On a sampled chart a point is an
average, so the panel opens the five bottles it is the average of — each
one's value, gauge, who took it, how far it sat from the sample's own mean,
and which of them is furthest out. That is the question an X-bar chart
cannot answer on its own: five bottles high is a filler setting, one bottle
high is a nozzle.
and every one of their stamps is marked on every trend — so which bottle was
measured while the nozzle pressure was falling is visible rather than
inferred. Everything else is Click a point on the SPC chart and see why it is there — no model, just the records #143's blocks asked of a sample: where it sat on
both halves and what fired, the gauge and its calibration, the machine's
state and what it had just come out of, the stops, maintenance, findings, and
each block's own coverage.
GET /quality/spc/{material}/{characteristic}/sample/{sample_id}is the sameread as an API, with the same
before_minutes/after_minutes/neighbour_hoursas the point read. Nothing in it is computed for the panel.One envelope key was added, as the brief allows: a top-level
range_chartblock on the chart read, beside
moving_range—centre,upper,lower,n,stable,verdict,flagged— so the lower half's words come from thesame place whichever kind of chart it is, with or without limits. It carries no
signals of its own: on a sampled chart rule 5 judges a sample and already
arrives in
signalsindexed by the same samples as rules 1–4, and a secondcopy would have a reader counting every range firing twice.
control.range_chart(the #145 contract) is unchanged, key for key.
And a caption bug the lab found. On 2026-10-06 the lab's bottling plant,
with no samples yet, drew
fill_heightunder the heading INDIVIDUALS ANDMOVING RANGE — 0 SAMPLES with the individuals legend and the individuals
explainer beneath it. The heading, the legend and both sentences now follow
kindwhether or not there is anything to draw. Decision 0040 made the charttype a property of the plan; a caption that does not follow it undoes that.
Why
Scott, 2026-10-06: "test if this type of graphing and functionality can apply
to a new type of SPC." It applies — the layout, the dot behaviour and the
panel carried over without a second design, and the one thing that genuinely
differs (a point is an average, so there are n readings and a window rather
than an instant) is two blocks and no new page.
How verified
pytest: full suite green. 33 new unit tests for the sample dossier(
test_a_sample_on_the_control_chart_carries_the_five_bottles_behind_it.py),the chart half of the sampled-screen tests rewritten, and 25 Playwright
tests in Click a point on the SPC chart and see why it is there — no model, just the records #143's style
(
test_clicking_a_sample_on_the_spc_chart_opens_the_five_bottles.py): thedossier fetch held back 1.2 s through a route intercept, every assertion
waiting on the panel's own
data-sample— which the page writes last —rather than on the box that will hold it; four themes with the palette
probe; an operator and a supervisor both open it. They key on identity: one
dot is flagged and it is the sample after the changeover, the bottle that
pulled its average is the 147.0 mm one.
OPC 4871, stopped afterwards through
fsmes plant … stop; the lab fleet's8090/8110/8120/4941/4942 and the demo's 9030/5433 were listening before and
after. Sixteen samples of five bottles, limits appearing on their own at
spc_min_points,X̿141.71,UCL143.46,R̄3.04,D4·R̄6.42, verdictin control but not capable (Cpk 0.69). Clicked; the panel opened on the
five readings with the furthest marked; added a four-at-a-time characteristic
with no readings at all and the heading, legend and both sentences were the
sampled ones.
(The image is in the diff at
docs/plant/images/spc-xbar-and-r.png; the figuresabove are the same run.)
The acceptance table from #143, extended
The bottling pack plants one fill-height story: the nozzle the changeover left
behind — for twenty-five line minutes after each changeover the sample means
sit about half a millimetre high while the ranges stay where they were.
X̿ ± 1.75 mm, becauseR̄is 3.04 mm. A planted 0.5 mm shift against a 3 mm within-sample spread is a sixth of the limit width. A reader sees the saw-tooth by eye; nothing on the screen points at itQI01, the inspection bench; the changeover is atFILL01. The machine block is the sample's own station's, so it shows the bench going idle between inspections, and the three trends areCameraLux,InspectMs,RejectPct. The filler is one machine upstreamHEIGHT-FILL-01, calibrated 2026-09-24, due 2027-03-23, resolution 0.1 mm against a 6 mm toleranceSo: not in one click, and the reason is the same one #143 found with the
washer. The panel answers what happened at this station, and the planted
fill-height cause is a station upstream — the link to the line's other stations
over the same window is on the board as its own item, and this PR does not
build it. The second finding is about the pack rather than the product: a
simulated bottle-to-bottle spread six times the shift it plants makes the
story invisible to any control chart, including a correct one.
Honest, about the screen
The panel is 4,114 px tall on a sample. That is not a regression — the same
plant's point panel is 4,140 px — but on a sample the first screenful is
now the readings table, so what the rules said starts below the fold. The
block that should be collapsed first is this station's process values
(1,122 px). Collapsing the lower blocks is the separate waiting item and is
deliberately not in here; it is needed.
Not in here
No model. No new tab, no new table, no migration. The upstream-station link and
the collapsing panel are both on the board. Count charts and quality-team limit
editing are out of scope.
control.range_chart, the individuals chart and thepoint dossier are unchanged.
The red cell on this PR, and the seventh commit that fixes it
Everything passed after the rebase except
test (windows-latest, 3.12), andthe two attempts failed on two different tests, neither in a file this
branch touches. Both are now fixed here, because both block a strict main and
a third re-run would only have found a third one.
tests/test_the_simulated_floor_plants_causes_an_engineer_can_find.py::test_the_heights_after_a_changeover_sit_half_a_millimetre_highasked one sample of five either side of a changeover to see the pack's 0.5 mm
offset: the planted lift measured 0.06 mm against a tolerance of 0.35 mm.
Five bottles held at one weight still stand about 0.36 mm apart, so the
standard error of that difference is about 0.23 mm — the assertion was roughly
one standard error wide. It is worse at night, where the pack widens the
bench's spread from 0.10 mm to 0.25 mm, which is why it failed repeatably:
checked out and run here it failed 12 times out of 12 on
mainat3bdc0886and on
f86ced15before it, because the clock made it a night shift. It nowtests what the pack actually promises, in the pack's own words — "a shift a
reader finds by splitting the means on the changeover, not one that trips a
control rule" — over the run: twelve samples a side, and the tolerance is
four standard errors of the difference worked out from the pack's own spreads
at the wider of the two shifts rather than a number chosen by hand. The shift
it is looking for is six standard errors out, so an offset that went missing
is still caught. Over ten seeds the answer landed 0.39–0.62 mm, at most 1.5
standard errors from 0.5.
tests/test_read_never_waits.py::test_a_login_answers_while_the_floor_screen_is_computingasserted a one-second wall clock (1.06 s on main's own
ubuntu-latest, 3.13in run 37497028434; 1.41 s here on
windows-latest, 3.12). I took the firstof the two options: the sign-in is now measured against the screen's own
measured time, not the clock — it has to answer before the screen is half
way through. That comparison only became true once the screen was genuinely
slow, and making it genuinely slow turned up the real bug in the test: the
refresh it was supposedly racing was never signed in, so it was a 401 that
opened no transaction and held nothing. The test has been measuring a sign-in
with nothing behind it since #80. Both clients now sign in, and the screen is
held open inside its own read transaction by a
time.sleep, which hands theGIL back — the other reason the old number drifted, since
TestClientrunsthe app in this process and a refresh that computes in Python starves the
sign-in thread regardless of any lock. The test asserts separately that the
screen really was held open, so the comparison has teeth.
Tests only in that commit; no behaviour changes, so no CHANGELOG entry.