#!/usr/bin/env python3 """Triaj: cine primeste raspuns, cine primeste intrebari, cine cheama programatorii. Trei decizii pe care cautarea in documente nu le poate lua singura: 1. **Mesaj prea vag.** „Am o eroare" nu e o intrebare — e inceputul uneia. Fara operatiunea, ecranul si textul erorii, si un om ar ghici. Maria intreaba, nu ghiceste (si nu deranjeaza suportul cu „am o eroare"). 2. **Erori care oricum ajung la programatori.** Dictionarul spune, pentru fiecare eroare, `cand suni suportul`. Noua dintre ele incep cu „Intotdeauna" sau „Imediat": nu se rezolva din aplicatie, orice explicatie i-ar da utilizatorului nu-l ajuta sa iasa din ea. Pentru astea raspunsul se compune din chiar campurile dictionarului, iar mesajul pleaca automat la suport. De ce fara model: pe captura cu ORA-06550, avand in context exact intrarea care spune „nu se rezolva din aplicatie", modelul a raspuns „verifica schema de date din aplicatia ROA (...) sau foloseste procedura alternativa MI_pack_parteneri_old". A inventat o procedura, a cerut unui contabil sa umble in schema si a contrazis documentul. Textul din dictionar e deja scris pentru clienti; parafrazarea lui nu adauga nimic si poate strica tot. 3. **Cat e de urgent.** Aceeasi eroare poate bloca omul complet sau poate fi ocolita pana maine. Nu se deduce din text, deci se intreaba — iar raspunsul lui se ataseaza escaladarii deja deschise, nu porneste alta. """ from __future__ import annotations import re import rank # Prima vorba din `cand suni suportul`. Restul („Daca revine dupa restart") sunt # conditionate: acolo utilizatorul are ce incerca, deci raspunde modelul. _INTOTDEAUNA = ("intotdeauna", "imediat") _CAMP = re.compile(r"^([a-z ]+):\s*(.*)$") # Cuvintele cu care oamenii anunta o problema fara sa spuna care. _ACUZA = re.compile( r"\b(eroare|erori|eroarea|problema|probleme|nu merge|nu functioneaza|" r"nu mai merge|s-a blocat|crapa|imi da|nu pot)\b" ) def camp(chunk: str, nume: str) -> str: """Valoarea unui camp din chunk-ul XML randat (`mesaj eroare: ...`).""" tinta = nume.replace("_", " ") for line in chunk.splitlines(): m = _CAMP.match(line.strip()) if m and m.group(1).strip() == tinta: return m.group(2).strip() return "" def nivel_suport(chunk: str) -> str | None: """`"intotdeauna"` / `"imediat"` daca eroarea cere suport oricum, altfel `None`.""" valoare = camp(chunk, "cand suni suportul").lower() for cuvant in _INTOTDEAUNA: if valoare.startswith(cuvant): return cuvant return None def prea_vag(text: str, are_imagine: bool) -> bool: """Mesajul anunta o problema fara sa spuna care. Se intreaba DUPA cautare, si numai cand nu exista acoperire in documente — altfel gardul taia si intrebari cu raspuns („nu pot incarca factura in SPV, imi da eroare de certificat" e in documente, dar suna a reclamatie). Avea si un plafon de 12 cuvinte, pe ideea ca un mesaj lung spune destul. Nu spune: „Buna . Am si eu o factura pe luna august cu eroare in spv la trimitere AUTO SULE" are 14 si tot nu zice CE eroare e — a trecut de gard si a primit un raspuns inventat (2026-09-01). Lungimea nu masoara cat de precis e mesajul; acoperirea in documente da. """ if are_imagine: return False t = rank.fold(text.strip()) if not t or rank.codes(t): return False return bool(_ACUZA.search(t)) CERE_DETALII = ( "Ca sa-mi dau seama despre ce e vorba, spune-mi te rog:\n" "1. ce faceai exact cand a aparut (ce operatiune),\n" "2. in ce ecran sau meniu erai,\n" "3. ce scrie exact in mesaj — cel mai simplu, trimite o captura de ecran cu " "fereastra de eroare." ) # Ce se trimite la o eroare care oricum ajunge la programatori. Explicatia si # indicatia sunt luate ca atare din dictionar, nu reformulate. _URGENT = ("Este o eroare care afecteaza toti utilizatorii, nu doar pe tine, asa ca " "am anuntat echipa tehnica imediat si i-am trimis {ce}") _NORMAL = "Am trimis {ce} mai departe, catre echipa tehnica" INTREBARE_URGENTA = ( "Spune-mi te rog daca te blocheaza complet sau poti continua pe alta " "operatiune — asa stiu cat e de urgent." ) def raspuns_escaladat(chunk: str, nivel: str, ref: str, notificat: bool, cu_imagine: bool = False) -> str: """Raspunsul pentru o eroare care oricum ajunge la programatori. Compus din campurile dictionarului, fara model: textul de acolo e deja scris pentru clienti, iar aici nu e nimic de dedus — doar de spus limpede. """ parti: list[str] = [] explicatie = camp(chunk, "ce inseamna") if explicatie: parti.append(explicatie) rezolvare = camp(chunk, "rezolvare") if rezolvare: parti.append(rezolvare) ce = "mesajul si captura" if cu_imagine else "mesajul" trimis = (_URGENT if nivel == "imediat" else _NORMAL).format(ce=ce) if notificat: parti.append(f"{trimis} (referinta {ref}). Te contacteaza cineva.") else: # Nu promitem ce nu s-a intamplat: notificarea a esuat, dar e inregistrata. parti.append( f"Am inregistrat problema pentru echipa tehnica (referinta {ref}), dar " f"nu am putut sa o trimit chiar acum. Daca te blocheaza, suna-ne." ) parti.append(INTREBARE_URGENTA) return "\n\n".join(parti) # --- ce fel de completare e raspunsul omului -------------------------------- _URGENTA = re.compile( r"\b(urgent|urgenta|foarte urgent|ma blocheaza|blocheaza|nu pot lucra|" r"nu pot factura|nu merge nimic|sta toata firma|s-a oprit tot)\b" ) _STARE = re.compile( r"(cat mai dureaza|cat dureaza|cand ma contacteaza|nu m-?a contactat|" r"nu m-?a sunat|tot nimic|nimic inca|chiar nimic|ati uitat|mai dureaza|" r"s-a rezolvat|ce se intampla|vreo veste|veste|noutati|s-a miscat ceva|" r"aveti un raspuns)" ) def fel_completare(text: str) -> str: """`"urgenta"` / `"stare"` / `"detaliu"` — ce vrea omul cu mesajul asta. Trei lucruri diferite primeau acelasi raspuns sablon („am adaugat si asta la M-…"): un semnal de urgenta, o intrebare la care Maria nici nu raspundea, si un detaliu in plus. Ordinea conteaza: „urgent, cat mai dureaza?" e intai urgenta. """ t = fold_text(text) if _URGENTA.search(t): return "urgenta" if _STARE.search(t): return "stare" return "detaliu" def fold_text(text: str) -> str: return rank.fold(text or "") # Confirmari rotite: trei mesaje identice unul dupa altul suna a robot. _CONFIRMARI = ( "Am adaugat si asta la {ref}.", "Notat — am trecut si asta in {ref}.", "Am completat {ref} cu ce mi-ai spus.", ) URGENTA_MARCATA = ( "Am marcat problema ca urgenta si am anuntat echipa ({ref}). " "Daca intre timp te blocheaza complet, spune-mi." ) def confirmare(ref: str, a_cata: int) -> str: return _CONFIRMARI[a_cata % len(_CONFIRMARI)].format(ref=ref) def minute_text(n: int) -> str: """„3 minute", dar „25 de minute" — regula romaneasca a lui „de" peste 19.""" return f"{n} minute" if n < 20 else f"{n} de minute" # Cand omul intreaba a doua oara si nu s-a schimbat nimic, nu repetam acelasi text. _ASTEPTARE = ( "Inca nimic nou de la echipa. Te anunt imediat ce am un raspuns.", "Inca nu mi-a raspuns nimeni. Problema e in lista lor, cu {ref}.", ) def raspuns_stare(ref: str, minute: int, reamintit: bool, telefon: str = "", a_cata: int = 0) -> str: """Ce se raspunde la „cat mai dureaza?" — cu ce stim, nu cu formule. Peste o jumatate de ora fara raspuns, „echipa vede detaliul cand preia problema" nu mai e un raspuns onest: omul asteapta de o jumatate de ora si o stie mai bine decat noi. """ unde = f" la {telefon}" if telefon else "" if minute >= 30: return ( f"Au trecut {minute_text(minute)} de cand am trimis problema ({ref}) si inca " f"nu mi-a raspuns nimeni. Nu vreau sa te tin cu vorbe: daca te blocheaza, " f"suna direct la suport{unde}." ) if reamintit: return ( f"Am trimis problema acum {minute_text(minute)} (referinta {ref}) si tocmai " f"am reamintit echipei. Inca nu am un raspuns de la ei." ) if a_cata > 0: return _ASTEPTARE[(a_cata - 1) % len(_ASTEPTARE)].format(ref=ref) return ( f"Am trimis problema acum {minute_text(minute)} (referinta {ref}) si inca nu am " f"un raspuns de la ei. Echipa e deja anuntata." )