core/tests/test_kernel_facts.py
Shay 58a94c8e4b
feat: implement kernel substrate tranche 1 base-layer foundations (#829)
* 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.
2026-06-18 18:46:20 -07:00

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