feat(maria): profil de client (firma), asociat cu mai multe numere

Un client poate avea mai multi angajati care scriu pe WhatsApp de pe
numere diferite; toti gasesc acum acelasi profil (rag/client.py,
~/.maria-bridge/clients.json). Profilul intra in prompt sub o sectiune
DESPRE CLIENT, la fiecare mesaj, stateless ca restul Mariei — dar nu
atinge cautarea/scorul din rag/rank.py, ca sa nu strice pragurile deja
calibrate. Administrare din dashboard (sectiune noua "Clienti cunoscuti"),
nu din chat, ca sa ramana pe canalul autentificat.

117/117 teste maria-whatsapp-bridge + teste noi pentru rutele de dashboard.

Co-Authored-By: Claude Agent <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-09-08 10:35:30 +00:00
parent f25a8157fa
commit 610638ad30
7 changed files with 398 additions and 12 deletions

View File

@@ -70,6 +70,8 @@ MARIA_LOG_DIR = MARIA_STATE_DIR / "logs"
MARIA_VENV_PY = MARIA_STATE_DIR / "venv" / "bin" / "python"
MARIA_RAG_DIR = _BRIDGE.parent / "maria-whatsapp-bridge" / "rag"
_MARIA_SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]{1,200}$")
MARIA_CLIENTS_FILE = MARIA_STATE_DIR / "clients.json"
_MARIA_CLIENT_ID = re.compile(r"^[a-z0-9_-]{1,40}$")
def maria_env() -> dict:
@@ -239,6 +241,56 @@ def maria_delete_document(name: str) -> bool:
return False
def maria_clients() -> list[dict]:
"""Oglinda lui `maria-whatsapp-bridge/rag/client.py:toti` — tine-o la fel.
Nu importam modulul acela (colizeaza pe "config", ca la documente).
"""
try:
date = json.loads(MARIA_CLIENTS_FILE.read_text(encoding="utf-8"))
except (OSError, ValueError):
date = {}
return [{"id": cid, **c} for cid, c in sorted(date.items())]
def _maria_save_clients(date: dict) -> None:
MARIA_STATE_DIR.mkdir(parents=True, exist_ok=True)
tmp = MARIA_CLIENTS_FILE.with_suffix(".tmp")
tmp.write_text(json.dumps(date, ensure_ascii=False, indent=2), encoding="utf-8")
os.replace(tmp, MARIA_CLIENTS_FILE)
def maria_save_client(cid: str, nume: str, profil: str, numere: list[str]) -> None:
if not cid or not _MARIA_CLIENT_ID.match(cid):
raise ValueError(f"id de client invalid: {cid!r}")
normalizate = [n for n in (re.sub(r"\D", "", x) for x in numere) if n]
try:
date = json.loads(MARIA_CLIENTS_FILE.read_text(encoding="utf-8"))
except (OSError, ValueError):
date = {}
for alt_id, alt in date.items():
if alt_id == cid:
continue
furate = set(normalizate) & set(alt.get("numere", []))
if furate:
raise ValueError(
f"numarul {sorted(furate)[0]} e deja atribuit clientului {alt.get('nume', alt_id)!r}")
date[cid] = {"nume": nume, "profil": profil, "numere": normalizate}
_maria_save_clients(date)
def maria_delete_client(cid: str) -> bool:
try:
date = json.loads(MARIA_CLIENTS_FILE.read_text(encoding="utf-8"))
except (OSError, ValueError):
date = {}
if cid not in date:
return False
del date[cid]
_maria_save_clients(date)
return True
def _maria_run(script: str, timeout: float = 600.0, extra_args: list[str] | None = None) -> dict:
interpreter = str(MARIA_VENV_PY) if MARIA_VENV_PY.exists() else sys.executable
args = [interpreter, str(MARIA_RAG_DIR / script), *(extra_args or [])]
@@ -751,6 +803,8 @@ class Handler(SimpleHTTPRequestHandler):
return self.send_json({"documents": maria_documents()})
if path == "/api/maria/escalations":
return self.send_json({"escalations": maria_escalations()})
if path == "/api/maria/clients":
return self.send_json({"clients": maria_clients()})
if path == "/api/maria/logs":
try:
n = min(max(int(qs.get("lines", ["200"])[0]), 1), 2000)
@@ -791,6 +845,10 @@ class Handler(SimpleHTTPRequestHandler):
return self.handle_maria_document_write()
if path == "/api/maria/documents/delete":
return self.handle_maria_document_delete()
if path == "/api/maria/clients":
return self.handle_maria_client_write()
if path == "/api/maria/clients/delete":
return self.handle_maria_client_delete()
if path == "/api/maria/reindex":
return self.send_json(maria_reindex())
if path == "/api/maria/sync":
@@ -911,6 +969,25 @@ class Handler(SimpleHTTPRequestHandler):
return self.send_json({"ok": False, "error": str(exc)}, 400)
return self.send_json({"ok": ok, "name": name}, 200 if ok else 404)
def handle_maria_client_write(self):
data = self.read_json()
cid = str(data.get("id") or "")
numere = data.get("numere") or []
if isinstance(numere, str):
numere = numere.splitlines()
try:
maria_save_client(cid, str(data.get("nume") or ""), str(data.get("profil") or ""),
list(numere))
except (ValueError, OSError) as exc:
return self.send_json({"ok": False, "error": str(exc)}, 400)
return self.send_json({"ok": True, "id": cid})
def handle_maria_client_delete(self):
data = self.read_json()
cid = str(data.get("id") or "")
ok = maria_delete_client(cid)
return self.send_json({"ok": ok, "id": cid}, 200 if ok else 404)
def main() -> None:
bind = config.get("DASHBOARD_BIND") or "127.0.0.1"