186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
recept_resolver.py — vyřizuje "čekající" žádosti o recept přes Telegram.
|
||
|
||
Jediný proces, který mluví s Telegramem (kvůli getUpdates). Ve smyčce:
|
||
1) pošle otázky pro nové pending záznamy (které ještě otázku nemají),
|
||
2) long-polluje odpovědi; odpověď (reply na otázku) → podle obsahu:
|
||
• RČ (9–10 číslic) → definitivní volba pacienta → založí požadavek
|
||
• číslo kandidáta → vybere kandidáta → založí požadavek
|
||
• „ne“ → přeskočí (nezakládá)
|
||
Po založení označí původní e-mail kategorií ClaudeZpracovalRecept.
|
||
|
||
Telegram přenos je v recept_telegram.py (token/chat dodá uživatel).
|
||
Bez odpovědi záznam zůstává 'ceka' (čeká se libovolně dlouho).
|
||
|
||
Spuštění: python recept_resolver.py
|
||
"""
|
||
import re
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
try:
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
except Exception:
|
||
pass
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
sys.path.insert(0, str(HERE.parent / "EmailAgent"))
|
||
sys.path.insert(0, str(HERE.parent))
|
||
|
||
import recept_pending as PEND # noqa: E402
|
||
import recept_dialog as DLG # noqa: E402
|
||
import recept_telegram as TG # noqa: E402
|
||
import graph_mail # noqa: E402 (značení mailu)
|
||
import mcp_medevio as MED # noqa: E402 (zaloz_pozadavek_recept)
|
||
from Knihovny.mysql_db import connect_mysql # noqa: E402
|
||
|
||
MAILBOX = "ordinace@buzalkova.cz"
|
||
PROCESSED_CATEGORY = "ClaudeZpracovalRecept"
|
||
SINCE_FILE = HERE / "_resolver_since.txt" # poslední zpracované message_id
|
||
|
||
|
||
def log(msg: str) -> None:
|
||
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
|
||
|
||
|
||
def _norm_rc(s: str) -> str:
|
||
return re.sub(r"\D", "", s or "")
|
||
|
||
|
||
def najdi_pacienta_dle_rc(rc: str):
|
||
"""RČ → (uuid, jmeno, prijmeni) z medevio_pacient, nebo None."""
|
||
rc = _norm_rc(rc)
|
||
if not rc:
|
||
return None
|
||
try:
|
||
conn = connect_mysql()
|
||
conn.ping(reconnect=True)
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
"SELECT patient_id, name, surname FROM medevio_pacient "
|
||
"WHERE REPLACE(identification_number,'/','') = %s LIMIT 1",
|
||
[rc],
|
||
)
|
||
row = cur.fetchone()
|
||
return row if row else None
|
||
except Exception as e:
|
||
log(f"[uuid lookup chyba] {type(e).__name__}: {e}")
|
||
return None
|
||
|
||
|
||
def _load_since():
|
||
try:
|
||
return int(SINCE_FILE.read_text(encoding="utf-8").strip())
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def _save_since(s: int) -> None:
|
||
SINCE_FILE.write_text(str(s), encoding="utf-8")
|
||
|
||
|
||
def posli_nove_otazky() -> None:
|
||
"""Pošle otázku do Telegramu pro každý pending záznam bez otázky."""
|
||
for rec in PEND.cekajici_bez_otazky():
|
||
try:
|
||
mid = TG.posli_otazku(DLG.format_otazka(rec))
|
||
PEND.aktualizuj(rec["id"], otazka_message_id=mid)
|
||
log(f"otázka odeslána (pending {rec['id'][:8]}, msg {mid})")
|
||
except Exception as e:
|
||
log(f"otázku nelze odeslat ({type(e).__name__}: {e}) — zkusím příště")
|
||
break # nejspíš výpadek / nenakonfigurováno → nech na příští kolo
|
||
|
||
|
||
def _zaloz_pro_rc(rec: dict, rc: str) -> None:
|
||
info = najdi_pacienta_dle_rc(rc)
|
||
if not info:
|
||
TG.posli_zpravu(f"⚠ RČ {rc} není v Medeviu — nezakládám. Zkus jiné RČ.")
|
||
return
|
||
uuid_, jmeno, prijmeni = info[0], info[1], info[2]
|
||
try:
|
||
res = MED.zaloz_pozadavek_recept(
|
||
uuid_, rec.get("leky_str", ""), rec.get("pozn_str", "")
|
||
)
|
||
except Exception as e:
|
||
TG.posli_zpravu(f"❌ Chyba při zakládání: {type(e).__name__}: {e}")
|
||
return
|
||
PEND.aktualizuj(rec["id"], stav="zalozeno",
|
||
vysledek={"request_id": res["request_id"], "rc": rc,
|
||
"pacient": f"{prijmeni} {jmeno}"})
|
||
try:
|
||
graph_mail.add_category(MAILBOX, rec["email_message_id"], PROCESSED_CATEGORY)
|
||
except Exception as e:
|
||
log(f"[mail označení] {type(e).__name__}: {e}")
|
||
TG.posli_zpravu(f"✅ Založeno: {prijmeni} {jmeno} (RČ {rc}) — "
|
||
f"{rec.get('leky_str', '')}")
|
||
log(f"založeno {res['request_id']} pro {prijmeni} {jmeno}")
|
||
|
||
|
||
def zpracuj_odpoved(u: dict) -> None:
|
||
"""Zpracuje jednu příchozí Telegram zprávu."""
|
||
rid = u.get("reply_to_message_id")
|
||
rec = PEND.najdi_dle_otazky(rid) if rid else None
|
||
|
||
if rec is None:
|
||
cekaji = PEND.cekajici()
|
||
if len(cekaji) == 1:
|
||
rec = cekaji[0] # jediný čekající → ber to na něj
|
||
else:
|
||
if cekaji:
|
||
TG.posli_zpravu("Odpověz prosím jako reply na konkrétní dotaz "
|
||
"(čeká jich víc).")
|
||
return
|
||
|
||
if rec.get("stav") != "ceka":
|
||
return
|
||
|
||
d = DLG.parse_odpoved(u.get("text", ""))
|
||
if d["akce"] == "preskoc":
|
||
PEND.aktualizuj(rec["id"], stav="preskoceno")
|
||
TG.posli_zpravu("OK, nezakládám.")
|
||
elif d["akce"] == "rc":
|
||
_zaloz_pro_rc(rec, d["rc"])
|
||
elif d["akce"] == "kandidat":
|
||
kand = rec.get("kandidati") or []
|
||
i = d["index"] - 1
|
||
if 0 <= i < len(kand):
|
||
_zaloz_pro_rc(rec, kand[i].get("rc", ""))
|
||
else:
|
||
TG.posli_zpravu(f"Kandidát {d['index']} neexistuje (mám {len(kand)}).")
|
||
else:
|
||
TG.posli_zpravu("Nerozumím. Pošli RČ pacienta, číslo kandidáta, nebo „ne“.")
|
||
|
||
|
||
def smycka(poll_s: int = 5) -> None:
|
||
try:
|
||
TG.priprav() # naprimuj entitu Vlada (jinak fresh session spadne)
|
||
except Exception as e:
|
||
log(f"[priprav] {type(e).__name__}: {e}")
|
||
since = _load_since()
|
||
if since is None:
|
||
# první start — vezmi aktuální stav jako základ, starou historii ignoruj
|
||
since = TG.baseline_since()
|
||
_save_since(since)
|
||
log(f"baseline since_id={since}")
|
||
log("Resolver běží (Ctrl+C ukončí).")
|
||
while True:
|
||
try:
|
||
posli_nove_otazky()
|
||
nove, since = TG.nacti_odpovedi(since)
|
||
for u in nove:
|
||
zpracuj_odpoved(u)
|
||
_save_since(since)
|
||
except KeyboardInterrupt:
|
||
log("Konec.")
|
||
break
|
||
except Exception as e:
|
||
log(f"[smyčka] {type(e).__name__}: {e}")
|
||
time.sleep(poll_s)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
smycka()
|