Implements the layered cascade strategy from #000040 §3.1 (originally drafted as #000039 — renumbered after collision with parallel-shift's sqlite-vec ticket). What landed =========== arborist/qa/warrant_resolver.py: - _phrase_for_axiom(theorem_name) — strips leading categorical prefix ("Axiom of " / "Theorem " / "Principle ") and trailing parenthetical, returns FTS5 phrase syntax ('"line incidence"', '"plane incidence"', '"side angle side"', etc.) when the theorem name has 2+ tokens. - _content_tokens(chunk_content, max_n=8) — extract discriminating tokens from a claim-pack chunk's body. Drops stopwords / generic theorem terms / common-English (small hand-curated set). Requires count >= 2 to ditch typo / LaTeX residue singletons. Sorts by length DESC then first-position ASC. - _build_record_query_cascade(c, theorem_name, content) — returns ordered list of FTS5 queries to try: 1. Phrase from title 2. Content-tokens AND-joined 3. Existing discriminating-tokens AND-join (legacy) 4. Existing OR-fallback (legacy) - resolve_chunks gains a `record_content` parameter; tries each cascade query in order, first hit wins. - iter_claim_pack_records yields a 5-tuple including content so callers can thread it through. Tests: 6 new unit tests for the cascade helpers (phrase extraction, parenthetical stripping, single-token fallback, content-token filtering, count-2 minimum, cascade ordering). 20 total in test_warrant_resolver.py. Full suite: 1603 passed / 28 skipped. End-to-end honest result ======================== Re-running warrant-resolve on the existing shard cluster: records_total=92, records_resolved=11 (unchanged from Phase 4). The cascade is correct; the lift didn't materialize for Hilbert pillar IV's 7 missing records because of TERMINOLOGY MISMATCH, not query strategy: - claim-pack records (g4 2025) use modern post-1950s names: "Axiom of Line Incidence", "Group I: Axioms of Incidence". - Hilbert's 1902 Townsend translation uses the original "Verknüpfung" / "axioms of connection". - Empirically: the literal token "incidence" appears ZERO times in the ingested Hilbert TeX surface; "connection" is the relevant synonym. No matter how clever the query, you can't find a word that isn't there. The cascade is preserved for any future textbook where cited vocabulary matches textbook prose (modern Stanley / Brualdi / Knuth, etc.). Next-link follow-up: file #000042 term-aliases table (("incidence", "geometry") → ("connection", "geometry")). Sibling design to the citation-alias proposal at #000041. Renumbering note: the Phase 5 ticket file was renumbered 000039 → 000040 mid-session because parallel-shift took 000039 for sqlite-vec at nearly the same time. Internal references in the file follow the post-rename numbering (#000041 = citation-alias, #000042 = term-alias).
218 lines
6.5 KiB
Python
218 lines
6.5 KiB
Python
"""Tests for the warrant resolver (#000031 Phase 2).
|
|
|
|
Pure unit tests over the citation parser; the FTS5 resolver +
|
|
Merkle proof writer are exercised end-to-end via the
|
|
``arborist warrant-resolve`` CLI on real shards. The CLI smoke test
|
|
is documented in the ticket; this file stays offline / DB-free.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from arborist.qa.warrant_resolver import (
|
|
Citation,
|
|
parse_citation,
|
|
)
|
|
|
|
|
|
# --- "Title by Author" pattern (most common) -------------------------
|
|
|
|
|
|
def test_simple_title_by_author():
|
|
out = parse_citation("Introduction to Mathematical Logic by Elliott Mendelson")
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.title == "Introduction to Mathematical Logic"
|
|
assert c.authors == ("Elliott Mendelson",)
|
|
|
|
|
|
def test_title_with_punctuation():
|
|
out = parse_citation("The Lambda Calculus: Its Syntax and Semantics by H.P. Barendregt")
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.title == "The Lambda Calculus: Its Syntax and Semantics"
|
|
assert c.authors == ("H.P. Barendregt",)
|
|
|
|
|
|
# --- multi-author Oxford comma --------------------------------------
|
|
|
|
|
|
def test_multi_author_oxford_comma():
|
|
out = parse_citation(
|
|
"Classical Mechanics by Herbert Goldstein, Charles P. Poole, and John L. Safko"
|
|
)
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.title == "Classical Mechanics"
|
|
assert c.authors == ("Herbert Goldstein", "Charles P. Poole", "John L. Safko")
|
|
|
|
|
|
def test_multi_author_et_al():
|
|
out = parse_citation("Classical Mechanics by Herbert Goldstein et al.")
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.title == "Classical Mechanics"
|
|
assert c.authors == ("Herbert Goldstein",)
|
|
|
|
|
|
# --- semicolon-separated multi-citation -----------------------------
|
|
|
|
|
|
def test_semicolon_split():
|
|
out = parse_citation(
|
|
"Knuth TAOCP Volume 1 §1.2.6; Stanley §1.2; Brualdi §3.5"
|
|
)
|
|
assert len(out) == 3
|
|
assert out[0].authors == ("Knuth",)
|
|
assert out[1].authors == ("Stanley",)
|
|
assert out[2].authors == ("Brualdi",)
|
|
|
|
|
|
def test_semicolon_with_year_and_section():
|
|
out = parse_citation(
|
|
"Knuth TAOCP Volume 1 §1.2.6 equation (13); Vandermonde 1772"
|
|
)
|
|
assert len(out) == 2
|
|
assert out[0].section
|
|
assert "§1.2.6" in out[0].section
|
|
assert out[1].year == "1772"
|
|
|
|
|
|
# --- compact form ---------------------------------------------------
|
|
|
|
|
|
def test_compact_year_form():
|
|
out = parse_citation("Pascal 1654")
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.year == "1654"
|
|
assert "Pascal" in c.authors
|
|
|
|
|
|
def test_compact_section_only():
|
|
out = parse_citation("Brualdi §3.5")
|
|
assert len(out) == 1
|
|
c = out[0]
|
|
assert c.authors == ("Brualdi",)
|
|
assert "§3.5" in c.section
|
|
|
|
|
|
# --- edge cases ------------------------------------------------------
|
|
|
|
|
|
def test_empty_input():
|
|
assert parse_citation("") == []
|
|
assert parse_citation(" ") == []
|
|
|
|
|
|
def test_year_extraction():
|
|
out = parse_citation("Foundations of Probability by Andrey Kolmogorov 1933")
|
|
assert len(out) == 1
|
|
assert out[0].year == "1933"
|
|
|
|
|
|
def test_section_extraction():
|
|
out = parse_citation("Classical Mechanics by Goldstein Volume 1 §3.2")
|
|
assert len(out) == 1
|
|
assert "§3.2" in out[0].section
|
|
assert "Volume 1" in out[0].section
|
|
|
|
|
|
def test_oeis_identifier():
|
|
out = parse_citation("OEIS A000108")
|
|
assert len(out) == 1
|
|
# OEIS identifiers parse as compact form — first token is "author".
|
|
assert out[0].authors == ("OEIS",)
|
|
|
|
|
|
# --- Citation dataclass invariants ----------------------------------
|
|
|
|
|
|
def test_citation_is_empty():
|
|
assert Citation().is_empty()
|
|
assert not Citation(title="Foo").is_empty()
|
|
assert not Citation(authors=("Bar",)).is_empty()
|
|
|
|
|
|
def test_raw_field_preserved():
|
|
raw = "Some weird citation by Some Author"
|
|
out = parse_citation(raw)
|
|
assert out[0].raw == raw
|
|
|
|
|
|
# --- Phase 5: phrase + content-token cascade (#000039) --------------
|
|
|
|
|
|
def test_phrase_for_axiom_strips_categorical_prefix():
|
|
from arborist.qa.warrant_resolver import _phrase_for_axiom
|
|
|
|
assert _phrase_for_axiom("Axiom of Line Incidence") == '"line incidence"'
|
|
assert _phrase_for_axiom("Axiom of Plane Incidence") == '"plane incidence"'
|
|
assert _phrase_for_axiom("Theorem of Pythagoras") == '"pythagoras"' or _phrase_for_axiom("Theorem of Pythagoras") == ""
|
|
|
|
|
|
def test_phrase_for_axiom_drops_parenthetical():
|
|
from arborist.qa.warrant_resolver import _phrase_for_axiom
|
|
|
|
assert _phrase_for_axiom("Axiom of Side-Angle-Side (SAS)") == '"side angle side"'
|
|
|
|
|
|
def test_phrase_for_axiom_returns_empty_for_single_token():
|
|
from arborist.qa.warrant_resolver import _phrase_for_axiom
|
|
|
|
# Single-token axioms have nothing to phrase-match — fall through
|
|
# to AND-join strategy.
|
|
assert _phrase_for_axiom("Pasch's Axiom") == ""
|
|
assert _phrase_for_axiom("Pythagorean Theorem") == ""
|
|
|
|
|
|
def test_content_tokens_filters_common_words():
|
|
from arborist.qa.warrant_resolver import _content_tokens
|
|
|
|
content = (
|
|
"this axiom states that for every triangle there exists "
|
|
"a unique line through any two points; the system follows. "
|
|
"Triangle triangle triangle vertex vertex vertex."
|
|
)
|
|
toks = _content_tokens(content)
|
|
# "triangle" appears 4 times → discriminating; "this", "that",
|
|
# "every", "system" → common, filtered.
|
|
assert "triangle" in toks
|
|
assert "this" not in toks
|
|
assert "system" not in toks
|
|
assert "every" not in toks
|
|
|
|
|
|
def test_content_tokens_requires_count_at_least_2():
|
|
from arborist.qa.warrant_resolver import _content_tokens
|
|
|
|
# Singleton tokens ditched (likely typo / LaTeX residue).
|
|
content = "betweenness betweenness consider unique helpfully"
|
|
toks = _content_tokens(content)
|
|
assert "betweenness" in toks
|
|
# singletons dropped
|
|
assert "consider" not in toks
|
|
assert "unique" not in toks
|
|
|
|
|
|
def test_build_record_query_cascade_orders_correctly():
|
|
from arborist.qa.warrant_resolver import (
|
|
Citation,
|
|
_build_record_query_cascade,
|
|
)
|
|
|
|
citation = Citation(
|
|
title="The Foundations of Geometry",
|
|
authors=("David Hilbert",),
|
|
raw="The Foundations of Geometry by David Hilbert",
|
|
)
|
|
queries = _build_record_query_cascade(
|
|
citation,
|
|
theorem_name="Axiom of Line Incidence",
|
|
record_content="The line line line connection between point point points axiom incidence relation."
|
|
)
|
|
# Phrase is first; legacy AND-join is last.
|
|
assert queries[0] == '"line incidence"'
|
|
# The content-token AND-join should appear in the cascade.
|
|
assert any(" AND " in q for q in queries[1:])
|