Increment 2 for ADR-0252 Track B. Extends Increment 1's structure-mapping slice with pure-family canonicals S2–S4, overlapping-waves selector, and a structure-mapping-owned pure-S1 text extract that recovers four real holdout cases the serving reader misses (0148, 0228, 0234, 0441). Bar results (command-backed via scripts/measure_trackb_inc2.py): - generalization ratio S1 = 9.0 (9 holdout cases / 1 template) - coverage gain organ 5 → trackb 9, wrong=0 - selector routes S1/S2/S3; refuses empty; surface≠structure Off-serving: no organ retirement, serving reader untouched. S2–S4 holdout ratios are 0 (parser frontier). S3/S4 emit refuses at multi-register scope without weakening the three-gate wrong=0 path. [Verification]: Smoke suite passed locally (~132s, 176 passed); trackb unit tests 35 passed; measure_trackb_inc2 all modes green.
83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""End-to-end off-serving structure-mapping pipeline.
|
|
|
|
``text → (serving parse | SM-owned extract) → role graph → selector → solve``
|
|
|
|
Serving reader is tried first; SM-owned extractors run only on refuse, and
|
|
never feed the live organ path. Gold labels never enter.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from generate.math_candidate_graph import parse_and_solve
|
|
from generate.math_problem_graph import MathProblemGraph
|
|
from generate.structure_mapping.convert import graph_to_role_graph
|
|
from generate.structure_mapping.solve import SolveOutcome, try_structure_map_and_solve
|
|
from generate.structure_mapping.text_extract import extract_pure_s1
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PipelineTrace:
|
|
"""Diagnostics for measurement scripts (not a gold label)."""
|
|
|
|
organ_parsed: bool
|
|
organ_answer: float | None
|
|
organ_refusal: str | None
|
|
extract_used: bool
|
|
extract_pattern: str | None
|
|
graph_source: str # "organ" | "sm_extract" | "none"
|
|
|
|
|
|
def run_structure_mapping_pipeline(
|
|
problem: str,
|
|
*,
|
|
families: tuple[str, ...] = ("S1", "S2", "S3", "S4"),
|
|
allow_sm_extract: bool = True,
|
|
) -> tuple[SolveOutcome, PipelineTrace, MathProblemGraph | None]:
|
|
"""Full off-serving path for one problem text.
|
|
|
|
Returns ``(outcome, trace, graph_used)``.
|
|
"""
|
|
organ = parse_and_solve(problem)
|
|
graph: MathProblemGraph | None = organ.selected_graph
|
|
extract_used = False
|
|
extract_pattern: str | None = None
|
|
graph_source = "organ" if graph is not None else "none"
|
|
|
|
if graph is None and allow_sm_extract:
|
|
ext = extract_pure_s1(problem)
|
|
if ext.graph is not None:
|
|
graph = ext.graph
|
|
extract_used = True
|
|
extract_pattern = ext.pattern_id
|
|
graph_source = "sm_extract"
|
|
|
|
trace = PipelineTrace(
|
|
organ_parsed=organ.selected_graph is not None,
|
|
organ_answer=organ.answer,
|
|
organ_refusal=organ.refusal_reason,
|
|
extract_used=extract_used,
|
|
extract_pattern=extract_pattern,
|
|
graph_source=graph_source,
|
|
)
|
|
|
|
if graph is None:
|
|
return (
|
|
SolveOutcome(
|
|
emitted=False,
|
|
answer=None,
|
|
refusal_reason="parser_frontier:no_graph",
|
|
binding=None,
|
|
derivation=None,
|
|
multi_register_certified=False,
|
|
classical_verified=False,
|
|
),
|
|
trace,
|
|
None,
|
|
)
|
|
|
|
# Optional: convert for hash; solve path re-converts.
|
|
_ = graph_to_role_graph(graph)
|
|
out = try_structure_map_and_solve(graph, families=families)
|
|
return out, trace, graph
|