Skip to content

Lower min_n to 1 and move the caution into the apply step (capitals get a running form) #273

Description

@MarkusNeusinger

Decision (2026-08-03, author)

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.
  • Writing a median is rendering — and since Give apply-laufform a surface: see the difference, then confirm #277 that is its own deliberate, previewed, confirmed step (LaufformApplyDialog), which already lists n_instances per glyph before anything is written.

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

  1. 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.
  2. 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.
  3. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions