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:
@@ -24,6 +24,7 @@ siguranta: daca un prag nou strica un caz care mergea, se vede imediat.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
@@ -34,8 +35,10 @@ import config # noqa: E402
|
||||
import consumer # noqa: E402
|
||||
import rank # noqa: E402
|
||||
|
||||
# (intrebare, are raspuns in documente?)
|
||||
CAZURI: list[tuple[str, bool]] = [
|
||||
# (intrebare, are raspuns in documente?) sau, pentru continuari pe un fir,
|
||||
# (ancora, mesaj_nou, are raspuns in documente?): regasirea se face pe ancora plus
|
||||
# mesajul nou (exact ce produce fir.interogare), dovada doar pe mesajul nou.
|
||||
CAZURI: list[tuple] = [
|
||||
("token efactura expirat", True),
|
||||
("Nu exista nici un CIF pentru care sa aveti drept in SPV", True),
|
||||
("cum trimit declaratia D406 SAF-T", True),
|
||||
@@ -80,6 +83,31 @@ CAZURI: list[tuple[str, bool]] = [
|
||||
("vremea de maine in Bucuresti", False),
|
||||
("cum imi resetez parola de la Windows", False),
|
||||
("imi da eroare 0x80070005 la instalarea unui joc pe Steam", False),
|
||||
# --- continuari pe fir, fara cod de eroare: aici decid termenii distinctivi ---
|
||||
# Ancora politicoasa aduce in interogare „buna/ziua/puteti/ajutati", cuvinte care
|
||||
# nu apar niciodata in documente. Raspunsul (token expirat) e in
|
||||
# efactura_knowledge.md:118-134 (TOPIC token_anaf, expirare la 90 de zile).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?",
|
||||
"imi zice ca tokenul e expirat", True),
|
||||
# Aceeasi continuare, ancora fara formule de politete: verdictul trebuie sa fie
|
||||
# acelasi. Daca difera, politetea din ancora e cea care escaladeaza.
|
||||
("Am o problema la trimiterea in SPV",
|
||||
"imi zice ca tokenul e expirat", True),
|
||||
# Aceeasi ancora politicoasa, dar raspunsul NU e in documente: „CUI cumparator
|
||||
# incorect" nu apare in niciun document (grep peste documents/, 2026-09-01).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?",
|
||||
"imi da eroare CUI cumparator incorect la validare", False),
|
||||
# Fir lung: a patra replica, ancora crescuta cu schimburile de dinainte.
|
||||
# Verdictul nu are voie sa se degradeze cu lungimea (cosinusul urca oricum).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?\n"
|
||||
"imi zice ca tokenul e expirat\n"
|
||||
"am apasat Actualizare in borderoul eFactura dar tot nu merge",
|
||||
"unde apas ca sa generez tokenul nou?", True),
|
||||
# Continuare pe un fir deschis de o captura (escaladarea M-260901-06EB): ancora
|
||||
# e mesajul omului plus OCR-ul, plin de zgomot de interfata. Eroarea din captura
|
||||
# nu e in documente, deci si continuarea trebuie sa plece la suport.
|
||||
("Buna . Am si eu o factura pe luna august cu eroare in spv la trimitere AUTO SULEA MOBILE SRL . Poti te rog sa ma ajuti?\nTotal\nLit\nO) Pers. Juridice (798) O Netrimise DI Trimise (fara raspuns) [5 Timis (cu raspuns Erori) (1). O Tamise (cu diferente Reg. V2.) (1)\nNr Articol Cantitate UM | Pret unitar Valoare fara TVA Discount fara TVA % TVA Descriere Detalii\n5 Detalii raspuns\n663| | <2xml version=\"1.0” encoding=\"UTF-8\" standalone=\"yes\"?>\n<header xmins=\"mfp:anaf-dgtefactura:mesajroriFactuta:v1” Index_incarcare=\"\nMesaj raspuns. | Factura cu id incarcare=6638295792 nu po] | | <Error errorMessage=\"CUI cumparator incorect\"/>\nTri\nMesaj trimitere | Transmis cu succes. Index incarcare\n1670404158\" Cif_emitent=\"\n3137200\">\n@ inclusiv Pers. Fizice\nSelecteaz!",
|
||||
"si acum ce fac cu factura asta?", False),
|
||||
]
|
||||
|
||||
CACHE = config.STATE_DIR / "calibrare-cache.json"
|
||||
@@ -92,14 +120,27 @@ def incarca_cache() -> dict[str, list[float]]:
|
||||
return {}
|
||||
|
||||
|
||||
def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dict]]:
|
||||
def desfa(caz: tuple) -> tuple[str, str | None, bool]:
|
||||
"""(interogare de regasire, interogare de dovada, asteptat).
|
||||
|
||||
Un 3-tuplu e o continuare pe fir: se cauta dupa ancora plus mesajul nou (ca in
|
||||
fir.interogare), dar dovada e doar ce a intrebat omul acum.
|
||||
"""
|
||||
if len(caz) == 3:
|
||||
ancora, mesaj, asteptat = caz
|
||||
return f"{ancora}\n{mesaj}", mesaj, asteptat
|
||||
return caz[0], None, caz[1]
|
||||
|
||||
|
||||
def pregateste(cazuri: list[tuple]) -> tuple[consumer.Index, list[dict]]:
|
||||
index = consumer.load_index()
|
||||
if not len(index):
|
||||
sys.exit(f"index gol sau lipsa: {config.INDEX_FILE}")
|
||||
cache = incarca_cache()
|
||||
date = []
|
||||
nou = 0
|
||||
for intrebare, asteptat in cazuri:
|
||||
for caz in cazuri:
|
||||
intrebare, dovada, asteptat = desfa(caz)
|
||||
vec = cache.get(intrebare)
|
||||
if vec is None:
|
||||
print(f" embed: {intrebare[:50]}…", file=sys.stderr)
|
||||
@@ -112,6 +153,7 @@ def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dic
|
||||
: config.get_int("TOP_K", 3)]
|
||||
date.append({
|
||||
"intrebare": intrebare,
|
||||
"dovada": dovada,
|
||||
"asteptat": asteptat,
|
||||
# ca in consumer.search: cosinusul chunk-urilor date modelului
|
||||
"best": max((cos[i] for i in ordine), default=0.0),
|
||||
@@ -124,13 +166,19 @@ def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dic
|
||||
return index, date
|
||||
|
||||
|
||||
# rank.assess primeste `dovada` doar dupa reparatia termenilor distinctivi; pana
|
||||
# atunci se poate rula numai varianta de azi.
|
||||
ARE_DOVADA = "dovada" in inspect.signature(rank.assess).parameters
|
||||
|
||||
|
||||
def evalueaza(index: consumer.Index, date: list[dict], weak: float,
|
||||
min_ratio: float) -> tuple[int, list[str]]:
|
||||
min_ratio: float, dovada_separata: bool = False) -> tuple[int, list[str]]:
|
||||
corecte, gresite = 0, []
|
||||
for d in date:
|
||||
extra = {"dovada": d["dovada"]} if dovada_separata else {}
|
||||
ok, _ = rank.assess(
|
||||
d["intrebare"], d["best"], d["top_texts"], index.bm25,
|
||||
weak=weak, min_ratio=min_ratio,
|
||||
weak=weak, min_ratio=min_ratio, **extra,
|
||||
)
|
||||
if ok == d["asteptat"]:
|
||||
corecte += 1
|
||||
@@ -147,7 +195,7 @@ def main() -> None:
|
||||
|
||||
cazuri = CAZURI
|
||||
if args.cases:
|
||||
cazuri = [(q, bool(a)) for q, a in json.loads(pathlib.Path(args.cases).read_text())]
|
||||
cazuri = [tuple(c) for c in json.loads(pathlib.Path(args.cases).read_text())]
|
||||
|
||||
index, date = pregateste(cazuri)
|
||||
print(f"index: {len(index)} chunk-uri | cazuri: {len(date)}\n")
|
||||
@@ -156,9 +204,11 @@ def main() -> None:
|
||||
rezultate = []
|
||||
for weak in (0.50, 0.54, 0.58, 0.60, 0.64):
|
||||
for ratio in (0.0, 0.34, 0.5, 0.67, 1.0):
|
||||
corecte, _ = evalueaza(index, date, weak, ratio)
|
||||
corecte, _ = evalueaza(index, date, weak, ratio, ARE_DOVADA)
|
||||
rezultate.append((corecte, weak, ratio))
|
||||
rezultate.sort(key=lambda r: (-r[0], r[1]))
|
||||
mod = "cu dovada separata" if ARE_DOVADA else "fara separare (rank.assess nu are inca `dovada`)"
|
||||
print(f"mod: {mod}\n")
|
||||
print(f"{'corecte':>8} weak ratio")
|
||||
for corecte, weak, ratio in rezultate[:15]:
|
||||
print(f"{corecte:>5}/{len(date)} {weak:.2f} {ratio:.2f}")
|
||||
@@ -171,10 +221,14 @@ def main() -> None:
|
||||
semn = "OK " if ok == d["asteptat"] else "GRESIT"
|
||||
stare = "ACOPERIT " if ok else "ESCALADAT"
|
||||
print(f"{semn} [{stare}] {d['intrebare'][:44]:46} {motiv}")
|
||||
corecte, gresite = evalueaza(index, date, weak, ratio)
|
||||
print(f"\n{corecte}/{len(date)} corecte")
|
||||
for g in gresite:
|
||||
print(f" - {g}")
|
||||
for eticheta, separata in (("fara separare (azi)", False), ("cu dovada separata ", True)):
|
||||
if separata and not ARE_DOVADA:
|
||||
print("\ncu dovada separata : rank.assess nu accepta inca `dovada`, sarit")
|
||||
continue
|
||||
corecte, gresite = evalueaza(index, date, weak, ratio, separata)
|
||||
print(f"\n{eticheta}: {corecte}/{len(date)} corecte")
|
||||
for g in gresite:
|
||||
print(f" - {g}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user