feat(maria): fir de discutie — continuarea nu mai e tratata ca eroare noua
Fiecare mesaj pornea de la zero. Masurat pe indexul viu, dupa o escaladare pentru
ORA-06550: "da, ma blocheaza complet" primea raspuns despre ordinul de plata la
Trezorerie (cosinus 0,642, "acoperit"), "eram la salvarea unei facturi" despre
corectia unei eFacturi (0,742), iar "am incercat si tot nu merge" cerea din nou
detaliile tocmai date. Patru cuvinte fara context seamana cu ceva din documente,
iar cautarea nu avea de unde sti ca sunt raspunsul la intrebarea Mariei.
rag/fir.py tine ancora (textul erorii), codurile ei, referinta escaladarii si
ultimele 6 schimburi, in ~/.maria-bridge/conversations/ (expira, ca si capturile).
La o continuare: cautarea e pe ancora + mesajul nou, modelul primeste istoricul,
mesajul se adauga la escaladarea deschisa de cate ori e nevoie, iar "mesaj prea
vag" nu se mai aplica. Escaladarea duce firul intreg la suport, nu un mesaj rupt
din context.
Firul se rupe doar la o captura noua sau un cod de eroare diferit — schimbarea
subiectului in cuvinte e prea usor de confundat cu o continuare.
In plus:
- Maria tace 60 min cand preia un om, dar automat doar in grupuri cu >= 2
participanti: in self-chat totul e `fromMe`. Comenzi explicite oriunde
("Maria, stop" / "Maria, continua").
- ALLOWED_GROUP_JIDS: testarea se muta in grupul "Maria Test"
(120363409761730101@g.us), ca sa nu mai poluam chatul "Eu". Filtru si in punte,
si in consumer. NU echo-test: puntea lui Echo nu filtreaza fromMe in grupuri,
deci cei doi boti ar intra in bucla.
Teste: 94 pass. Calibrare 23/23, cu doua cazuri de continuare.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Firul de discutie: continuare vs subiect nou, ancora, tacerea la preluare."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
import fir
|
||||
|
||||
|
||||
JID = "40712345678@s.whatsapp.net"
|
||||
EROARE = ("[Oracle][ODBC][Ora]ORA-06550: line 1, column 7\n"
|
||||
"PLS-00906: object VADECO PACK PARTENERI is invalid")
|
||||
|
||||
|
||||
def test_firul_retine_codurile_ancorei():
|
||||
f = fir.deschide(JID, EROARE)
|
||||
assert "ora-06550" in f["coduri"] and "pls-00906" in f["coduri"]
|
||||
|
||||
|
||||
def test_un_raspuns_scurt_e_continuare_nu_eroare_noua():
|
||||
"""Cazul masurat: „da, ma blocheaza" cautat singur nimerea ordinul de plata."""
|
||||
f = fir.deschide(JID, EROARE)
|
||||
assert fir.este_continuare(f, "da, ma blocheaza complet", False)
|
||||
assert fir.este_continuare(f, "eram la salvarea unei facturi", False)
|
||||
# ancora intra in cautare, ca sa nu se caute patru cuvinte fara context
|
||||
q = fir.interogare(f, "da, ma blocheaza complet")
|
||||
assert "ORA-06550" in q and "ma blocheaza" in q
|
||||
|
||||
|
||||
def test_captura_noua_sau_alt_cod_rup_firul():
|
||||
f = fir.deschide(JID, EROARE)
|
||||
assert not fir.este_continuare(f, "imi da acum ORA-12154", False)
|
||||
assert not fir.este_continuare(f, "si asta ce e?", True) # captura noua
|
||||
# acelasi cod nu rupe firul
|
||||
assert fir.este_continuare(f, "tot ORA-06550 imi da", False)
|
||||
|
||||
|
||||
def test_fara_fir_nu_exista_continuare():
|
||||
assert not fir.este_continuare(None, "da", False)
|
||||
|
||||
|
||||
def test_firul_expira(monkeypatch):
|
||||
f = fir.deschide(JID, EROARE)
|
||||
fir.salveaza(f)
|
||||
assert fir.incarca(JID) is not None
|
||||
|
||||
tarziu = time.time() + fir.ttl_s() + 1
|
||||
monkeypatch.setattr(fir.time, "time", lambda: tarziu)
|
||||
assert fir.incarca(JID) is None
|
||||
|
||||
|
||||
def test_istoricul_se_opreste_la_max_schimburi():
|
||||
f = fir.deschide(JID, EROARE)
|
||||
for i in range(fir.MAX_SCHIMBURI + 4):
|
||||
fir.adauga(f, "client", f"mesaj {i}")
|
||||
assert len(f["schimburi"]) == fir.MAX_SCHIMBURI
|
||||
assert f["schimburi"][-1]["text"] == f"mesaj {fir.MAX_SCHIMBURI + 3}"
|
||||
assert fir.istoric(f)[-1]["role"] == "user"
|
||||
|
||||
|
||||
def test_comenzile_de_oprire_si_revenire():
|
||||
assert fir.comanda("Maria, stop") == "stop"
|
||||
assert fir.comanda("preiau eu") == "stop"
|
||||
assert fir.comanda("Maria, continua") == "start"
|
||||
assert fir.comanda("cum trimit D406") is None
|
||||
|
||||
|
||||
def test_tacerea_expira(monkeypatch):
|
||||
f = fir.deschide(JID, EROARE)
|
||||
fir.taci(f)
|
||||
assert fir.tace(f)
|
||||
tarziu = time.time() + fir.tacere_s() + 1
|
||||
monkeypatch.setattr(fir.time, "time", lambda: tarziu)
|
||||
assert not fir.tace(f)
|
||||
|
||||
|
||||
def test_preluarea_doar_in_grupuri_cu_mai_multi_oameni(monkeypatch):
|
||||
"""In self-chat si in grupul de test tot ce se scrie e fromMe."""
|
||||
import consumer
|
||||
monkeypatch.setattr(consumer, "grup_info",
|
||||
lambda jid: {"participants": 1 if "test" in jid else 5})
|
||||
|
||||
assert not consumer.preluare_de_om({"fromMe": True, "isGroup": False, "from": JID})
|
||||
assert not consumer.preluare_de_om(
|
||||
{"fromMe": True, "isGroup": True, "from": "test@g.us", "text": "salut"})
|
||||
assert consumer.preluare_de_om(
|
||||
{"fromMe": True, "isGroup": True, "from": "client@g.us", "text": "preiau eu cazul"})
|
||||
# propriul raspuns al Mariei nu e „un om care a preluat"
|
||||
assert not consumer.preluare_de_om(
|
||||
{"fromMe": True, "isGroup": True, "from": "client@g.us",
|
||||
"text": consumer.REPLY_PREFIX + "buna"})
|
||||
@@ -74,22 +74,25 @@ def test_raspunsul_despre_urgenta_intra_in_escaladarea_deschisa(monkeypatch):
|
||||
monkeypatch.setattr(config, "get", lambda k, d=None: "" if k == "SUPPORT_JID"
|
||||
else config.DEFAULTS.get(k, d))
|
||||
_escaladare("M-TEST-1")
|
||||
consumer.pending_set("40712@s.whatsapp.net", "M-TEST-1")
|
||||
assert consumer.pending_get("40712@s.whatsapp.net")["ref"] == "M-TEST-1"
|
||||
|
||||
assert consumer.append_followup("M-TEST-1", {"id": "x"}, "ma blocheaza, nu pot factura")
|
||||
|
||||
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"] == "ma blocheaza, nu pot factura"
|
||||
|
||||
consumer.pending_clear("40712@s.whatsapp.net")
|
||||
assert consumer.pending_get("40712@s.whatsapp.net") is None
|
||||
|
||||
|
||||
def test_completarea_expira(monkeypatch):
|
||||
def test_completarile_se_aduna_nu_se_inlocuiesc(monkeypatch):
|
||||
"""Omul raspunde de mai multe ori; prima data nu inchide firul."""
|
||||
import config
|
||||
import consumer
|
||||
consumer.pending_set("40799@s.whatsapp.net", "M-TEST-2")
|
||||
peste_o_ora = time.time() + consumer.PENDING_TTL_S + 1
|
||||
monkeypatch.setattr(consumer.time, "time", lambda: peste_o_ora)
|
||||
assert consumer.pending_get("40799@s.whatsapp.net") is None
|
||||
|
||||
monkeypatch.setattr(config, "get", lambda k, d=None: "" if k == "SUPPORT_JID"
|
||||
else config.DEFAULTS.get(k, d))
|
||||
_escaladare("M-TEST-3")
|
||||
consumer.append_followup("M-TEST-3", {"id": "a"}, "da, ma blocheaza")
|
||||
consumer.append_followup("M-TEST-3", {"id": "b"}, "eram la salvarea unei facturi")
|
||||
|
||||
fisier = next((config.STATE_DIR / "escalations").glob("*-M-TEST-3.json"))
|
||||
record = json.loads(fisier.read_text(encoding="utf-8"))
|
||||
assert [c["text"] for c in record["completari"]] == [
|
||||
"da, ma blocheaza", "eram la salvarea unei facturi"]
|
||||
|
||||
Reference in New Issue
Block a user