fix(llm): RAG citation pages, scrub placeholders, disease-aware document choice
CI / ruff · pytest (pull_request) Successful in 1m2s

- converter marks a paragraph glued across a page break with ⟦стр. N⟧; the chunker
  strips the marker and extends the chunk's page range (wrong-page citations 24 -> 3,
  the rest are scale-form table rows split by a page)
- [NAME]/[PHONE] placeholders from privacy.scrub() no longer count as unknown words
  (a scrubbed question used to fall below the coverage gate)
- a question naming one disease boosts its guideline in retrieval and passage choice
  ("ТМС при депрессии" was answered from the PTSD guideline); "антидепрессант" is not a cue

Refs #62

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 04:31:59 +03:00
co-authored by Claude Opus 5.5
parent 6c212d560c
commit fe29ebd629
8 changed files with 206 additions and 91 deletions
+35
View File
@@ -307,3 +307,38 @@ async def test_llm_rephrasing_kept_when_faithful(monkeypatch: pytest.MonkeyPatch
reply = await answer(S(q), audience="patient")
assert reply is not None and reply.phrased_by_llm
assert simplified in reply.text and reply.excerpt == excerpt # the verbatim quote is kept for audit
# --- regressions from the post-merge audit (#62) -------------------------------------------------------
def test_paragraph_flowing_to_next_page_cites_both_pages() -> None:
"""The converter glues a paragraph across a page break and marks the join with ⟦стр. N⟧."""
chunks = load_chunks()
assert not any("⟦" in c.text for c in chunks) # markers never reach the text
# KR PTSD: the comment on antipsychotics starts on p.22 and continues on p.23
chunk = next(c for c in chunks if "Препараты могут применяться в комбинированной терапии" in c.text)
assert chunk.page_start <= 23 <= chunk.page_end
async def test_scrub_placeholders_do_not_break_answers() -> None:
plain = await answer(S("что такое ПТСР"), audience="patient")
scrubbed = await answer(S("[NAME], [PHONE], что такое ПТСР"), audience="patient")
assert plain is not None and scrubbed is not None
assert scrubbed.excerpt == plain.excerpt
assert tokens("[NAME] сон") == ["сон"]
def test_question_naming_a_disease_prefers_its_guideline() -> None:
index = get_index()
assert index.cued_docs("ТМС при депрессии") == {"kr_depression"}
assert index.cued_docs("кошмары после войны") == {"kr_ptsd"}
assert index.cued_docs("депрессия и ПТСР") == set() # both named: no preference
assert index.cued_docs("когда подействуют антидепрессанты") == set() # a drug class, not the disease
hit = index.search("транскраниальная магнитная стимуляция при депрессии", audience="patient")[0]
assert hit.chunk.doc.id == "kr_depression"
async def test_answer_follows_disease_cue() -> None:
reply = await answer(S("транскраниальная магнитная стимуляция при депрессии"), audience="patient")
assert reply is not None and reply.citations[0].doc == "kr_depression"