Files
brain-of-reese/tests/unit/test_retriever.py
T
ducoterra a5b63f83ad
Build and Push Containers / build-and-push-app (push) Successful in 2m1s
Build and Push Containers / build-and-push-db (push) Successful in 18s
phase: 119_name_signal_read_chips
All verification complete. Final report:

**Phase 119 final verification pass — all criteria verified, one stale pin fixed.**
- Verified implementation of all 6 tasks: D1 component name-hit rule (`name_hit` flag, titles never matched, retired length tie-break), D2 `BOR_NAME_HIT_BONUS` (0.005 default, 0 = byte-identical kill switch, negative fails startup, selection-layer only, `eval_retrieval` `suggested:` line), D3 suggested-folder lines (after `SUGGEST_INTRO`, before first block), D4 cite-discipline `SUGGEST_INTRO` sentence (PERSONA/LOW/`TOOLS_SECTION` byte-pins intact), D5 `done.sources` = read docs only (frontend no-op on empty confirmed), D6 mock `repeat your folder map` echo + new suite + telemetry.
- Battery (replica restored per skill, fingerprint docs=1000/chunks=8866 verified, `eval_retrieval --from-file tests/fixtures/retrieval_battery.txt` re-run): **GATE PASS** — gitea README #4 in suggested top-5, forgejo 5/5 (README #1), gateway README in top-5 (#4), qwen3.8-27b quadlets top-5, Mongolia HIGH/fts=5 unchanged.
- New E2E in isolation: `4 passed` ×2 (deterministic). All 27 modified E2E suites in isolation: 26 green; **1 stale pin fixed** — `test_source_chip_quality.py` durable-record order pin pre-dated the D1 re-rank (`aliases` stem sub-component name-hits `ssh_aliases.txt`, deterministically lifting `backups.md` over `kubernetes.md`; probe-verified 0.016277 vs 0.016036, 4/4 stable) — re-pinned with the phase-119 rationale; suite green ×2.
- Gates: `uv run pytest --cov=app --cov-report=term-missing` → **2547 passed, app coverage 99%** (>90%); `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
- Completion criteria: 1 ✅ (battery, recorded), 2 ✅ (folder lines; block/LOW byte-identical pins green), 3 ✅ (read-only chips, zero-read chips nothing, related row + durable record untouched — unit+E2E agree), 4 ✅ (all green), 5 → commit/phase-move left to the harness per pass rules (nothing committed).
- Deviations: battery output + real-model telemetry recorded in `.agents/reports/119_name_signal_read_chips/task06_battery_and_e2e.md` and `TOOL_CALLING_TESTING.md` §11 (task files in `complete/` are immutable to this pass); gateway canonical doc at #4 vs overview's #3 was already documented at task 06 (containment gate met).
- Next pending phase: **none** — `todo/` holds only phase 119.
2026-09-16 15:50:48 -04:00

1349 lines
56 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit: retriever — ordering and dedup, no context cap (A7 revised) (fake rows).
The SQL side of :func:`app.rag.retriever.retrieve` is exercised by the
chat integration tests against real Postgres; the pure mapping logic in
:func:`select_documents` is tested here with in-memory rows.
"""
from __future__ import annotations
import uuid
from types import SimpleNamespace
import pytest
from app.config import Settings
from app.models import Document
from app.rag import retriever
from app.rag.retriever import (
TRUNCATION_MARKER,
RetrievedChunk,
select_documents,
select_documents_tiered,
select_related,
select_suggested,
)
def _doc(path: str, content: str, source: str = "Homelab", title: str | None = None) -> Document:
return Document(
id=uuid.uuid4(),
source=source,
path=path,
full_path=f"/tmp/{path}",
title=title or path,
content=content,
content_hash="0" * 64,
)
def _chunk(doc: Document, score: float, position: int = 0) -> RetrievedChunk:
return RetrievedChunk(
chunk_id=uuid.uuid4(),
position=position,
content=doc.content[:40],
score=score,
document=doc,
)
def test_ranks_by_best_chunk_score_not_first_hit() -> None:
"""A doc whose *later* chunk scores highest must still rank first."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
chunks = [
_chunk(a, 0.4, position=0), # a's weak chunk comes first
_chunk(b, 0.8),
_chunk(a, 0.9, position=2), # a's best chunk comes last
_chunk(c, 0.5),
]
docs = select_documents(chunks, n=3)
assert [d.path for d in docs] == ["a.md", "b.md", "c.md"]
def test_dedups_to_one_document_per_hit_set() -> None:
a = _doc("a.md", "A" * 50)
chunks = [_chunk(a, 0.2), _chunk(a, 0.7), _chunk(a, 0.5)]
docs = select_documents(chunks, n=2)
assert len(docs) == 1
assert docs[0] is a
def test_caps_at_n_documents() -> None:
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(4)]
chunks = [_chunk(d, 0.5 - 0.1 * i) for i, d in enumerate(docs_in)]
out = select_documents(chunks, n=2)
assert [d.path for d in out] == ["d0.md", "d1.md"]
def test_content_never_truncated_even_past_old_budget() -> None:
"""Whole documents, never truncated (A7 revised, owner permission 2026-08-24).
Two documents of 20 000 + 15 000 chars — 35 000 combined, well past
the old 24 000 context budget — come back with content
**byte-identical** to the originals, and the truncation marker is
absent from both.
"""
big = _doc("big.md", "B" * 20_000)
small = _doc("small.md", "S" * 15_000)
chunks = [_chunk(big, 0.9), _chunk(small, 0.6)]
out = select_documents(chunks, n=2)
assert [d.path for d in out] == ["big.md", "small.md"]
assert out[0].content == "B" * 20_000
assert out[1].content == "S" * 15_000
assert TRUNCATION_MARKER not in out[0].content
assert TRUNCATION_MARKER not in out[1].content
def test_empty_hits_yield_no_documents() -> None:
assert select_documents([], n=2) == []
# ---------------------------------------------------------------------------
# Phase 113 — the usefulness bar: cited vs related tiers
# ---------------------------------------------------------------------------
def _cos_chunk(doc: Document, score: float, cosine: float, position: int = 0) -> RetrievedChunk:
"""A candidate with *score* (fused rank key) and *cosine* (gate input) decoupled."""
return RetrievedChunk(
chunk_id=uuid.uuid4(),
position=position,
content=doc.content[:40],
score=score,
document=doc,
cosine=cosine,
)
def test_tiered_both_clear_floor_both_cited() -> None:
"""Both docs clear the bar → both cited, nothing related."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.9, 0.50), _cos_chunk(b, 0.8, 0.40)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["a.md", "b.md"]
assert related == []
def test_tiered_strong_plus_weak_one_cited_one_related() -> None:
"""The recurring incident shape: a strong 1st doc and a weak 2nd — the
weak doc loses its citation slot and lands in the related tier."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.9, 0.50), _cos_chunk(b, 0.8, 0.10)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["a.md"]
assert [d.path for d in related] == ["b.md"]
def test_tiered_both_weak_zero_cited_all_related() -> None:
"""Neither doc clears the bar → no citation slot at all (the bar
filters, it never backfills), the weak hits become the related tier."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.9, 0.20), _cos_chunk(b, 0.8, 0.15)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert cited == []
assert [d.path for d in related] == ["a.md", "b.md"] # rank order kept
def test_tiered_related_cap_respected() -> None:
"""The related tier is capped (related_max_docs) in rank order."""
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(3)]
chunks = [_cos_chunk(d, 0.9 - 0.1 * i, 0.10 - 0.02 * i) for i, d in enumerate(docs_in)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert cited == []
assert [d.path for d in related] == ["d0.md", "d1.md"]
def test_tiered_n_is_ceiling_not_quota() -> None:
"""A single strong doc yields ONE cited doc — the bar never pads the
cited tier up to ``n`` (LOCKED A2). And docs that clear the bar but
exceed the ceiling fall through to the related tier (the next docs in
rank order, never overlapping cited)."""
only = _doc("only.md", "O" * 50)
cited, related = select_documents_tiered(
[_cos_chunk(only, 0.9, 0.80)], n=2, floor=0.35, related_cap=2
)
assert [d.path for d in cited] == ["only.md"]
assert related == []
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(4)]
chunks = [_cos_chunk(d, 0.9 - 0.1 * i, 0.8 - 0.05 * i) for i, d in enumerate(docs_in)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["d0.md", "d1.md"] # the ceiling
assert [d.path for d in related] == ["d2.md", "d3.md"] # the next in rank order
def test_tiered_bar_skips_weak_rank_one() -> None:
"""A weak rank-1 doc does not consume a citation slot: the next-ranked
bar-clearing docs take it (the bar filters, it does not backfill)."""
w = _doc("w.md", "W" * 50)
s1 = _doc("s1.md", "1" * 50)
s2 = _doc("s2.md", "2" * 50)
chunks = [
_cos_chunk(w, 0.9, 0.20), # rank 1 — below the bar
_cos_chunk(s1, 0.8, 0.90),
_cos_chunk(s2, 0.7, 0.80),
]
cited, related = select_documents_tiered(chunks, n=2, floor=0.5, related_cap=2)
assert [d.path for d in cited] == ["s1.md", "s2.md"]
assert [d.path for d in related] == ["w.md"]
def test_tiered_tracks_best_chunk_cosine_across_a_docs_chunks() -> None:
"""The bar is on the doc's BEST hit-chunk cosine — a weak first chunk
(rank 1) does not sink a doc whose later chunk is vector-strong."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [
_cos_chunk(a, 0.9, 0.10, position=0), # a's weak chunk ranks first
_cos_chunk(a, 0.5, 0.90, position=2), # a's strong chunk
_cos_chunk(b, 0.4, 0.0), # lexical-only b
]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["a.md"]
assert [d.path for d in related] == ["b.md"]
def test_tiered_lexical_only_hit_goes_to_related_above_zero_floor() -> None:
"""A lexical-only doc (cosine 0.0 by construction) is vector-unsupported
by definition: above a zero floor it never earns a cited slot (LOCKED A2)."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
lexical_only = RetrievedChunk(
chunk_id=uuid.uuid4(),
position=0,
content="X" * 10,
score=0.9, # top fused rank (the FTS hit)
document=a,
cosine=0.0, # no vector rank — lexical-only
fts_hit=True,
)
chunks = [lexical_only, _cos_chunk(b, 0.8, 0.5)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["b.md"]
assert [d.path for d in related] == ["a.md"]
def test_tiered_floor_zero_is_no_bar() -> None:
"""A zero floor admits every scored document — lexical-only (cosine
0.0) and weak alike — so the bar can be disabled per deployment."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.9, 0.0), _cos_chunk(b, 0.8, 0.1)]
cited, related = select_documents_tiered(chunks, n=2, floor=0.0, related_cap=2)
assert [d.path for d in cited] == ["a.md", "b.md"]
assert related == []
def test_tiered_empty_chunks_yield_empty_tiers() -> None:
assert select_documents_tiered([], n=2, floor=0.35, related_cap=2) == ([], [])
def test_select_documents_wrapper_is_legacy_tiering() -> None:
"""The wrapper (floor 0.0, cap 0) is the legacy "any score, top-N"
selection — byte-identical for the shapes the existing callers see:
rank order, dedupe, multi-chunk docs, ties, lexical-only hits."""
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(5)]
chunks = [
_chunk(docs_in[0], 0.5), # cosine 0.0 (lexical-only)
_cos_chunk(docs_in[0], 0.9, 0.4, position=1),
_chunk(docs_in[1], 0.8),
_cos_chunk(docs_in[1], 0.8, 0.2, position=1), # fused tie across docs
_cos_chunk(docs_in[2], 0.7, 0.0),
_cos_chunk(docs_in[3], 0.6, 0.1),
_chunk(docs_in[4], 0.1),
]
for n in (1, 2, 3, 10):
assert select_documents(chunks, n=n) == select_documents_tiered(
chunks, n, 0.0, 0
)[0]
# ---------------------------------------------------------------------------
# Phase 118 — select_suggested: the top-N "start here" tier, NO floor (A3)
# ---------------------------------------------------------------------------
def test_suggested_rank_order_by_first_seen_chunk() -> None:
"""The SAME stable walk as ``select_documents_tiered``: a document's
rank is fixed by its FIRST seen chunk in score-descending order — a
doc whose best chunk appears later in the input list still ranks
where that chunk falls."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
chunks = [
_chunk(a, 0.4, position=0), # a's weak chunk comes first
_chunk(b, 0.8),
_chunk(a, 0.9, position=2), # a's best chunk comes last
_chunk(c, 0.5),
]
out = select_suggested(chunks, n=5)
assert [d.path for d in out] == ["a.md", "b.md", "c.md"]
# The rows carry the full content byte-identical (A6: the content is
# what ``read`` serves later — never truncated).
assert out[0].content == "A" * 50
assert TRUNCATION_MARKER not in out[0].content
def test_suggested_dedupes_multiple_chunks_to_one_row() -> None:
"""Multiple hit chunks of one document collapse to a single row."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [
_chunk(a, 0.2),
_chunk(b, 0.7),
_chunk(a, 0.9, position=2),
_chunk(a, 0.5),
]
out = select_suggested(chunks, n=5)
assert [d.path for d in out] == ["a.md", "b.md"] # one row per document
assert out[0] is a
def test_suggested_caps_at_n_in_rank_order() -> None:
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(7)]
chunks = [_chunk(d, 0.9 - 0.1 * i) for i, d in enumerate(docs_in)]
out = select_suggested(chunks, n=3)
assert [d.path for d in out] == ["d0.md", "d1.md", "d2.md"]
def test_suggested_default_cap_is_the_settings_value(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``n`` omitted → ``BOR_SUGGESTED_DOCS`` caps the walk — default 5
(LOCKED A3, the top-5 "start here" directive, TODO L3) — and the cap
is the setting's LIVE value, not a frozen constant."""
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(7)]
chunks = [_chunk(d, 0.9 - 0.1 * i) for i, d in enumerate(docs_in)]
settings = Settings(_env_file=None) # pyright: ignore[reportCallIssue]
assert settings.suggested_docs == 5 # the production default
monkeypatch.setattr(retriever, "get_settings", lambda: settings)
assert [d.path for d in select_suggested(chunks)] == [
f"d{i}.md" for i in range(5)
]
small = Settings(_env_file=None, suggested_docs=2) # pyright: ignore[reportCallIssue]
monkeypatch.setattr(retriever, "get_settings", lambda: small)
assert [d.path for d in select_suggested(chunks)] == ["d0.md", "d1.md"]
def test_suggested_never_filters_on_cosine_floor() -> None:
"""NO floor (LOCKED A3): a lexical-only hit (cosine 0.0 by
construction) is a suggestion when it ranks — the contrast pin
against ``select_documents_tiered``'s floored cited tier on the SAME
input, which demotes it to the related tier."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
lexical_only = RetrievedChunk(
chunk_id=uuid.uuid4(),
position=0,
content="X" * 10,
score=0.9, # top fused rank (the FTS hit)
document=a,
cosine=0.0, # no vector rank — lexical-only
fts_hit=True,
)
chunks = [lexical_only, _cos_chunk(b, 0.8, 0.5)]
# Suggested: the floor never filters — a leads, b follows in rank order.
assert [d.path for d in select_suggested(chunks, n=5)] == ["a.md", "b.md"]
# Contrast: the same input through the phase-113 cited tier — the
# 0.35 usefulness bar demotes the lexical-only doc to related.
cited, related = select_documents_tiered(chunks, n=2, floor=0.35, related_cap=2)
assert [d.path for d in cited] == ["b.md"]
assert [d.path for d in related] == ["a.md"]
def test_suggested_tie_break_inherited_from_fused_order() -> None:
"""Equal fused scores keep the input (fused) order — the stable
score-only walk inherits ``fuse()``'s (-score, -cosine, path,
position) tie-break; the selector never re-sorts it away."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.7, 0.5), _cos_chunk(b, 0.7, 0.4)] # a wins on cosine
assert [d.path for d in select_suggested(chunks, n=5)] == ["a.md", "b.md"]
# A FULL tie (score AND cosine): the input position — ``fuse()``'s
# path/position tie-break already applied — decides. ``m.md`` sorts
# AFTER ``b2.md`` alphabetically, so any re-sort by path would flip
# the order; the fused input order must win.
m = _doc("m.md", "M" * 50)
b2 = _doc("b2.md", "B" * 50)
chunks = [_cos_chunk(m, 0.7, 0.4), _cos_chunk(b2, 0.7, 0.4)]
assert [d.path for d in select_suggested(chunks, n=5)] == ["m.md", "b2.md"]
def test_suggested_empty_chunks_yield_no_documents() -> None:
assert select_suggested([], n=5) == []
# ---------------------------------------------------------------------------
# Phase 118, task 05 — select_related: the rank-6+ tier after the suggested set
# ---------------------------------------------------------------------------
def test_related_walk_order_after_excluded_set() -> None:
"""The SAME stable score-descending walk as ``select_suggested``:
a document's rank is fixed by its FIRST seen chunk; documents in
*excluded_ids* (the suggested set) are skipped and the rest come
back in rank order."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
d = _doc("d.md", "D" * 50)
chunks = [
_chunk(a, 0.4, position=0), # a's weak chunk comes first
_chunk(b, 0.8),
_chunk(a, 0.9, position=2), # a's best chunk last — a ranks first
_chunk(c, 0.5),
_chunk(d, 0.3),
]
out = select_related(chunks, {a.id, b.id}, cap=2)
assert [x.path for x in out] == ["c.md", "d.md"]
def test_related_skips_excluded_documents() -> None:
"""Every document in *excluded_ids* is skipped, even when it would
rank inside the cap — the suggested set never rides the related row."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
chunks = [_chunk(a, 0.9), _chunk(b, 0.8), _chunk(c, 0.5)]
out = select_related(chunks, {a.id, b.id}, cap=5)
assert out == [c]
# Every doc excluded → empty, even with room left in the cap.
assert select_related(chunks, {a.id, b.id, c.id}, cap=5) == []
def test_related_caps_at_cap_in_rank_order() -> None:
"""The phase-118 turn wiring on 9 docs: suggested = the top 5,
related = rank 6–7 (capped at 2), disjoint from the suggested set."""
docs_in = [_doc(f"d{i}.md", "X" * 20) for i in range(9)]
chunks = [_chunk(d, 0.9 - 0.1 * i) for i, d in enumerate(docs_in)]
suggested = select_suggested(chunks, n=5)
out = select_related(chunks, {d.id for d in suggested}, cap=2)
assert [x.path for x in out] == ["d5.md", "d6.md"] # rank 6–7, capped
suggested_paths = {d.path for d in suggested}
assert suggested_paths.isdisjoint({x.path for x in out})
def test_related_cap_zero_yields_empty() -> None:
"""cap=0 is the kill switch (related_max_docs=0): no related docs,
the pre-phase-113 visibility."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
chunks = [_chunk(a, 0.9), _chunk(b, 0.8), _chunk(c, 0.5)]
assert select_related(chunks, {a.id}, cap=0) == []
def test_related_never_filters_on_cosine_floor() -> None:
"""NO floor: a lexical-only (cosine 0.0) doc is related when it
ranks after the excluded set — the related tier is visibility, not
citation (phase 118 applies no cosine floor to it)."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
lexical_only = RetrievedChunk(
chunk_id=uuid.uuid4(),
position=0,
content="X" * 10,
score=0.9, # top fused rank (the FTS hit)
document=a,
cosine=0.0, # no vector rank — lexical-only
fts_hit=True,
)
chunks = [lexical_only, _cos_chunk(b, 0.8, 0.5)]
out = select_related(chunks, set(), cap=5)
assert [x.path for x in out] == ["a.md", "b.md"]
def test_related_dedupes_multiple_chunks_to_one_row() -> None:
"""Multiple hit chunks of one document collapse to a single row
(first-seen-chunk rank, dedupe by document.id — the shared walk)."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_chunk(a, 0.2), _chunk(b, 0.7), _chunk(a, 0.9, position=2)]
out = select_related(chunks, set(), cap=5)
assert [x.path for x in out] == ["a.md", "b.md"] # one row per document
assert out[0] is a
def test_related_tie_break_inherited_from_fused_order() -> None:
"""Equal fused scores keep the input (fused) order — the stable
score-only walk inherits ``fuse()``'s (-score, -cosine, path,
position) tie-break; the selector never re-sorts it away."""
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
chunks = [_cos_chunk(a, 0.7, 0.5), _cos_chunk(b, 0.7, 0.4)] # a wins on cosine
assert [x.path for x in select_related(chunks, set(), cap=5)] == ["a.md", "b.md"]
m = _doc("m.md", "M" * 50)
b2 = _doc("b2.md", "B" * 50)
chunks = [_cos_chunk(m, 0.7, 0.4), _cos_chunk(b2, 0.7, 0.4)] # full tie
assert [x.path for x in select_related(chunks, set(), cap=5)] == ["m.md", "b2.md"]
def test_related_empty_chunks_yield_no_documents() -> None:
assert select_related([], set(), cap=5) == []
# ---------------------------------------------------------------------------
# Hybrid retrieval (A7): RRF fusion + lexical tsquery
# ---------------------------------------------------------------------------
from app.rag.retriever import fuse, lexical_tsquery # noqa: E402
def _rc(
doc_path: str, cosine: float = 0.0, fts_hit: bool = False, position: int = 0,
is_summary: bool = False,
) -> RetrievedChunk:
return RetrievedChunk(
chunk_id=uuid.uuid4(),
position=position,
content="x" * 20,
score=0.0,
document=_doc(doc_path, "x" * 20),
cosine=cosine,
fts_hit=fts_hit,
is_summary=is_summary,
)
def test_lexical_tsquery_tokens_lowercased_deduped_in_order() -> None:
assert lexical_tsquery("How did I Install GITLAB gitlab?") == "how | did | i | install | gitlab"
def test_lexical_tsquery_punctuation_and_umlauts_ignored() -> None:
assert lexical_tsquery("c3-r00t? -- what's up!") == "c3 | r00t | what | s | up"
def test_lexical_tsquery_pure_symbols_return_none() -> None:
assert lexical_tsquery("??? ???") is None
assert lexical_tsquery("") is None
def test_lexical_tsquery_stopwords_left_to_postgres() -> None:
# lexical_tsquery passes raw tokens through; Postgres's to_tsquery
# lexing drops the stopwords (verified against real PG in
# test_retrieve_empty_kb / integration tests).
assert lexical_tsquery("how do i") == "how | do | i"
def test_lexical_tsquery_dotted_tokens_kept_whole() -> None:
"""The 2026-09-05 incident: the default parser lexes dotted words
as ONE lexeme ("llama.cpp" → 'llama.cpp', "Qwen 3.8" → '3.8'), so
the query carries them whole — split tokens (llama | cpp) can never
match the document side."""
assert lexical_tsquery(
"What are the correct llama.cpp arguments for Qwen 3.8?"
) == "what | are | the | correct | llama.cpp | arguments | for | qwen | 3.8"
# The dash still splits (only dots group): ai | internal.network.
assert lexical_tsquery("how did I set up ai-internal.network?") == (
"how | did | i | set | up | ai | internal.network"
)
def test_fuse_combines_both_lists_for_double_hits() -> None:
v1 = _rc("a.md", cosine=0.9)
v2 = _rc("b.md", cosine=0.5)
l1 = _rc("a.md", cosine=0.1) # same chunk id -> matched in place
a_id = v1.chunk_id
l1.chunk_id = a_id
out = fuse([v1, v2], [l1], k=60)
by_id = {rc.chunk_id: rc for rc in out}
# a: 1/61 (vector rank 1) + 1/61 (lexical rank 1); b: 1/62 only.
assert by_id[a_id].score == pytest.approx(2 / 61)
assert by_id[a_id].fts_hit is True
assert by_id[v2.chunk_id].score == pytest.approx(1 / 62)
assert by_id[v2.chunk_id].fts_hit is False
assert [rc.chunk_id for rc in out] == [a_id, v2.chunk_id]
def test_fuse_lexical_only_chunks_enter_with_zero_cosine() -> None:
vector = [_rc("a.md", cosine=0.8)]
lexical = [_rc("b.md", cosine=0.0, fts_hit=True)]
out = fuse(vector, lexical, k=60)
assert len(out) == 2
b = next(rc for rc in out if rc.document.path == "b.md")
assert b.cosine == 0.0
assert b.fts_hit is True
# Still ranked by its (only) RRF term.
assert b.score == pytest.approx(1 / 61)
def test_fuse_orders_by_score_then_cosine_then_path() -> None:
# Two chunks share an RRF score (both rank 1 in different lists):
# the higher-cosine one must sort first.
hi = _rc("z.md", cosine=0.9)
lo = _rc("a.md", cosine=0.2)
out = fuse([hi], [lo], k=60)
assert [rc.document.path for rc in out] == ["z.md", "a.md"]
# Equal score AND cosine -> path order.
p1 = _rc("b.md", cosine=0.5)
p2 = _rc("a.md", cosine=0.5)
out = fuse([p1], [p2], k=60)
assert [rc.document.path for rc in out] == ["a.md", "b.md"]
# Equal score, cosine, path -> position order.
s1 = _rc("a.md", cosine=0.5, position=1)
s2 = _rc("a.md", cosine=0.5, position=0)
out = fuse([s1], [s2], k=60)
assert [rc.position for rc in out] == [0, 1]
def test_fuse_rejects_nonpositive_k() -> None:
with pytest.raises(ValueError):
fuse([], [], k=0)
with pytest.raises(ValueError):
fuse([], [], k=-1)
def test_fuse_empty_lists() -> None:
assert fuse([], [], k=60) == []
# ---------------------------------------------------------------------------
# Phase 30: is_summary survives both candidate lists and the fusion
# ---------------------------------------------------------------------------
from app.models import Chunk # noqa: E402
from app.rag.retriever import ( # noqa: E402
NAME_HIT_LIMIT,
_lexical_candidates,
_name_hit_chunks,
_normalize_name,
_vector_candidates,
name_hit_tokens,
)
class _FakeResult:
"""Stands in for SQLAlchemy's RowMapping result (``.all()`` only)."""
def __init__(self, rows: list) -> None:
self._rows = rows
def all(self) -> list:
return self._rows
class _FakeSession:
"""Returns canned rows from ``execute`` without touching Postgres.
One list of rows (legacy form) is returned for EVERY call; several
lists (one per successive ``execute``) model a query sequence — the
name-hit lexical path (2026-09-05) issues the document-projection
query and, when hits exist, the LATERAL chunk query, BEFORE the FTS
query.
"""
def __init__(self, *rowsets: list) -> None:
if len(rowsets) == 1 and not (
rowsets[0] and isinstance(rowsets[0][0], list)
):
rowsets = (rowsets[0],) # the single-rowset legacy form
self._rowsets = rowsets
self._call = 0
self.statements: list = []
def execute(self, stmt, params: dict | None = None) -> _FakeResult:
self.statements.append((stmt, params))
rows = self._rowsets[min(self._call, len(self._rowsets) - 1)]
self._call += 1
return _FakeResult(rows)
def _chunk_row(is_summary: bool) -> Chunk:
doc = _doc("summary-src.yaml", "RAW_YAML_CONTENT")
return Chunk(
id=uuid.uuid4(),
document_id=doc.id,
position=-1, # the summary chunk's position (phase 30)
content="Summary text",
is_summary=is_summary,
)
def test_vector_candidates_carry_is_summary_flag() -> None:
"""The vector list copies ``Chunk.is_summary`` onto each candidate."""
doc = _doc("summary-src.yaml", "RAW_YAML_CONTENT")
summary = _chunk_row(is_summary=True)
ordinary = _chunk_row(is_summary=False)
ordinary.position = 0
ordinary.content = "ordinary content"
rows = [
(summary, 0.123456, doc),
(ordinary, 0.2, doc),
]
out = _vector_candidates(_FakeSession(rows), [0.0] * 768, limit=5) # pyright: ignore[reportArgumentType]
assert len(out) == 2
by_pos = {rc.position: rc for rc in out}
assert by_pos[-1].is_summary is True # the summary chunk (position −1)
assert by_pos[0].is_summary is False # ordinary content chunk
assert by_pos[-1].cosine == pytest.approx(0.876544) # 1 − distance, still rounded
def test_vector_candidates_default_is_summary_false_for_legacy_chunks() -> None:
"""Pre-phase-30 rows have ``is_summary=false`` — candidates stay False."""
doc = _doc("legacy.md", "LEGACY")
legacy = Chunk(
id=uuid.uuid4(),
document_id=doc.id,
position=0,
content="legacy content",
is_summary=False,
)
out = _vector_candidates(_FakeSession([(legacy, 0.5, doc)]), [0.0] * 768, limit=5) # pyright: ignore[reportArgumentType]
assert out[0].is_summary is False
def _lexical_row(is_summary: bool, doc_path: str) -> object:
"""One row of ``_LEXICAL_SQL`` (attribute access, as SQLAlchemy returns)."""
doc = _doc(doc_path, "DOC_BODY")
return SimpleNamespace(
chunk_id=uuid.uuid4(),
position=-1 if is_summary else 0,
content="summary chunk text" if is_summary else "content chunk text",
doc_id=doc.id,
source=doc.source,
path=doc.path,
full_path=doc.full_path,
title=doc.title,
doc_content=doc.content,
content_hash=doc.content_hash,
indexed_at=None,
created_at=None,
is_summary=is_summary,
rank=0.33,
)
def test_lexical_candidates_carry_is_summary_flag() -> None:
"""The lexical list reads ``c.is_summary`` from the raw row.
The question's tokens are name candidates (class-agnostic, phase
119), so the name-hit projection runs — but the (empty) catalog
yields no path match, the LATERAL fetch is skipped, and the FTS
rowset answers the second call: the list is the plain FTS rows,
every one ``name_hit=False``.
"""
rows = [_lexical_row(True, "summary-src.yaml"), _lexical_row(False, "other.md")]
out = _lexical_candidates(
_FakeSession([], rows), "how do i configure the thing", limit=10 # pyright: ignore[reportArgumentType]
)
assert len(out) == 2
by_path = {rc.document.path: rc for rc in out}
assert by_path["summary-src.yaml"].is_summary is True
assert by_path["summary-src.yaml"].position == -1
assert by_path["other.md"].is_summary is False
assert all(rc.fts_hit is True for rc in out)
assert all(rc.name_hit is False for rc in out) # ordinary FTS rows
def test_fuse_keeps_is_summary_on_double_hit() -> None:
"""A summary chunk in both lists keeps the flag after fusion."""
v1 = _rc("s.yaml", cosine=0.9, is_summary=True)
l1 = _rc("s.yaml", cosine=0.9, is_summary=True) # lexical copy of the same chunk
l1.chunk_id = v1.chunk_id
out = fuse([v1], [l1], k=60)
assert len(out) == 1
assert out[0].is_summary is True
assert out[0].fts_hit is True
assert out[0].score == pytest.approx(2 / 61)
def test_fuse_keeps_is_summary_on_lexical_only_hit() -> None:
"""A summary-only lexical hit (no vector rank) keeps the flag."""
out = fuse([], [_rc("s.yaml", is_summary=True)], k=60)
assert len(out) == 1
assert out[0].is_summary is True
assert out[0].fts_hit is True
assert out[0].cosine == 0.0
def test_fuse_default_is_summary_stays_false_for_legacy_chunks() -> None:
"""Neither list flagged ⇒ fusion never invents a summary flag."""
out = fuse([_rc("a.md", cosine=0.8)], [_rc("b.md")], k=60)
assert len(out) == 2
assert all(rc.is_summary is False for rc in out)
# ---------------------------------------------------------------------------
# Name-hit lexical signal (the 2026-09-05 incident — the versioned-name
# case the default parser lexes incompatibly: "Qwen 3.8" → qwen/3/8 can
# never match a document's qwen3/8/27b tokens)
# ---------------------------------------------------------------------------
INCIDENT_QUESTION = "What are the correct llama.cpp arguments for Qwen 3.8?"
def test_normalize_name() -> None:
assert _normalize_name("Qwen 3.8") == "qwen38"
assert _normalize_name("qwen3.8-27b-juggernaut-vulkan") == "qwen3827bjuggernautvulkan"
assert _normalize_name("Mixed CASE-99") == "mixedcase99"
assert _normalize_name("!!!") == ""
def test_name_hit_tokens_incident_question() -> None:
"""Phase 119 (LOCKED A2): the candidate list is CLASS-AGNOSTIC —
every normalized token of length >= 4 (dotted kept whole:
``llama.cpp`` → ``llamacpp``) plus the versioned join ``qwen38``.
The digit distinction moved to the match side (:func:`_name_hit_chunks`) —
prose precision now comes from the match class (a digitless token
must EQUAL a whole path component). The single digits ("3", "8")
and the bare "38" are < 4 chars; the digit-leading ``38show``
boundary artifact cannot survive (the join only fires on a purely
numeric SECOND token)."""
tokens = name_hit_tokens(INCIDENT_QUESTION)
assert tokens == ["what", "correct", "llamacpp", "arguments", "qwen", "qwen38"]
for absent in ("3", "8", "38", "38show", "server"):
assert absent not in tokens
def test_name_hit_tokens_digitless_question_yields_long_tokens() -> None:
"""A question with NO digit-bearing token still yields candidates
(every normalized token of length >= 4) — the 2026-09-16 fix:
product names without digits ("gitea", "gateway") must get a name
signal. Prose joins (``correctcaddy``) never count (the second
token is not purely numeric)."""
assert name_hit_tokens("what is the correct caddy config") == [
"what", "correct", "caddy", "config",
]
assert name_hit_tokens("a e i o u 3 8") == []
def test_name_hit_tokens_bare_digit_bearing_token() -> None:
"""A single written token that carries a digit (``1panel``) is a
name candidate on its own — no join needed — alongside the plain
prose tokens of the same question (class-agnostic list)."""
tokens = name_hit_tokens("what is my 1panel dashboard setup")
assert tokens == ["what", "1panel", "dashboard", "setup"]
def test_name_hit_tokens_versioned_join_and_short_tokens() -> None:
"""The versioned join survives the class-agnostic change ("Qwen
3.8" → ``qwen38``), and short tokens (< :data:`NAME_TOKEN_MIN_LEN`
normalized — the single digits, "3.8" → ``38``) never become
candidates, with or without a join."""
tokens = name_hit_tokens("help me with Qwen 3.8 please")
# The join is appended at its FIRST token's position (after "qwen").
assert tokens == ["help", "with", "qwen", "qwen38", "please"]
assert name_hit_tokens("3.8 8 16 9") == [] # 38 / 8 / 16 / 9 / 816 / 169 all < 4
# Word-after-version: the word itself is a candidate, but the
# digit-leading join artifact ("38show") cannot survive (the join
# only fires on a purely numeric SECOND token).
assert name_hit_tokens("3.8 show") == ["show"]
def _name_row(doc: Document) -> tuple:
"""One row of the name-hit document projection (catalog order)."""
return (doc.id, doc.source, doc.path, doc.title)
def _name_hit_lateral_row(doc: Document, is_summary: bool = False) -> SimpleNamespace:
"""One row of the name-hit LATERAL chunk query."""
return SimpleNamespace(
doc_id=doc.id,
source=doc.source,
path=doc.path,
full_path=doc.full_path,
title=doc.title,
doc_content=doc.content,
content_hash=doc.content_hash,
indexed_at=None,
created_at=None,
chunk_id=uuid.uuid4(),
position=-1 if is_summary else 0,
content="summary chunk" if is_summary else "content chunk",
is_summary=is_summary,
)
def test_name_hit_chunks_no_tokens_skips_all_queries() -> None:
"""A question with no name tokens (every normalized token < 4)
issues no queries at all."""
session = _FakeSession([]) # any call would surface a statement
assert _name_hit_chunks(session, "a e i o u 3 8") == [] # pyright: ignore[reportArgumentType]
assert session.statements == []
def test_name_hit_chunks_no_matching_doc_returns_empty() -> None:
"""Name tokens exist but no document name carries one: the
projection runs, the LATERAL fetch does not."""
doc = _doc("quadlets/other.container", "body")
name_rows = [_name_row(doc)]
session = _FakeSession(name_rows, [])
assert _name_hit_chunks(session, INCIDENT_QUESTION) == [] # pyright: ignore[reportArgumentType]
assert len(session.statements) == 1 # projection only — no LATERAL fetch
def test_name_hit_chunks_digitless_exact_part_stem_subcomponent() -> None:
"""A DIGITLESS token EQUALS a normalized path part (the ``gitea/``
folder), the file stem (``gitea.md``), or a stem sub-component
(``kubernetes_gitea``, ``gitea-values``, ``test-gateway`` — the
stem split on non-alphanumeric runs) — the 2026-09-16 product-name
signal (LOCKED A2)."""
question = "how do i set up gitea or the gateway" # tokens: [gitea, gateway]
docs = [
_doc("deploy/reeseapps/gitea/README.md", "body"), # path part
_doc("notes/gitea.md", "body"), # file stem
_doc("deploy/k8s/kubernetes_gitea.md", "body"), # sub-component
_doc("deploy/k8s/gitea-values.yaml", "body"), # sub-component
_doc("deploy/istio/test-gateway.yaml", "body"), # sub-component (gateway)
_doc("notes/gitlab.md", "body"), # NO component matches — excluded
]
name_rows = [_name_row(d) for d in docs]
session = _FakeSession(name_rows, [_name_hit_lateral_row(d) for d in docs[:5]])
out = _name_hit_chunks(session, question) # pyright: ignore[reportArgumentType]
# Five one-token hits, catalog order (source, path):
assert [rc.document.path for rc in out] == [
"deploy/istio/test-gateway.yaml",
"deploy/k8s/gitea-values.yaml",
"deploy/k8s/kubernetes_gitea.md",
"deploy/reeseapps/gitea/README.md",
"notes/gitea.md",
]
assert all(rc.name_hit is True for rc in out)
assert all(rc.fts_hit is True for rc in out) # the lexical signal
assert all(rc.cosine == 0.0 for rc in out) # no vector rank
def test_name_hit_chunks_digitless_title_never_matched() -> None:
"""The owner-verified failure mode of the naive relaxation: a doc
under a ``Deployments/`` folder titled "Deployments" does NOT hit
the common token ``deploy`` (the part normalizes to
``deployments`` ≠ ``deploy``), and a doc titled "Gitea" with no
gitea path component does NOT hit ``gitea`` — TITLES ARE NEVER
MATCHED (LOCKED A2)."""
question = "how do i deploy gitea" # tokens: [deploy, gitea]
docs = [
_doc("Deployments/reeseapps/README.md", "body", title="Deployments"),
_doc("notes/internal-notes.md", "body", title="Gitea"), # title only
]
session = _FakeSession([_name_row(d) for d in docs], [])
assert _name_hit_chunks(session, question) == [] # pyright: ignore[reportArgumentType]
assert len(session.statements) == 1 # projection only — no LATERAL fetch
def test_name_hit_chunks_digit_bearing_prefix_not_midword() -> None:
"""A DIGIT-BEARING token is a PREFIX of a normalized part or stem
(``qwen38`` → ``qwen3.8-27b-epic-vulkan.container``) — a stem that
merely CONTAINS the token mid-word (``xqwen38y…``) does NOT hit;
sub-components are in the exact-match class only (LOCKED A2)."""
question = "what are the arguments for qwen 3.8" # tokens: what, arguments, qwen, qwen38
hit = _doc("quadlets/qwen3.8-27b-epic-vulkan.container", "body")
miss = _doc("quadlets/xqwen38y-test.container", "body") # mid-word containment
name_rows = [_name_row(hit), _name_row(miss)]
session = _FakeSession(name_rows, [_name_hit_lateral_row(hit)])
out = _name_hit_chunks(session, question) # pyright: ignore[reportArgumentType]
assert [rc.document.path for rc in out] == [hit.path]
assert out[0].name_hit is True
def test_name_hit_chunks_ranked_by_count_then_catalog() -> None:
"""A question (``deploy`` + ``gitea`` + ``qwen`` + ``qwen38``):
the document whose path carries BOTH a digitless component and a
digit-bearing prefix (2 matched tokens) leads; the two
single-token documents tie on count and fall to CATALOG ORDER —
the old total-matched-length tie-break is RETIRED (it would have
put the 6-char ``qwen38`` hit, ``quadlets/…``, before the 5-char
``gitea`` hit, ``gitea/notes.md`` — the flip is pinned). The
"Deployments"-titled doc and the title-only "Gitea" doc never
appear (titles are never matched)."""
precision = _doc("Deployments/reeseapps/README.md", "body", title="Deployments")
gitea_notes = _doc("gitea/notes.md", "body", title="Internal notes")
both = _doc("gitea/qwen3.8-model.container", "body", title="The model quadlet")
q38 = _doc("quadlets/qwen3.8-27b-juggernaut-vulkan.container", "body", title="juggernaut")
title_only = _doc("notes/internal-notes.md", "body", title="Gitea")
name_rows = [_name_row(d) for d in (precision, gitea_notes, both, q38, title_only)]
question = "how do i deploy gitea with qwen 3.8"
lateral_rows = [
_name_hit_lateral_row(q38, is_summary=True), # LATERAL may return any order
_name_hit_lateral_row(both),
_name_hit_lateral_row(gitea_notes),
]
session = _FakeSession(name_rows, lateral_rows)
out = _name_hit_chunks(session, question) # pyright: ignore[reportArgumentType]
assert [rc.document.path for rc in out] == [
"gitea/qwen3.8-model.container", # 2 matched tokens (gitea + qwen38) — leads
"gitea/notes.md", # 1 token (gitea, 5 chars) — catalog order beats quadlets
"quadlets/qwen3.8-27b-juggernaut-vulkan.container", # 1 token (qwen38, 6 chars)
]
assert all(rc.name_hit is True for rc in out)
assert all(rc.fts_hit is True for rc in out) # the lexical signal
assert all(rc.cosine == 0.0 for rc in out) # no vector rank
assert all(rc.score == 0.0 for rc in out) # fuse fills the score
by_path = {rc.document.path: rc for rc in out}
# The representative chunk keeps its summary flag (the LATERAL
# choice: is_summary DESC, position ASC — chunk 0 otherwise).
assert by_path["quadlets/qwen3.8-27b-juggernaut-vulkan.container"].is_summary is True
assert by_path["quadlets/qwen3.8-27b-juggernaut-vulkan.container"].position == -1
assert by_path["gitea/notes.md"].is_summary is False
def test_name_hit_chunks_short_tokens_never_hit() -> None:
"""Short tokens (< 4 normalized — "3.8" → ``38``, the single
digits) are never candidates, so they can never hit, with or
without the versioned join."""
session = _FakeSession([]) # any call would surface a statement
assert _name_hit_chunks(session, "3.8 8 16 9") == [] # pyright: ignore[reportArgumentType]
assert session.statements == []
def test_name_hit_chunks_capped_at_limit() -> None:
"""Twelve tied name hits (one matched token each — the ``qwen38``
stem prefix) yield exactly ``NAME_HIT_LIMIT`` of them — catalog
order (the deterministic tie-break)."""
docs = [_doc(f"quadlets/qwen3.8-m{i:02d}.container", "body") for i in range(12)]
name_rows = [_name_row(d) for d in docs]
# Only the ten winners (catalog order — the deterministic tie-break
# of the twelve identical scores) reach the LATERAL fetch; the fake
# answers with exactly those rows.
lateral_rows = [_name_hit_lateral_row(d) for d in docs[:NAME_HIT_LIMIT]]
session = _FakeSession(name_rows, lateral_rows)
out = _name_hit_chunks(
session, "tell me about the qwen 3.8 models" # pyright: ignore[reportArgumentType]
)
assert len(out) == NAME_HIT_LIMIT
assert [rc.document.path for rc in out] == [
f"quadlets/qwen3.8-m{i:02d}.container" for i in range(10)
]
assert all(rc.name_hit is True for rc in out)
def test_lexical_candidates_name_hits_lead_and_dedupe_with_fts() -> None:
"""The full lexical list: name hits LEAD (their representative
chunks), the FTS rows follow, and an FTS row sharing the name hit's
chunk id appears exactly once (deduped)."""
q38 = _doc("quadlets/qwen3.8-27b-juggernaut-vulkan.container", "body")
other = _doc("quadlets/qwen38-other.container", "body") # 1 matched token
name_rows = [_name_row(q38), _name_row(other)]
q38_chunk = uuid.uuid4()
def _lateral(doc: Document) -> SimpleNamespace:
row = _name_hit_lateral_row(doc)
if doc is q38:
row.chunk_id = q38_chunk
return row
lateral_rows = [_lateral(q38), _lateral(other)]
fts_rows = [
# an FTS hit on the SAME chunk as the q38 name hit (deduped away)
SimpleNamespace(
chunk_id=q38_chunk, position=1, content="c", doc_id=q38.id,
source=q38.source, path=q38.path, full_path=q38.full_path,
title=q38.title, doc_content=q38.content, content_hash=q38.content_hash,
indexed_at=None, created_at=None, is_summary=False, rank=0.1,
),
# an FTS hit on a different chunk of the OTHER doc (kept)
_lexical_row(False, "quadlets/qwen38-other.container"),
]
session = _FakeSession(name_rows, lateral_rows, fts_rows)
out = _lexical_candidates(session, INCIDENT_QUESTION, limit=10) # pyright: ignore[reportArgumentType]
assert len(out) == 3 # q38 (once), other (name hit), other (FTS chunk)
# Both name hits tie on count (1) — catalog order: "qwen3." (ASCII 46)
# sorts before "qwen38" (ASCII 56).
assert out[0].document.path == "quadlets/qwen3.8-27b-juggernaut-vulkan.container"
assert out[1].document.path == "quadlets/qwen38-other.container"
assert out[0].chunk_id == q38_chunk # the name-hit representative row
assert {
rc.chunk_id for rc in out
} == {q38_chunk, fts_rows[1].chunk_id, lateral_rows[1].chunk_id}
assert all(rc.fts_hit is True for rc in out)
# Phase 119: the name-hit representative rows are flagged, the plain
# FTS row is not (the selection tier's bonus input, task 02).
assert out[0].name_hit is True
assert out[1].name_hit is True
assert out[2].name_hit is False
# ---------------------------------------------------------------------------
# Phase 119, D1 — the name_hit flag through fusion
# ---------------------------------------------------------------------------
def test_fuse_keeps_name_hit_on_lexical_only_hit() -> None:
"""A name-hit row with no vector rank keeps ``name_hit=True``
through the fusion (the ``replace()`` copy carries the field)."""
nh = _rc("gitea/README.md")
nh.name_hit = True
out = fuse([], [nh], k=60)
assert len(out) == 1
assert out[0].name_hit is True
assert out[0].fts_hit is True
assert out[0].cosine == 0.0
def test_fuse_or_s_name_hit_on_double_hit() -> None:
"""A vector row that is ALSO the name hit's representative chunk
(the RRF merge dedupes by chunk id) keeps ``name_hit=True`` — the
merge ORs the flag in, so the selection tier (task 02) still sees
the name hit on the fused list."""
v = _rc("gitea/README.md", cosine=0.9)
l1 = _rc("gitea/README.md", cosine=0.1) # the lexical copy of the same chunk
l1.chunk_id = v.chunk_id
l1.name_hit = True
out = fuse([v], [l1], k=60)
assert len(out) == 1
assert out[0].name_hit is True
assert out[0].fts_hit is True
assert out[0].score == pytest.approx(2 / 61)
def test_fuse_default_name_hit_stays_false_for_ordinary_rows() -> None:
"""Neither list flagged ⇒ fusion never invents a name-hit flag —
ordinary vector and FTS rows are ``name_hit=False``."""
out = fuse([_rc("a.md", cosine=0.8)], [_rc("b.md", fts_hit=True)], k=60)
assert len(out) == 2
assert all(rc.name_hit is False for rc in out)
# ---------------------------------------------------------------------------
# Phase 119, D2 — the bounded name-hit bonus (LOCKED A3)
# ---------------------------------------------------------------------------
from app.rag.retriever import _selection_order, weak_hit_titles # noqa: E402
def _bonus_rc(
doc: Document,
score: float,
cosine: float,
position: int = 0,
name_hit: bool = False,
) -> RetrievedChunk:
"""One fused-list candidate (name-hit rows follow the D1 lexical
convention: ``cosine=0.0``, ``fts_hit=True``)."""
return RetrievedChunk(
chunk_id=uuid.uuid4(),
position=position,
content=doc.content[:20],
score=score,
document=doc,
cosine=cosine,
fts_hit=cosine == 0.0,
name_hit=name_hit,
)
def _bonus_chunks() -> list[RetrievedChunk]:
"""A mixed FUSED list — already in the ``fuse()`` key order
(−score, −cosine, path, position) — with one name-hit document
(``gitea/README.md``, the D1 convention: cosine 0.0) and ordinary
vector/FTS documents: ``a.md`` carries two chunks, and ``b.md`` /
``c.md`` tie on the fused score (separated only by cosine).
The pre-phase (bonus-0) document order this list walks — the golden
the kill switch must reproduce — is a (0.0200) → gitea (0.0160) →
b (0.0150, cos 0.7) → c (0.0150, cos 0.6) → d (0.0100).
"""
gitea = _doc("gitea/README.md", "G" * 50)
a = _doc("a.md", "A" * 50)
b = _doc("b.md", "B" * 50)
c = _doc("c.md", "C" * 50)
d = _doc("d.md", "D" * 50)
return [
_bonus_rc(a, 0.0200, 0.9, 0),
_bonus_rc(gitea, 0.0160, 0.0, 0, name_hit=True),
_bonus_rc(b, 0.0150, 0.7, 0),
_bonus_rc(c, 0.0150, 0.6, 0),
_bonus_rc(a, 0.0120, 0.5, 1),
_bonus_rc(d, 0.0100, 0.1, 0),
]
#: The golden document order the OLD pre-phase loop (stable score-
#: descending walk, first-seen-chunk dedupe) produces over
#: :func:`_bonus_chunks` — pinned byte-identical by the kill switch.
GOLDEN_PRE_PHASE_ORDER = ["a.md", "gitea/README.md", "b.md", "c.md", "d.md"]
def test_selection_order_bonus_zero_is_the_pre_phase_golden_walk() -> None:
"""LOCKED A3 kill switch: ``bonus=0`` returns the EXACT pre-phase
document order of the old score-descending first-seen walk — the
golden list pinned from the old loop over the mixed fused list
(incl. the b/c fused-score tie resolved by the input order the
fusion produced — the walk never re-sorts it away)."""
out = _selection_order(_bonus_chunks(), 0.0)
assert [d.path for d, _eff, _cos, _idx in out] == GOLDEN_PRE_PHASE_ORDER
# The re-rank inputs are exposed and exact: effective == best fused
# score (no bonus), best cosine tracked across a doc's chunks (a: 0.9
# from its rank-1 chunk, not 0.5), first-seen index in the
# score-descending walk.
assert [eff for _d, eff, _cos, _idx in out] == [
0.0200, 0.0160, 0.0150, 0.0150, 0.0100
]
assert [cos for _d, _eff, cos, _idx in out] == [
pytest.approx(v) for v in (0.9, 0.0, 0.7, 0.6, 0.1)
]
assert [idx for _d, _eff, _cos, idx in out] == [0, 1, 2, 3, 5]
def test_selection_order_bonus_inert_without_name_hits() -> None:
"""No name-hit chunk present → the bonus cannot fire: the order is
IDENTICAL to the pre-phase walk even with the default bonus on
(LOCKED A3)."""
chunks = _bonus_chunks()
for rc in chunks:
rc.name_hit = False
out = _selection_order(chunks, 0.005)
assert [d.path for d, _eff, _cos, _idx in out] == GOLDEN_PRE_PHASE_ORDER
def test_selection_order_bonus_lifts_name_hit_doc_below_bonus_gap() -> None:
"""The name-hit doc's effective 0.016 + 0.005 = 0.021 EXCEEDS a's
0.020 — a gap of 0.004 < bonus 0.005 — so the bonus lifts it to
rank 1; the rest keep their fused order (the bonus re-ranks, it
does not inflate)."""
out = _selection_order(_bonus_chunks(), 0.005)
assert [d.path for d, _eff, _cos, _idx in out] == [
"gitea/README.md", "a.md", "b.md", "c.md", "d.md"
]
assert out[0][1] == pytest.approx(0.016 + 0.005)
def test_selection_order_bonus_does_not_lift_above_bonus_gap() -> None:
"""A gap LARGER than the bonus is not closed: the name-hit doc's
best 0.010 + 0.005 = 0.015 ties b/c on effective and LOSES to both
on the (−effective, −best_cosine) tie-break (its D1 cosine is 0.0) —
a keeps the lead (0.020). A second name-hit doc (``e.md``, also
0.010/cos 0.0) trails gitea on the ``document.path`` tie-break —
the full re-rank key pinned."""
chunks = _bonus_chunks()
chunks[1].score = 0.010 # the name-hit doc drops to a 0.010 best
e = _doc("e.md", "E" * 50)
chunks.insert(2, _bonus_rc(e, 0.010, 0.0, 0, name_hit=True))
out = _selection_order(chunks, 0.005)
assert [d.path for d, _eff, _cos, _idx in out] == [
"a.md", "b.md", "c.md", "e.md", "gitea/README.md", "d.md"
]
def test_selection_order_first_seen_breaks_equal_path_ties() -> None:
"""Two documents sharing a path across sources (``notes.md`` in two
sources) can tie on (effective, cosine, path) — the pre-bonus
first-seen rank decides (the last key element)."""
s1 = _doc("notes.md", "X" * 50, source="Src1")
s2 = _doc("notes.md", "Y" * 50, source="Src2")
chunks = [
_bonus_rc(s1, 0.016, 0.0, 0, name_hit=True),
_bonus_rc(s2, 0.016, 0.0, 0, name_hit=True),
]
out = _selection_order(chunks, 0.005)
assert [d.source for d, *_ in out] == ["Src1", "Src2"]
def test_selection_order_bonus_applied_once_per_document() -> None:
"""The bonus is per DOCUMENT — applied ONCE no matter how many of
the doc's chunks are name hits (3×bonus would push the name-hit doc
above the 0.030 leader; one bonus cannot)."""
gitea = _doc("gitea/README.md", "G" * 50)
a = _doc("a.md", "A" * 50)
chunks = [
_bonus_rc(a, 0.030, 0.8),
_bonus_rc(gitea, 0.016, 0.0, 0, name_hit=True),
_bonus_rc(gitea, 0.010, 0.0, 1, name_hit=True),
_bonus_rc(gitea, 0.008, 0.0, 2, name_hit=True),
]
out = _selection_order(chunks, 0.005)
assert [d.path for d, _eff, _cos, _idx in out] == ["a.md", "gitea/README.md"]
assert out[1][1] == pytest.approx(0.016 + 0.005) # best + ONE bonus
def test_selection_order_bonus_fires_when_name_hit_is_not_first_chunk() -> None:
"""The bonus fires on ANY name-hit chunk of the document — including
when the doc's first-seen (best) chunk is an ordinary vector row and
only a lower-ranked chunk is the D1 name-hit representative (a
document's name hit and its best chunk can be different chunks).
The bonus still lands on the doc's BEST fused score, and the doc's
best cosine stays tracked across ALL its chunks."""
gitea = _doc("gitea/README.md", "G" * 50)
a = _doc("a.md", "A" * 50)
chunks = [
_bonus_rc(a, 0.024, 0.8),
_bonus_rc(gitea, 0.020, 0.2, 0), # the doc's best — an ordinary chunk
_bonus_rc(gitea, 0.016, 0.55, 2, name_hit=True), # the name-hit rep
]
assert [d.path for d, *_ in _selection_order(chunks, 0.0)] == [
"a.md", "gitea/README.md"
]
out = _selection_order(chunks, 0.005)
assert [d.path for d, *_ in out] == ["gitea/README.md", "a.md"]
assert out[0][1] == pytest.approx(0.020 + 0.005) # bonus on the BEST score
# The doc's best cosine is tracked across ALL its chunks — the
# lower-ranked name-hit chunk (0.55) beats the first-seen chunk's
# 0.2 (the re-rank tie-break input).
assert out[0][2] == pytest.approx(0.55)
def test_suggested_bonus_default_from_settings_and_kill_switch(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The *bonus* parameter defaults to the LIVE ``BOR_NAME_HIT_BONUS``
setting (the ``n`` parameter's settings-read pattern — the default
0.005 is the production value, not a frozen constant); an explicit
``bonus=0`` and a settings kill switch both reproduce the pre-phase
golden walk (LOCKED A3)."""
chunks = _bonus_chunks()
settings = Settings(_env_file=None) # pyright: ignore[reportCallIssue]
assert settings.name_hit_bonus == 0.005 # the production default
monkeypatch.setattr(retriever, "get_settings", lambda: settings)
assert [d.path for d in select_suggested(chunks, n=5)] == [
"gitea/README.md", "a.md", "b.md", "c.md", "d.md"
]
# Explicit kill switch: the byte-identical pre-phase order.
assert [d.path for d in select_suggested(chunks, n=5, bonus=0.0)] == GOLDEN_PRE_PHASE_ORDER
# Settings kill switch (BOR_NAME_HIT_BONUS=0) — the same golden walk.
off = Settings(_env_file=None, name_hit_bonus=0.0) # pyright: ignore[reportCallIssue]
monkeypatch.setattr(retriever, "get_settings", lambda: off)
assert [d.path for d in select_suggested(chunks, n=5)] == GOLDEN_PRE_PHASE_ORDER
def test_related_skips_excluded_ids_under_the_bonus() -> None:
"""Exclusion is orthogonal to the bonus: excluded ids are skipped
exactly as before, on the bonus-adjusted walk — an excluded doc
never rides the related row even when the bonus would lift it to
the lead."""
chunks = _bonus_chunks()
b_id = chunks[2].document.id
d_id = chunks[5].document.id
out = select_related(chunks, {b_id, d_id}, cap=5, bonus=0.005)
assert [d.path for d in out] == ["gitea/README.md", "a.md", "c.md"]
# Kill switch: the same exclusions on the pre-phase walk.
out0 = select_related(chunks, {b_id, d_id}, cap=5, bonus=0.0)
assert [d.path for d in out0] == ["a.md", "gitea/README.md", "c.md"]
# The name-hit doc itself excluded → the lead goes to the next doc.
g_id = chunks[1].document.id
out2 = select_related(chunks, {g_id}, cap=5, bonus=0.005)
assert [d.path for d in out2][0] == "a.md"
def test_weak_hit_titles_bonus_adjusted_order() -> None:
"""Titles follow the bonus-adjusted selection walk (``_doc`` titles
equal paths here, so the title list mirrors the doc order); the
kill switch returns the pre-phase golden order."""
chunks = _bonus_chunks()
assert weak_hit_titles(chunks, bonus=0.005) == [
"gitea/README.md", "a.md", "b.md", "c.md", "d.md"
]
assert weak_hit_titles(chunks, bonus=0.0) == GOLDEN_PRE_PHASE_ORDER
def test_bonus_lives_in_the_selection_layer_only() -> None:
"""LOCKED A3: the bonus never touches the chunk objects —
``score``/``cosine``/``fts_hit`` (the A8 gate's inputs —
``query_log.top_score`` is the best fused chunk score, the same
values) are unchanged after every selection walk, even with the
bonus lifting a document."""
chunks = _bonus_chunks()
before = {
rc.chunk_id: (rc.score, rc.cosine, rc.fts_hit, rc.name_hit) for rc in chunks
}
select_suggested(chunks, n=5, bonus=0.005)
select_related(chunks, set(), cap=5, bonus=0.005)
weak_hit_titles(chunks, bonus=0.005)
after = {
rc.chunk_id: (rc.score, rc.cosine, rc.fts_hit, rc.name_hit) for rc in chunks
}
assert before == after