feat(maria): asociere prin cod de telefon si citirea capturilor cu erori
Doua lucruri cerute de utilizator, ambele pe acelasi drum: sa poti lega
puntea fara sa ai un ecran de scanat, si sa poti trimite Mariei o poza cu
eroarea in loc sa transcrii mesajul.
Asociere prin cod de 8 caractere (alternativa la QR):
- endpoint-ul /pair exista, dar nu era folosibil: fara `browser` explicit
WhatsApp refuza codul, iar dupa introducerea lui corecta serverul cere un
restart (515) care consuma din bugetul de reincercari si putea opri puntea.
Acum descriptorul e Browsers.ubuntu('Chrome') si restartRequired reconecteaza
imediat, fara sa numere.
- codul are TTL de 3 minute, iar /status il da doar cat timp e valabil —
un cod expirat afisat in dashboard trimite omul sa tasteze degeaba.
- dashboard: camp pentru numar + buton, in acelasi card cu QR-ul.
Imagini cu erori (capturi de ecran):
- puntea descarca imaginile in ~/.maria-bridge/media/ (imageMessage sau
document cu mimetype image/*, si prin ambalajele efemer/"vezi o data" —
fara despachetare pareau mesaje fara continut si se aruncau tacut).
- rag/ocr.py: tesseract ron+eng. Modelul de raspuns e strict text, deci OCR
nu e o optiune de calitate, e singura cale.
- cautarea in index merge DOAR pe liniile care arata a eroare; o fereastra
intreaga de meniuri si totaluri dilueaza embedding-ul si scoate chunk-uri
fara legatura. Modelul primeste captura intreaga, marcata ca text OCR.
- capturile se sterg imediat dupa citire (pot contine date de client).
- cand nu se citeste nimic si nu exista legenda, Maria cere textul erorii
in loc sa raspunda in gol.
16 teste noi (42 in total). install.sh verifica tesseract; README documenteaza
ambele metode de asociere si drumul unei capturi.
Separat, in docs/chatboti-si-punti.md: chatul "Eu" e vazut de AMBELE punti de pe
numar. Puntea lui Echo (LXC 110) e asociata ca dispozitiv :11 al aceluiasi cont,
momentan nelegata dar pornita — daca se reasociaza, raspunde in "Eu" langa Maria.
Notat si ca serviciile lui Echo sunt unitati de UTILIZATOR: `systemctl is-active`
ca root raspunde "inactive" desi botul ruleaza.
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,90 @@
|
||||
"""`consumer.prepare_query`: ce ajunge la model cand mesajul e o captura de ecran.
|
||||
|
||||
Nu porneste nici puntea, nici LLM-ul — se testeaza doar decizia dintre „raspund
|
||||
pe baza imaginii", „raspund pe baza legendei" si „cer omului sa scrie textul".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import consumer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def imagine(tmp_path, monkeypatch):
|
||||
"""Un mesaj cu imagine, cu OCR-ul inlocuit de un text dat."""
|
||||
def _face(ocr_text: str, caption: str = "", *, esueaza: bool = False):
|
||||
fisier = tmp_path / "captura.png"
|
||||
fisier.write_bytes(b"nu conteaza continutul, ocr e inlocuit")
|
||||
|
||||
def fals_run(path, langs=None):
|
||||
if esueaza:
|
||||
raise RuntimeError("tesseract lipseste")
|
||||
return ocr_text
|
||||
|
||||
monkeypatch.setattr(consumer.ocr, "run", fals_run)
|
||||
return {"text": caption, "media": {"path": str(fisier), "mimetype": "image/png"}}, fisier
|
||||
return _face
|
||||
|
||||
|
||||
def test_mesaj_text_simplu_trece_neatins():
|
||||
intrebare, cautare, imediat = consumer.prepare_query({"text": "cum emit o factura?"})
|
||||
assert intrebare == "cum emit o factura?"
|
||||
assert cautare == "cum emit o factura?"
|
||||
assert imediat is None
|
||||
|
||||
|
||||
def test_captura_cu_eroare_cauta_doar_linia_de_eroare(imagine):
|
||||
msg, _ = imagine("Meniu Fisier Editare\nORA-01722: invalid number\nRand 4 din 9")
|
||||
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
||||
|
||||
assert imediat is None
|
||||
# cautarea in index merge pe eroare, nu pe toata fereastra
|
||||
assert cautare == "ORA-01722: invalid number"
|
||||
# modelului ii dam captura intreaga, marcata ca OCR
|
||||
assert "OCR" in intrebare
|
||||
assert "Meniu Fisier Editare" in intrebare
|
||||
|
||||
|
||||
def test_legenda_intra_si_in_intrebare_si_in_cautare(imagine):
|
||||
msg, _ = imagine("ORA-00942: table or view does not exist", caption="nu merge raportul")
|
||||
intrebare, cautare, _ = consumer.prepare_query(msg)
|
||||
assert intrebare.startswith("nu merge raportul")
|
||||
assert cautare.splitlines()[0] == "nu merge raportul"
|
||||
|
||||
|
||||
def test_captura_se_sterge_dupa_citire(imagine):
|
||||
msg, fisier = imagine("ORA-01722: invalid number")
|
||||
consumer.prepare_query(msg)
|
||||
# capturile pot contine date de client — nu raman pe disc dupa OCR
|
||||
assert not fisier.exists()
|
||||
|
||||
|
||||
def test_captura_ilizibila_cere_textul_erorii(imagine):
|
||||
msg, fisier = imagine("")
|
||||
intrebare, _, imediat = consumer.prepare_query(msg)
|
||||
assert intrebare is None
|
||||
assert imediat == consumer.NO_TEXT_IN_IMAGE
|
||||
assert not fisier.exists()
|
||||
|
||||
|
||||
def test_ocr_esuat_dar_cu_legenda_raspunde_pe_legenda(imagine):
|
||||
msg, _ = imagine("", caption="imi da eroare la salvare", esueaza=True)
|
||||
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
||||
assert imediat is None
|
||||
assert intrebare == cautare == "imi da eroare la salvare"
|
||||
|
||||
|
||||
def test_imagine_nedescarcata_fara_legenda_cere_reincercare():
|
||||
msg = {"text": "", "media": {"error": "imagine prea mare (12.0 MB > 8 MB)"}}
|
||||
intrebare, _, imediat = consumer.prepare_query(msg)
|
||||
assert intrebare is None
|
||||
assert "scrie-mi" in imediat.lower()
|
||||
|
||||
|
||||
def test_imagine_nedescarcata_cu_legenda_raspunde_pe_legenda():
|
||||
msg = {"text": "eroare la export SAF-T", "media": {"error": "download failed"}}
|
||||
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
||||
assert imediat is None
|
||||
assert intrebare == cautare == "eroare la export SAF-T"
|
||||
Reference in New Issue
Block a user