3d0850c1326b8f95320cb1a5f143bd395a30033a
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c2bc939608 |
improvise-x1ns (2nd): the preflight counts a date measure's parts
P1 from ChatGPT Codex on #97, live at
|
||
|
|
f128087999 |
review: correct six prose claims, four of them false
An adversarial review of this branch found that the prose was worse than the diff — including, twice, in the exact genre the branch was congratulating itself for catching. Recorded rather than quietly rewritten, because that is the shape. Must-fix, all verified before correcting: - `fit.rs`'s comment on the exact check still read "this is the check that enforces the bound in both directions of a drift" — verbatim the sentence gp.md §14 now spends 25 lines refuting. Gotcha 48's third clause exactly: a fact stated twice is changed by grep, not by edit. I refuted the copy in the prose and left the copy in the code. - "false for eight months" was fabricated. `encoded_width` landed 2026-09-19, the sentence 2026-09-20; the repository itself is six months old. A paragraph whose point is that unchecked cost claims are checkable closed on one. - "NumericCategory was the one artefact family with no coverage" is false: it was one of three, and `ValueOfAMeasure` and `AbsentCoordinate` still have none. Corrected in both files that said it, and the remaining gap is named. The decline's stated reason was wrong, which matters most because a decline is the artefact nothing checks. It claimed one-hot is the only encoding of a category whose splits are spellable; `is_expr` is a `primary`, so an ordinal split IS spellable as a weighted indicator sum. Verified by running `parse_formula`, not by re-reading the grammar, and now pinned by `an_ordinal_split_on_a_category_is_spellable_as_an_indicator_sum` so the paragraph cannot go quietly wrong again. e8no stays declined on grounds that survive: a category's items carry insertion order rather than magnitude, and the emitted formula spells one IS per item either way, so the width is not saved. Also weakened two overclaims — "marking it a Label yields the same fit" (false: the feature count moves the RNG draw sequence, gotcha 37b) and "asserted rather than argued" (the dichotomy is argued from spell()'s exhaustive match; the test asserts one instance) — narrowed "the only coordinate predicate is is_expr" to what is true of it, corrected an uncheckable test count, named which two of the three consumers of parse_number_format are actually guarded, and moved an evidence block that had been inserted under the wrong module heading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
e045f9871b |
improvise-x1ns: the preflight counts the (missing) one-hot; improvise-e8no declined
improvise-x1ns (found by ChatGPT Codex on PR #90, live on main at
|
||
|
|
e50473df71 |
docs(gp): fitted_formula takes Features, not Overrides
Second self-review finding in the same paragraph, and the same class: §11 said "`crate::dataset` and `fitted_formula` take the caller's `Overrides`". Only the first does. `fitted_formula` takes a `Features`, which `Features::of` read off whatever `Dataset` it was handed — the Overrides are two steps upstream. The conclusion was right and the mechanism was wrong, which is the kind of sentence that survives review because the reader checks the conclusion. Rewritten to name the actual chain. Also verified while checking it, and worth recording because it looked like a drift and is not: the two test call sites still passing `Overrides::default()` to `crate::dataset` describe the SAME dataset production now builds. In `preprocess` the `present.is_empty()` drop happens BEFORE `ov.force` is consulted, so forcing an entirely-missing column categorical changes nothing — `an_empty_category_before_a_measure_does_not_break_the_role_check`'s precondition is as strong as it reads. The other site's fixture has only Label categories, so `category_columns` is 0 and the two Overrides are literally equal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
e6e39a03ff |
review(gp): the zv8t prose claimed a cost it has, and the cost is measurable
Self-review of
|
||
|
|
99cfb7fb41 |
fix(gp): a numeric- or date-valued category is spellable (improvise-zv8t)
`fit_measure` handed `preprocess` an `Overrides::default()`, so it never said which header columns are document categories — although the very next line told `Features::of`. `preprocess` then inferred Numeric -> Date -> Categorical from the values, so a `Year` category with items 2024/2025 became a numeric column and a category of ISO dates became a date column, and both were refused as encoding artefacts with `Year IS 2024` spellable all along. `fit_measure` now builds its `Overrides` from the same `header[..category_columns]` slice it already hands `Features::of`, forcing each to `Forced::Categorical`, so the one place the document's own answer was known is now the only place either caller reads it from. `Forced::Categorical` already existed; no formula-language change was needed. Three things follow, and the last is a cost. The date decline in gp.md §11 now means what it says. A `DatePart` could arrive over a *category* column by exactly this accident, so that decline was narrower than it read; it now covers a genuine date measure and nothing else. The `Artefact` arms stay and stay correct. `crate::dataset` and `fitted_formula` take the caller's `Overrides`, so a caller that does not force can still produce a `Numeric` role over a category column. The arm is no longer reachable from `fit_measure`, which is the only production entry point — it is not dead code, and deleting it would derive the classification from one caller's habits. Forcing widens the encoding, deliberately: a two-item `Year` is now two one-hots rather than one numeric feature, and a two-hundred-item one is refused by `MAX_FEATURES` where before it was refused one stage later as an artefact. Both refuse, so nothing that fitted before stops fitting; what changes is which guard speaks, and the width guard's message names the repair. `encoded_width` had to learn the same slice for that to hold, or it counted such a column as one feature and handed the refusal to the exact check *after* materialising the matrix — the side of the cost the preflight exists to stay on (§14). Evidence. The two spelling tests needed no fault injected: on the unfixed tree `fit_measure` refuses with the two real messages the ticket named. Each fixture gives the model one regular category, no other feature column, and a target that category alone determines — because the refusal is conditioned on `used_features`, so a fit whose champion happened to be a bare leaf would be accepted and prove nothing. Asserting `formula.raw` contains `Year IS` rather than merely `is_ok()` closes that in both directions. The preflight test did need one: dropping the `col >= category_columns` clause makes it fail by name, with the *exact* check speaking where the preflight should. The prose this corrects was itself the ticket's subject. improvise-wd6n's §11 called `Artefact::NumericCategory` a permanent refusal that "no vocabulary closes", which is false and is what filed improvise-zv8t. §11 and the gp test-evidence entry are both updated to say it is closed rather than open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
630b84066c |
fix(gp): NumericCategory is a filed gap, not a permanent refusal
Review findings on this branch, all three in the gp commit. The prose was the worst of them: §11 called Artefact::NumericCategory a permanent refusal that "no vocabulary closes". It is not. fit_measure hands preprocess an Overrides::default(), so it never says which header columns are categories — although the next line tells Features::of — and preprocess then infers Numeric -> Date -> Categorical from the values. A `Year` category with items 2024/2025 becomes a numeric column and is refused, with `Year IS 2024` spellable all along. Forced::Categorical is the existing knob. Filed as improvise-zv8t. Prose that calls something permanent is what stops the ticket being filed, which makes this the branch's own defect class one level up. MissingFlag's words said "that measure", but its arm is not conditioned on is_category and dataset.rs writes "" for an absent coordinate, so a .missing flag over a category is reachable — the same "declined in words naming something they are not" the commit was written to fix. Now "that column". ValueOfAMeasure's tail likewise covered only the present case. The test module claimed "each artefact family" while its fixture reaches three of five; named for what it reaches. Re-injected the collapsed-reason fault after the reword: still fails, so the negative clauses still bite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
173d9820a6 |
feat(gp): decline the artefact families separately, in their own words
improvise-wd6n. Spelling::Artefact was one variant standing for five distinct families, the last five reached through a `_ =>` wildcard in spell(). The one refusal they fed said the tree "splits on a col.missing flag or a date part", so three of the five were declined in words naming something they are not. Artefact now names which family, spell() is exhaustive (a sixth FeatureRole is a compile error rather than a sixth thing that message misdescribes), and the refusal gives one clause per family. The two families the ticket asks about are declined in writing in context/code/gp.md §11, separately, because each needs different vocabulary: col.missing needs a word for absence that reaches apply_agg and eval_agg, and the date parts need a date type improvise does not have anywhere. Nothing new is emitted, so improvise-4zl7's route-then-aggregate rule is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
022e12f9b4 |
fit: route per record and aggregate, never route on the aggregate
improvise-4zl7. A tree is fitted on individual records; an improvise formula is evaluated at a CellKey, and an aggregating cell used to aggregate each Expr::Ref first and evaluate the IF once on the totals. That is the same answer for a linear formula and a different one for anything that routes, so every fitted tree meant one thing on a record row and something else one row up. The user ruled: aggregate the rule over the members. The rule needed no new aggregate vocabulary. SUM(e) already means "fold e over the members" and simply did not: eval_agg specialised Expr::Ref and, for every other inner expression, summed the raw matching cells and ignored the expression entirely. It now folds — one entry per member record, the expression evaluated at that record's own coordinates. eval_agg is shared by eval_expr and eval_expr_cached, so the fold has exactly one home; each evaluator passes its own recursion in. Expr::Is(Filter) is the new arm in both evaluators: `Cat IS Item` is 1.0 when the cell's own Cat coordinate is Item, keeping the evaluator f64-only and reusing Formula::filter's own context.get/unwrap_or(false). A grammar production in primary, before ref_expr, with both operands through improvise_formula::quote's one home; IS joins the keyword guard. The emitter reads feature index -> role off preprocess's plan rather than reconstructing it: gp_tree::data::feature_roles is the one statement of the expansion order and encode_row now drives off it too. improvise_gp::Features carries one Spelling per feature — Measure, Coordinate or Artefact — decided by role and column, never by the name's shape. A one-hot over a real category is emitted as `Region IS North` and accepted; col.missing and the date parts stay Artefact and stay refused (improvise-wd6n). fit-measure on a document a category explains now lands instead of refusing, and the refusal no longer depends on which features the search happened to pick. Evidence: eight hand-computed tests in improvise-core over a four-record fixture whose Cost straddles the threshold inside each region, so route-then-aggregate and aggregate-then-route differ at every level. Two faults injected and watched to fail — reverting the fold fails 7 of 8, and a constant-false eval_is fails exactly the 3 coordinate tests. Reordering feature_roles fails both it and encode_row_replays_the_training_expansion. The fold is O(members) per evaluation per fixed-point pass and unbenchmarked: improvise-9iyv. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5629f4c295 |
docs(gp): drop the two ```text fences; only a runnable one earns the exemption
rustdoc does not execute a ```text fence, so `improvise-gp/src/lib.rs`'s sample
formula and `fit_csv.rs`'s invocation block were prose wearing a fence and were
claiming the checker's doctest exemption. Both move verbatim into
context/code/gp.md (§10 and §17) and the cap now applies to both headers.
What remains fenced in this unit is the two blocks promoted in
|
||
|
|
15774afd7f |
docs(gp-tree): cap the comment runs, and make two sketches real doctests
Every run in crates/gp-tree is now at most two lines; the arguments are in context/code/gp.md and the bug narratives in context/test-evidence/gp.md. Two `ignore` fences were the only code blocks in this crate, and both had drifted: the `fit` sketch passed `(&ds.rows, &y, &spec, ...)`, a signature improvise-dkqn removed, and the `breed` sketch called a `rank` that has never existed. `ignore` is why nothing caught either. Both are now compiled and run doctests (+2 tests), on `fit::fit` and on `breed` respectively. `real_unit_mse` carried `run_fit`'s first two lines of doc, left behind when the two functions were reordered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2c30ef0138 |
docs(gp): move the gp-tree / improvise-gp reasoning into context/
The arguments in these two crates' comments, written out where a comment run cap cannot reach them: context/code/gp.md for the design arguments and context/test-evidence/gp.md for the bug narratives, keyed by test name. Nothing in crates/ changes yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |