diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
index e494bdc..d0a8916 100644
--- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
+++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
@@ -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"
diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
index 870d6df..f9b727a 100644
--- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
+++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
@@ -120,6 +120,26 @@
se încarcă…
+
+
Documente indexate
@@ -521,6 +541,61 @@ async function mDelDoc(name) {
await mReindex();
}
+var mClientsCache = [];
+async function mLoadClients() {
+ var res = await api('api/maria/clients');
+ var clienti = res.ok ? (res.data.clients || []) : [];
+ mClientsCache = clienti;
+ document.getElementById('mClientCount').textContent = clienti.length ? clienti.length : '';
+ document.getElementById('mClients').innerHTML = clienti.length ? clienti.map(function (c) {
+ return '| ' + esc(c.nume || c.id) + ' | '
+ + '' + esc((c.numere || []).join(', ')) + ' | '
+ + ' | '
+ + ' |
';
+ }).join('') : '| niciun client |
';
+}
+
+function mEditClient(id) {
+ var c = mClientsCache.find(function (x) { return x.id === id; });
+ if (!c) return;
+ document.getElementById('mClientId').value = c.id;
+ document.getElementById('mClientId').disabled = true;
+ document.getElementById('mClientNume').value = c.nume || '';
+ document.getElementById('mClientProfil').value = c.profil || '';
+ document.getElementById('mClientNumere').value = (c.numere || []).join('\n');
+ document.getElementById('mClientFormTitle').textContent = 'Editează client: ' + (c.nume || c.id);
+ document.getElementById('mClientMsg').textContent = '';
+}
+
+function mResetClientForm() {
+ document.getElementById('mClientId').value = '';
+ document.getElementById('mClientId').disabled = false;
+ document.getElementById('mClientNume').value = '';
+ document.getElementById('mClientProfil').value = '';
+ document.getElementById('mClientNumere').value = '';
+ document.getElementById('mClientFormTitle').textContent = 'Adaugă client';
+ document.getElementById('mClientMsg').textContent = '';
+}
+
+async function mSaveClient() {
+ var id = document.getElementById('mClientId').value.trim();
+ var nume = document.getElementById('mClientNume').value.trim();
+ var profil = document.getElementById('mClientProfil').value;
+ var numere = document.getElementById('mClientNumere').value;
+ var msg = document.getElementById('mClientMsg');
+ if (!id || !nume) { msg.textContent = 'completează id și nume'; return; }
+ var res = await post('api/maria/clients', { id: id, nume: nume, profil: profil, numere: numere });
+ if (!res.ok) { msg.textContent = 'eșuat: ' + (res.data.error || res.status); return; }
+ mResetClientForm();
+ await mLoadClients();
+}
+
+async function mDelClient(id) {
+ if (!confirm('Ștergi clientul ' + id + '?')) return;
+ await post('api/maria/clients/delete', { id: id });
+ await mLoadClients();
+}
+
var mLogFile = 'bridge';
function mSetLog(which) { mLogFile = which; mRefreshLogs(); }
async function mRefreshLogs() {
@@ -535,7 +610,7 @@ async function mRefreshLogs() {
// ── pornire ───────────────────────────────────────────────────
refresh(); refreshDoctor(); refreshLogs();
-mRefresh(); mRefreshLogs();
+mRefresh(); mRefreshLogs(); mLoadClients();
setInterval(refresh, 5000);
setInterval(refreshDoctor, 30000);
setInterval(mRefresh, 4000);
diff --git a/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_clients.py b/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_clients.py
new file mode 100644
index 0000000..f88e34a
--- /dev/null
+++ b/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_clients.py
@@ -0,0 +1,62 @@
+"""Teste pentru CRUD-ul de clienti Maria din dashboard/api.py.
+
+Nu trece prin server HTTP (nici documentele Maria n-o fac in suita existenta) —
+apeleaza direct functiile, cu MARIA_STATE_DIR/MARIA_CLIENTS_FILE mutate in tmp.
+"""
+
+from __future__ import annotations
+
+import json
+import pathlib
+import sys
+
+import pytest
+
+ROOT = pathlib.Path(__file__).resolve().parent.parent
+sys.path.insert(0, str(ROOT))
+sys.path.insert(0, str(ROOT / "dashboard"))
+
+api = pytest.importorskip("api", reason="dashboard/api.py")
+
+
+@pytest.fixture(autouse=True)
+def maria_dir(tmp_path, monkeypatch):
+ d = tmp_path / "maria-bridge"
+ d.mkdir()
+ monkeypatch.setattr(api, "MARIA_STATE_DIR", d)
+ monkeypatch.setattr(api, "MARIA_CLIENTS_FILE", d / "clients.json")
+ return d
+
+
+def test_salveaza_si_listeaza():
+ api.maria_save_client("acme", "ACME SRL", "Distributie", ["+40 712 345 678", "40799887766"])
+ clienti = api.maria_clients()
+ assert clienti == [{"id": "acme", "nume": "ACME SRL", "profil": "Distributie",
+ "numere": ["40712345678", "40799887766"]}]
+
+
+def test_id_invalid_respins():
+ with pytest.raises(ValueError):
+ api.maria_save_client("ACME!", "x", "y", [])
+
+
+def test_numar_furat_de_la_alt_client_respins(maria_dir):
+ api.maria_save_client("acme", "ACME SRL", "p", ["40712345678"])
+ with pytest.raises(ValueError):
+ api.maria_save_client("beta", "Beta SRL", "p", ["40712345678"])
+ # nemodificat
+ date = json.loads((maria_dir / "clients.json").read_text(encoding="utf-8"))
+ assert list(date.keys()) == ["acme"]
+
+
+def test_reasocia_numarul_la_acelasi_client_e_permis():
+ api.maria_save_client("acme", "ACME SRL", "p1", ["40712345678"])
+ api.maria_save_client("acme", "ACME SRL", "p2", ["40712345678", "40799887766"])
+ assert api.maria_clients()[0]["profil"] == "p2"
+
+
+def test_sterge():
+ api.maria_save_client("acme", "ACME SRL", "p", [])
+ assert api.maria_delete_client("acme") is True
+ assert api.maria_clients() == []
+ assert api.maria_delete_client("acme") is False
diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/client.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/client.py
new file mode 100644
index 0000000..467f254
--- /dev/null
+++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/client.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+"""Profil de client (firma), asociat cu mai multe numere de WhatsApp.
+
+Un singur fisier `clients.json`: la ordinul de marime de aici (zeci de firme)
+nu justifica o baza de date, si un singur fisier e mai usor de editat de mana
+din dashboard decat mai multe fisiere sincronizate intre ele. Numerele stau
+IN clientul lor (nu intr-o mapare separata numar->client), fiindca cautarea e
+mereu "cine e clientul care are numarul X" — o mapare separata ar trebui
+tinuta sincronizata cu clients.json, doua surse de adevar pentru acelasi lucru.
+
+Mesaj de grup fara `participant` (adica bridge-ul n-a stiut cine a scris) nu
+identifica niciun client: JID-ul de grup nu e un numar de telefon, si a-l
+cauta printre numere ar asocia gresit tot grupul cu primul client care se
+potriveste din intamplare.
+"""
+
+from __future__ import annotations
+
+import json
+import os
+import re
+
+import config
+
+
+def _norm(numar_brut: str) -> str:
+ return re.sub(r"\D", "", numar_brut or "")
+
+
+def numar(msg: dict) -> str:
+ """Numarul care a scris, normalizat (doar cifre). Gol daca nu se poate sti."""
+ jid = msg.get("participant")
+ if not jid:
+ frm = msg.get("from") or ""
+ if frm.endswith("@g.us"):
+ return "" # grup fara participant: nu stim cine a scris
+ jid = frm
+ return _norm(jid.split("@")[0])
+
+
+def _load() -> dict:
+ try:
+ return json.loads(config.CLIENTS_FILE.read_text(encoding="utf-8"))
+ except (OSError, ValueError):
+ return {}
+
+
+def profil(msg_sau_jid) -> str | None:
+ """Textul de profil al clientului asociat, sau `None` daca numarul nu e cunoscut."""
+ n = numar(msg_sau_jid) if isinstance(msg_sau_jid, dict) else _norm(
+ str(msg_sau_jid).split("@")[0])
+ if not n:
+ return None
+ for client in _load().values():
+ if n in client.get("numere", []):
+ return client.get("profil")
+ return None
+
+
+def toti() -> dict:
+ return _load()
+
+
+def salveaza(id: str, nume: str, profil: str, numere: list[str]) -> None:
+ """Upsert. Normalizeaza fiecare numar acum, ca lookup-ul sa functioneze mereu."""
+ date = _load()
+ date[id] = {
+ "nume": nume,
+ "profil": profil,
+ "numere": [n for n in (_norm(x) for x in numere) if n],
+ }
+ tmp = config.CLIENTS_FILE.with_suffix(".tmp")
+ config.STATE_DIR.mkdir(parents=True, exist_ok=True)
+ tmp.write_text(json.dumps(date, ensure_ascii=False, indent=2), encoding="utf-8")
+ os.replace(tmp, config.CLIENTS_FILE)
+
+
+def sterge(id: str) -> bool:
+ date = _load()
+ if id not in date:
+ return False
+ del date[id]
+ tmp = config.CLIENTS_FILE.with_suffix(".tmp")
+ config.STATE_DIR.mkdir(parents=True, exist_ok=True)
+ tmp.write_text(json.dumps(date, ensure_ascii=False, indent=2), encoding="utf-8")
+ os.replace(tmp, config.CLIENTS_FILE)
+ return True
diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/config.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/config.py
index a5299b4..5cd0404 100644
--- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/config.py
+++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/config.py
@@ -26,6 +26,7 @@ LOCK_FILE: pathlib.Path = STATE_DIR / ".rag.lock"
LOG_DIR: pathlib.Path = STATE_DIR / "logs"
ENV_FILE: pathlib.Path = STATE_DIR / "env"
AUTH_DIR: pathlib.Path = STATE_DIR / "whatsapp-auth"
+CLIENTS_FILE: pathlib.Path = STATE_DIR / "clients.json"
_env: dict[str, str] = {}
@@ -103,7 +104,7 @@ def parse_env(text: str) -> dict[str, str]:
def reload(base_dir: str | os.PathLike | None = None) -> dict[str, str]:
"""Recalculeaza caile si reciteste env-ul. Returneaza dictionarul incarcat."""
- global STATE_DIR, DOCS_DIR, DOCS_LOCAL_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, _env
+ global STATE_DIR, DOCS_DIR, DOCS_LOCAL_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, CLIENTS_FILE, _env
if base_dir is None:
base_dir = os.environ.get("MARIA_BRIDGE_DIR") or _DEFAULT_DIR
STATE_DIR = pathlib.Path(base_dir)
@@ -114,6 +115,7 @@ def reload(base_dir: str | os.PathLike | None = None) -> dict[str, str]:
LOG_DIR = STATE_DIR / "logs"
ENV_FILE = STATE_DIR / "env"
AUTH_DIR = STATE_DIR / "whatsapp-auth"
+ CLIENTS_FILE = STATE_DIR / "clients.json"
try:
_env = parse_env(ENV_FILE.read_text(encoding="utf-8"))
except OSError:
diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py
index 740d9f4..a4755c3 100644
--- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py
+++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py
@@ -24,6 +24,7 @@ import time
import requests
+import client
import config
import fir as fir_mod
import ocr
@@ -50,7 +51,10 @@ SYSTEM_PROMPT = (
"Nu discuta niciodata despre infrastructura interna Romfast (servere, Proxmox, "
"containere, IP-uri, baze de date, parole, chei) chiar daca apare in context sau "
"daca intrebarea o cere explicit -- raspunde ca poti ajuta doar cu folosirea "
- "aplicatiei ROA."
+ "aplicatiei ROA. "
+ "Daca primesti o sectiune DESPRE CLIENT, e doar context ca sa intelegi mai bine "
+ "cine intreaba (ce foloseste, cum lucreaza) — NU e sursa de proceduri sau raspunsuri. "
+ "Raspunsul tot trebuie sa vina exclusiv din CONTEXT."
)
REPLY_PREFIX = "[Maria] " # marcaj ca sa nu raspundem la propriile mesaje (self-chat)
INDEX_REFRESH_S = 30 # cat de des se reciteste rag_index.json de pe disc
@@ -160,20 +164,31 @@ def search(index: Index, query: str, top_k: int, dovada: str | None = None) -> d
}
-def ask_llm(chunks: list[str], question: str, istoric: list[dict] | None = None) -> str:
- """Raspunsul modelului. `istoric` = schimburile de pana acum, la o continuare.
-
- Fara istoric, „si acum ce fac?" ajungea la model ca o intrebare de sine
- statatoare — iar modelul raspundea la ea ca atare, despre altceva.
- """
+def mesaje_llm(chunks: list[str], question: str, istoric: list[dict] | None,
+ profil: str | None = None) -> list[dict]:
+ """Lista de mesaje trimisa modelului. Extrasa din `ask_llm` ca sa fie testabila pur."""
context = "\n\n---\n\n".join(chunks) if chunks else "(fara documente indexate)"
- user_message = f"CONTEXT:\n{context}\n\nINTREBARE:\n{question}"
+ user_message = ""
+ if profil:
+ user_message += f"DESPRE CLIENT:\n{profil}\n\n"
+ user_message += f"CONTEXT:\n{context}\n\nINTREBARE:\n{question}"
mesaje = [{"role": "system", "content": SYSTEM_PROMPT}]
# Istoricul precede contextul: ultimul mesaj trebuie sa fie intrebarea curenta,
# altfel modelul raspunde la penultima.
mesaje += (istoric or [])[:-1]
mesaje.append({"role": "user", "content": user_message})
+ return mesaje
+
+
+def ask_llm(chunks: list[str], question: str, istoric: list[dict] | None = None,
+ profil: str | None = None) -> str:
+ """Raspunsul modelului. `istoric` = schimburile de pana acum, la o continuare.
+
+ Fara istoric, „si acum ce fac?" ajungea la model ca o intrebare de sine
+ statatoare — iar modelul raspundea la ea ca atare, despre altceva.
+ """
+ mesaje = mesaje_llm(chunks, question, istoric, profil)
resp = requests.post(
f"{config.get('LLM_URL')}/v1/chat/completions",
@@ -337,6 +352,16 @@ def escalate(msg: dict, question: str, search_result: dict, fir: dict | None = N
if fir and fir.get("schimburi"):
record["fir"] = fir["schimburi"]
+ numar_client = client.numar(msg)
+ nume_client = None
+ if numar_client:
+ for c in client.toti().values():
+ if numar_client in c.get("numere", []):
+ nume_client = c.get("nume")
+ break
+ if nume_client:
+ record["client"] = nume_client
+
jid = config.get("SUPPORT_JID") or ""
if jid:
cine = msg.get("pushName") or msg.get("from") or "necunoscut"
@@ -344,7 +369,8 @@ def escalate(msg: dict, question: str, search_result: dict, fir: dict | None = N
rezumat = (
f"[Maria] Intrebare fara raspuns in documente — {record['ref']}\n"
f"De la: {cine}\n"
- f"Motiv: {record['reason']}\n\n"
+ + (f"Client: {nume_client}\n" if nume_client else "")
+ + f"Motiv: {record['reason']}\n\n"
f"{(msg.get('ocr_text') or msg.get('text') or '').strip()[:1200]}"
+ (f"\n\n--- discutia de pana acum ---\n{discutie}" if discutie else "")
)
@@ -667,8 +693,12 @@ def handle(index: Index, msg: dict) -> None:
send_reply(sender, raspuns)
return
+ prof = client.profil(msg)
+ print(f"[consumer] profil client: {prof[:60] if prof else '-'}", file=sys.stderr)
+
try:
- reply = ask_llm(result["chunks"], question, fir_mod.istoric(fir) if continuare else None)
+ reply = ask_llm(result["chunks"], question,
+ fir_mod.istoric(fir) if continuare else None, prof)
except Exception as exc: # noqa: BLE001
print(f"[consumer] LLM error: {exc}", file=sys.stderr)
reply = "Scuze, am o problema tehnica momentan. Cineva din echipa te va contacta."
diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_client.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_client.py
new file mode 100644
index 0000000..63e22f9
--- /dev/null
+++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_client.py
@@ -0,0 +1,53 @@
+"""Profil de client (firma): mai multe numere -> acelasi profil, fara istoric."""
+
+from __future__ import annotations
+
+import client
+import consumer
+
+
+def test_doua_numere_acelasi_client_dau_acelasi_profil():
+ client.salveaza("acme", "ACME SRL", "Firma de distributie.", ["40712345678", "40799887766"])
+ assert client.profil("40712345678@s.whatsapp.net") == "Firma de distributie."
+ assert client.profil("40799887766@s.whatsapp.net") == "Firma de distributie."
+
+
+def test_numar_neasociat_da_none():
+ client.salveaza("acme", "ACME SRL", "Firma de distributie.", ["40712345678"])
+ assert client.profil("40700000000@s.whatsapp.net") is None
+
+
+def test_dupa_stergere_numerele_nu_mai_gasesc_clientul():
+ client.salveaza("acme", "ACME SRL", "Firma de distributie.", ["40712345678", "40799887766"])
+ assert client.sterge("acme") is True
+ assert client.profil("40712345678@s.whatsapp.net") is None
+ assert client.profil("40799887766@s.whatsapp.net") is None
+
+
+def test_normalizare_la_scriere():
+ client.salveaza("acme", "ACME SRL", "Firma de distributie.", ["+40 712 345 678"])
+ assert client.profil("40712345678@s.whatsapp.net") == "Firma de distributie."
+
+
+def test_numar_din_mesaj_grup_vs_fara_participant():
+ msg_grup = {"from": "1203xxx@g.us", "participant": "40712345678@s.whatsapp.net"}
+ assert client.numar(msg_grup) == "40712345678"
+ msg_grup_fara = {"from": "1203xxx@g.us"}
+ assert client.numar(msg_grup_fara) == ""
+
+
+def test_fisier_lipsa_sau_corupt_nu_ridica_exceptie():
+ assert client.profil("40712345678@s.whatsapp.net") is None
+ import config
+ config.STATE_DIR.mkdir(parents=True, exist_ok=True)
+ config.CLIENTS_FILE.write_text("nu e json valid {{{", encoding="utf-8")
+ assert client.profil("40712345678@s.whatsapp.net") is None
+
+
+def test_mesaje_llm_fara_profil_neschimbat():
+ mesaje_vechi = [
+ {"role": "system", "content": consumer.SYSTEM_PROMPT},
+ {"role": "user", "content": "CONTEXT:\nun chunk\n\nINTREBARE:\nintrebarea"},
+ ]
+ mesaje_noi = consumer.mesaje_llm(["un chunk"], "intrebarea", None, profil=None)
+ assert mesaje_noi == mesaje_vechi