* feat: implement kernel substrate tranche 1 base-layer foundations * fix(kernel): ground scalar spans and tighten morphology labels Add extract_scalar_candidates() with exact source spans and problem_text provenance while keeping canonicalize_scalar() as the detached pack helper. Morphology labels now emit missing_* only when substrate frame/unit lookups actually fail, not on mere trigger-surface presence.
113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
"""Tests for generate/kernel_facts.py."""
|
|
from __future__ import annotations
|
|
|
|
from fractions import Fraction
|
|
import pytest
|
|
|
|
from generate.kernel_facts import (
|
|
SourceSpan,
|
|
KernelProvenance,
|
|
KernelHazard,
|
|
GroundedScalar,
|
|
GroundedUnit,
|
|
RelationRole,
|
|
CandidateRelation,
|
|
SubstrateFact,
|
|
)
|
|
|
|
|
|
def test_source_span_validation() -> None:
|
|
# Valid span
|
|
span = SourceSpan("half", 0, 4, sentence_index=0)
|
|
assert span.text == "half"
|
|
assert span.start == 0
|
|
assert span.end == 4
|
|
assert span.sentence_index == 0
|
|
|
|
# Invalid start
|
|
with pytest.raises(ValueError, match="SourceSpan.start must be >= 0"):
|
|
SourceSpan("half", -1, 4)
|
|
|
|
# Invalid end
|
|
with pytest.raises(ValueError, match="must be >= start"):
|
|
SourceSpan("half", 4, 3)
|
|
|
|
|
|
def test_kernel_provenance_validation() -> None:
|
|
# Invalid kind
|
|
with pytest.raises(ValueError, match="must be one of"):
|
|
KernelProvenance(kind="unknown_kind")
|
|
|
|
# problem_text requires spans
|
|
with pytest.raises(ValueError, match="requires non-empty source_spans"):
|
|
KernelProvenance(kind="problem_text")
|
|
|
|
# derived requires input_fact_ids
|
|
with pytest.raises(ValueError, match="requires non-empty input_fact_ids"):
|
|
KernelProvenance(kind="derived")
|
|
|
|
# pack/world must not carry source spans
|
|
span = SourceSpan("cent", 0, 4)
|
|
with pytest.raises(ValueError, match="must not carry source_spans"):
|
|
KernelProvenance(kind="kernel_unit", source_spans=(span,))
|
|
|
|
# Valid combinations
|
|
prov_text = KernelProvenance(kind="problem_text", source_spans=(SourceSpan("half", 0, 4),))
|
|
assert prov_text.kind == "problem_text"
|
|
|
|
prov_derived = KernelProvenance(kind="derived", input_fact_ids=("fact-1",))
|
|
assert prov_derived.kind == "derived"
|
|
|
|
prov_unit = KernelProvenance(kind="kernel_unit", pack_id="en_units_v1")
|
|
assert prov_unit.kind == "kernel_unit"
|
|
|
|
|
|
def test_immutability() -> None:
|
|
span = SourceSpan("half", 0, 4)
|
|
with pytest.raises(AttributeError):
|
|
span.start = 1 # type: ignore
|
|
|
|
prov = KernelProvenance(kind="problem_text", source_spans=(span,))
|
|
with pytest.raises(AttributeError):
|
|
prov.kind = "derived" # type: ignore
|
|
|
|
|
|
def test_substrate_fact_construction() -> None:
|
|
span = SourceSpan("half", 0, 4)
|
|
prov = KernelProvenance(kind="problem_text", source_spans=(span,))
|
|
hazard = KernelHazard("haz-1", "unbound_base_quantity", "half", "unbound")
|
|
|
|
# 1. GroundedScalar
|
|
scalar = GroundedScalar("fact-1", "half", Fraction(1, 2), prov, (hazard,))
|
|
fact_scalar = SubstrateFact("fact-1", "grounded_scalar", scalar, prov, (hazard,))
|
|
assert fact_scalar.fact_type == "grounded_scalar"
|
|
assert fact_scalar.content == scalar
|
|
assert not fact_scalar.is_speculative
|
|
|
|
# Type check for value
|
|
with pytest.raises(TypeError, match="must be Fraction"):
|
|
GroundedScalar("fact-1", "half", 0.5, prov) # type: ignore
|
|
|
|
# 2. GroundedUnit
|
|
unit = GroundedUnit("fact-2", "dollar", "money", "dollar", prov)
|
|
fact_unit = SubstrateFact("fact-2", "grounded_unit", unit, prov)
|
|
assert fact_unit.fact_type == "grounded_unit"
|
|
assert fact_unit.content == unit
|
|
|
|
# 3. CandidateRelation
|
|
role = RelationRole("buyer", True, "The buyer")
|
|
rel = CandidateRelation("rel-1", "transaction", (role,), prov, (hazard,))
|
|
fact_rel = SubstrateFact("rel-1", "candidate_relation", rel, prov)
|
|
assert fact_rel.fact_type == "candidate_relation"
|
|
assert fact_rel.content == rel
|
|
|
|
# Mismatched fact_type and content
|
|
with pytest.raises(TypeError, match="expects GroundedScalar content"):
|
|
SubstrateFact("fact-1", "grounded_scalar", unit, prov) # type: ignore
|
|
|
|
|
|
def test_speculative_fact() -> None:
|
|
prov = KernelProvenance(kind="speculative")
|
|
unit = GroundedUnit("fact-2", "dollar", "money", "dollar", prov)
|
|
fact = SubstrateFact("fact-2", "grounded_unit", unit, prov)
|
|
assert fact.is_speculative
|