"""Ordonarea hibrida si decizia de escaladare. Fara retea: embeddings-urile sunt inlocuite cu scoruri cosinus date direct, ca sa se testeze fuziunea si pragurile, nu Ollama. """ from __future__ import annotations import rank DOCS = [ "mesaj eroare: ORA-01722 invalid number. rezolvare: verifica formatul coloanei numerice", "mesaj eroare: token efactura expirat. rezolvare: reautorizeaza aplicatia in SPV", "cum se trimite declaratia D406 SAF-T catre ANAF din meniul Rapoarte", "instructiuni pentru inregistrarea unei facturi de achizitie in modulul Facturare", ] def test_tokenize_pastreaza_codurile_intregi(): assert "ora-01722" in rank.tokenize("Imi da ORA-01722 la salvare") # diacriticele nu trebuie sa rupa potrivirea assert rank.tokenize("factură") == rank.tokenize("factura") def test_tokenize_scoate_cuvintele_de_umplutura(): assert rank.tokenize("cum se salveaza si de ce nu") == ["salveaza"] def test_bm25_gaseste_codul_de_eroare(): bm = rank.Bm25(DOCS) scoruri = bm.scores("ORA-01722") assert scoruri[0] > 0 assert max(scoruri) == scoruri[0] def test_bm25_zero_cand_nu_exista_niciun_token_comun(): bm = rank.Bm25(DOCS) assert all(s == 0 for s in bm.scores("uleiul de motor al masinii")) def test_rare_terms_prinde_codurile_si_ignora_cuvintele_comune(): bm = rank.Bm25(DOCS) rare = bm.rare_terms("ORA-01722 la factura") assert "ora-01722" in rare def test_assess_gaseste_termenul_la_alta_forma_gramaticala(): # Documentul scrie „token"/„tokenuri", omul scrie „tokenul". Comparat pe cuvantul # intreg, singurul cuvant care conteaza nu se gaseste si intrebarea escaladeaza. bm = rank.Bm25(DOCS) covered, motiv = rank.assess("imi zice ca tokenul a expirat", 0.64, DOCS[1:2], bm) assert covered assert "tokenul" in motiv def test_assess_codurile_se_compara_intregi_nu_pe_radacina(): # „ora-01722" si „ora-01720" au aceleasi cinci litere de la inceput. bm = rank.Bm25(DOCS) covered, _ = rank.assess("eroarea ORA-01720", 0.64, DOCS[:1], bm) assert not covered def test_assess_dovada_judeca_mesajul_nou_nu_ancora(): # Continuare pe un fir: ancora e salutul plus reclamatia vaga, mesajul nou e # chiar intrebarea. Fara dovada separata, „buna/august" (care nu apar nicaieri) # trag raportul sub prag si escaladeaza degeaba. bm = rank.Bm25(DOCS) ancora = "Buna ziua . Am o problema pe luna august la firma AUTO SULEA . Ma ajutati?" query = f"{ancora}\ntokenul a expirat" fara, _ = rank.assess(query, 0.64, DOCS[1:2], bm) cu, motiv = rank.assess(query, 0.64, DOCS[1:2], bm, dovada="tokenul a expirat") assert not fara assert cu and "expirat" in motiv def test_assess_continuare_scurta_cade_inapoi_pe_interogarea_intreaga(): # „da, ma blocheaza complet" n-are termeni distinctivi proprii. Judecata singura # ar escalada orice continuare scurta — exact regresia pentru care exista ancora. bm = rank.Bm25(DOCS) query = "token efactura expirat\nda" covered, _ = rank.assess(query, 0.64, DOCS[1:2], bm, dovada="da") fara, _ = rank.assess(query, 0.64, DOCS[1:2], bm) assert covered is fara is True def test_rank_urca_chunkul_confirmat_de_ambele_metode(): # cosinusul favorizeaza documentul 3, dar lexical se potriveste documentul 0; # documentul 0 e sustinut de amandoua (cosinus decent + BM25 mare) si iese primul cos = [0.62, 0.30, 0.31, 0.64] bm = [8.0, 0.0, 0.0, 0.0] assert rank.rank(cos, bm)[0] == 0 def test_rank_fara_potrivire_lexicala_ramane_ordinea_semantica(): cos = [0.30, 0.40, 0.80, 0.20] assert rank.rank(cos, [0.0, 0.0, 0.0, 0.0])[0] == 2 def test_assess_cosinus_mare_raspunde(): bm = rank.Bm25(DOCS) covered, motiv = rank.assess("token efactura expirat", 0.77, DOCS[1:2], bm) assert covered assert "0.770" in motiv or "0.77" in motiv def test_assess_cosinus_mic_escaladeaza(): bm = rank.Bm25(DOCS) covered, motiv = rank.assess("cum schimb uleiul la masina", 0.53, DOCS[:1], bm) assert not covered assert "0.53" in motiv def test_assess_zona_de_mijloc_salvata_de_codul_de_eroare(): # Exact cazul pentru care exista partea lexicala: cosinusul e mediocru, dar # codul din intrebare apare textual in chunk. bm = rank.Bm25(DOCS) covered, motiv = rank.assess("ORA-01722", 0.61, DOCS[:1], bm) assert covered assert "ora-01722" in motiv def test_assess_zona_de_mijloc_fara_termeni_in_context_escaladeaza(): bm = rank.Bm25(DOCS) covered, motiv = rank.assess("ORA-99999", 0.61, DOCS[:1], bm) assert not covered assert "nu apar in documente" in motiv def test_assess_un_singur_termen_din_multi_nu_e_dovada(): # Cazul care a scos la iveala nevoia de fractiune: „resetez parola de la Windows" # era salvata gresit doar fiindca „parola" apare in documente. Un termen din # trei nu inseamna ca intrebarea e acoperita. bm = rank.Bm25(DOCS + ["reautorizeaza aplicatia si schimba parola in SPV"]) covered, motiv = rank.assess("cum resetez parola de la Windows", 0.64, ["reautorizeaza aplicatia si schimba parola in SPV"], bm) assert not covered assert "windows" in motiv def test_assess_motivul_numeste_termenii_lipsa_nu_pe_toti(): # Fara cod in intrebare: acolo decid termenii rari, nu regula codurilor. bm = rank.Bm25(DOCS) _, motiv = rank.assess("factura la imprimanta LaserJet", 0.61, DOCS[3:], bm) # „factura" chiar apare in context, deci nu are ce cauta in lista lipsurilor assert "factura," not in motiv assert "imprimanta" in motiv def test_assess_intrebare_vaga_in_zona_de_mijloc_escaladeaza(): bm = rank.Bm25(DOCS) covered, motiv = rank.assess("nu merge", 0.61, DOCS[:1], bm) assert not covered assert "distinctivi" in motiv def test_pragurile_vin_din_env(monkeypatch): import config bm = rank.Bm25(DOCS) monkeypatch.setitem(config._env, "RANK_WEAK_COSINE", "0.90") covered, motiv = rank.assess("factura", 0.85, DOCS[:1], bm) assert not covered and "0.9" in motiv def test_cosinusul_mare_singur_nu_mai_e_acoperire(): """Interogarea creste pe fir si cosinusul cu ea; dovada ramane cea lexicala. Cazul real: aceeasi captura a dat 0,778 singura si 0,836 cu mesajul de dinainte, fara sa apara vreo dovada noua in documente. """ bm = rank.Bm25(DOCS) covered, motiv = rank.assess("cumparator incorect la incarcare", 0.84, DOCS[:1], bm) assert not covered assert "cumparator" in motiv # --- coduri de eroare: identificatori exacti, nu cuvinte --------------------- def test_codul_lipit_de_ocr_e_tot_gasit(): """OCR-ul da „[Oracle][ODBC][OraJORA-06550"; codul nu are voie sa se piarda.""" import rank assert rank.codes("[Oracle][ODBC][OraJORA-06550: line 1, column 7") == ["ora-06550"] assert rank.codes("PLS-00906: object X is invalid") == ["pls-00906"] assert rank.codes("cum trimit D406") == ["d406"] assert rank.codes("nu merge nimic") == [] def test_chunkul_cu_codul_cerut_urca_in_clasament(): import rank texts = [ "ORA-12541 TNS no listener, serverul nu raspunde", "ORA-03113 end-of-file on communication channel", "ORA-06550 eroare de compilare, procedura lipseste", ] assert rank.by_codes("imi da ORA-06550 la salvare", texts) == [2] assert rank.by_codes("intrebare fara coduri", texts) == [] def test_cosinus_mare_nu_acopera_alt_cod(monkeypatch): """Doua erori Oracle diferite se scriu la fel; cosinusul nu le deosebeste. Cazul real din 2026-08-31: captura cu ORA-06550 primea raspunsul despre ORA-12541, cu cosinus 0,736 — peste pragul „sigur". """ import rank bm = rank.Bm25(["ORA-12541 TNS no listener"]) ok, motiv = rank.assess("ORA-06550 PLS-00906 la salvare", 0.736, ["ORA-12541 TNS no listener"], bm) assert ok is False assert "ora-06550" in motiv def test_codul_gasit_acopera_si_la_cosinus_mic(): import rank bm = rank.Bm25(["ORA-06550 eroare de compilare"]) ok, motiv = rank.assess("ORA-06550", 0.42, ["ORA-06550 eroare de compilare"], bm) assert ok is True assert "ora-06550" in motiv