Doua greseli din grupul "Maria Test", 2026-09-01, pe care 27409fb le-a atenuat dar
nu le-a rezolvat.
1. La o reclamatie vaga Maria cauta, in loc sa intrebe. "Buna . Am si eu o factura
pe luna august cu eroare in spv la trimitere AUTO SULE" nu spune CE eroare e —
n-are ce cauta in documente si n-are ce trimite la suport, fiindca programatorul
ar pune exact aceeasi intrebare. Gardul (triaj.prea_vag) exista, dar cerea text
sub 12 cuvinte; mesajul are 14. Gresea si invers: "nu pot incarca factura in SPV,
imi da eroare de certificat" are 9 cuvinte si ARE raspuns in documente, si era
oprita degeaba. Lungimea nu masoara cat de precis e mesajul.
-> gardul nu mai numara cuvinte si se aplica DUPA cautare, doar cand nu exista
acoperire. O singura data pe fir: daca nici intrebat omul nu spune mai mult,
mesajul pleaca la suport.
2. Textul si captura, trimise una dupa alta, erau tratate ca doua probleme fara
legatura. `este_continuare` rupea firul la ORICE imagine — dar cazul frecvent e
tocmai omul care scrie problema si trimite captura imediat dupa, sau care
raspunde la "trimite-mi o captura". Textul se pierdea, captura se cauta doar pe
OCR-ul ei, se deschideau doua fire si se puteau deschide doua escaladari pentru
aceeasi problema.
-> o captura in primele FIR_IMAGINE_MIN (5) minute continua firul: textul citit
din ea intra in ancora (cu tot cu coduri), cautarea se face pe mesaj +
captura, iar pe o escaladare deschisa pleaca la aceeasi referinta — cu
imaginea, nu doar cu textul citit din ea (_notifica_suport ia si media).
Si, ca urmare a lui (2): RANK_STRONG_COSINE dispare. Interogarea pe un fir e ancora
plus mesajul nou, deci cosinusul urca la fiecare replica fara sa apara vreo dovada
noua — aceeasi captura da 0,778 singura si 0,836 cu mesajul de dinainte, adica peste
0,80 pus ieri. Orice prag fix de sus e trecut de o discutie destul de lunga. Acoperirea
ramane pe dovada lexicala (termenii distinctivi ai intrebarii chiar in chunk-uri),
care e stabila la lungime. Niciun caz cu raspuns in documente nu avea nevoie de
scurtatura.
ops/calibrate-rank.py: 26/26, cu interogarea combinata adaugata la set. Teste: 102 pass.
Verificat end-to-end pe scenariul real (mesaj, apoi captura la 10 secunde): intrebare
de detalii, apoi o singura escaladare care poarta si textul si captura.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
"""Triajul: cand se cer detalii, cand pleaca automat la programatori."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
|
|
import triaj
|
|
|
|
|
|
CHUNK_06550 = """[eroare ORA 06550]
|
|
keywords: ORA-06550 PLS-00201 eroare compilare
|
|
mesaj eroare: ORA-06550: eroare de compilare in program
|
|
ce inseamna: Programul nu e adus la zi peste tot.
|
|
rezolvare: Nu se rezolva din aplicatie si nu ajuta sa incerci din nou.
|
|
cand suni suportul: Intotdeauna. Trimite si o captura de ecran."""
|
|
|
|
CHUNK_04068 = """[eroare ORA 04068]
|
|
mesaj eroare: ORA-04068: existing state of packages has been discarded
|
|
ce inseamna: Programul a fost actualizat in timp ce aplicatia ta era deschisa.
|
|
rezolvare: Inchide complet aplicatia si redeschide-o.
|
|
cand suni suportul: Daca revine si dupa restartul aplicatiei."""
|
|
|
|
|
|
def test_nivelul_se_citeste_din_dictionar():
|
|
assert triaj.nivel_suport(CHUNK_06550) == "intotdeauna"
|
|
assert triaj.nivel_suport(CHUNK_04068) is None
|
|
assert triaj.nivel_suport("cand suni suportul: Imediat. Blocheaza tot.") == "imediat"
|
|
|
|
|
|
def test_raspunsul_nu_vorbeste_ca_pentru_programatori():
|
|
"""Cazul din 2026-09-01: modelul ii cerea unui contabil sa verifice schema."""
|
|
text = triaj.raspuns_escaladat(CHUNK_06550, "intotdeauna", "M-260901-A1B2",
|
|
True, cu_imagine=True)
|
|
jos = text.lower()
|
|
for cuvant in ("procedura", "schema", "pl/sql", "obiect", "variabila", "compilare"):
|
|
assert cuvant not in jos, cuvant
|
|
assert "M-260901-A1B2" in text
|
|
assert "captura" in jos
|
|
assert "blocheaza complet" in jos # intreaba cat e de urgent
|
|
|
|
|
|
def test_cand_notificarea_nu_a_plecat_nu_promitem_ca_a_plecat():
|
|
text = triaj.raspuns_escaladat(CHUNK_06550, "intotdeauna", "M-1", False)
|
|
assert "am inregistrat" in text.lower()
|
|
assert "te contacteaza cineva" not in text.lower()
|
|
|
|
|
|
def test_prea_vag_doar_pentru_acuzatii_fara_continut():
|
|
assert triaj.prea_vag("am o eroare", False)
|
|
assert triaj.prea_vag("nu merge nimic", False)
|
|
# cazul real din 2026-09-01: lung, dar tot nu spune CE eroare e. Plafonul de
|
|
# 12 cuvinte il lasa sa treaca, si a primit un raspuns inventat.
|
|
assert triaj.prea_vag(
|
|
"Buna . Am si eu o factura pe luna august cu eroare in spv la trimitere AUTO SULE",
|
|
False)
|
|
# o intrebare adevarata nu se opreste ca sa i se ceara detalii
|
|
assert not triaj.prea_vag("cum trimit declaratia D406 SAF-T", False)
|
|
# un cod inseamna ca avem deja dupa ce cauta
|
|
assert not triaj.prea_vag("imi da ORA-01722 la salvare", False)
|
|
# captura vorbeste de la sine
|
|
assert not triaj.prea_vag("am o eroare", True)
|
|
|
|
|
|
# --- completarea la o escaladare deschisa -----------------------------------
|
|
|
|
def _escaladare(ref: str) -> None:
|
|
import config
|
|
d = config.STATE_DIR / "escalations"
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
(d / f"{int(time.time())}-{ref}.json").write_text(
|
|
json.dumps({"ref": ref, "text": "eroare initiala"}), encoding="utf-8")
|
|
|
|
|
|
def _fara_suport(monkeypatch):
|
|
import config
|
|
monkeypatch.setattr(config, "get", lambda k, d=None: "" if k == "SUPPORT_JID"
|
|
else config.DEFAULTS.get(k, d))
|
|
|
|
|
|
def test_completarea_intra_in_escaladarea_deschisa(monkeypatch):
|
|
import config
|
|
import consumer
|
|
|
|
_fara_suport(monkeypatch)
|
|
_escaladare("M-TEST-1")
|
|
raspuns = consumer.completeaza("M-TEST-1", {"id": "x"}, "eram la salvarea unei facturi")
|
|
assert "M-TEST-1" in raspuns
|
|
|
|
fisier = next((config.STATE_DIR / "escalations").glob("*-M-TEST-1.json"))
|
|
record = json.loads(fisier.read_text(encoding="utf-8"))
|
|
assert record["completari"][0]["text"] == "eram la salvarea unei facturi"
|
|
assert record["completari"][0]["fel"] == "detaliu"
|
|
|
|
|
|
def test_completarile_se_aduna_si_nu_repeta_acelasi_text(monkeypatch):
|
|
"""Trei confirmari identice una dupa alta suna a robot."""
|
|
import config
|
|
import consumer
|
|
|
|
_fara_suport(monkeypatch)
|
|
_escaladare("M-TEST-3")
|
|
raspunsuri = [consumer.completeaza("M-TEST-3", {"id": str(i)}, t) for i, t in enumerate(
|
|
["eram la facturi", "si la rapoarte", "si la import"])]
|
|
assert len(set(raspunsuri)) == 3
|
|
|
|
fisier = next((config.STATE_DIR / "escalations").glob("*-M-TEST-3.json"))
|
|
record = json.loads(fisier.read_text(encoding="utf-8"))
|
|
assert len(record["completari"]) == 3
|
|
|
|
|
|
def test_urgenta_marcheaza_escaladarea_si_anunta(monkeypatch):
|
|
import config
|
|
import consumer
|
|
|
|
trimise = []
|
|
monkeypatch.setattr(consumer, "_notifica_suport",
|
|
lambda text: trimise.append(text) or True)
|
|
_escaladare("M-TEST-U")
|
|
raspuns = consumer.completeaza("M-TEST-U", {"id": "u"}, "Este foarte urgent, ma blocheaza")
|
|
|
|
record = json.loads(next((config.STATE_DIR / "escalations").glob("*-M-TEST-U.json"))
|
|
.read_text(encoding="utf-8"))
|
|
assert record["urgenta"] is True
|
|
assert record["completari"][0]["fel"] == "urgenta"
|
|
assert trimise and trimise[0].startswith("[Maria] URGENT")
|
|
assert "urgenta" in raspuns.lower()
|
|
|
|
|
|
def test_intrebarea_de_stare_primeste_un_raspuns_nu_o_confirmare(monkeypatch):
|
|
"""„Nu m-a contactat nimeni. Cat mai dureaza?" primea „am adaugat si asta"."""
|
|
import config
|
|
import consumer
|
|
|
|
trimise = []
|
|
monkeypatch.setattr(consumer, "_notifica_suport",
|
|
lambda text: trimise.append(text) or True)
|
|
_escaladare("M-TEST-S")
|
|
raspuns = consumer.completeaza("M-TEST-S", {"id": "s"},
|
|
"Nu m-a contactat nimeni. Cat mai dureaza?")
|
|
assert "am adaugat" not in raspuns.lower()
|
|
assert "M-TEST-S" in raspuns
|
|
assert trimise and "intreaba" in trimise[0]
|
|
|
|
# a doua intrebare la un minut distanta nu mai suna telefonul echipei
|
|
trimise.clear()
|
|
consumer.completeaza("M-TEST-S", {"id": "s2"}, "tot nimic")
|
|
assert not trimise
|
|
|
|
|
|
def test_dupa_o_jumatate_de_ora_maria_o_spune_pe_fata():
|
|
import triaj
|
|
text = triaj.raspuns_stare("M-1", 42, False, telefon="0241 000 000")
|
|
assert "42" in text and "0241 000 000" in text
|
|
assert "nu mi-a raspuns nimeni" in text
|
|
|
|
|
|
def test_fel_completare():
|
|
import triaj
|
|
assert triaj.fel_completare("Este foarte urgent") == "urgenta"
|
|
assert triaj.fel_completare("ma blocheaza complet") == "urgenta"
|
|
assert triaj.fel_completare("Cat mai dureaza?") == "stare"
|
|
assert triaj.fel_completare("Tot nimic") == "stare"
|
|
assert triaj.fel_completare("eram la salvarea unei facturi") == "detaliu"
|
|
# ordinea conteaza: urgenta bate intrebarea de stare
|
|
assert triaj.fel_completare("urgent, cat mai dureaza?") == "urgenta"
|
|
|
|
|
|
def test_a_doua_intrebare_de_stare_nu_repeta_acelasi_text():
|
|
import triaj
|
|
intai = triaj.raspuns_stare("M-1", 10, False, a_cata=0)
|
|
apoi = triaj.raspuns_stare("M-1", 11, False, a_cata=1)
|
|
assert intai != apoi
|
|
# „acum 10 minute", nu „acum 10 de minute"
|
|
assert "10 minute" in intai and "10 de minute" not in intai
|
|
assert triaj.minute_text(25) == "25 de minute"
|