notebookvb

This commit is contained in:
Vladimir Buzalka
2026-06-14 12:07:35 +02:00
parent 9133fe9497
commit 2bdac59676
16 changed files with 1484 additions and 29 deletions
+185
View File
@@ -0,0 +1,185 @@
#!/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Č (910 čí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()