162 lines
5.5 KiB
Python
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()
|