arborist/tests/test_frame.py
russell@unturf.com 9ec9469c4d
qa(frame): #000002 land — reference-frame polarity contract (D3 → ✓)
The phrase-pattern retrieval route (commit 1b8677d) closed the
RETRIEVAL side of reference-frame failure; this ticket closes
the ANSWER side.

New module aborist/qa/frame.py:
  FrameDetection dataclass (frame_kind, reference_title,
    reference_uri, confidence). Sidecar — never enters cache_key
    or governance_policy_hash.
  detect_frame(question, sources, phrase_match_roots) — heuristic
    detector. Reference-frame classification fires when:
    (a) phrase route surfaced at least one source, AND
    (b) at least one phrase-matched source is a reference work,
        determined by:
          - title parenthetical disambig (`(novel)` / `(film)` /
            `(play)` / `(franchise)` / etc.), OR
          - body sample contains ≥3 DISTINCT fiction markers
            (novel / published / protagonist / plot / ...).
    Distinct-marker count keeps the heuristic robust against a
    history article saying "novel approach" twice.

aborist/qa/query.py:
  Calls detect_frame for lattice modes only. Body sample uses the
  ARTICLE LEAD (chunk_idx=0, post-wikitext-strip) — fiction
  markers cluster in the lead on Wikipedia, not in plot chunks
  that may have been query-relevance-ranked higher.
  New policy field claim_lattice_polarity_preamble injected as a
  user-role message before the grounding_reminder when
  frame_kind == "reference". Format-string with
  {reference_title} placeholder.
  Result dict carries frame_detection (kind / title / uri /
  confidence) for renderer + bench consumption.

aborist/cli.py:
  Renderer adds a `reference frame: <title>` line when
  frame_detection.kind == "reference". Skipped for literal /
  no-phrase-route / ambiguous rows.

Live verification — Orwell case:
  PRE  : "The text does not directly state that Oceania has always
          been at war with East Asia."
  POST : "In George Orwell's dystopian novel Nineteen Eighty-Four,
          the nation of Oceania is always at war with Eastasia,
          but this is a result of propaganda and doublethink, not
          actual historical continuity. The war with Eastasia is
          a fabricated conflict to maintain control..."

Multi-frame compilation: distinguishes propaganda claim from
fictional-actual continuity, exactly the polarity contract from
the ticket §2 abstraction.

Literal queries (capital of france) unchanged — polarity preamble
only injects when frame_kind classifies as "reference".

22 new tests (19 in test_frame.py + 3 in test_cli_render.py).
Full suite: 734 passed (was 712, +22).

Directive D3 status: ½ → ✓. Ticket #000002 closed.

All seven structural directives now ✓:
  D1, D2, D5 (were ✓);
  D3 #000002, D4 #000001, D6 #000003, D7 #000005 (closed in this
  series).
2026-05-01 20:18:13 -04:00

280 lines
10 KiB
Python

"""Frame-detection tests (#000002 / Module L scope).
Pins the heuristic detector that classifies a claim-lattice query as
``literal`` / ``reference`` / ``ambiguous`` / ``no_phrase_route`` based
on the phrase-pattern retrieval signal + per-source reference-work
indicators.
"""
from __future__ import annotations
from aborist.qa.frame import (
FrameDetection,
_body_indicates_reference_work,
_source_is_reference_work,
_title_indicates_reference_work,
detect_frame,
)
# ---------------------------------------------------------------------------
# Title-suffix detection
# ---------------------------------------------------------------------------
def test_title_suffix_recognises_novel_film_play():
assert _title_indicates_reference_work("Jurassic Park (film)")
assert _title_indicates_reference_work("Jurassic Park (novel)")
assert _title_indicates_reference_work("Hamlet (play)")
assert _title_indicates_reference_work("The Lord of the Rings (franchise)")
assert _title_indicates_reference_work("Star Wars (video game)")
def test_title_suffix_handles_multiword_disambig():
assert _title_indicates_reference_work("Some Comic (graphic novel)")
assert _title_indicates_reference_work("Some Show (TV series)")
assert _title_indicates_reference_work("Some Plot (short story)")
def test_title_suffix_doesnt_fire_on_literal_geography():
assert not _title_indicates_reference_work("Oceania")
assert not _title_indicates_reference_work("Asia")
assert not _title_indicates_reference_work("New York City")
assert not _title_indicates_reference_work("Springfield, Missouri")
def test_title_suffix_doesnt_fire_on_disambig_for_real_things():
"""Geographic / biographical disambig suffixes should NOT trip
the reference-work detector."""
assert not _title_indicates_reference_work("Cleveland (Ohio)")
assert not _title_indicates_reference_work("Mercury (planet)")
assert not _title_indicates_reference_work("Mercury (element)")
assert not _title_indicates_reference_work(
"John Smith (politician)"
)
def test_title_suffix_handles_none_or_empty():
assert not _title_indicates_reference_work(None)
assert not _title_indicates_reference_work("")
# ---------------------------------------------------------------------------
# Body fiction-marker density
# ---------------------------------------------------------------------------
def test_body_density_recognises_orwell_lead():
"""The Nineteen_Eighty-Four article opens with high fiction-
marker density: 'novel by George Orwell' + 'fiction' + 'plot' /
'characters' / 'published' typically all in the first KB."""
sample = (
"Nineteen Eighty-Four is a dystopian social science fiction "
"novel by English novelist George Orwell. The narrative "
"follows the protagonist Winston Smith, a low-ranking "
"member of the ruling Party. The novel was published in "
"1949. The plot explores themes of totalitarianism. "
"Characters include Big Brother and Julia."
)
assert _body_indicates_reference_work(sample)
def test_body_density_doesnt_fire_on_geography():
sample = (
"Oceania is a continent located in the Pacific Ocean. It "
"comprises Australia, New Zealand, and various smaller "
"Pacific island nations. The continent has a diverse "
"geography with mountains, deserts, and coral reefs. The "
"population is concentrated along the coastlines."
)
assert not _body_indicates_reference_work(sample)
def test_body_density_doesnt_fire_on_one_incidental_novel_use():
"""A history article saying 'this was a novel approach' shouldn't
trip the detector — single use vs marker density."""
sample = (
"The reform of 1850 introduced a novel approach to "
"regional administration. Local governors retained "
"significant autonomy under the new framework. The "
"policy persisted for several decades before being "
"replaced. " * 2
)
assert not _body_indicates_reference_work(sample)
def test_body_density_handles_none_or_empty():
assert not _body_indicates_reference_work(None)
assert not _body_indicates_reference_work("")
# ---------------------------------------------------------------------------
# _source_is_reference_work — composition
# ---------------------------------------------------------------------------
def test_source_is_reference_work_via_title():
src = {
"title": "Hamlet (play)",
"body_sample": "Hamlet is a tragedy.",
}
assert _source_is_reference_work(src)
def test_source_is_reference_work_via_body_only():
src = {
"title": "Nineteen Eighty-Four",
"body_sample": (
"Nineteen Eighty-Four is a dystopian science fiction "
"novel by George Orwell, published in 1949. The plot "
"follows Winston Smith. The novel explores themes of "
"totalitarianism. Characters include Big Brother."
),
}
assert _source_is_reference_work(src)
def test_source_is_not_reference_work_for_geography():
src = {
"title": "Oceania",
"body_sample": (
"Oceania is a geographic region comprising Australia, "
"New Zealand, and Pacific Islands."
),
}
assert not _source_is_reference_work(src)
# ---------------------------------------------------------------------------
# detect_frame — end-to-end classification
# ---------------------------------------------------------------------------
def test_detect_frame_no_phrase_route():
"""No phrase route fired → no_phrase_route classification."""
sources = [{"document_root": "abc", "title": "Oceania"}]
fd = detect_frame("oceania population", sources, phrase_match_roots=set())
assert fd.frame_kind == "no_phrase_route"
assert fd.reference_title is None
def test_detect_frame_no_phrase_route_when_arg_is_none():
sources = [{"document_root": "abc", "title": "Oceania"}]
fd = detect_frame("oceania population", sources, phrase_match_roots=None)
assert fd.frame_kind == "no_phrase_route"
def test_detect_frame_reference_via_title_suffix():
"""Phrase route fired AND matched source has a (film) suffix →
reference-frame classification."""
sources = [
{
"document_root": "aaa",
"title": "Jurassic Park (film)",
"document_uri": "https://en.wikipedia.org/wiki/Jurassic_Park_(film)",
"body_sample": "Jurassic Park is a 1993 American science fiction action film.",
},
]
fd = detect_frame(
"what dinosaurs were in jurassic park?",
sources,
phrase_match_roots={"aaa"},
)
assert fd.frame_kind == "reference"
assert fd.reference_title == "Jurassic Park (film)"
assert fd.confidence > 0.5
def test_detect_frame_reference_via_body_density_orwell():
"""Phrase route fired AND matched source has fiction body markers
→ reference-frame even without a title suffix (Nineteen
Eighty-Four canonical case)."""
sources = [
{
"document_root": "bbb",
"title": "Nineteen Eighty-Four",
"document_uri": "https://en.wikipedia.org/wiki/Nineteen_Eighty-Four",
"body_sample": (
"Nineteen Eighty-Four is a dystopian science fiction "
"novel by George Orwell, published in 1949. The plot "
"follows Winston Smith, a Party member. Characters "
"include Big Brother and Julia. The novel explores "
"totalitarianism."
),
},
]
fd = detect_frame(
"has oceania always been at war with east asia",
sources,
phrase_match_roots={"bbb"},
)
assert fd.frame_kind == "reference"
assert fd.reference_title == "Nineteen Eighty-Four"
def test_detect_frame_ambiguous_when_phrase_matched_but_not_reference():
"""Phrase route fired but the matched source is a non-fiction
article (e.g. a verbatim quotation that happens to appear in a
history page). Classification: ambiguous."""
sources = [
{
"document_root": "ccc",
"title": "List of common phrases",
"document_uri": "https://example.org/list",
"body_sample": (
"This is a list page. It contains various phrases "
"and their meanings. Geographic regions include "
"Asia, Africa, and Europe."
),
},
]
fd = detect_frame(
"always been at war with east asia",
sources,
phrase_match_roots={"ccc"},
)
assert fd.frame_kind == "ambiguous"
assert fd.reference_title is None
def test_detect_frame_picks_first_reference_source_when_multiple():
"""When phrase route surfaced multiple sources and ≥2 are
reference works, pick the first by retrieval order."""
sources = [
{
"document_root": "aaa",
"title": "Jurassic Park (film)",
"body_sample": "An action film.",
},
{
"document_root": "bbb",
"title": "Jurassic Park (novel)",
"body_sample": "A novel by Michael Crichton.",
},
]
fd = detect_frame(
"dinosaurs in jurassic park",
sources,
phrase_match_roots={"aaa", "bbb"},
)
assert fd.frame_kind == "reference"
# First-by-order is the film.
assert fd.reference_title == "Jurassic Park (film)"
def test_detect_frame_skips_non_phrase_matched_sources():
"""A reference-work source that wasn't phrase-matched doesn't
trigger the reference frame (otherwise any retrieval that
happens to include a (film)-suffixed source would over-fire)."""
sources = [
{
"document_root": "aaa",
"title": "Hamlet (play)", # reference work but not phrase-matched
"body_sample": "A tragedy.",
},
]
fd = detect_frame(
"hamlet plot summary",
sources,
phrase_match_roots=set(), # no phrase route
)
assert fd.frame_kind == "no_phrase_route"