You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
min_n goes to 1 — better once than never. A key observed a single time should be visible as a statistic; the plates are what they are, and withholding the only evidence there is helps nobody.
The concern the original issue raised stays valid, but it is answerable now, because it was really two questions wearing one number:
Seeing a median is measurement — nothing renders, so there is no reason to hide an n=1 row.
So the gate splits: min_n = 1 at the aggregate layer, and the caution moves to the apply, where a human is already looking at the numbers and pressing a button. That is the same doctrine the rest of the pipeline follows — measuring layers show everything, the rendering step is deliberate.
What to implement
min_n default 1 for POST /hands/{id}/aggregates/rebuild (api/routers/aggregates.py), matching the pair rebuild which already defaults to 1 for the same "pairs are sparse" reason. The gate itself stays in core/aggregate.py::aggregate_instances and keeps counting skipped["below_min_n"] for whatever threshold is passed.
Low-n is flagged where it can do harm — in the apply dialog's preview table, not in the statistics. A row with n below a small threshold (2? 4?) gets a visible marker, so "this running form is one occurrence's idiosyncrasy" is stated at the moment of the decision rather than buried in a column.
MAD is undefined at n=1 — the letter statistics block and the sketch must not print a spread they do not have (the codebase already has this rule: "an absent MAD prints no ± clause"). Check LensStats handles n=1 rows without drawing a zero-radius circle that reads as "no spread".
Still open: how far a small-n median should travel
Lowering the gate makes the keys visible; it does not answer how much a 1–3 occurrence median should be trusted when it is applied. Option (b) from the original proposal remains the interesting one and is now independent of the gate:
shrinkage toward the chart form — blend median and chart anchors by n/(n+k), so small-n keys move partially and never wildly; fits the H1 doctrine that variant 100 is a derivation, and touches only core/aggregate.py plus the apply step.
With min_n = 1 plus shrinkage, a capital seen once moves a little toward its observed form instead of either not existing (today) or jumping fully onto a single sample (naive n=1). Measure with the wordbench guard; metric and fixtures stay frozen, headline same-hand.
Options (a) per-key override and (c) accept-and-document are superseded: (a) is the special-casing H3 wants to shrink, and (c) is no longer needed once the keys exist at all.
Original context
POST /hands/{id}/aggregates/rebuild defaults to min_n=4 (api/routers/aggregates.py:93); the gate itself lives in core/aggregate.py::aggregate_instances (line 106, counter skipped["below_min_n"]). Only a key that reaches four clean occurrences gets an aggregate — and only an aggregate can feed apply-laufform.
The Abb. 19/20 plates give n = 4–39 per letter, most ≤ 20, and nearly every capital stays below 4 (handmodell-stufenplan.md §4, H5). Those keys never get an aggregate, hence never a derived Laufform, hence render chart-true while their neighbours in the same fluent run use the widened running form (core/compose.py ~line 1421, run ≥ ASCENDER_LEAN_MIN_RUN). That is a systematic rhythm break in exactly the words that carry a capital — the word start, where it is most visible.
Size & dependencies
Small now that the decision is made: a default change plus a chip in the apply dialog. The shrinkage question above is a separate, medium follow-up. Related: #270 (the apply surface this leans on, shipped in #277), #278 (a word-level chain fit would raise the clean-fit yield and push more keys over any threshold).
Decision (2026-08-03, author)
min_ngoes to 1 — better once than never. A key observed a single time should be visible as a statistic; the plates are what they are, and withholding the only evidence there is helps nobody.The concern the original issue raised stays valid, but it is answerable now, because it was really two questions wearing one number:
LaufformApplyDialog), which already listsn_instancesper glyph before anything is written.So the gate splits:
min_n = 1at the aggregate layer, and the caution moves to the apply, where a human is already looking at the numbers and pressing a button. That is the same doctrine the rest of the pipeline follows — measuring layers show everything, the rendering step is deliberate.What to implement
min_ndefault 1 forPOST /hands/{id}/aggregates/rebuild(api/routers/aggregates.py), matching the pair rebuild which already defaults to 1 for the same "pairs are sparse" reason. The gate itself stays incore/aggregate.py::aggregate_instancesand keeps countingskipped["below_min_n"]for whatever threshold is passed.LensStatshandlesn=1rows without drawing a zero-radius circle that reads as "no spread".Still open: how far a small-n median should travel
Lowering the gate makes the keys visible; it does not answer how much a 1–3 occurrence median should be trusted when it is applied. Option (b) from the original proposal remains the interesting one and is now independent of the gate:
With
min_n = 1plus shrinkage, a capital seen once moves a little toward its observed form instead of either not existing (today) or jumping fully onto a single sample (naive n=1). Measure with the wordbench guard; metric and fixtures stay frozen, headline same-hand.Options (a) per-key override and (c) accept-and-document are superseded: (a) is the special-casing H3 wants to shrink, and (c) is no longer needed once the keys exist at all.
Original context
POST /hands/{id}/aggregates/rebuilddefaults tomin_n=4(api/routers/aggregates.py:93); the gate itself lives incore/aggregate.py::aggregate_instances(line 106, counterskipped["below_min_n"]). Only a key that reaches four clean occurrences gets an aggregate — and only an aggregate can feedapply-laufform.The Abb. 19/20 plates give n = 4–39 per letter, most ≤ 20, and nearly every capital stays below 4 (handmodell-stufenplan.md §4, H5). Those keys never get an aggregate, hence never a derived Laufform, hence render chart-true while their neighbours in the same fluent run use the widened running form (
core/compose.py~line 1421, run ≥ASCENDER_LEAN_MIN_RUN). That is a systematic rhythm break in exactly the words that carry a capital — the word start, where it is most visible.Size & dependencies
Small now that the decision is made: a default change plus a chip in the apply dialog. The shrinkage question above is a separate, medium follow-up. Related: #270 (the apply surface this leans on, shipped in #277), #278 (a word-level chain fit would raise the clean-fit yield and push more keys over any threshold).