docs(ADR-0127): units pack + units-aware parser + conversion graph (proposed)

Diagnostic from ADR-0126's first train-sample run (0/0/50): every
refusal happens at the first statement of each problem, and every
refused first statement fails on the unit-of-measurement construction,
not on the operation grammar. Adding more verb regexes is the per-axis
treadmill that produced 4 zero-lift ADRs. Units form a finite, externally
well-defined ontology (NIST SI tables, currency, English container nouns)
that is semantic substrate the candidate-graph parser was designed to
consume.

Scope:
- en_units_v1 pack: dimensions, units (<=60), containers, rate connectors
- conversions.jsonl: directed weighted graph of within-dimension unit pairs
- 3 new initial-possession shapes + rate-declaration extractor in the
  candidate parser
- Round-trip filter gains optional pack-typed-unit check
- Solver gains dimensional canonicalization helper (shortest path through
  conversion graph); fired edges join SolutionTrace.steps for replay
- Pack ratification invariants: round-trip identity, per-dimension
  connectivity, path consistency, canonical unit per dimension

Wire the same train-sample exit criterion as ADR-0126 (correct >=10/50,
wrong==0). If passed -> sealed holdout. If still missed -> Path B
trigger is REAL (full deterministic design with units substrate failed),
demote GSM8K, re-target math expert promotion.

Also commits the empirical evidence: train_sample/v1/runner.py swapped
_score_one -> _score_one_candidate_graph; report.json baseline 0/0/50
confirming the candidate-graph topology refuses cleanly without units
substrate.
This commit is contained in:
Shay 2026-05-23 06:42:39 -07:00
parent feeb64818c
commit fde62d713d
3 changed files with 391 additions and 52 deletions

View file

@ -0,0 +1,339 @@
# ADR-0127 — `en_units_v1` Pack + Units-Aware Candidate Extractors
**Status:** Proposed (scope-only; implementation follow-up to ADR-0126)
**Date:** 2026-05-23
**Author:** CORE agents + reviewers
**Depends on:**
- ADR-0115 / 0116 / 0117 / 0118 (parser / solver / verifier / realizer)
- ADR-0122 (Rate operand)
- ADR-0126 (candidate-graph parser + round-trip filter)
**Supersedes:** none
**Blocks:** the Path-B decision for the GSM8K-math lane (the real
empirical answer can't be obtained until the parser consumes a
units pack).
---
## Context
ADR-0126's first empirical run produced **0 / 50 correct, 0 wrong,
50 refused** on the GSM8K train-sample inner-loop gate. Inspection
of the refusal reasons revealed a pattern: every refusal happens at
the *first statement* of each problem, and every refused first
statement shares the same shape — **the parser fails on the
unit-of-measurement construction, not on the operation grammar**.
Representative train-sample first statements (all refused):
| Case | First statement | What's structurally not recognized |
|------|----------------|-----------------------------------|
| 1 | `Tina makes $18.00 an hour.` | Money + time = rate dimension |
| 2 | `Jan buys 1000 feet of cable.` | Length unit + substance qualifier |
| 3 | `... bookstore donated 48 boxes of erasers.` | Container + count-content |
| 5 | `In one hour, Addison mountain's temperature ...` | Temperature dimension + fractional ratio |
The shared failure mode is not "unknown verb" or "unknown sentence
shape." It is "the parser has no ontology of units." The current
parser asks "is `feet` a valid noun?" via `_canonical_unit` which
just pluralizes. The right question is "what *dimension* is `feet`,
and does `of cable` modify it as a substance qualifier?"
This is structurally different from the per-axis grammar treadmill
that produced four zero-lift ADRs (0122 / 0123 / 0123a / 0123b).
Vocabulary expansion is unbounded and adversarial; **units of
measurement form a finite, externally well-defined ontology**
(NIST SI tables for physical units; closed sets for currency, time,
English container nouns). A units pack is *semantic substrate the
parser consults*, not more grammar regex.
ADR-0126's candidate-graph topology is built to consume exactly
this kind of substrate — the round-trip filter already does
token-level grounding; adding a "matched_unit_token must resolve
to a known dimension in the pack" check is a natural extension.
Without ADR-0127 the ADR-0126 empirical result is uninformative:
0/50 reflects P2's deliberate minimum-viable scope, not the
architecture's capacity. The architecture's real verdict lives
behind the units pack.
---
## Decision
Add a `language_packs/data/en_units_v1/` ratified semantic pack and
extend the ADR-0126 candidate parser to consult it during candidate
emission. **No new operation kinds.** **No new ADR-0114a
obligations.** **No new exit gates beyond ADR-0126's** (`correct ≥
10/50, wrong == 0` on the train sample, this time with units pack
in scope).
### Pack content (`en_units_v1`)
Structure mirrors `en_arithmetic_v1`: `lexicon.jsonl` +
`manifest.json` + `glosses.jsonl` + a self-sealing
`.mastery_report.json` ratification artifact.
**Dimension classes** (entry_id prefix `en-units-dim-`):
| Dimension | Notes |
|-----------|-------|
| `count` | Discrete countable nouns (apples, kids, boxes, books) |
| `length` | feet, inches, miles, yards, meters, centimeters |
| `time` | hours, minutes, days, weeks, months, years |
| `mass` | pounds, ounces, kilograms, grams |
| `money` | dollars, cents (anchored by `$` / `¢` symbols) |
| `temperature` | degrees, Fahrenheit, Celsius |
| `volume` | gallons, quarts, cups, liters |
**Unit lemmas** (entry_id prefix `en-units-unit-`):
Each unit binds to exactly one dimension. Carries `singular`,
`plural`, and `symbol` (where applicable: `$`, `°`). Initial pack
targets ≤ 60 lemmas — bounded by GSM8K-frequent vocabulary, not
NIST-complete coverage.
**Container nouns** (entry_id prefix `en-units-container-`):
Closed set: `box`, `bag`, `pack`, `basket`, `batch`, `dozen`,
`group`, `set`, `case`, `pair`, `pile`, `stack`. Each declares
its canonical syntax (`<count> <container> of <content>`) and an
optional `default_size` (only `dozen=12`, `pair=2`, others null).
**Rate connectors** (entry_id prefix `en-units-rate-`):
Closed set: `per`, `an`, `every`, `each`. Each declares the
template `<money|count> <rate_connector> <time|count>` it
participates in.
**Substance qualifiers** (entry_id prefix `en-units-substance-`):
Pattern `N <unit> of <substance>` (e.g., "1000 feet of cable").
The pack does NOT enumerate substances — that's open class. It
encodes the *structural rule* that any measure-dimension unit
admits an `of <NP>` substance tail, which the round-trip filter
treats as a discarded grounded modifier.
**Unit-conversion graph** (`conversions.jsonl`, separate file):
Each line declares a directed edge `(from_unit, to_unit, ratio)`
where `to_unit = ratio × from_unit`. The pack ratifies the
*connected* weighted graph per dimension. Examples:
```jsonl
{"edge_id":"en-units-conv-001","from":"inches","to":"feet","ratio":0.0833333333,"dimension":"length"}
{"edge_id":"en-units-conv-002","from":"feet","to":"inches","ratio":12,"dimension":"length"}
{"edge_id":"en-units-conv-003","from":"cents","to":"dollars","ratio":0.01,"dimension":"money"}
{"edge_id":"en-units-conv-004","from":"dollars","to":"cents","ratio":100,"dimension":"money"}
{"edge_id":"en-units-conv-005","from":"minutes","to":"hours","ratio":0.01666666667,"dimension":"time"}
{"edge_id":"en-units-conv-006","from":"hours","to":"days","ratio":0.04166666667,"dimension":"time"}
```
The graph is **the conversion table as data**, not as code. The
solver consults it; the parser doesn't need to. Initial coverage
target: complete edges for `length`, `time`, `money`, `mass`,
`volume` between GSM8K-frequent unit pairs — bounded by NIST SI
tables (closed set), not free expansion.
### Why conversions matter
Without conversions, the pack solves only single-unit arithmetic
(`5 apples + 3 apples`). With conversions, it solves
within-dimension mixed-unit arithmetic — which is the majority
of real word problems:
| Without conversions | With conversions |
|--------------------|------------------|
| `5 feet + 8 inches = ?` (refused) | canonicalize to feet (or inches), add, emit |
| `$2 + 75¢ = ?` (refused) | canonicalize to cents (or dollars), add, emit |
| `2 hours 30 minutes` (refused) | canonicalize to minutes (or hours), normalize |
Solver-side responsibility (delegated to ADR-0127.5 below):
when an operation's operand has a unit in the same dimension as
the actor's last quantity but a different unit, the solver
canonicalizes via shortest path in the conversion graph before
performing arithmetic. The canonical_bytes of `SolutionTrace`
records which edges fired, preserving determinism and
replay-equality.
### Graph ratification invariants (`en_units_v1`-specific)
The pack ratification process validates *graph correctness*, not
just lexicon well-formedness:
1. **Round-trip identity** — for every edge `(A, B, r)`, there
must exist an edge `(B, A, 1/r)` with `|round_trip_error| <
1e-9`. Asymmetric tables are rejected at ratification.
2. **Per-dimension connectivity** — within each dimension, the
subgraph induced by that dimension's units must be connected
(every unit reachable from every other). Isolated unit
lemmas are rejected.
3. **Path consistency** — for any two units A and C in the same
dimension, all shortest paths from A to C must yield the
same product of ratios within `1e-9`. Inconsistent paths
(e.g., 12 in/ft × 3 ft/yd ≠ 1 yd/36 in) are rejected at
ratification, not at runtime.
4. **Canonical unit per dimension** — each dimension declares
one canonical unit (`feet` for length, `seconds` or
`minutes` for time, `dollars` for money, etc.). All
canonicalization routes through the canonical unit; this
bounds the shortest-path computation to O(1) lookups.
These invariants live in `tests/test_adr_0127_pack_ratification.py`
and run at every pack-change PR — the conversion graph cannot
ship broken.
### Parser integration
Three load-bearing changes to `generate/math_candidate_parser.py`
(no changes to legacy `math_parser.py`):
1. **`extract_initial_candidates` widens** to recognize three
additional shapes when pack consultation confirms dimensional
typing:
- `<Entity> has N <pack-unit>` (with `of <substance>` tail
discarded)
- `<Entity> has N <pack-container> of <content>`
- `There are N <pack-unit> [of <substance>]`
2. **New `extract_rate_declaration_candidates`** recognizes
`<Entity> makes $N <rate-connector> <pack-time-unit>` as an
`apply_rate` candidate (ADR-0122 shape; the pack supplies the
dimensional check that today's narrow regex misses).
3. **Round-trip filter gains a dimensional check.** The existing
`roundtrip_admissible` adds an optional `require_pack_typed_unit`
parameter (default False to preserve P1 behavior). When True,
`matched_unit_token` must resolve to a known unit lemma in
`en_units_v1`. This is the wrong-answer firewall for unit
hallucination: a parser that fires on `Sam buys 3 contemplations`
would now fail because `contemplations` is not pack-typed.
4. **Solver gains a dimensional-canonicalization helper.** New
module `generate/math_unit_conversion.py` exports
`canonicalize_to_dimension_canonical(quantity, conversion_graph)
-> (quantity_in_canonical_unit, [edges_fired])`. Operations whose
operand unit differs from the actor's tracked unit (but shares
dimension) get canonicalized before arithmetic. The fired-edges
list joins `SolutionTrace.steps` so replay reproduces the
conversion path byte-equal. Mixed-dimension operands
(`apples + dollars`) remain a SolveError — units type the
arithmetic.
### What ADR-0127 explicitly does NOT do
- Does NOT model fractional-of-prior-quantity phrasing
(`3/4 of its temperature`). That's a separate compositional
pattern, not a unit-ontology question.
- Does NOT model multi-step rate compositions
(`overtime = base + 1/2 base`). That's solver-level.
- Does NOT add new operation kinds. ADR-0126 + 0122 cover the
needed shapes (add, subtract, transfer, multiply, divide,
apply_rate, compare_*). The pack just helps the parser
*recognize* the operand inputs.
- Does NOT replace `_canonical_unit`. The pack lookup is
*additive* — when the pack confirms a token, dimensional
routing kicks in; when not, the legacy plural-canonicalization
fallback remains.
- Does NOT touch the sealed holdout. Re-runs against the
unsealed train sample only.
---
## Invariants preserved / added
| Invariant | Preserved or added | How |
|-----------|--------------------|-----|
| `wrong == 0` | Preserved | Pack-typed unit check is a *stricter* gate, never a looser one |
| `trace_hash` byte-equality | Preserved | Pack consultation is deterministic; same pack version → same lookup result |
| Pack-binding (ADR-0114a #10) | Reinforced | The parser now explicitly cites pack entry_ids in `SolutionTrace.provenance` for unit-typed candidates |
| Round-trip admissibility | Strengthened | When `require_pack_typed_unit=True`, hallucinated units fail to ground |
| `versor_condition(F) < 1e-6` | Untouched | No runtime field changes |
| Manifest checksum SHA-256 of bytes-on-disk | Required | Mastery report self-seals like `en_arithmetic_v1` |
## Exit criterion
**Same gate as ADR-0126 but with `en_units_v1` mounted:**
```
correct >= 10 / 50 on evals/gsm8k_math/train_sample/v1/cases.jsonl
wrong == 0
```
**If passed:** run sealed holdout *once* and freeze the number in
ADR-0127-results. Architecture + units substrate jointly validated.
**If missed (Path-B trigger, now real):** the deterministic
parser-by-rule approach + units ontology + candidate-graph
topology is the *full* design we believed was right, and it still
doesn't move GSM8K. That is the empirical signal to demote GSM8K
and re-target the math expert promotion to a benchmark where
exact-recall and determinism are the discriminators.
---
## Alternatives considered
### A. Skip the units pack, just expand parser regex.
Rejected — this IS the per-axis grammar treadmill (4 zero-lift
ADRs). Adding patterns like `<Entity> has N \w+ of \w+` without
dimensional typing produces wrong candidates the round-trip
filter can't catch (any noun would ground; nothing rejects "5
contemplations of cable").
### B. Pull units from a third-party library (Pint, etc.).
Rejected — violates "no opaque dependencies in the runtime path."
A curated pack of ≤ 60 lemmas is auditable, version-pinnable,
ratifiable, and inspectable; an external library is none of those.
### C. Defer units, ship ADR-0126 with the honest 0/50 + open
question. Considered. Discarded because 0126's empirical result
is genuinely uninformative without the substrate it was designed
to consume. Shipping the architecture with a misleading "0 lift"
headline invites the wrong conclusion (Path B prematurely).
### D. Build the units pack but DON'T integrate it; ship as
inventory. Rejected — the pack must be load-bearing per CLAUDE.md
"prefer compact, curated packs" plus the project's general stance
against decoration without integration.
---
## Implementation plan (proposed sub-phases)
| Phase | Module | Description |
|-------|--------|-------------|
| 0127.1 | `language_packs/data/en_units_v1/` | Pack content: lexicon (dimensions + units + containers + rate connectors) + `conversions.jsonl` + manifest + glosses + mastery report |
| 0127.2 | `language_packs/loader.py` (or sibling) | Pack loader API: `lookup_unit(token) -> UnitEntry \| None`; `get_conversion_graph(dimension) -> ConversionGraph` |
| 0127.3 | `generate/math_roundtrip.py` | Optional `require_pack_typed_unit` parameter on `roundtrip_admissible` |
| 0127.4 | `generate/math_candidate_parser.py` | Three new initial-possession shapes + rate-declaration extractor, consulting the pack loader |
| 0127.5 | `generate/math_unit_conversion.py` (new) | `canonicalize_to_dimension_canonical(quantity, graph)`; shortest-path lookup + edges-fired provenance for `SolutionTrace.steps` |
| 0127.6 | `generate/math_solver.py` | Wire dimensional canonicalization into add/subtract/transfer/compare arithmetic; mixed-dimension operands → SolveError |
| 0127.7 | `evals/gsm8k_math/train_sample/v1/runner.py` | Re-run with units pack engaged; new `report.json` |
| 0127.8 | `tests/test_adr_0127_*.py` | Pack ratification (round-trip identity + connectivity + path consistency + canonical-unit-per-dimension) + parser integration + solver canonicalization + train-sample lift gate |
Regression gates (must remain green at every phase):
- `core test --suite smoke -q`
- `core test --suite math -q` (existing 714/714 + ADR-0126 74/74)
- `core test --suite packs -q` (new `en_units_v1` ratification entries)
- ADR-0126 P3+P4 tests (the candidate-graph machinery is unchanged)
## PR checklist (when proposing for merge)
```
What capability did this add?
→ Pack-typed unit recognition for the candidate-graph parser; the
semantic substrate ADR-0126 was designed to consume.
What invariant proves the field remains valid?
→ Pack-typed unit check (new); wrong == 0 (preserved); manifest
checksum SHA-256 of bytes-on-disk (required).
Which CLI suite/eval proves the lane?
→ smoke + math + packs + train_sample_runner (this is the
re-measurement that decides Path B or not).
Did this avoid hidden normalization, stochastic fallback,
approximate recall, unreviewed mutation?
→ Yes. Pack lookup is deterministic; unit ontology is bounded
and curated; no learned scoring; ratified pack only.
If it touches user input, what trust boundary was enforced?
→ Pack file paths are validated via the existing safe_pack_id
sanitiser (ADR-0051). Pack content is mastery-report-sealed.
```

View file

@ -13,252 +13,252 @@
"per_case": [
{
"case_id": "gsm8k-train-sample-v1-0001",
"reason": "parser: could not parse statement clause: 'Tina makes $18.00 an hour' (in sentence: 'Tina makes $18.00 an hour.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Tina makes $18.00 an hour.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0002",
"reason": "parser: could not parse statement clause: 'Jan buys 1000 feet of cable' (in sentence: 'Jan buys 1000 feet of cable.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jan buys 1000 feet of cable.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0003",
"reason": "parser: could not parse statement clause: 'The student council sells scented erasers in the morning before school starts to help raise money for school dances' (in sentence: 'The student council sells scented erasers in the morning before school starts to help raise money for school dances.')",
"reason": "candidate_graph: no admissible candidate for statement: 'The student council sells scented erasers in the morning before school starts to help raise money for school dances.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0004",
"reason": "parser: could not parse statement clause: 'There are some kids in camp' (in sentence: 'There are some kids in camp.')",
"reason": "candidate_graph: no admissible candidate for statement: 'There are some kids in camp.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0005",
"reason": "parser: could not parse statement clause: \"In one hour, Addison mountain's temperature will decrease to 3/4 of its temperature\" (in sentence: \"In one hour, Addison mountain's temperature will decrease to 3/4 of its temperature.\")",
"reason": "candidate_graph: no admissible candidate for statement: \"In one hour, Addison mountain's temperature will decrease to 3/4 of its temperature.\"",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0006",
"reason": "parser: could not parse statement clause: 'Mandy started reading books with only 8 pages when she was 6 years old' (in sentence: 'Mandy started reading books with only 8 pages when she was 6 years old.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Mandy started reading books with only 8 pages when she was 6 years old.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0007",
"reason": "parser: could not parse statement clause: 'Francine has five full boxes of crayons and 5 loose crayons' (in sentence: 'Francine has five full boxes of crayons and 5 loose crayons, and her friend has 27 loose crayons.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Francine has five full boxes of crayons and 5 loose crayons, and her friend has 27 loose crayons.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0008",
"reason": "parser: could not parse statement clause: 'Marnie makes bead bracelets' (in sentence: 'Marnie makes bead bracelets.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Marnie makes bead bracelets.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0009",
"reason": "parser: could not parse statement clause: 'Jen has 10 more ducks than four times the number of chickens' (in sentence: 'Jen has 10 more ducks than four times the number of chickens.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jen has 10 more ducks than four times the number of chickens.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0010",
"reason": "parser: could not parse statement clause: 'Yun had 20 paperclips initially, but then lost 12' (in sentence: 'Yun had 20 paperclips initially, but then lost 12.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Yun had 20 paperclips initially, but then lost 12.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0011",
"reason": "parser: could not parse statement clause: 'Alexa has a lemonade stand where she sells lemonade for $2 for one cup' (in sentence: 'Alexa has a lemonade stand where she sells lemonade for $2 for one cup.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Alexa has a lemonade stand where she sells lemonade for $2 for one cup.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0012",
"reason": "parser: could not parse statement clause: 'Dennis collected 10 rocks' (in sentence: 'Dennis collected 10 rocks.')",
"reason": "candidate_graph: no admissible candidate for statement: 'He put all of them in his aquarium but his fish ate half of them.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0013",
"reason": "parser: could not parse statement clause: 'Allison, a YouTuber, uploads 10 one-hour videos of food reviews each day to her channel' (in sentence: 'Allison, a YouTuber, uploads 10 one-hour videos of food reviews each day to her channel.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Allison, a YouTuber, uploads 10 one-hour videos of food reviews each day to her channel.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0014",
"reason": "parser: could not parse statement clause: 'Bob can shuck 10 oysters in 5 minutes' (in sentence: 'Bob can shuck 10 oysters in 5 minutes.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Bob can shuck 10 oysters in 5 minutes.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0015",
"reason": "parser: could not parse statement clause: 'Traveling from Manhattan to the Bronx, Andrew rides the subway for 10 hours, takes the train and rides for twice as much time as the subway ride' (in sentence: 'Traveling from Manhattan to the Bronx, Andrew rides the subway for 10 hours, takes the train and rides for twice as much time as the subway ride, and then bikes the remaining distance for 8 hours.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Traveling from Manhattan to the Bronx, Andrew rides the subway for 10 hours, takes the train and rides for twice as much time as the subway ride, and then bikes the remaining distance for 8 hours.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0016",
"reason": "parser: could not parse statement clause: \"On Rudolph's car trip across town, he traveled 2 more than 5 miles and encountered 3 less than 17 stop signs\" (in sentence: \"On Rudolph's car trip across town, he traveled 2 more than 5 miles and encountered 3 less than 17 stop signs.\")",
"reason": "candidate_graph: no admissible candidate for statement: \"On Rudolph's car trip across town, he traveled 2 more than 5 miles and encountered 3 less than 17 stop signs.\"",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0017",
"reason": "parser: could not parse statement clause: 'Jason has a carriage house that he rents out' (in sentence: 'Jason has a carriage house that he rents out.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jason has a carriage house that he rents out.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0018",
"reason": "parser: could not parse statement clause: 'Xavier plays football with his friends' (in sentence: 'Xavier plays football with his friends.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Xavier plays football with his friends.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0019",
"reason": "parser: could not parse statement clause: 'John adopts a dog from a shelter' (in sentence: 'John adopts a dog from a shelter.')",
"reason": "candidate_graph: no admissible candidate for statement: 'John adopts a dog from a shelter.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0020",
"reason": "parser: could not parse statement clause: 'Two puppies, two kittens' (in sentence: 'Two puppies, two kittens, and three parakeets were for sale at the pet shop.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Two puppies, two kittens, and three parakeets were for sale at the pet shop.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0021",
"reason": "parser: could not parse statement clause: 'John is lifting weights' (in sentence: 'John is lifting weights.')",
"reason": "candidate_graph: no admissible candidate for statement: 'John is lifting weights.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0022",
"reason": "parser: could not parse statement clause: 'Erica lives near a lake where most locals sell fish as their main source of income, earning $20 per kg of fish' (in sentence: 'Erica lives near a lake where most locals sell fish as their main source of income, earning $20 per kg of fish.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Erica lives near a lake where most locals sell fish as their main source of income, earning $20 per kg of fish.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0023",
"reason": "parser: could not parse statement clause: 'Nicole collected 400 Pokemon cards' (in sentence: 'Nicole collected 400 Pokemon cards.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Nicole collected 400 Pokemon cards.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0024",
"reason": "parser: could not parse statement clause: 'Sidney does 20 jumping jacks on Monday, 36 on Tuesday, 40 on Wednesday' (in sentence: 'Sidney does 20 jumping jacks on Monday, 36 on Tuesday, 40 on Wednesday, and 50 on Thursday.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Sidney does 20 jumping jacks on Monday, 36 on Tuesday, 40 on Wednesday, and 50 on Thursday.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0025",
"reason": "parser: could not parse statement clause: 'Lilibeth and her friends go strawberry picking' (in sentence: 'Lilibeth and her friends go strawberry picking.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Lilibeth and her friends go strawberry picking.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0026",
"reason": "parser: could not parse statement clause: 'Aaron and his brother Carson each saved up $40 to go to dinner' (in sentence: 'Aaron and his brother Carson each saved up $40 to go to dinner.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Aaron and his brother Carson each saved up $40 to go to dinner.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0027",
"reason": "parser: could not parse statement clause: 'Malcolm has 240 followers on Instagram and 500 followers on Facebook' (in sentence: 'Malcolm has 240 followers on Instagram and 500 followers on Facebook.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Malcolm has 240 followers on Instagram and 500 followers on Facebook.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0028",
"reason": "parser: could not parse statement clause: 'Tom opens an amusement park' (in sentence: 'Tom opens an amusement park.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Tom opens an amusement park.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0029",
"reason": "parser: could not parse statement clause: 'Fabian bought a brand new computer mouse and keyboard to be able to work from home' (in sentence: 'Fabian bought a brand new computer mouse and keyboard to be able to work from home.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Fabian bought a brand new computer mouse and keyboard to be able to work from home.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0030",
"reason": "parser: could not parse statement clause: 'Jake decides to go to the beach for a fun day' (in sentence: 'Jake decides to go to the beach for a fun day.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jake decides to go to the beach for a fun day.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0031",
"reason": "parser: could not parse statement clause: 'Jeremie wants to go to an amusement park with 3 friends at the end of summer' (in sentence: 'Jeremie wants to go to an amusement park with 3 friends at the end of summer.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jeremie wants to go to an amusement park with 3 friends at the end of summer.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0032",
"reason": "parser: could not parse statement clause: 'John decides to take up illustration' (in sentence: 'John decides to take up illustration.')",
"reason": "candidate_graph: no admissible candidate for statement: 'John decides to take up illustration.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0033",
"reason": "parser: could not parse statement clause: 'Rachel is 12 years old' (in sentence: 'Rachel is 12 years old, and her grandfather is 7 times her age.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Rachel is 12 years old, and her grandfather is 7 times her age.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0034",
"reason": "parser: could not parse statement clause: 'Georgie is a varsity player on a football team' (in sentence: 'Georgie is a varsity player on a football team.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Georgie is a varsity player on a football team.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0035",
"reason": "parser: could not parse statement clause: 'She decided to split them among her friends' (in sentence: 'She decided to split them among her friends.')",
"reason": "candidate_graph: no admissible candidate for statement: 'She decided to split them among her friends.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0036",
"reason": "parser: could not parse statement clause: 'Monica way studying for an exam' (in sentence: 'Monica way studying for an exam.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Monica way studying for an exam.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0037",
"reason": "parser: could not parse statement clause: 'Michael wants to lose 10 pounds by June' (in sentence: 'Michael wants to lose 10 pounds by June.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Michael wants to lose 10 pounds by June.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0038",
"reason": "parser: could not parse statement clause: 'In a building, there are a hundred ladies on the first-floor studying' (in sentence: 'In a building, there are a hundred ladies on the first-floor studying.')",
"reason": "candidate_graph: no admissible candidate for statement: 'In a building, there are a hundred ladies on the first-floor studying.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0039",
"reason": "parser: could not parse statement clause: 'At the family reunion, everyone ate too much food and gained weight' (in sentence: 'At the family reunion, everyone ate too much food and gained weight.')",
"reason": "candidate_graph: no admissible candidate for statement: 'At the family reunion, everyone ate too much food and gained weight.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0040",
"reason": "parser: could not parse statement clause: 'Over several years, Daniel has adopted any stray animals he sees on the side of the road' (in sentence: 'Over several years, Daniel has adopted any stray animals he sees on the side of the road.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Over several years, Daniel has adopted any stray animals he sees on the side of the road.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0041",
"reason": "parser: could not parse statement clause: 'Troy bakes 2 pans of brownies, cut into 16 pieces per pan' (in sentence: 'Troy bakes 2 pans of brownies, cut into 16 pieces per pan.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Troy bakes 2 pans of brownies, cut into 16 pieces per pan.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0042",
"reason": "parser: could not parse statement clause: 'Ella has 4 bags with 20 apples in each bag and six bags with 25 apples in each bag' (in sentence: 'Ella has 4 bags with 20 apples in each bag and six bags with 25 apples in each bag.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Ella has 4 bags with 20 apples in each bag and six bags with 25 apples in each bag.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0043",
"reason": "parser: could not parse statement clause: 'Sandra wants to buy some sweets' (in sentence: 'Sandra wants to buy some sweets.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Sandra wants to buy some sweets.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0044",
"reason": "parser: could not parse statement clause: 'John invests in a bank and gets 10% simple interest' (in sentence: 'John invests in a bank and gets 10% simple interest.')",
"reason": "candidate_graph: no admissible candidate for statement: 'John invests in a bank and gets 10% simple interest.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0045",
"reason": "parser: could not parse statement clause: 'Bart fills out surveys to earn money' (in sentence: 'Bart fills out surveys to earn money.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Bart fills out surveys to earn money.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0046",
"reason": "parser: could not parse statement clause: 'A school has 100 students' (in sentence: 'A school has 100 students.')",
"reason": "candidate_graph: no admissible candidate for statement: 'A school has 100 students.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0047",
"reason": "parser: could not parse statement clause: 'John bakes 12 coconut macaroons, each weighing 5 ounces' (in sentence: 'John bakes 12 coconut macaroons, each weighing 5 ounces.')",
"reason": "candidate_graph: no admissible candidate for statement: 'John bakes 12 coconut macaroons, each weighing 5 ounces.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0048",
"reason": "parser: could not parse statement clause: 'Jed collects stamp cards' (in sentence: 'Jed collects stamp cards.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Jed collects stamp cards.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0049",
"reason": "parser: could not parse statement clause: 'Malcolm is trying to find the fastest walk to school and is currently comparing two routes' (in sentence: 'Malcolm is trying to find the fastest walk to school and is currently comparing two routes.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Malcolm is trying to find the fastest walk to school and is currently comparing two routes.'",
"verdict": "refused"
},
{
"case_id": "gsm8k-train-sample-v1-0050",
"reason": "parser: could not parse statement clause: 'Mark does a gig every other day for 2 weeks' (in sentence: 'Mark does a gig every other day for 2 weeks.')",
"reason": "candidate_graph: no admissible candidate for statement: 'Mark does a gig every other day for 2 weeks.'",
"verdict": "refused"
}
],

View file

@ -25,7 +25,7 @@ import sys
from pathlib import Path
from typing import Any
from evals.gsm8k_math.runner import _score_one
from evals.gsm8k_math.runner import _score_one_candidate_graph
_HERE = Path(__file__).resolve().parent
_CASES_PATH = _HERE / "cases.jsonl"
@ -71,7 +71,7 @@ def build_report(cases: list[dict[str, Any]]) -> dict[str, Any]:
per_case: list[dict[str, str]] = []
counts = {"correct": 0, "wrong": 0, "refused": 0}
for raw in cases:
outcome = _score_one(_adapt(raw))
outcome = _score_one_candidate_graph(_adapt(raw))
counts[outcome.outcome] += 1
per_case.append(
{