fix(maria): acoperirea se judeca pe ce a intrebat omul acum, si pe radacina cuvantului

Doua reparatii pe poarta de acoperire, amandoua masurate pe setul de calibrare
extins (31 de cazuri, de la 26).

**Termenii distinctivi se comparau pe cuvantul intreg.** Documentatia si clientul
nu folosesc aceleasi forme: documentul scrie „token" si „tokenuri", omul scrie
„tokenul"; documentul „Generare", omul „generez". Trei continuari legitime din
cinci escaladau desi chunk-ul cu raspunsul era chiar in context. Comparatia se
face acum pe primele 5 litere; codurile raman intregi (ORA-01722 si ORA-01720 au
aceleasi cinci).

**Interogarea avea doua roluri amestecate.** Pe un fir, regasirea are nevoie de
ancora („da, ma blocheaza" singur nu gaseste nimic), dar acoperirea nu: judecata
pe ancora, ea cere sa apara in documente si salutul, si numele firmei, si luna,
deci raportul scade la fiecare replica si firul lung escaladeaza degeaba.
`rank.assess` primeste `dovada` — mesajul nou, plus OCR-ul capturii lui; cand
acesta n-are termeni proprii, se cade inapoi pe interogarea intreaga.

Setul de calibrare: 31/31 cu reparatia, 30/31 fara.

Ce NU s-a facut, desi era propus in handover: lista de formule de politete in
`rare_terms`. Masuratoarea nu o justifica — cu potrivirea pe radacina, cazurile cu
ancora politicoasa trec si fara ea, iar intr-una din configuratii facea rau.
Cauza reala nu era politetea din ancora, ci morfologia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
Claude Agent
2026-09-01 19:57:49 +00:00
parent 89145d437e
commit 653f5cbba9
6 changed files with 184 additions and 20 deletions

View File

@@ -128,8 +128,13 @@ def cosine(a: list[float], b: list[float]) -> float:
return dot / (na * nb) if na and nb else 0.0
def search(index: Index, query: str, top_k: int) -> dict:
"""Cauta si decide daca avem acoperire. Intoarce si dovezile, pentru log."""
def search(index: Index, query: str, top_k: int, dovada: str | None = None) -> dict:
"""Cauta si decide daca avem acoperire. Intoarce si dovezile, pentru log.
`query` regaseste, `dovada` judeca. Pe un fir, `query` e ancora plus mesajul
nou (ancora chiar ajuta regasirea), dar acoperirea nu se judeca pe ea — vezi
`rank.assess`.
"""
if not len(index):
return {"chunks": [], "covered": False, "reason": "index gol",
"best_cosine": 0.0, "top": []}
@@ -141,7 +146,7 @@ def search(index: Index, query: str, top_k: int) -> dict:
# cosinusul chunk-urilor CHIAR date modelului, nu cel mai bun din tot indexul:
# altfel acoperirea se judeca pe o dovada care nu ajunge in context.
best = max((cosines[i] for i in order), default=0.0)
covered, reason = rank.assess(query, best, texts, index.bm25)
covered, reason = rank.assess(query, best, texts, index.bm25, dovada=dovada)
return {
"chunks": texts,
"covered": covered,
@@ -558,6 +563,7 @@ def handle(index: Index, msg: dict) -> None:
if not question:
return
dovada = None
if continuare:
fir_mod.adauga(fir, "client", text or "[captura de ecran]")
fir_mod.extinde_ancora(fir, msg.get("ocr_text") or "")
@@ -571,6 +577,9 @@ def handle(index: Index, msg: dict) -> None:
send_reply(sender, raspuns)
return
fir["ref"] = None # escaladarea nu mai exista; tratam mesajul normal
# ce a intrebat omul ACUM (mesajul nou, sau textul retinut din captura lui):
# acoperirea se judeca pe asta, nu pe ancora care ramane in interogare.
dovada = search_query
search_query = fir_mod.interogare(fir, search_query)
print(f"[consumer] continuare pe firul deschis; caut dupa {search_query[:90]!r}",
file=sys.stderr)
@@ -579,7 +588,7 @@ def handle(index: Index, msg: dict) -> None:
fir_mod.adauga(fir, "client", text or "[captura de ecran]")
try:
result = search(index, search_query, config.get_int("TOP_K", 3))
result = search(index, search_query, config.get_int("TOP_K", 3), dovada)
except Exception as exc: # noqa: BLE001
print(f"[consumer] cautare esuata: {exc}", file=sys.stderr)
send_reply(sender, "Scuze, am o problema tehnica momentan. Cineva din echipa te va contacta.")