Files
rar-autopass/tests/test_enrich_corpus_etichetat.py
Claude Agent 809823a8ae perf+calitate sugestii k-NN: matvec numpy, vot top-5 cu prag calibrat, indicatori import
- embeddings: corpus ca matrice numpy cu norme precalculate; suggest_nearest
  = un matvec (~0.6ms/query fata de ~500ms cosine pur-Python la 17k vectori)
- enrich_suggestions: vot ponderat cu similaritatea pe top-5 vecini (NUL =
  eticheta proprie); prag 0.5 -> 0.88, calibrat LOO pe corpusul SILVER
  (tools/mapare-llm/knn_calibrate.py): precizie 90.5% -> 93.1%, cod gresit
  preselectat 7.2% -> 4.7%; sub prag abtinere -> preselectie fuzzy
- UI: codul sugerat de sistem afisat explicit cu sursa si scorul, separat de
  lista fuzzy ("potrivire text"); indicator de progres reparat pe upload
  (display:inline anula .htmx-indicator) si adaugat pe pasii 2->3 si
  "Salveaza maparile"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:10:58 +00:00

186 lines
7.0 KiB
Python

"""enrich_suggestions = pre-filtru NUL + k-NN pe corpus etichetat.
Ordinea de precedenta: pre-filtru NUL -> (daca NUL: fara cod) altfel GOLD partajat >
exact (SILVER) > k-NN embeddings. k-NN sub prag -> abtinere. Vecin k-NN NUL -> supresie.
Nimic din enrich_suggestions nu intra in resolve_prestatii/load_mapping.
"""
from __future__ import annotations
import os
import tempfile
import pytest
@pytest.fixture()
def env(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "us006.db"))
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "true")
from app.config import get_settings
get_settings.cache_clear()
from app.db import init_db
init_db()
yield monkeypatch
get_settings.cache_clear()
@pytest.fixture()
def conn(env):
from app.db import get_connection
c = get_connection()
yield c
c.close()
def _silver(conn, denumire_norm, cod, is_nul=0):
conn.execute(
"INSERT OR IGNORE INTO mapping_suggestions "
"(denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, 'llm_seed', 0.7)",
(denumire_norm, cod, is_nul),
)
conn.commit()
def _mock_embedding(monkeypatch, cod, sim, is_nul=False):
import app.embeddings as emb
monkeypatch.setattr(emb, "has_corpus", lambda: True)
monkeypatch.setattr(emb, "suggest_nearest",
lambda text, top_k=1: [{"cod": cod, "is_nul": is_nul, "similaritate": sim}])
def test_prefiltru_nul_supreseaza_inainte_de_knn(conn, monkeypatch):
# Embedding-ul AR sugera un cod, dar pre-filtrul NUL trebuie sa scurtcircuiteze.
chemat = {"da": False}
import app.embeddings as emb
monkeypatch.setattr(emb, "has_corpus", lambda: True)
def spion(text, top_k=1):
chemat["da"] = True
return [{"cod": "OE-1", "is_nul": False, "similaritate": 0.99}]
monkeypatch.setattr(emb, "suggest_nearest", spion)
from app.mapping import enrich_suggestions
out = enrich_suggestions(conn, "13 X ITP")
assert out["sugestie_principala"] is None # non-operatie -> fara cod
assert out["surse"]["nul"] is True
assert chemat["da"] is False # k-NN nici macar interogat
def test_precedenta_gold_exact_embedding(conn, monkeypatch):
from app.shared_store import record_human_validation
from app.mapping import enrich_suggestions, normalize_for_match
den = "OPERATIE DE TEST UNICA"
norm = normalize_for_match(den)
# Toate trei sursele dau coduri diferite.
record_human_validation(conn, den, "OE-1") # GOLD partajat
_silver(conn, norm, "OE-2") # SILVER exact
_mock_embedding(monkeypatch, "OE-3", 0.99) # embedding
conn.commit()
out = enrich_suggestions(conn, den)
assert out["sugestie_principala"] == {"cod_prestatie": "OE-1", "sursa": "gold_partajat"}
# Fara GOLD -> castiga SILVER.
conn.execute("DELETE FROM shared_mappings")
conn.commit()
out = enrich_suggestions(conn, den)
assert out["sugestie_principala"]["sursa"] == "silver"
assert out["sugestie_principala"]["cod_prestatie"] == "OE-2"
# Fara GOLD si fara SILVER -> castiga embedding.
conn.execute("DELETE FROM mapping_suggestions")
conn.commit()
out = enrich_suggestions(conn, den)
assert out["sugestie_principala"] == {"cod_prestatie": "OE-3", "sursa": "embedding"}
def test_prag_similaritate(conn, monkeypatch):
from app.mapping import enrich_suggestions, EMB_MIN_SIMILARITATE
_mock_embedding(monkeypatch, "OE-3", EMB_MIN_SIMILARITATE + 0.01)
out = enrich_suggestions(conn, "CEVA NEVAZUT")
assert out["surse"]["embedding"] == "OE-3"
def test_abtinere_sub_prag(conn, monkeypatch):
from app.mapping import enrich_suggestions, EMB_MIN_SIMILARITATE
_mock_embedding(monkeypatch, "OE-3", EMB_MIN_SIMILARITATE - 0.01)
out = enrich_suggestions(conn, "CEVA NEVAZUT")
assert out["surse"]["embedding"] is None # sub prag -> abtinere
assert out["sugestie_principala"] is None
def _mock_embedding_multi(monkeypatch, vecini):
"""Mock suggest_nearest cu o lista de vecini [(cod, sim, is_nul), ...]."""
import app.embeddings as emb
monkeypatch.setattr(emb, "has_corpus", lambda: True)
monkeypatch.setattr(
emb, "suggest_nearest",
lambda text, top_k=1: [
{"cod": c, "is_nul": n, "similaritate": s} for c, s, n in vecini
][:top_k],
)
def test_vot_topk_bate_top1_pe_etichete_contradictorii(conn, monkeypatch):
"""Corpus cu etichete contradictorii pe denumiri aproape identice:
top-1 ar da OE-1, dar votul ponderat (2x OE-8 vs 1x OE-1) da OE-8."""
from app.mapping import enrich_suggestions
_mock_embedding_multi(monkeypatch, [
("OE-1", 0.94, False),
("OE-8", 0.93, False),
("OE-8", 0.92, False),
])
out = enrich_suggestions(conn, "INLOCUIRE ANVELOPE")
assert out["surse"]["embedding"] == "OE-8"
assert out["surse"]["embedding_similaritate"] == 0.93
def test_vot_vecini_sub_prag_nu_voteaza(conn, monkeypatch):
"""Vecinii sub EMB_MIN_SIMILARITATE nu intra in vot, chiar daca sunt majoritari."""
from app.mapping import enrich_suggestions, EMB_MIN_SIMILARITATE
_mock_embedding_multi(monkeypatch, [
("OE-3", EMB_MIN_SIMILARITATE + 0.01, False),
("OE-1", EMB_MIN_SIMILARITATE - 0.05, False),
("OE-1", EMB_MIN_SIMILARITATE - 0.05, False),
])
out = enrich_suggestions(conn, "CEVA NEVAZUT")
assert out["surse"]["embedding"] == "OE-3"
def test_vot_nul_majoritar_supreseaza(conn, monkeypatch):
"""Majoritate NUL in vecinatate -> supresie, chiar daca top-1 e un cod."""
from app.mapping import enrich_suggestions
_mock_embedding_multi(monkeypatch, [
("OE-1", 0.93, False),
(None, 0.92, True),
(None, 0.92, True),
])
out = enrich_suggestions(conn, "CEVA CARE SEAMANA CU GUNOI")
assert out["surse"]["embedding"] is None
assert out["surse"]["nul"] is True
assert out["sugestie_principala"] is None
def test_vecin_knn_nul_supreseaza(conn, monkeypatch):
from app.mapping import enrich_suggestions
_mock_embedding(monkeypatch, None, 0.99, is_nul=True) # vecin NUL peste prag
out = enrich_suggestions(conn, "CEVA CARE SEAMANA CU GUNOI")
assert out["surse"]["embedding"] is None # NUL -> nu produce cod
assert out["surse"]["nul"] is True
assert out["sugestie_principala"] is None
def test_invariant_13_resolve_neatins(conn):
"""SILVER populat NU produce auto-rezolvare in resolve_prestatii."""
from app.mapping import resolve_prestatii, normalize_for_match
_silver(conn, normalize_for_match("OPERATIE X"), "OE-1")
resolved, unmapped = resolve_prestatii(
[{"cod_op_service": "OPERATIE X", "denumire": "OPERATIE X"}], mapping={}, valid_codes={"OE-1"}
)
assert resolved[0]["cod_prestatie"] is None # ramane nemapat, NU ia codul din SILVER
assert unmapped and unmapped[0]["cod_op_service"] == "OPERATIE X"