Files
rar-autopass/tests/test_mapare_integrare_l14.py
Claude Agent 756f77730f feat(5.18): corpus k-NN exemple etichetate + seed real Haiku (17181 op)
Seed app/data/operatii-etichetate.json regenerat cu subagenti Haiku pe TOATE
cele 17181 operatii distincte (ordine frecventa, 100%), inlocuind seed-ul Groq
(3758). Validare Haiku vs Groq pe 157 op etichetate: la dezacorduri Haiku corect
~22/30, Groq ~0. Haiku prinde gunoiul ratat de Groq (ITP, chirie anvelope, nume
piese fara actiune): NUL 2200 (12.8%) vs ~7.6% Groq; adaptare electronica OE-7
(nu OE-5), placute frana uzura OE-1 (nu OE-F avarie).

US-001..006: prefiltru NUL determinist, etichetator offline, generator seed,
seeder mapping_suggestions (in init_db, gated seed_operatii_enabled), embeddings
indexeaza corpus etichetat, enrich NUL+kNN. Distributie seed: OE-1 80.1%, NUL
12.8%, OE-2 3.5%, restul rar (OE-4/3/7/8/R/I/5, AITLV, R-ODO).

config: seed_operatii_enabled=True + embeddings_enabled=True implicit (SILVER
populat + sugestii semantice; ambele suggestion-only, dezactivabile prin env).

Suita: 1387 passed, 1 deselected (live).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:59:15 +00:00

583 lines
22 KiB
Python

"""TDD L14-S6 — Integrare Layer 2/3 in editor (suggestion-only, DUPA 5.15).
Scenarii acoperite:
- F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit (resolve_prestatii neschimbat)
- pending_unmapped include sugestie GOLD partajat > SILVER > embeddings (precedenta Eng-F2)
- record_human_validation apelat la confirmare umana (POST /mapari -> shared_mappings)
- Degradare gratioasa cand embeddings indisponibil (mock is_available=False)
- Separare structurala #13: resolve_prestatii/load_mapping NU citesc tabelele de sugestii
"""
from __future__ import annotations
import os
import tempfile
import pytest
# --------------------------------------------------------------------------- #
# Fixtures #
# --------------------------------------------------------------------------- #
@pytest.fixture()
def env(monkeypatch):
"""DB temporara cu schema initiata, auth dezactivata (mod dev)."""
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "l14_s6_test.db"))
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
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()
# Seed nomenclator (OE-1, OE-2, OE-3, OE-4 suficient pentru teste)
c.executemany(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
[
("OE-1", "REPARATIE MOTOR"),
("OE-2", "INTRETINERE"),
("OE-3", "REVIZIE PERIODICA"),
("OE-4", "REGLARE"),
],
)
c.commit()
yield c
c.close()
@pytest.fixture()
def client(env):
from app.main import app
from fastapi.testclient import TestClient
with TestClient(app) as c:
yield c
# --------------------------------------------------------------------------- #
# F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit #
# --------------------------------------------------------------------------- #
def test_f1_silver_nu_auto_trimite(conn):
"""CRITICAL F1: un cod in SILVER (mapping_suggestions) NU produce auto-trimitere.
resolve_prestatii cu mapping gol + SILVER existent -> operatie ramane nemapata.
Submissionul ar ramane needs_mapping, NU queued.
"""
from app.shared_store import seed_suggestions
from app.mapping import resolve_prestatii
seed_suggestions(conn, [
{"denumire": "Revizie periodica", "cod_prestatie": "OE-3", "source": "llm", "confidence": 0.95},
])
conn.commit()
# resolve_prestatii cu mapping gol -> SILVER nu se vede
resolved, unmapped = resolve_prestatii(
[{"cod_op_service": "OP-REV", "denumire": "Revizie periodica"}],
{}, # operations_mapping gol
)
# Operatia ramane nemapata (SILVER nu e in resolve, #13)
assert resolved[0]["cod_prestatie"] is None
assert len(unmapped) == 1
def test_f1_shared_gold_nu_auto_trimite(conn):
"""CRITICAL F1: un cod in shared_mappings (GOLD partajat) NU produce auto-trimitere.
resolve_prestatii cu mapping gol + shared GOLD existent -> operatie ramane nemapata.
"""
from app.shared_store import record_human_validation
from app.mapping import resolve_prestatii
record_human_validation(conn, "Schimb ulei motor", "OE-3")
conn.commit()
# resolve_prestatii cu mapping gol -> GOLD partajat nu se vede
resolved, unmapped = resolve_prestatii(
[{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
{}, # operations_mapping gol
)
# Operatia ramane nemapata (GOLD partajat nu e in resolve, #13)
assert resolved[0]["cod_prestatie"] is None
assert len(unmapped) == 1
def test_f1_load_mapping_nu_citeste_shared_gold(conn):
"""Separare #13: load_mapping NU returneaza coduri din shared_mappings."""
from app.shared_store import record_human_validation
from app.mapping import load_mapping
record_human_validation(conn, "Revizie anuala", "OE-3")
conn.commit()
mapping = load_mapping(conn, account_id=1)
# GOLD partajat nu trebuie sa apara in load_mapping (citit de resolve_prestatii)
assert "Revizie anuala" not in mapping
# Maparea propriu-zisa (operations_mapping) ramane goala
assert len(mapping) == 0
# --------------------------------------------------------------------------- #
# enrich_suggestions: GOLD partajat > SILVER > embeddings #
# --------------------------------------------------------------------------- #
def test_enrich_fara_surse_returneaza_none(conn):
"""Fara GOLD/SILVER/embedding -> sugestie_principala = None."""
from app.mapping import enrich_suggestions
result = enrich_suggestions(conn, "Operatie inexistenta")
assert result["sugestie_principala"] is None
assert result["surse"]["gold_partajat"] is None
assert result["surse"]["silver"] is None
assert result["surse"]["embedding"] is None
def test_enrich_include_gold_partajat(conn):
"""enrich_suggestions returneaza sugestie GOLD partajat cand shared_mappings are match."""
from app.shared_store import record_human_validation
from app.mapping import enrich_suggestions
record_human_validation(conn, "Schimb ulei", "OE-3")
conn.commit()
result = enrich_suggestions(conn, "Schimb ulei")
assert result["sugestie_principala"] is not None
assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
assert result["sugestie_principala"]["sursa"] == "gold_partajat"
assert result["surse"]["gold_partajat"] == "OE-3"
def test_enrich_include_silver(conn):
"""enrich_suggestions returneaza sugestie SILVER cand mapping_suggestions are match."""
from app.shared_store import seed_suggestions
from app.mapping import enrich_suggestions
seed_suggestions(conn, [
{"denumire": "Reparatie motor", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.9},
])
conn.commit()
result = enrich_suggestions(conn, "Reparatie motor")
assert result["sugestie_principala"] is not None
assert result["sugestie_principala"]["cod_prestatie"] == "OE-1"
assert result["sugestie_principala"]["sursa"] == "silver"
assert result["surse"]["silver"] == "OE-1"
def test_enrich_precedenta_gold_peste_silver(conn):
"""Precedenta Eng-F2: GOLD partajat castiga fata de SILVER cand ambele exista."""
from app.shared_store import seed_suggestions, record_human_validation
from app.mapping import enrich_suggestions
# SILVER spune OE-1, GOLD spune OE-3
seed_suggestions(conn, [
{"denumire": "Verificare tehnica", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.8},
])
record_human_validation(conn, "Verificare tehnica", "OE-3")
conn.commit()
result = enrich_suggestions(conn, "Verificare tehnica")
assert result["sugestie_principala"] is not None
assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
assert result["sugestie_principala"]["sursa"] == "gold_partajat"
# SILVER prezent dar nu castiga
assert result["surse"]["silver"] == "OE-1"
assert result["surse"]["gold_partajat"] == "OE-3"
def test_enrich_degradare_embeddings_indisponibil(conn, monkeypatch):
"""Degradare gratioasa (#16b): cand embeddings nu e disponibil, nu eroare."""
import app.embeddings as emb_mod
monkeypatch.setattr(emb_mod, "is_available", lambda: False)
from app.mapping import enrich_suggestions
# Fara surse -> sugestie_principala = None, fara exceptie
result = enrich_suggestions(conn, "Operatie demo", include_embeddings=True)
assert result["sugestie_principala"] is None
assert result["surse"]["embedding"] is None
def test_enrich_corpus_gol_nu_incarca_modelul(conn, monkeypatch):
"""Bug fix (code-review): enrich_suggestions NU lazy-load-eaza modelul de 220MB
cand corpus-ul embeddings e gol.
Implementarea veche apela `is_available()` neconditionat -> `_get_engine()` ->
`_load_engine()` -> `FastEmbedBackend()` (incarcare sincrona 30-120s) chiar daca
`index_corpus` nu a fost apelat niciodata in productie -> corpus gol ->
`suggest_nearest` ar fi returnat [] oricum (zero beneficiu, cost mare).
Fix: poarta `has_corpus()` (ieftina, nu construieste engine-ul cand `_engine is None`).
"""
import app.embeddings as emb_mod
# Engine ne-initializat -> corpus gol prin definitie.
monkeypatch.setattr(emb_mod, "_engine", None, raising=False)
incarcari = {"n": 0}
orig_load = emb_mod._load_engine
def _spy_load():
incarcari["n"] += 1
return orig_load()
monkeypatch.setattr(emb_mod, "_load_engine", _spy_load)
from app.mapping import enrich_suggestions
result = enrich_suggestions(conn, "Operatie oarecare", include_embeddings=True)
assert result["surse"]["embedding"] is None
assert incarcari["n"] == 0, (
"Modelul de embeddings NU trebuie incarcat cand corpus-ul e gol "
f"(index_corpus nu e wired). _load_engine apelat de {incarcari['n']} ori."
)
class _FakeEmbedBackend:
"""Backend embedding determinist (3 dimensiuni keyword) — fara model real 230MB."""
def embed(self, texts):
out = []
for t in texts:
tl = str(t).lower()
out.append([
1.0 if "ulei" in tl else 0.0,
1.0 if "motor" in tl else 0.0,
1.0 if "frana" in tl else 0.0,
])
return out
def test_embeddings_functional_cand_flag_activ(conn, monkeypatch):
"""PRD #15: cu AUTOPASS_EMBEDDINGS_ENABLED=true, embeddings produce efectiv o sugestie.
Wire-uieste ensure_embeddings_corpus (corpus din nomenclator) + enrich_suggestions.
Backend injectat (determinist) -> nu incarca modelul real de 230MB.
"""
import app.embeddings as emb_mod
from app.embeddings import EmbeddingEngine
from app.config import get_settings
# Activeaza flagul + injecteaza backend fals in singleton-ul global.
monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "true")
get_settings.cache_clear()
monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))
# Corpusul sursa = mapping_suggestions (SILVER) -- PRD 5.18 US-005.
# (Inainte era nomenclator_rar; migrat la mapping_suggestions ca k-NN sa
# opereze pe exemple reale etichetate, nu pe categorii generice RAR.)
conn.execute(
"INSERT OR REPLACE INTO mapping_suggestions "
"(denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, ?, ?)",
("Schimb ulei", "UL-1", 0, "llm", 0.95),
)
conn.execute(
"INSERT OR REPLACE INTO mapping_suggestions "
"(denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, ?, ?)",
("Placute frana", "FR-1", 0, "llm", 0.95),
)
conn.commit()
from app.mapping import ensure_embeddings_corpus, enrich_suggestions
ensure_embeddings_corpus(conn)
assert emb_mod.has_corpus(), "corpusul trebuie indexat cand flagul e activ"
# "schimbat uleiul motor" -> vector [1,1,0] -> cel mai apropiat = UL-1 (Schimb ulei).
result = enrich_suggestions(conn, "schimbat uleiul motor", include_embeddings=True)
assert result["surse"]["embedding"] == "UL-1", (
f"embeddings trebuie sa sugereze UL-1, got {result['surse']}"
)
get_settings.cache_clear()
def test_embeddings_flag_off_ramane_noop(conn, monkeypatch):
"""Cu flagul off (default), ensure_embeddings_corpus e no-op total (nu indexeaza)."""
import app.embeddings as emb_mod
from app.embeddings import EmbeddingEngine
from app.config import get_settings
monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "false")
get_settings.cache_clear()
# Engine cu backend disponibil, dar flagul off -> NU se indexeaza nimic.
monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))
from app.mapping import ensure_embeddings_corpus
ensure_embeddings_corpus(conn)
assert not emb_mod.has_corpus(), "flag off -> corpusul NU trebuie indexat"
get_settings.cache_clear()
def test_enrich_silver_nul_ignorat(conn):
"""SILVER cu is_nul=1 (non-operatie) NU apare ca sugestie."""
from app.shared_store import seed_suggestions
from app.mapping import enrich_suggestions
seed_suggestions(conn, [
{"denumire": "ITP CT 12 ABC", "is_nul": True, "source": "llm", "confidence": 0.99},
])
conn.commit()
result = enrich_suggestions(conn, "ITP CT 12 ABC")
assert result["sugestie_principala"] is None
assert result["surse"]["silver"] is None
# --------------------------------------------------------------------------- #
# pending_unmapped: include sugestie_principala #
# --------------------------------------------------------------------------- #
def test_pending_unmapped_include_sugestie_principala(conn):
"""pending_unmapped returneaza entries cu sugestie_principala din GOLD/SILVER."""
from app.shared_store import record_human_validation
from app.mapping import pending_unmapped
import json
record_human_validation(conn, "Schimb ulei motor", "OE-3")
conn.commit()
# Creeaza un submission needs_mapping cu "Schimb ulei motor"
conn.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
"VALUES (1, 'needs_mapping', ?, 'key-test-001')",
(json.dumps({
"vin": "WVWZZZ1KZAW001111",
"prestatii": [{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
}),),
)
conn.commit()
pending = pending_unmapped(conn, account_id=1)
assert len(pending) == 1
entry = pending[0]
# sugestie_principala adaugat de enrich_suggestions (L14-S6)
assert "sugestie_principala" in entry
sp = entry["sugestie_principala"]
assert sp is not None
assert sp["cod_prestatie"] == "OE-3"
assert sp["sursa"] == "gold_partajat"
def test_pending_unmapped_fara_surse_sugestie_principala_none(conn, monkeypatch):
"""pending_unmapped -> sugestie_principala = None cand nu exista nicio sursa.
Dezactiveaza embeddings prin poarta reala `has_corpus`=False (gate-ul folosit de
enrich_suggestions dupa wiring), independent de starea singleton-ului global lasata
de alte teste (izolare de ordine).
"""
import app.embeddings as emb_mod
monkeypatch.setattr(emb_mod, "has_corpus", lambda: False)
monkeypatch.setattr(emb_mod, "is_available", lambda: False)
from app.mapping import pending_unmapped
import json
conn.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
"VALUES (1, 'needs_mapping', ?, 'key-test-002')",
(json.dumps({
"vin": "WVWZZZ1KZAW002222",
"prestatii": [{"cod_op_service": "OP-FARA-SURSA", "denumire": "Operatie de nisa"}],
}),),
)
conn.commit()
pending = pending_unmapped(conn, account_id=1)
assert len(pending) == 1
entry = pending[0]
assert "sugestie_principala" in entry
assert entry["sugestie_principala"] is None
# --------------------------------------------------------------------------- #
# record_human_validation apelat la confirmare umana #
# --------------------------------------------------------------------------- #
def test_record_human_validation_la_post_mapari(env, client):
"""POST /mapari (tab Mapari) -> record_human_validation scrie in shared_mappings.
Testul verifica ca GOLD partajat se populeaza automat la confirmarea umana
din interfata de mapari.
"""
from app.db import get_connection
import json
# Creeaza un submission needs_mapping
conn_setup = get_connection()
try:
conn_setup.executemany(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
[("OE-3", "REVIZIE PERIODICA"), ("OE-1", "REPARATIE")],
)
conn_setup.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
"VALUES (1, 'needs_mapping', ?, 'key-hv-001')",
(json.dumps({
"vin": "WVWZZZ1KZAW003333",
"prestatii": [{"cod_op_service": "OP-REV", "denumire": "Revizie anuala"}],
}),),
)
conn_setup.commit()
finally:
conn_setup.close()
# POST /mapari cu denumire (L14-S6: form include denumire hidden)
resp = client.post(
"/mapari",
data={
"cod_op_service": "OP-REV",
"cod_prestatie": "OE-3",
"denumire": "Revizie anuala",
"csrf_token": "",
},
)
assert resp.status_code == 200, resp.text
# Verifica ca shared_mappings contine intrarea
conn_check = get_connection()
try:
from app.shared_store import lookup_shared_gold
row = lookup_shared_gold(conn_check, "Revizie anuala")
assert row is not None, "record_human_validation nu a scris in shared_mappings"
assert row["cod_prestatie"] == "OE-3"
finally:
conn_check.close()
def test_record_human_validation_la_mapeaza_inline(env, client):
"""POST /trimitere/{id}/mapeaza -> record_human_validation scrie in shared_mappings.
Testul verifica ca GOLD partajat se populeaza la maparea inline din panoul de detaliu.
"""
from app.db import get_connection
import json
# Setup submission needs_mapping
conn_setup = get_connection()
try:
conn_setup.executemany(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
[("OE-1", "REPARATIE"), ("OE-3", "REVIZIE")],
)
conn_setup.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
"VALUES (1, 'needs_mapping', ?, 'key-inline-001')",
(json.dumps({
"vin": "WVWZZZ1KZAW004444",
"data_prestatie": "2026-06-15",
"odometru_final": 100000,
"prestatii": [{"cod_op_service": "OP-REP", "denumire": "Reparatie chiulasa"}],
}),),
)
conn_setup.commit()
# Preia ID-ul submission-ului
sid = conn_setup.execute("SELECT id FROM submissions WHERE idempotency_key='key-inline-001'").fetchone()["id"]
finally:
conn_setup.close()
resp = client.post(
f"/trimitere/{sid}/mapeaza",
data={
"cod_op_service": "OP-REP",
"cod_prestatie": "OE-1",
"csrf_token": "",
},
)
assert resp.status_code == 200, resp.text
# Verifica shared_mappings
conn_check = get_connection()
try:
from app.shared_store import lookup_shared_gold
row = lookup_shared_gold(conn_check, "Reparatie chiulasa")
assert row is not None, "record_human_validation nu a scris in shared_mappings pentru mapeaza inline"
assert row["cod_prestatie"] == "OE-1"
finally:
conn_check.close()
def test_mapare_salvata_fara_denumire_nu_polueaza_gold(env, client):
"""Bug fix (code-review 5.15): editarea unei mapari salvate FARA denumire NU scrie
o intrare bogus in GOLD partajat (cheiata pe cod_op_service in loc de denumire umana).
Formularul din _mapari.html nu trimite denumire; vechiul fallback `denumire or
cod_op_service` scria shared_mappings cheiat pe cod_op_service -> lookup_shared_gold
(pe denumirea umana) nu il potrivea niciodata -> poluare. Fix: _record_gold_validation
sare scrierea cand denumire lipseste sau == cod_op_service.
"""
from app.db import get_connection
conn_setup = get_connection()
try:
conn_setup.execute(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
("OE-1", "REPARATIE"),
)
conn_setup.commit()
finally:
conn_setup.close()
# Editare mapare salvata FARA denumire (ca formularul real din _mapari.html).
resp = client.post(
"/mapari/salvate",
data={
"cod_op_service": "OP-SALV",
"cod_prestatie": "OE-1",
"csrf_token": "",
},
)
assert resp.status_code == 200, resp.text
conn_check = get_connection()
try:
from app.shared_store import lookup_shared_gold
# NICIO intrare bogus cheiata pe cod_op_service.
assert lookup_shared_gold(conn_check, "OP-SALV") is None, (
"GOLD partajat poluat cu cod_op_service ca si cheie (denumire lipsa)"
)
finally:
conn_check.close()
# --------------------------------------------------------------------------- #
# Separare structurala #13 (redundant cu test_shared_store dar explicit L14) #
# --------------------------------------------------------------------------- #
def test_separare_silver_din_resolve_prestatii():
"""#13: resolve_prestatii nu citeste mapping_suggestions (SILVER)."""
from app.mapping import resolve_prestatii
# Apelam fara conn (pur) — SILVER nu e parametru si nu e accesat
resolved, unmapped = resolve_prestatii(
[{"cod_op_service": "OP-TEST", "denumire": "Test silver"}],
{}, # mapping gol
)
assert resolved[0]["cod_prestatie"] is None
assert len(unmapped) == 1
def test_separare_shared_gold_din_resolve_prestatii():
"""#13: resolve_prestatii nu citeste shared_mappings (GOLD partajat)."""
from app.mapping import resolve_prestatii
resolved, unmapped = resolve_prestatii(
[{"cod_op_service": "OP-TEST2", "denumire": "Test gold partajat"}],
{}, # mapping gol
)
assert resolved[0]["cod_prestatie"] is None
assert len(unmapped) == 1