Files
ordinaceprojekt/mcp_insurance.py
T
Vladimir Buzalka 9b6f89f437 notebookvb
2026-06-16 10:21:19 +02:00

162 lines
5.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MCP server pro pojišťovny (insurance) — používá oficiální MCP SDK (FastMCP)
Spustit: python mcp_insurance.py
Dotazy na VZP B2B portál pomocí klientského certifikátu (mTLS).
Vychází z knihovny Knihovny/vzpb2b_client.py a skriptů v Insurance/.
Nástroje:
- registrovani_lekari(rodne_cislo, k_datu) — kdo jsou registrující lékaři
pacienta (praktik 001, gynekolog 002, stomatolog 014, ...) ke dni k_datu.
- stav_pojisteni(rodne_cislo, k_datu, prijmeni) — jestli je pacient ke dni
k_datu platně pojištěný a u které pojišťovny.
"""
import re
import sys
import traceback
from pathlib import Path
from datetime import date
from typing import Optional
from mcp.server.fastmcp import FastMCP
PROJECT_ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(PROJECT_ROOT))
from Knihovny.vzpb2b_client import VZPB2BClient
# Všechny logy MUSÍ jít na stderr — stdout je rezervován pro JSON-RPC
def log(msg: str):
print(msg, file=sys.stderr, flush=True)
# ── Certifikát ──────────────────────────────────────────────────────────────
PFX_PATH = PROJECT_ROOT / "Insurance" / "Certificates" / "picka.pfx"
PFX_PASS = "Vlado7309208104+"
if not PFX_PATH.exists():
log(f"Chyba: certifikát nenalezen: {PFX_PATH}")
sys.exit(1)
try:
vzp = VZPB2BClient("prod", str(PFX_PATH), PFX_PASS)
log("VZP B2B klient inicializován (prod)")
except Exception as e:
log(f"Chyba inicializace VZP B2B klienta: {e}")
sys.exit(1)
def _norm_rc(rodne_cislo: str) -> str:
"""Rodné číslo bez lomítka a nečíselných znaků."""
return re.sub(r"\D", "", rodne_cislo or "")
# MCP server
mcp = FastMCP("insurance")
@mcp.tool()
def registrovani_lekari(rodne_cislo: str, k_datu: Optional[str] = None) -> dict:
"""Zjisti registrující lékaře pacienta u VZP ke dni k_datu.
Dotáže se VZP B2B služby RegistracePojistencePZSB2B a vrátí, kdo je
registrující praktický lékař (odbornost 001), gynekolog (002),
stomatolog (014) a případně další, pokud je VZP eviduje.
Args:
rodne_cislo: Rodné číslo pacienta (lomítko nevadí).
k_datu: Datum ve formátu YYYY-MM-DD. Pokud chybí, použije se dnešek.
Returns:
dict s rodným číslem, datem dotazu, počtem nalezených registrací
a seznamem lékařů (každý: kód a název odbornosti, IČZ, IČP, jméno
lékaře, název ZZZ, pojišťovna, data registrace/zahájení/ukončení).
"""
try:
rc = _norm_rc(rodne_cislo)
if not rc:
return {"error": "Neplatné rodné číslo."}
if k_datu:
k_datu = k_datu.strip()
else:
k_datu = date.today().isoformat()
xml = vzp.registrace_lekare(rc=rc, k_datu=k_datu, odbornosti=None)
zaznamy = vzp.parse_registrace_lekare(xml)
return {
"rodne_cislo": rc,
"k_datu": k_datu,
"pocet": len(zaznamy),
"lekari": zaznamy,
}
except Exception:
log(f"registrovani_lekari chyba: {traceback.format_exc()}")
raise
# Význam pole "stav" v odpovědi stavPojisteniB2B
_STAV_POPIS = {
"1": "pojištěn u uvedené pojišťovny",
"4": "cizinec (smluvní/EU pojištění) — považováno za pojištěného",
}
_STAV_POJISTEN = ("1", "4") # tyto kódy znamenají platné pojištění
@mcp.tool()
def stav_pojisteni(rodne_cislo: str, k_datu: Optional[str] = None,
prijmeni: Optional[str] = None) -> dict:
"""Zjisti, jestli je pacient ke dni k_datu platně pojištěný a u které pojišťovny.
Dotáže se VZP B2B služby stavPojisteniB2B. VZP centrálně eviduje pojištěnce
všech pojišťoven, takže odpověď vrátí i pojišťovnu jinou než VZP.
Args:
rodne_cislo: Rodné číslo pacienta (lomítko nevadí).
k_datu: Datum ve formátu YYYY-MM-DD. Pokud chybí, použije se dnešek.
prijmeni: Příjmení (volitelné) — VZP umožňuje křížovou kontrolu se jménem.
Returns:
dict s rodným číslem, datem dotazu, příznakem pojisteny (True/False),
kódem a popisem stavu, kódem zpracování požadavku a údaji o pojišťovně
(kód, název, kód pojistného vztahu).
"""
try:
rc = _norm_rc(rodne_cislo)
if not rc:
return {"error": "Neplatné rodné číslo."}
k_datu = k_datu.strip() if k_datu else date.today().isoformat()
prijmeni = prijmeni.strip() if prijmeni else None
xml = vzp.stav_pojisteni(rc=rc, k_datu=k_datu, prijmeni=prijmeni)
parsed = vzp.parse_stav_pojisteni(xml)
stav = parsed.get("stav")
pojisteny = stav in _STAV_POJISTEN
return {
"rodne_cislo": rc,
"k_datu": k_datu,
"pojisteny": pojisteny,
"stav": stav,
"stav_popis": _STAV_POPIS.get(stav, "nepojištěn u této pojišťovny / nenalezen"),
"stav_vyrizeni": parsed.get("stavVyrizeni"),
"kod_pojistovny": parsed.get("kodPojistovny"),
"nazev_pojistovny": parsed.get("nazevPojistovny"),
"pojisteni_kod": parsed.get("pojisteniKod"),
}
except Exception:
log(f"stav_pojisteni chyba: {traceback.format_exc()}")
raise
if __name__ == "__main__":
log("MCP Insurance server spuštěn (FastMCP)")
mcp.run()