Skip to content

Physics-semantics evaluation layer and the 6-scorer architecture - #5

Merged
sherryzyh merged 28 commits into
mainfrom
feat/roadmap
Jun 22, 2026
Merged

Physics-semantics evaluation layer and the 6-scorer architecture#5
sherryzyh merged 28 commits into
mainfrom
feat/roadmap

Conversation

@sherryzyh

Copy link
Copy Markdown
Owner

Summary

First PR for the feat/roadmap branch (27 commits). It builds out PRKit's physics-aware evaluation and reshapes the toolkit's public contract around a clean, version-stamped scoring surface. The work falls into five themes:

1. Physics-semantics judgement layer (PASEC)

A deterministic, question-conditioned equivalence engine — judges Eq(a_pred, a_ref ; q) instead of string-matching.

  • Typed answer/question semantics, a five-step build/extraction API on prkit.semantics, and reference-free (symmetric) judgement.
  • Equivalence lanes: symbol-domain assumptions + de-radicalization, guarded numeric-identity testing beyond simplify, a sign-convention reconciliation bridge, and an answer-structure decision layer.

2. Scoring architecture — a 6-scorer family (this branch's headline)

All implement the frozen prkit.api.ScorerVerdict contract:

  • SemanticsScorer (deterministic binary), EedScorer / SeedScorer (faithful vendored PHYBench-EED / CMPhysBench-SEED baselines), SemanticsEedScorer / SemanticsSeedScorer (our-semantics front-end over the same pure cores), and LLMJudgeScorer (model-graded).
  • This enables a clean front-end × algorithm ablation (our parsing/classification vs latex2sympy2+heuristics; EED vs SEED).
  • Upstream EED/SEED are vendored (front-end-free pure cores split out, with verbatim LICENSE + attribution) under prkit.evaluation.baselines. New optional [baselines] extra (pins pint); import prkit.scoring / prkit.verify stay free of pint/openai.
  • Verdict.score == -1.0 reserved as an honest not-applicable sentinel (excluded from aggregation).
  • PartialCreditScorer removed (superseded by the two semantics edit-distance scorers).

3. Streamlining the domain + contract

  • PhysicsAnswer is now a thin observation record (value, unit, source_type, metadata) — the canonical answer ontology (AnswerObjectKind/AnswerStructure, 9 kinds) lives only in the semantics layer; legacy AnswerCategory retired.
  • API_VERSION held at provisional "1.0"; breaking changes are tracked, not signalled by a major bump.

4. Domain & module renames (breaking, provisional 1.0)

  • AnswerPhysicsAnswer, PhysicalDatasetPhysicsDataset (four-noun Physics* symmetry); ontology module answer_kinds.pyanswer_taxonomy.py. No deprecation alias.

5. Infrastructure & hygiene

  • Light-import prkit.verify facade; prkit CLI; dataset license registry + gated auto-download; toolkit-independent .env loading (own pyproject.toml only); pre-commit (ruff/black/mypy/pytest) convergent with CI; cmphysbench dataset loader.

Validation

  • Full suite 2099 passed, 2 skipped; coverage 82% (gate 60%).
  • ruff / black / mypy clean; import-isolation test green (pint/openai off the scoring/verify path).

Notes for review

  • Breaking renames are intentional and documented in CONTRACT.md / CHANGELOG.md; the contract stays provisional 1.0.
  • Forward requirement: when an accuracy aggregator lands (roadmap N4), it MUST exclude score == -1.0.

🤖 Generated with Claude Code

sherryzyh and others added 28 commits June 17, 2026 22:10
…r-backed

- New light-import prkit.verify (parse/verify) wrapping SemanticsScorer; an
  import-isolation test asserts no clients/hub/datasets/pandas on the path.
- Enrich Verdict with correct/units_ok/symbolic_equiv/numeric_within_tol/
  extracted_answer (derived losslessly in scoring/_adapt.py); partial_credit
  and rationale reserved as None.
- AccuracyEvaluator gains an injectable scorer= (default SemanticsScorer) and
  routes evaluate() through it; the legacy comparator= path is preserved.
- Broaden SemanticsScorer.score to accept PhysicsAnswerSemantics inputs.
- Bump package to 0.2.0; document the facade and Verdict fields in
  CONTRACT.md and README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pre-commit ran both ruff-format and black, which format differently and
ping-ponged files on every `pre-commit run --all-files`; CI enforces only
black (`black --check`). Drop the ruff-format hook (keep ruff as the linter),
bump the pinned black from 24.8.0 to 26.1.0 to match the version in the
venv/CI, and exclude vendored minified katex assets from the
end-of-file/whitespace hooks. Apply the resulting stable formatting to the
docs and test file that were not yet clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The deterministic verifier is binary, so a near-miss answer scores the same
as a completely wrong one. Add a graded scorer based on PHYBench Expression
Edit Distance and CMPhysBench Scalable EED, run on PRKit's existing SymPy
parser and unit backend (no pint / latex2sympy dependency).

- New pure package prkit/semantics/edit_distance/: ExprNode + sympy_to_tree,
  an extended Zhang-Shasha tree-edit distance (forest matrix initialized to
  math.inf, fixing the upstream 1000-sentinel), a cost-aware subtree discount
  and eed_score, a thread-safe simplify timeout, and the SEED dispatch
  pipeline (eed_compare).
- New PartialCreditScorer (Scorer protocol) maps the graded result onto
  Verdict, populating partial_credit and rationale; PARTIAL_CREDIT / BINARY /
  TOLERANCE modes.
- verify(partial_credit=True) now routes to the scorer instead of raising.
- CONTRACT.md and the conformance gate updated to cover the new scorer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decide equality relations by their homogeneous form H = L - R: two
equalities are equivalent iff their denominator-cleared numerators agree up
to a nonzero constant. This admits cross-"=" rearrangement (F=ma vs a=F/m,
1/f=1/u+1/v vs f=uv/(u+v)) while rejecting equations with extra roots
(x=0 vs x*y=0). The per-operator criteria (_equalities_equivalent /
_inequalities_equivalent) are stated directly rather than as fallbacks after
a stricter check.

Fold functional-form notation into the canonical clause form so r(t)=... is
compared as r=..., and fix two parsing canonicalizations that previously
blocked symbolic matches: an "=" inside a summation limit (\sum_{n=1}^{N})
no longer splits relation clauses, and compact products carrying a subscript
expand (NmV_r -> N*m*V_r).

Add accept and adversarial-reject tests for each behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EQUIVALENCE.md, a detailed reference for compare_protocol_answers: the
pipeline, the per-object-kind criteria, the cross-kind bridges, the policy
modes and numeric tolerance, with diagrams and a worked example for every
condition. Add METHODOLOGY.md, the precision-preserving design discipline
for changing the judgement (canonical forms and principled per-kind
criteria, not fallback rescues). Point the answer-semantics enum taxonomy
comment at the new reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Carry per-symbol real-domain assumptions into SymPy parsing so expression/relation equivalence is decided over the intended real-physical domain instead of the generic complex default:

- symbol assumptions: realness derived in-engine (precision-safe), positivity/nonnegativity declared via q.symbol_assumptions; threaded to Symbol(token, **assumptions).
- de-radicalization: a solved even root (c = sqrt(E/m)) canonicalizes to its squared form when the non-radical side is provably nonnegative, falling into the existing exact equality criterion.
- numeric identity testing: domain-honoring multi-point evaluation decides 'is a - b the zero function', rejecting on any disagreement (exact) and guarding assumption-empowered symbolic accepts.

Also rename the previously dormant symbol_domains -> symbol_assumptions (enum SymbolAssumption, model PhysicsSymbolAssumptionSemantics, inner field assumption; coercion still accepts the legacy 'domain' key).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tomic)

Structure misclassification is an unrecoverable false negative (the structure-mismatch gate has no bridge), so reliably reach genuine atomic-vs-atomic before the atomic judgement:

- canonicalize_structure: a symmetric, idempotent, precision-safe pre-comparison pass that collapses structural degeneracies to atomic (1-element tuple/set/vector, closed point-interval [a,a], single-case trivially-conditioned piecewise), inserted last in _repair_answer_for_comparison.
- gate non-atomic comparison to proven-sound accepts only: ordered (tuple/interval/vector with atomic cells), piecewise, and label-aligned per-part are enabled; set/unordered accept only an exact multiset match (sound tolerant matching is a deferred roadmap milestone); matrix/tensor, per-part positional fallback, one-sided coordinate frame, and unparsed shaped payloads return a TBD sentinel (comparison_mode=not_implemented), or raise under STRICT_STRUCTURE_COMPARISON.
- contract admits ATOMIC whenever a collapsible structure is admitted, so a collapsed answer is not a spurious contract violation.

Adds STRUCTURE.md, structure decision guidance in the inference prompt role, a gold structure corpus + confusion harness, and per-comparator adversarial-reject batteries (incl. the {1.0,1.0} != {1.0,1.1} set-tolerance regression). Also carries the atomic-equivalence test battery (shared test file).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spell out cardinality (was abbreviated 'Card.') and add a short explanation of what each signature column means — denotation (meaning, drives equivalence), cardinality (part count, drives length checks and degeneracy collapses), ordering, and surface evidence (textual cues, drives classification) — and why keeping denotation separate from surface evidence is the point.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…red, reference-free judgement

Build the question/answer semantics the equivalence judgement consumes with the same
precision discipline the engine enforces, and expose the three steps as independent
capabilities.

Reference creation: a staged builder produces q_ref + a_ref (problem + golden) and q_prob
(problem only). The deterministic backbone (normalize_physics_answer + canonicalize_structure)
is authoritative for structure/object_kind; advisory LLM calls run best-effort and only clean
surfaces, fill question policy, and declare justified symbol assumptions, each cross-checked
(round-trip, contract self-consistency, q_ref<->a_ref mutual consistency) with a provenance
build report. Symbol assumptions are declared-not-derived and keyed by canonical post-alias
tokens; numeric tolerance is relative (never absolute); allowed_* are widen-only. Replaces the
single fused reference call (old body kept commented for review).

Answer generation: problem-only isolated solve with a leakage guard, producing an
LLM-structured record (a_pred_llm) and a deterministically-extracted one (a_pred_ext); it
degrades to a_pred_ext alone when native structured output is unavailable.

Equivalence judgement: add a symmetric reference-free compare_predictions(q_prob) that builds
an explicit contract and requires agreement both directions, so neither prediction's printed
precision nor kind/structure decides and no self-contract-violation arises; the reference-based
path is unchanged. The verify/scoring facade gains an optional, duck-typed q_ref context so a
caller can supply reference-built assumptions without the light facade importing the inference
layer (backward compatible).

The three steps are independent: the judgement core imports nothing from the build/generation
layer at runtime, generation never calls the reference build, and each entry takes plain
problem/semantics inputs. Methodology, the three-step ecosystem, and the engine-compatibility
rules are documented in the comparison reference docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two directional answers can differ by a global sign because each was
expressed under an opposite, unstated axis/sign-convention choice (a
reference velocity -20 m/s right-as-positive vs a prediction +20 m/s
left-as-positive describe the identical motion). These were false
negatives.

Add a convention-aware criterion that reconciles two *stated*
conventions to a common frame. The convention is concrete per-answer
data, not a toggle: a global flip is forgiven iff the question fixes no
convention (q.sign_convention/q.coordinate_frame absent), both answers
declare conventions whose positive axes are a provable global reversal
(antonym directions), and the values are an exact global -1. Covers
number / physical_quantity / sign_direction (atomic) and vector
(component-wise) answers; reuses _proportional_ratio (already returns -1
for a negation) and numbers_close.

New comparison/sign_convention.py holds the pure criteria; engine wiring
runs it before the strict same-kind criterion (so it can also override a
plain-equality false accept) and refines the _compare_shaped both-set-
incompatible frame branch (opposite global-reversal frames reconcile,
genuinely-incompatible frames still reject, partial flips reject).
Registered as the audited sign_convention TIER2 bridge: on under
audited, blocked under strict, with bridge_id/tier/evidence recorded.

Precision is held two ways. A flip is forgiven only with both stated
opposite conventions, so a bare intrinsic sign (charge -5 C vs +5 C) is
never reconciled -- directional intent is declared, not derived. And the
precision dual ships alongside the accept: opposite conventions with
equal values denote physically opposite quantities and are rejected
under every policy, avoiding an asymmetric relaxation.

Also preserve answer-level coordinate_frame/sign_convention through
_repair_atomic_answer's reparse so the lane can read them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record the implemented lane across the three comparison references:
METHODOLOGY.md flips the section 4 recall-gap row to implemented and adds
the discipline (convention as concrete per-answer data, the canonical-
reframe lever, the admitted-class proof, and the precision dual) plus the
section 5 residual. EQUIVALENCE.md documents the sign_direction
resolution (7.5), the sign_convention bridge row and same-kind note (8),
a policy example (9), the mode catalogue entry (11), and an end-to-end
trace (13). STRUCTURE.md documents the refined _compare_shaped frame
branch for vectors (both-unset / one-sided / opposite / incompatible) and
adds the per-axis-frame residual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on built data

The committed sign-convention lane reconciles a global sign flip on a directional
answer only when both answers carry stated, opposite conventions and the question
fixes none. It was correct-but-dormant on built data: the build never populated
answer-level conventions and routed any inferred convention onto q_ref, which closes
the lane's gate.

Split q vs a so each record carries its convention where the judge reads it:
- a_ref: Call A declares the convention the golden is expressed in (free axis);
  sign_convention added to _ANSWER_SURFACE_FILL_FIELDS, adopted fill-only.
- q_ref: Call B sets a convention only when the problem text itself fixes one.
- a_pred: a solver-declared field for a_pred_llm; a conservative, declaration-only
  parser (_extract_sign_convention_declaration) for a_pred_ext that reads an explicit
  "<dir> as positive" clause, strips it before value parsing, and never guesses from a
  bare sign.

reference_pair_consistency flags a q-fixed-vs-a_ref-opposite convention. Conventions
are recorded in sign_convention at every capture point (coordinate_frame stays for a
named frame) so the field-specific vector judge path cannot manufacture a spurious
one-sided TBD. Prompt versions bumped v5->v6 and v4->v5; no schema change.

Also make the prediction answer-semantics form a consumer choice rather than a
provider-capability decision: infer_prediction_semantics gains answer_semantics —
"structured" requires native structured output and raises otherwise, "extracted" uses
the plain-text deterministic route, and "auto" (default) keeps the prior best-effort
behavior.

Tests: extended staged-build and isolated-prediction suites; new parser adversarial
battery, built-records->verify integration cases (flip accepts; no-convention /
precision-dual / q-fixed reject; vector both-sided accept + one-sided TBD), and an
opt-in live smoke. Full gate green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
License metadata for the bundled benchmarks was duplicated as free-text strings in
each loader's get_info() and each downloader's download_info, with no shared source
of truth, no SPDX normalization, and several wrong/missing values (phybench, seephys,
and jeebench mislabeled "Research use"; physreason a non-SPDX "CC BY-NC-SA / MIT";
ugphysics and tpbench missing the key entirely). PhysReason's load() built a literal
info dict that dropped the license, so its two public read paths disagreed, and
auto_download fetched any dataset with no license check.

Introduce a typed LicenseSpec (SPDX id + machine-readable usage flags) and a registry
(license_registry.get_license) that owns one spec per dataset. Loaders and downloaders
now embed get_license(...).to_info_dict() at info["license"] plus an info["license_spdx"]
shortcut, so every read path reports the same corrected license. Gate
DatasetHub.load(auto_download=True) on the spec's redistributable flag (override with
allow_nonredistributable=True) and warn on eval_only. Corrected values: phybench,
jeebench, and physreason are MIT; seephys is Apache-2.0 (eval-only); ugphysics is
CC-BY-NC-SA-4.0; tpbench stays LicenseRef-unknown pending upstream verification.

Tests: new registry/gating/parity suite (every key resolves, unknown degrades to
non-redistributable, corrected flags per dataset, loader<->downloader parity, the
PhysReason load-vs-get_info regression, and the redistributable gate); the two phyx
assertions read the dict/shortcut. Full gate green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the semantics.inference subpackage to semantics.build (the layer creates
references and generates predictions, it does not only "infer"); keep
semantics.inference as a package-level deprecation shim that re-exports from
build and warns on import.

Rename the action functions infer_reference_semantics -> create_reference_semantics
and infer_prediction_semantics -> generate_prediction_semantics, keeping the old
names as deprecated aliases. create_reference_semantics gains a deterministic mode:
when model_client is None the three advisory LLM calls are skipped (None-guard in
_advisory_inference) and the build records the existing *_call_unavailable flags,
so it yields a (q_ref, a_ref) bundle with no LLM call. model_client stays
positional-or-keyword so the alias keeps working for positional callers.

Surface extract_prediction_answer_semantics on prkit.semantics, and remove the
duplicate prkit.verify.parse (callers use the extractor instead).

Anchor the ruff and git "build" excludes to the repo root (/build) so the new
semantics/build package is linted and tracked rather than silently skipped.
Repoint all internal test and cookbook imports to prkit.semantics.build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace docs/EVALUATION.md (which named deleted comparator/metric symbols) with a
scorer-focused stub, and add docs/PHYSICS_SEMANTICS.md: a narrative on-ramp covering
question/answer semantics, the five build/judge steps and their entry points, the
judgement at a glance, and the canonical doc map.

Add a physics-semantics section to the README and repoint the package overview at
prkit.scoring / prkit.verify. Fix the stale code anchors in EQUIVALENCE.md, extend
its comparison_mode catalogue (identical_text, not_implemented, and an asymmetric_match
note), repoint METHODOLOGY.md / CONTRACT.md / the semantics README at the build package
and the new function names, and drop parse from the contract examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move AnswerObjectKind/AnswerStructure (and the _StrEnum base) into prkit.core.domain as the toolkit's single canonical answer taxonomy, re-exported from prkit.semantics.schema for back-compat and promoted onto prkit.api. Add a 9th object kind, descriptive_text, for free-form answers, judged by conservative normalized-text equality (curated controlled-vocabulary phrases still classify as qualitative_label; sign/boolean detection now tolerates trailing punctuation).

Retire the legacy AnswerCategory enum: Answer.answer_category becomes answer_kind: AnswerObjectKind (mapping NUMBER->number, EQUATION->relation, PHYSICAL_QUANTITY->physical_quantity, FORMULA->expression, OPTION->choice, TEXT->descriptive_text), with the loaders, conformance suite, physics_problem, and the llm_judge payload migrated and the serialized key renamed to answer_kind.

Delete the deprecated evaluation comparator/evaluator/utils/similarities stacks (only llm_judge remains; the semantics layer is the canonical replacement).

Breaking change: bump API_VERSION to 2.0 and record the removal in CONTRACT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reduce Answer to a thin observed-data record (value/unit/source_type/
metadata) and keep the answer ontology (AnswerObjectKind/AnswerStructure)
solely in the semantics layer; simplify the dataset loaders that built the
heavier record. Mark the integration contract as provisional API_VERSION
"1.0" — breaking changes are tracked in the delta doc rather than via a
major bump — and drop the removed verify.parse from the facade docs.

Split the front-end-free EED/SEED tree-edit core (tree/zss/score/timeout)
out of semantics/edit_distance/ into evaluation/edit_distance/, leaving the
SEED dispatch glue under semantics/ to keep the package graph acyclic, and
narrow the import-isolation guard to prkit.evaluation.llm_judge. Refresh the
docs and tests to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce prkit.scoring.LLMJudgeScorer, the one non-deterministic reference
scorer. It adapts the OpenAIJudgeRunner (which stays in evaluation/llm_judge)
into the canonical Verdict: a "correct" judgement maps to score 1.0 /
equivalent True, the model's reasoning lands in rationale, and confidence /
expected_answer_type / raw_response / verdict_type / model are recorded in
details. partial_credit stays None (the judge is binary) and deterministic
False is reported via get_info().

All judge imports are deferred to method bodies (with TYPE_CHECKING-only
annotations) so that re-exporting the scorer keeps import prkit.scoring free
of openai and prkit.evaluation.llm_judge — verified by the import-isolation
test. Reframe the evaluation package docstring as the engine home the thin
scoring wrappers adapt. Tests inject a FakeJudgeRunner (no network/client).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The legacy/ directory was removed, so excluding it from ruff is no longer
needed. "/build", "dist", and "htmlcov" remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add faithful, lightly-modified forks of the two upstream edit-distance scorers
under evaluation/baselines/, each split into a front-end-free pure core and a
LaTeX front-end, with the verbatim upstream LICENSE/NOTICE/PROVENANCE shipped
alongside:

- phybench_eed/ (github.com/phybench-official/phybench@706feb4, MIT)
- cmphysbench_seed/ (github.com/CMPhysBench/CMPhysBench@b2cd857, Apache-2.0;
  attribution chain to PHYBench EED MIT + the zss BSD license preserved in NOTICE)

Local modifications (documented per-file and in each PROVENANCE.md): lift the
top-level latex front-end import out of the core so it imports without
latex2sympy2_extended; make pint a lazy singleton in the SEED core; and replace
the SIGALRM timeout_decorator bounds with the thread-safe
prkit.evaluation.edit_distance.timeout.run_with_timeout. Importing either pure
core pulls no latex2sympy2_extended/pint/timeout_decorator.

Wire the baselines into the Scorer contract with two thin wrappers, EedScorer
and SeedScorer, that lazily import the vendored core inside score() so
import prkit.scoring stays free of pint and the LaTeX front-end. SeedScorer
resolves its answer_type from the kwarg, then reference.source_type (validated
against the SEED enum), then default Expression, with an opt-in classifier
(default off) recorded in get_info().

Add the [baselines] extra (pint only) plus its all-aggregate entry, ship the
vendored licenses as package data, and exclude the vendored tree from
ruff/black/mypy and the whitespace pre-commit hooks so it stays auditable
against upstream.

Create the CMPhysBench loader (HF weidawang/CMPhysBench) mapping the curated
answer_type column into Answer.source_type as one of the five SEED tokens, and
register it in DatasetHub with an Apache-2.0 license-registry entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add pint to the import-isolation guard's forbidden list. The vendored CMPhysBench
SEED baseline pulls pint only on its unit-aware Numeric path, and the EedScorer /
SeedScorer wrappers import the vendored core lazily inside score(), so pint must
never reach import prkit.scoring or the verify facade. latex2sympy2_extended and
its antlr4 runtime are a required core dep already on this path by design and stay
deliberately unforbidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire PRKit's own answer normalization to the vendored PHYBench-EED and
CMPhysBench-SEED pure cores via a new semantics→pure-core adapter
(scoring/_edit_distance_adapt.py), shipping SemanticsEedScorer and
SemanticsSeedScorer. The adapter parses each answer with the semantics
parser (never latex2sympy2) and the object_kind+structure → SEED-type map,
then calls the cores' own tree-edit / numeric-tier algorithm verbatim, so a
SemanticsEed/SeedScorer number stays directly comparable to its vendor
Eed/SeedScorer baseline. The semantics path never loads pint.

Reserve score == -1.0 as the not-applicable sentinel: Verdict accepts it
(comparison_mode="not_applicable") for kinds/structures with no SEED type,
check_scorer accepts it and skips the equivalence/identity expectations for
it, and CONTRACT records that aggregators must exclude it.

Remove PartialCreditScorer (and PartialCreditMode), which the two semantics
edit-distance scorers supersede; verify(partial_credit=True) now uses
SemanticsSeedScorer. The PHYBench/CMPhysBench reimplementations under
evaluation/edit_distance and semantics/edit_distance are relabeled as parked
reference (eed-reimpl / seed-reimpl), no longer wired to any Scorer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the consumer-repo-locating machinery in core/project_env.py (the
PRKIT_TOOLKIT_ROOT env var, the src/prkit marker walk, and the named-sibling
fallback) with a single nearest-ancestor-with-pyproject.toml lookup, so the
toolkit resolves only its own project .env and never reaches into a sibling or
consumer repository. Off-repo callers (e.g. an installed wheel with no
pyproject.toml ancestor) get a best-effort empty result.

find_toolkit_root is dropped from the public surface; load_project_dotenv and
ensure_openai_api_key keep their signatures, so the model-client and llm-judge
importers are unaffected. The tests are reworked to key off pyproject.toml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
prkit/verify/ held only __init__.py, so flatten it to prkit/verify.py. The
import path prkit.verify is unchanged, so no caller, test, or doc edits are
needed; the light-import isolation guarantee and its test are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move core/domain/answer_kinds.py to answer_taxonomy.py (a clearer name for the
canonical AnswerObjectKind/AnswerStructure taxonomy) and repoint its two
importers (core/domain/__init__.py and semantics/schema/enums.py, plus the
latter's :mod: docstring reference). Symbol names are unchanged, so every
import-via-package site is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the two core domain classes Answer -> PhysicsAnswer and
PhysicalDataset -> PhysicsDataset (the latter also resolves the
physics_dataset.py file/class stem mismatch and completes the four-noun
Physics* symmetry with PhysicsProblem/PhysicsSolution). The other domain
nouns and the AnswerObjectKind/AnswerStructure taxonomy are unchanged.

Edits target only files that reference the domain symbols as code; string and
comment occurrences (mock-answer fixtures, "Answer:" prompt-section text, the
docs' unrelated structured-output Answer(BaseModel) example, the PASEC prose,
and historical release notes) are deliberately left untouched.

The contract stays provisional at API_VERSION "1.0": the breaking rename is
tracked in internal/PAPER_V1_TO_V2_DELTA.md rather than signalled by a major
bump, and no deprecation alias is provided. CONTRACT.md, CHANGELOG.md, and the
API docs (README, CORE, DATASETS, semantics/README) are updated accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two independent "green locally, red in CI" failures on this branch:

- test_model_client_satisfies_protocol built a real OpenAI client, which the
  openai SDK validates a credential for at construction. The key came from the
  gitignored .env locally; CI has neither .env nor a secret, so construction
  raised OpenAIError. Inject a dummy OPENAI_API_KEY in the test so it runs
  offline anywhere.

- mypy's Type check failed on Python 3.12 because a fresh install floated numpy
  to 2.5.0, whose bundled stub uses PEP 695 `type` statements that mypy rejects
  for a target python_version below 3.12 (we target 3.10). Skip following numpy
  stubs so dependency stub drift can't break the type gate.

Prevent recurrence: pin the gate tools (black/ruff/mypy) to exact versions so a
fresh CI install can't silently drift from local, add a `make ci` target that
mirrors the workflow with no .env and no key, and fold the previously-missing
typecheck into `make check`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sherryzyh
sherryzyh merged commit 8f7304b into main Jun 22, 2026
3 checks passed
@sherryzyh
sherryzyh deleted the feat/roadmap branch June 23, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant