fix(sugestii): inlocuirea de anvelope -> OE-8 + vot k-NN ponderat pe rang

Seed-ul Haiku eticheta sistematic "INLOCUIT ANVELOPE ..." cu OE-1 (223 vs 17
OE-8), desi RAR are cod dedicat OE-8 (inlocuire sezoniera anvelope), asa ca
votul k-NN propunea OE-1 pentru orice inlocuire de anvelope.

- tools/mapare-llm/fix_anvelope_oe8.py: re-etichetare deterministica (187
  randuri DB + seed JSON -> OE-8; regula exclude singular cu pozitie, D/R,
  janta, vulcanizare) + 13 intrari canonice curate manual pentru formularile
  sezoniere absente din corpus ("MONTAT CAUCIUCURI VARA" etc.); idempotent
- enrich_suggestions: ponderea votului = EMB_VOTE_DECAY^rang (0.7) in loc de
  similaritatea bruta — anizotropia modelului lasa zgomotul de coada (ex.
  INLOCUIT BECURII la 0.934 de INLOCUIRE ANVELOPE) sa invinga vecinii corecti
  de rang 1-2; LOO: precizie egala (93.1%), cod-gresit 4.67% -> 4.63%
- test_fix_anvelope_oe8: gardii pe regula + seed-ul din repo (regresie la
  regenerare); auditul k-NN al dezacordurilor nu a gasit alte erori
  sistematice (curatat=OE-2, reglat=OE-4, bujii/ulei=OE-3 raman apararabile)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-07-07 10:26:00 +00:00
parent 809823a8ae
commit 6674d0e3e1
5 changed files with 722 additions and 384 deletions

View File

@@ -638,11 +638,18 @@ def delete_text_rule(conn, account_id: int | None, pattern: str) -> None:
# 100% dar 7.4% cod gresit preselectat.
EMB_MIN_SIMILARITATE = 0.88
# Cati vecini intra in votul ponderat cu similaritatea. Vot > top-1: corpusul SILVER
# Cati vecini intra in votul ponderat pe rang. Vot > top-1: corpusul SILVER
# are etichete contradictorii pe denumiri aproape identice; votul e imun la ele
# (+~1pp precizie la acelasi coverage, masurat LOO).
EMB_VOTE_TOP_K = 5
# Ponderea unui vecin = EMB_VOTE_DECAY^rang (1-indexat). Ponderarea cu similaritatea
# bruta e pacalita de anizotropia modelului: vecini fara legatura scoreaza aproape
# cat cei buni (ex. INLOCUIT BECURII la 0.934 de INLOCUIRE ANVELOPE), deci coada
# de zgomot poate invinge 2 vecini corecti de rang 1-2. Ordinea rangurilor ramane
# informativa; decay 0.7 = cel mai mic cod-gresit la precizie egala (LOO).
EMB_VOTE_DECAY = 0.7
# Protejeaza secventa hash->load->embed->save->purge->index (embedding_cache) de
# executie concurenta intre warmup-ul de fundal (block=True) si calea de request
# (block=False, dupa ce modelul e deja incarcat) -- altfel purjarea uneia ar sterge
@@ -841,18 +848,18 @@ def enrich_suggestions(
# deci query-ul TREBUIE normalizat la fel — altfel cosine degradeaza si
# nu mai e configul sub care s-a masurat 94.3%.
nn = _emb.suggest_nearest(normalize_for_match(denumire), top_k=EMB_VOTE_TOP_K)
# Vot ponderat cu similaritatea pe vecinii peste prag; NUL e eticheta
# Vot ponderat pe rang (decay^rang) pe vecinii peste prag; NUL e eticheta
# proprie (castiga -> supresie, nu cod). Vecinii sub prag nu voteaza.
scoruri: dict[str, float] = {}
sim_max: dict[str, float] = {}
for v in nn:
for rang, v in enumerate(nn, 1):
sim = float(v.get("similaritate", 0))
if sim < EMB_MIN_SIMILARITATE:
continue
lab = "NUL" if v.get("is_nul") else (str(v["cod"]) if v.get("cod") else None)
if lab is None:
continue
scoruri[lab] = scoruri.get(lab, 0.0) + sim
scoruri[lab] = scoruri.get(lab, 0.0) + EMB_VOTE_DECAY ** rang
sim_max[lab] = max(sim_max.get(lab, 0.0), sim)
if scoruri:
castigator = max(scoruri, key=lambda k: scoruri[k])