core/generate/structure_mapping/canonicals.py
Shay e5643454d9 feat(trackb): S1–S4 symbolic SME, selector, pure-S1 coverage gain
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.
2026-07-19 19:49:07 -07:00

119 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Canonical role-predicate skeletons (expert schema library).
Track B Increment 2: S1S4. Each is a typed role skeleton with abstract
``var`` terms only — no surface literals as schema identity.
"""
from __future__ import annotations
from dataclasses import dataclass
from generate.structure_mapping.role_predicate import RolePredicate, RoleTerm
@dataclass(frozen=True, slots=True)
class CanonicalStructure:
"""A named schema with abstract role variables (not surface attributes)."""
structure_id: str
"""Stable id for the schema (e.g. ``S1``). Library key only — never read
from gold labels at match time."""
description: str
predicates: tuple[RolePredicate, ...]
"""Abstract skeleton; terms are ``var`` roles only."""
def _var(name: str) -> RoleTerm:
return RoleTerm(kind="var", name=name)
# S1: "b is k× a; find a+b"
# compare(b, a, k) ∧ contain(a, a_qty) ∧ total(a, b, sum)
# (seed may equivalently be on b; mapper recovers a_value = b_value/k)
S1_CANONICAL: CanonicalStructure = CanonicalStructure(
structure_id="S1",
description=(
"compare-multiplicative-then-total: b = k×a; query a+b. "
"Roles: a=reference quantity entity, b=scaled entity, k=factor."
),
predicates=(
RolePredicate(
kind="contain",
args=(_var("a"), _var("a_qty")),
),
RolePredicate(
kind="compare",
args=(_var("b"), _var("a"), _var("k")),
),
RolePredicate(
kind="total",
args=(_var("a"), _var("b"), _var("sum")),
),
),
)
# S2: transfer-then-query — contain(src), contain(dst), transfer(src→dst, qty);
# query one endpoint after the transfer.
S2_CANONICAL: CanonicalStructure = CanonicalStructure(
structure_id="S2",
description=(
"transfer-then-query: src and dst seeded; qty moves src→dst; "
"query one endpoint's post-transfer amount."
),
predicates=(
RolePredicate(kind="contain", args=(_var("src"), _var("src_qty"))),
RolePredicate(kind="contain", args=(_var("dst"), _var("dst_qty"))),
RolePredicate(
kind="transfer",
args=(_var("src"), _var("dst"), _var("xfer_qty")),
),
),
)
# S3: rate-application — rate × count → total (or invert).
S3_CANONICAL: CanonicalStructure = CanonicalStructure(
structure_id="S3",
description=(
"rate-application: per_unit rate applied over count yields total. "
"Roles: rate_value, count, total_slot."
),
predicates=(
RolePredicate(
kind="rate",
args=(_var("rate_value"), _var("count"), _var("total_slot")),
),
),
)
# S4: additive-comparison-then-total — b = a + delta; query a+b.
# Uses compare_add (additive twin of multiplicative compare).
S4_CANONICAL: CanonicalStructure = CanonicalStructure(
structure_id="S4",
description=(
"additive-comparison-then-total: b = a + delta; query a+b. "
"Roles: a=reference, b=offset entity, delta=additive gap."
),
predicates=(
RolePredicate(kind="contain", args=(_var("a"), _var("a_qty"))),
RolePredicate(
kind="compare_add",
args=(_var("b"), _var("a"), _var("delta")),
),
RolePredicate(
kind="total",
args=(_var("a"), _var("b"), _var("sum")),
),
),
)
CANONICAL_LIBRARY: tuple[CanonicalStructure, ...] = (
S1_CANONICAL,
S2_CANONICAL,
S3_CANONICAL,
S4_CANONICAL,
)
CANONICAL_BY_ID: dict[str, CanonicalStructure] = {
c.structure_id: c for c in CANONICAL_LIBRARY
}