notebookvb
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user