diff --git a/Insurance/KdoJeLékař/export_neregistrovani_vzp.py b/Insurance/KdoJeLékař/export_neregistrovani_vzp.py new file mode 100644 index 0000000..a8ccc07 --- /dev/null +++ b/Insurance/KdoJeLékař/export_neregistrovani_vzp.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Exportuje 151 pacientů registrovaných v Medicusu k 1.1.2025, +u nichž VZP k tomuto datu nevykazuje registraci v odbornosti 001 u IČP 09305001. +Výstup: Excel s komentářem a aktuálním stavem v Medicusu. +""" + +import sys +from pathlib import Path +from datetime import date +from collections import defaultdict + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) + +from Knihovny.mysql_db import connect_mysql +import fdb, socket +from openpyxl import Workbook +from openpyxl.styles import (Font, PatternFill, Alignment, Border, Side, + GradientFill) +from openpyxl.utils import get_column_letter + +# ── Konfigurace ──────────────────────────────────────────────────────────────── +K_DATU_HIST = "2025-01-01" +TODAY = date.today() +OUT_FILE = Path(__file__).resolve().parent / f"neregistrovani_vzp_20250101.xlsx" + +POJ_NAZVY = { + "111": "VZP", + "201": "ČPZP", + "205": "ČPZP (ex-OZP)", + "207": "OZP", + "209": "ZPŠ", + "211": "ZPMV", + "213": "RBP", +} + +# ── Barvy ────────────────────────────────────────────────────────────────────── +BLUE_HEADER = "1F497D" +WHITE = "FFFFFF" +LIGHT_BLUE = "DCE6F1" +LIGHT_GREEN = "EBF1DE" +LIGHT_YELLOW = "FFFFC0" +LIGHT_RED = "FCE4D6" +LIGHT_GREY = "F2F2F2" +ORANGE = "F4B942" + +# ── Data z MySQL ─────────────────────────────────────────────────────────────── +mysql = connect_mysql() +cur = mysql.cursor() + +cur.execute(""" + SELECT rc FROM vzp_registrace_raw WHERE k_datu = %s + AND rc NOT IN ( + SELECT rc FROM vzp_registrace_lekari + WHERE k_datu = %s AND kod_odbornosti = '001' + AND ICP = '09305001' AND ma_lekare = 1 + ) +""", (K_DATU_HIST, K_DATU_HIST)) +problematicke_rcs = [row[0] for row in cur.fetchall()] + +ph = ",".join(["%s"] * len(problematicke_rcs)) +cur.execute(f""" + SELECT rc, prijmeni, jmeno, kod_odbornosti, ma_lekare, ICP, + nazev_lekare, nazev_zzz, poj_kod, poj_zkratka + FROM vzp_registrace_lekari + WHERE k_datu = %s AND rc IN ({ph}) AND kod_odbornosti = '001' +""", (K_DATU_HIST, *problematicke_rcs)) + +vzp = {} +for rc, prijmeni, jmeno, odb, ma, icp, nazev_lek, nazev_zzz, poj_kod, poj_zkr in cur.fetchall(): + vzp[rc] = {"prijmeni": prijmeni or "", "jmeno": jmeno or "", + "ma_lekare": bool(ma), "ICP": icp or "", + "nazev_lekare": nazev_lek or "", "nazev_zzz": nazev_zzz or "", + "poj_kod": poj_kod or "", "poj_zkratka": poj_zkr or ""} + +mysql.close() + +# ── Data z Medicusu ──────────────────────────────────────────────────────────── +computer_name = socket.gethostname().upper() +dsn_map = { + "LEKAR": r"localhost:M:\medicus\data\medicus.fdb", + "SESTRA": r"192.168.1.10:m:\medicus\data\medicus.fdb", + "LENOVO": r"192.168.1.10:m:\medicus\data\medicus.fdb", +} +dsn = dsn_map.get(computer_name, r"localhost:c:\medicus 3\data\medicus.fdb") +fb_conn = fdb.connect(dsn=dsn, user="SYSDBA", password="masterkey", charset="win1250") +fb_cur = fb_conn.cursor() + +# Aktuálně aktivní pacienti +fb_cur.execute(""" + SELECT kar.rodcis FROM kar + WHERE kar.vyrazen = 'N' AND kar.rodcis IS NOT NULL AND kar.rodcis <> '' + AND EXISTS ( + SELECT 1 FROM registr r JOIN icp i ON r.idicp = i.idicp + WHERE r.idpac = kar.idpac + AND r.datum <= CURRENT_DATE + AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= CURRENT_DATE) + AND r.priznak IN ('V','D','A') + AND i.icp = '09305001' AND i.odb = '001' + ) +""") +aktualne_aktivni = {(row[0] or "").strip() for row in fb_cur.fetchall()} + +# Detail všech 151 pacientů +ph_fb = ",".join(["?" for _ in problematicke_rcs]) +fb_cur.execute(f""" + SELECT kar.rodcis, kar.prijmeni, kar.jmeno, kar.poj, kar.vyrazen, + r.datum, r.datum_zruseni, r.priznak + FROM kar + LEFT JOIN registr r ON r.idpac = kar.idpac + LEFT JOIN icp i ON r.idicp = i.idicp AND i.icp = '09305001' AND i.odb = '001' + WHERE kar.rodcis IN ({ph_fb}) + ORDER BY kar.rodcis, r.datum DESC +""", problematicke_rcs) + +medicus = {} +for row in fb_cur.fetchall(): + rc = (row[0] or "").strip() + if rc not in medicus: + medicus[rc] = { + "prijmeni": (row[1] or "").strip(), + "jmeno": (row[2] or "").strip(), + "poj": str(row[3] or "").strip(), + "vyrazen": (row[4] or "").strip(), + "reg_datum": row[5], + "reg_datum_zruseni":row[6], + "reg_priznak": (row[7] or "").strip(), + } + +fb_conn.close() + +# ── Kategorizace ─────────────────────────────────────────────────────────────── +def kategorie(rc, vzp_row, med_row, aktivni_set): + poj = med_row.get("poj", "") if med_row else "" + if not vzp_row: + if poj != "111": + return "JINÁ POJIŠŤOVNA", "VZP neregistruje — pojištěnec jiné pojišťovny.", LIGHT_BLUE + return "BEZ VZP ZÁZNAMU", "VZP nevrátila žádný záznam přesto, že jde o pojištěnce VZP. Pravděpodobně chybí registrace u VZP.", LIGHT_RED + + if vzp_row["ma_lekare"]: + return "REGISTROVÁN JINDE", f"VZP eviduje registraci u jiného lékaře: {vzp_row['nazev_zzz']} (ICP {vzp_row['ICP']}).", LIGHT_RED + + return "BEZ LÉKAŘE U VZP", "VZP eviduje pojištěnce, ale bez registrujícího lékaře v odbornosti 001.", LIGHT_YELLOW + + +# ── Sestavení řádků ──────────────────────────────────────────────────────────── +rows = [] +for rc in problematicke_rcs: + vzp_row = vzp.get(rc) + med_row = medicus.get(rc) + aktivni = rc in aktualne_aktivni + + prijmeni = (med_row or vzp_row or {}).get("prijmeni", "") + jmeno = (med_row or vzp_row or {}).get("jmeno", "") + poj_kod = med_row.get("poj", "") if med_row else (vzp_row or {}).get("poj_kod", "") + poj_nazev = POJ_NAZVY.get(poj_kod, poj_kod) + + med_stav = "Aktivní" if aktivni else ("Odregistrován" if med_row else "Nenalezen v Medicusu") + reg_datum = med_row.get("reg_datum") if med_row else None + reg_datum_zrus = med_row.get("reg_datum_zruseni") if med_row else None + + kat, komentar, barva = kategorie(rc, vzp_row, med_row, aktualne_aktivni) + + vzp_icp = vzp_row["ICP"] if vzp_row and vzp_row["ma_lekare"] else "" + vzp_lek = vzp_row["nazev_zzz"] if vzp_row and vzp_row["ma_lekare"] else "" + vzp_zzz = vzp_row["nazev_lekare"] if vzp_row and vzp_row["ma_lekare"] else "" + + rows.append({ + "prijmeni": prijmeni, + "jmeno": jmeno, + "rc": rc, + "poj_kod": poj_kod, + "poj_nazev": poj_nazev, + "med_stav": med_stav, + "reg_datum": reg_datum.strftime("%d.%m.%Y") if reg_datum else "", + "reg_zruseni": reg_datum_zrus.strftime("%d.%m.%Y") if reg_datum_zrus else "", + "kategorie": kat, + "komentar": komentar, + "vzp_icp": vzp_icp, + "vzp_lek": vzp_lek, + "vzp_zzz": vzp_zzz, + "barva": barva, + }) + +rows.sort(key=lambda r: (r["kategorie"], r["prijmeni"], r["jmeno"])) + +# ── Excel ────────────────────────────────────────────────────────────────────── +wb = Workbook() + +# ──────────────────────────────────────────────────────────────────────────────── +# SHEET 1: Přehled +# ──────────────────────────────────────────────────────────────────────────────── +ws_info = wb.active +ws_info.title = "Přehled" + +def hdr_cell(ws, row, col, value): + c = ws.cell(row=row, column=col, value=value) + c.font = Font(name="Arial", bold=True, color=WHITE, size=11) + c.fill = PatternFill("solid", fgColor=BLUE_HEADER) + c.alignment = Alignment(horizontal="center", vertical="center") + return c + +def val_cell(ws, row, col, value, bold=False, bg=None): + c = ws.cell(row=row, column=col, value=value) + c.font = Font(name="Arial", bold=bold, size=10) + c.alignment = Alignment(wrap_text=True, vertical="top") + if bg: + c.fill = PatternFill("solid", fgColor=bg) + return c + +ws_info.column_dimensions["A"].width = 36 +ws_info.column_dimensions["B"].width = 18 +ws_info.column_dimensions["C"].width = 60 + +# Titulek +ws_info.merge_cells("A1:C1") +t = ws_info["A1"] +t.value = f"Pacienti registrovaní v Medicusu k 1. 1. 2025, ale dle VZP bez registrace u IČP 09305001" +t.font = Font(name="Arial", bold=True, size=13, color=WHITE) +t.fill = PatternFill("solid", fgColor=BLUE_HEADER) +t.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) +ws_info.row_dimensions[1].height = 36 + +ws_info.merge_cells("A2:C2") +ws_info["A2"].value = f"Vygenerováno: {TODAY.strftime('%d. %m. %Y')} | Stav v Medicusu k dnešnímu dni" +ws_info["A2"].font = Font(name="Arial", italic=True, size=9, color="595959") +ws_info["A2"].alignment = Alignment(horizontal="center") + +# Souhrn počtů +counts = defaultdict(int) +counts_aktivni = defaultdict(int) +for r in rows: + counts[r["kategorie"]] += 1 + if r["med_stav"] == "Aktivní": + counts_aktivni[r["kategorie"]] += 1 + +hdr_cell(ws_info, 4, 1, "Kategorie") +hdr_cell(ws_info, 4, 2, "Počet pacientů") +hdr_cell(ws_info, 4, 3, "Komentář") + +KAT_BARVY = { + "REGISTROVÁN JINDE": LIGHT_RED, + "BEZ LÉKAŘE U VZP": LIGHT_YELLOW, + "JINÁ POJIŠŤOVNA": LIGHT_BLUE, + "BEZ VZP ZÁZNAMU": LIGHT_RED, +} +KAT_POPIS = { + "REGISTROVÁN JINDE": "VZP k 1.1.2025 eviduje registraci u jiného praktického lékaře. Pacient se pravděpodobně přeregistroval jinam, aniž by byl v Medicusu odregistrován.", + "BEZ LÉKAŘE U VZP": "VZP eviduje pojištěnce, ale v odbornosti 001 mu neeviduje žádného lékaře. Může jít o opožděné zpracování přihlášky nebo technickou chybu.", + "JINÁ POJIŠŤOVNA": "Pacient není pojištěncem VZP — VZP o něm data nemá, proto nebylo vráceno nic. To je očekávané chování.", + "BEZ VZP ZÁZNAMU": "VZP pojištěnec (111), ale VZP nevrátila žádný záznam. Může jít o nesprávné RC, neaktivní pojistný vztah nebo chybu v komunikaci.", +} + +for i, kat in enumerate(["REGISTROVÁN JINDE", "BEZ LÉKAŘE U VZP", "JINÁ POJIŠŤOVNA", "BEZ VZP ZÁZNAMU"]): + r = 5 + i + bg = KAT_BARVY[kat] + val_cell(ws_info, r, 1, kat, bold=True, bg=bg) + val_cell(ws_info, r, 2, f"{counts[kat]} ({counts_aktivni[kat]} stále aktivní)", bg=bg) + val_cell(ws_info, r, 3, KAT_POPIS[kat], bg=bg) + ws_info.row_dimensions[r].height = 42 + +ws_info.row_dimensions[4].height = 20 + +# Celkem +val_cell(ws_info, 10, 1, "CELKEM", bold=True) +val_cell(ws_info, 10, 2, f"{len(rows)} ({sum(counts_aktivni.values())} aktivní)", bold=True) + +# Metodika +ws_info.merge_cells("A12:C12") +ws_info["A12"].value = "Metodika" +ws_info["A12"].font = Font(name="Arial", bold=True, size=11, color=BLUE_HEADER) + +metodika_text = ( + "Skript kdojelekar_20250101.py dotázal VZP B2B na registrujícího lékaře (odbornost 001) " + "pro každého pacienta registrovaného k 1. 1. 2025 v Medicusu u IČP 09305001. " + "Pacienti v tomto souboru jsou ti, u nichž VZP k danému datu nevrátila záznam s ICP=09305001 a ma_lekare=1. " + "Stav v Medicusu je aktuální k dnešnímu dni (" + TODAY.strftime("%d. %m. %Y") + ")." +) +ws_info.merge_cells("A13:C13") +c = ws_info["A13"] +c.value = metodika_text +c.font = Font(name="Arial", size=9, color="595959") +c.alignment = Alignment(wrap_text=True, vertical="top") +ws_info.row_dimensions[13].height = 56 + +# Doporučení +ws_info.merge_cells("A15:C15") +ws_info["A15"].value = "Doporučení" +ws_info["A15"].font = Font(name="Arial", bold=True, size=11, color=BLUE_HEADER) + +ws_info.merge_cells("A16:C16") +c = ws_info["A16"] +c.value = ( + "1. REGISTROVÁN JINDE — ověřit s pacientem při návštěvě, zda se přeregistroval; pokud ano, odregistrovat v Medicusu.\n" + "2. BEZ LÉKAŘE U VZP — zkontrolovat, zda přihláška registrace byla správně odeslána a VZP ji eviduje.\n" + "3. JINÁ POJIŠŤOVNA — tyto pacienty prověřit u příslušné pojišťovny (ČPZP, OZP…) analogickým dotazem.\n" + "4. BEZ VZP ZÁZNAMU — ověřit správnost RC a aktivitu pojistného vztahu přímo u VZP." +) +c.font = Font(name="Arial", size=10) +c.alignment = Alignment(wrap_text=True, vertical="top") +ws_info.row_dimensions[16].height = 80 + +# ──────────────────────────────────────────────────────────────────────────────── +# SHEET 2: Data +# ──────────────────────────────────────────────────────────────────────────────── +ws = wb.create_sheet("Pacienti") + +COLS = [ + ("Příjmení", 20), + ("Jméno", 14), + ("Rodné číslo", 14), + ("Pojišťovna", 12), + ("Stav v Medicusu\ndnes", 16), + ("Datum registrace\nv Medicusu", 16), + ("Datum zrušení\nv Medicusu", 16), + ("Kategorie VZP problému", 22), + ("Komentář", 52), + ("VZP ICP jiného lékaře", 18), + ("VZP — jméno lékaře", 28), + ("VZP — název ZZZ", 36), +] + +for col_idx, (header, width) in enumerate(COLS, 1): + c = ws.cell(row=1, column=col_idx, value=header) + c.font = Font(name="Arial", bold=True, color=WHITE, size=10) + c.fill = PatternFill("solid", fgColor=BLUE_HEADER) + c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) + ws.column_dimensions[get_column_letter(col_idx)].width = width + +ws.row_dimensions[1].height = 32 +ws.freeze_panes = "A2" + +thin = Side(style="thin", color="BFBFBF") +border = Border(left=thin, right=thin, top=thin, bottom=thin) + +for row_idx, r in enumerate(rows, 2): + bg = r["barva"] + data = [ + r["prijmeni"], r["jmeno"], r["rc"], f"{r['poj_kod']} {r['poj_nazev']}", + r["med_stav"], r["reg_datum"], r["reg_zruseni"], + r["kategorie"], r["komentar"], + r["vzp_icp"], r["vzp_lek"], r["vzp_zzz"], + ] + for col_idx, value in enumerate(data, 1): + c = ws.cell(row=row_idx, column=col_idx, value=value) + c.font = Font(name="Arial", size=9) + c.fill = PatternFill("solid", fgColor=bg) + c.border = border + c.alignment = Alignment(vertical="top", wrap_text=(col_idx in (9, 11, 12))) + if r["med_stav"] == "Aktivní" and col_idx == 5: + c.font = Font(name="Arial", size=9, bold=True, color="375623") + elif r["med_stav"] != "Aktivní" and col_idx == 5: + c.font = Font(name="Arial", size=9, color="843C0C") + ws.row_dimensions[row_idx].height = 32 + +# AutoFilter +ws.auto_filter.ref = f"A1:{get_column_letter(len(COLS))}1" + +wb.save(OUT_FILE) +print(f"Uloženo: {OUT_FILE}") +print(f"Celkem řádků: {len(rows)}") diff --git a/Insurance/KdoJeLékař/neregistrovani_vzp_20250101.xlsx b/Insurance/KdoJeLékař/neregistrovani_vzp_20250101.xlsx new file mode 100644 index 0000000..590a60d Binary files /dev/null and b/Insurance/KdoJeLékař/neregistrovani_vzp_20250101.xlsx differ diff --git a/Insurance/StavPojisteni/batch_stav0_20250101.py b/Insurance/StavPojisteni/batch_stav0_20250101.py new file mode 100644 index 0000000..d9f7607 --- /dev/null +++ b/Insurance/StavPojisteni/batch_stav0_20250101.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +batch_stav0_20250101.py +======================== +Zpracuje 50 pacientů, kteří k 1. 1. 2025 vrátili stavVyrizeniPozadavku=0 +na dotaz registrace lékaře (= VZP pojištěnce nenašla). + +Pro každého: + 1. Dotáže VZP stavPojisteni k 2025-01-01 → uloží do vzp_stav_pojisteni. + 2. Pokud stav != '1' a != '4', binárně hledá zlom pojištění + a uloží do vzp_sledovani_pojisteni. + +Resumovatelný — pacienty, kteří už mají záznam v vzp_stav_pojisteni +k 2025-01-01, přeskočí. +""" + +import sys +import time +from pathlib import Path +from datetime import date, timedelta + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from Knihovny.mysql_db import connect_mysql +from Knihovny.vzpb2b_client import VZPB2BClient + +# ── KONFIGURACE ─────────────────────────────────────────────────────────────── + +K_DATU = date(2025, 1, 1) +API_DELAY = 1.5 + +PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx" +PFX_PASSWORD = "Vlado7309208104+" +ICZ = "00000000" +DIC = "00000000" +ENV = "prod" + +# ── PACIENTI ───────────────────────────────────────────────────────────────── + +PACIENTI = [ + ("415513130", "Rohlíková", "Marie"), + ("420622031", "Hamerník", "Josef"), + ("430127082", "Anderle", "Václav"), + ("435625017", "Kafková", "Marie"), + ("436005111", "Plzáková", "Ivana"), + ("445624103", "Kloučková", "Vlasta"), + ("446116017", "Strnadová", "Dagmar"), + ("456016085", "Kubcová", "Anna"), + ("485627038", "Poustková", "Jiřina"), + ("506109148", "Holubcová", "Svatava"), + ("6008040247", "Šulc", "Jiří"), + ("6054574130", "Přibová", "Darina"), + ("6102694070", "Elouchefoun", "Aziz"), + ("6654251978", "Svozilová", "Ivana"), + ("6808292018", "Moudrý", "Jiří"), + ("6853222079", "Milatová", "Martina"), + ("6909154934", "Novák", "Petr"), + ("7056764319", "Michlíková", "Anna"), + ("7157734210", "Moudry Molloy", "Joanne"), + ("7309300449", "Vojáček", "Aleš"), + ("7410540709", "Torres blanco", "Jose maria"), + ("7459303599", "Noháčová", "Kateřina"), + ("7651090106", "Matějková", "Jana"), + ("7803744597", "Barrell", "Peter"), + ("7855080420", "Vondřičková", "Zuzana"), + ("7908030427", "Smetana", "Libor"), + ("7957312099", "Nimeřická", "Michaela"), + ("7961794126", "Tomyshynets", "Halyna"), + ("8005291404", "Hanzl", "František"), + ("8156013041", "Maršíková", "Simona"), + ("8157544241", "Jarošová", "Zuzana"), + ("8203120299", "Otčenášek", "Vojtěch"), + ("8253215223", "Slavíková", "Kateřina"), + ("8301070558", "Kříž", "Michal"), + ("8352210438", "Bartáková", "Jana"), + ("8412175123", "Mičulka", "Jan"), + ("8454664262", "Feoktistová", "Irina"), + ("8462150147", "Říhová", "Markéta"), + ("8503120417", "Šindelář", "Tomáš"), + ("8552170517", "Slabá", "Gabriela"), + ("8558150227", "Horáková", "Lucie"), + ("8652034380", "Kopová", "Jana"), + ("8754280403", "Jindrová", "Helena"), + ("8755075153", "Babjáková", "Jana"), + ("8910584023", "Pham Van", "Duy"), + ("8953010330", "Špatná", "Markéta"), + ("8956039037", "Slavíková", "Zuzana"), + ("9002025956", "Banáš", "Martin"), + ("9010262448", "Bečica", "Marek"), + ("9811040305", "Sidej", "Natan"), +] + +# ── INIT ────────────────────────────────────────────────────────────────────── + +vzp = VZPB2BClient(ENV, str(PFX_PATH), PFX_PASSWORD, icz=ICZ, dic=DIC) +mysql = connect_mysql() +call_count = 0 +today = date.today() + +# ── HELPERS ─────────────────────────────────────────────────────────────────── + +def check_stav(rc: str, check_date: date) -> str: + global call_count + if call_count > 0: + time.sleep(API_DELAY) + call_count += 1 + print(f" [{call_count}] {check_date.isoformat()} ...", end=" ", flush=True) + xml = vzp.stav_pojisteni(rc=rc, k_datu=check_date.isoformat()) + stav = vzp.parse_stav_pojisteni(xml)["stav"] + print(f"stav = {stav!r}") + return stav + +def uloz_stav(rc, prijmeni, jmeno, k_datu, stav): + xml = vzp.stav_pojisteni(rc=rc, k_datu=k_datu.isoformat()) + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_stav_pojisteni (rc, prijmeni, jmeno, k_datu, stav, response_xml) + VALUES (%s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE stav=VALUES(stav), response_xml=VALUES(response_xml) + """, (rc, prijmeni, jmeno, k_datu, stav, xml)) + +def najdi_zlom(rc: str, last_ok_mysql) -> tuple: + if last_ok_mysql: + low = last_ok_mysql + high = today + print(f" MySQL last stav='1': {low} → [{low} … {high}]") + else: + print(" MySQL: žádný stav='1' — hledám zpětně po rocích ...") + prev_probe = today + low = high = None + for n in range(1, 21): + y = today.year - n + try: + probe = date(y, today.month, today.day) + except ValueError: + probe = date(y, today.month, today.day - 1) + stav = check_stav(rc, probe) + if stav == "1": + low = probe + high = prev_probe + print(f" Nalezeno stav='1' k {low} → [{low} … {high}]") + break + prev_probe = probe + if low is None: + print(" NELZE: stav='1' nenalezen ani 20 let zpět.") + return None, None + + stav_low = check_stav(rc, low) + stav_high = check_stav(rc, high) + + if stav_low != "1": + print(f" NELZE: dolní mez {low} má stav='{stav_low}'.") + return None, None + if stav_high == "1": + print(f" INFO: horní mez {high} má stav='1' — aktuálně pojištěn.") + return None, None + + print(f" Binární hledání ({(high - low).days} dní) ...") + while (high - low).days > 1: + mid = low + timedelta(days=(high - low).days // 2) + stav = check_stav(rc, mid) + if stav == "1": + low = mid + else: + high = mid + + return low, high # insured_to, uninsured_from + +# ── RESUME: načti již hotové ────────────────────────────────────────────────── + +with mysql.cursor() as cur: + cur.execute("SELECT rc FROM vzp_stav_pojisteni WHERE k_datu = %s", (K_DATU,)) + hotove = {row[0] for row in cur.fetchall()} + +zbyvaji = [(rc, p, j) for rc, p, j in PACIENTI if rc not in hotove] +print(f"Celkem pacientů: {len(PACIENTI)}, již hotovo: {len(hotove)}, zbývá: {len(zbyvaji)}") +print(f"API prodleva: {API_DELAY}s | K_DATU: {K_DATU}\n") +print("=" * 60) + +# ── HLAVNÍ SMYČKA ───────────────────────────────────────────────────────────── + +vysledky = [] + +for i, (rc, prijmeni, jmeno) in enumerate(zbyvaji, 1): + print(f"\n[{i}/{len(zbyvaji)}] {prijmeni} {jmeno} (RC: {rc})") + + # Krok 1: stav k K_DATU + if call_count > 0: + time.sleep(API_DELAY) + call_count += 1 + print(f" [{call_count}] {K_DATU.isoformat()} ...", end=" ", flush=True) + xml_hist = vzp.stav_pojisteni(rc=rc, k_datu=K_DATU.isoformat()) + stav_hist = vzp.parse_stav_pojisteni(xml_hist)["stav"] + print(f"stav = {stav_hist!r}") + + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_stav_pojisteni (rc, prijmeni, jmeno, k_datu, stav, response_xml) + VALUES (%s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE stav=VALUES(stav), response_xml=VALUES(response_xml) + """, (rc, prijmeni, jmeno, K_DATU, stav_hist, xml_hist)) + + # Krok 2: zlom (jen pokud stav != '1' a != '4') + insured_to = uninsured_from = None + + if stav_hist in ("1", "4"): + print(f" → pojištěn (stav={stav_hist!r}), zlom se nehledá") + else: + # Zkontroluj watchlist + with mysql.cursor() as cur: + cur.execute("SELECT insured_to, uninsured_from FROM vzp_sledovani_pojisteni WHERE rc = %s", (rc,)) + existuje = cur.fetchone() + + if existuje: + insured_to, uninsured_from = existuje + print(f" → již ve watchlistu: insured_to={insured_to}, uninsured_from={uninsured_from}") + else: + with mysql.cursor() as cur: + cur.execute( + "SELECT MAX(k_datu) FROM vzp_stav_pojisteni WHERE rc = %s AND stav = '1'", + (rc,) + ) + r = cur.fetchone() + last_ok = r[0] if r and r[0] else None + + insured_to, uninsured_from = najdi_zlom(rc, last_ok) + + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_sledovani_pojisteni + (rc, prijmeni, jmeno, insured_to, uninsured_from, + aktualni_stav, prvni_detekce, posledni_kontrola) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + insured_to=VALUES(insured_to), + uninsured_from=VALUES(uninsured_from), + aktualni_stav=VALUES(aktualni_stav), + posledni_kontrola=VALUES(posledni_kontrola) + """, (rc, prijmeni, jmeno, insured_to, uninsured_from, + stav_hist, today, today)) + + vysledky.append({ + "rc": rc, "prijmeni": prijmeni, "jmeno": jmeno, + "stav_20250101": stav_hist, + "insured_to": insured_to, + "uninsured_from": uninsured_from, + }) + +# ── SOUHRN ──────────────────────────────────────────────────────────────────── + +mysql.close() + +print(f"\n{'=' * 60}") +print(f" SOUHRN — {len(vysledky)} zpracovaných pacientů") +print(f"{'=' * 60}") +print(f" {'Příjmení':<20} {'Jméno':<14} {'Stav':>5} {'Pojištěn do':<13} {'Nepoj. od'}") +print(f" {'-'*58}") +for v in vysledky: + ins = str(v["insured_to"]) if v["insured_to"] else "-" + uni = str(v["uninsured_from"]) if v["uninsured_from"] else "-" + print(f" {v['prijmeni']:<20} {v['jmeno']:<14} {v['stav_20250101']:>5} {ins:<13} {uni}") + +print(f"\nCelkem VZP dotazů: {call_count}") diff --git a/Insurance/StavPojisteni/zkontroluj_rc_jednorazove.py b/Insurance/StavPojisteni/zkontroluj_rc_jednorazove.py new file mode 100644 index 0000000..93fa5ef --- /dev/null +++ b/Insurance/StavPojisteni/zkontroluj_rc_jednorazove.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +zkontroluj_rc_jednorazove.py +============================= +Jednorázový skript pro libovolné RC: + 1. Dotáže VZP na stav pojištění k zadanému K_DATU. + 2. Uloží do vzp_stav_pojisteni. + 3. Pokud stav != '1' a != '4', spustí binární hledání zlomu + a uloží do vzp_sledovani_pojisteni. +""" + +import sys +import time +from pathlib import Path +from datetime import date, timedelta + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from Knihovny.mysql_db import connect_mysql +from Knihovny.vzpb2b_client import VZPB2BClient + +# ── KONFIGURACE ─────────────────────────────────────────────────────────────── + +RC = "430127082" +PRIJMENI = "Anderle" +JMENO = "Václav" +K_DATU = date(2025, 1, 1) # datum pro první dotaz + +PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx" +PFX_PASSWORD = "Vlado7309208104+" +ICZ = "00000000" +DIC = "00000000" +ENV = "prod" + +API_DELAY = 2 + +# ── INIT ────────────────────────────────────────────────────────────────────── + +vzp = VZPB2BClient(ENV, str(PFX_PATH), PFX_PASSWORD, icz=ICZ, dic=DIC) +mysql = connect_mysql() +call_count = 0 +today = date.today() + +def check_stav(rc: str, check_date: date) -> str: + global call_count + if call_count > 0: + time.sleep(API_DELAY) + call_count += 1 + print(f" [{call_count}] VZP dotaz k {check_date.isoformat()} ...", end=" ", flush=True) + xml = vzp.stav_pojisteni(rc=rc, k_datu=check_date.isoformat()) + stav = vzp.parse_stav_pojisteni(xml)["stav"] + print(f"stav = {stav!r}") + return stav + +def uloz_stav(rc, prijmeni, jmeno, k_datu, stav, xml): + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_stav_pojisteni + (rc, prijmeni, jmeno, k_datu, stav, response_xml) + VALUES (%s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE stav=VALUES(stav), response_xml=VALUES(response_xml) + """, (rc, prijmeni, jmeno, k_datu, stav, xml)) + +def najdi_zlom(rc, last_ok_mysql): + if last_ok_mysql: + low = last_ok_mysql + high = today + print(f" MySQL last stav='1': {low} -> [{low} ... {high}]") + else: + print(" MySQL: žádný stav='1' — hledám zpětně po rocích ...") + prev_probe = today + low = high = None + for n in range(1, 21): + y = today.year - n + try: + probe = date(y, today.month, today.day) + except ValueError: + probe = date(y, today.month, today.day - 1) + stav = check_stav(rc, probe) + if stav == "1": + low = probe + high = prev_probe + print(f" Nalezeno stav='1' k {low} -> [{low} ... {high}]") + break + prev_probe = probe + if low is None: + print(" NELZE: stav='1' nenalezen ani 20 let zpět.") + return None, None + + stav_low = check_stav(rc, low) + stav_high = check_stav(rc, high) + + if stav_low != "1": + print(f" NELZE: dolní mez {low} má stav='{stav_low}'.") + return None, None + if stav_high == "1": + print(f" INFO: horní mez {high} má stav='1' — pacient je aktuálně pojištěn, žádný zlom.") + return None, None + + print(f" Binární hledání v rozsahu {(high - low).days} dní ...") + while (high - low).days > 1: + mid = low + timedelta(days=(high - low).days // 2) + stav = check_stav(rc, mid) + if stav == "1": + low = mid + else: + high = mid + + return low, high # insured_to, uninsured_from + +# ── KROK 1: Dotaz k zadanému datu ───────────────────────────────────────────── + +print(f"\n{PRIJMENI} {JMENO} (RC: {RC})") +print(f"── Krok 1: stav pojištění k {K_DATU} ─────────────────────────────────") + +xml_hist = vzp.stav_pojisteni(rc=RC, k_datu=K_DATU.isoformat()) +stav_hist = vzp.parse_stav_pojisteni(xml_hist)["stav"] +call_count += 1 +print(f" stav k {K_DATU}: {stav_hist!r}") + +uloz_stav(RC, PRIJMENI, JMENO, K_DATU, stav_hist, xml_hist) +print(f" Uloženo do vzp_stav_pojisteni (k_datu={K_DATU})") + +# ── KROK 2: Pokud není pojištěn, najdi zlom ─────────────────────────────────── + +if stav_hist in ("1", "4"): + print(f"\nStav '{stav_hist}' = pojištěn (nebo cizinec), zlom se nehledá.") +else: + print(f"\nStav '{stav_hist}' = nepojištěn/nenalezen") + print(f"── Krok 2: hledám zlom pojištění ─────────────────────────────────────") + + # Zkontroluj, zda je pacient již ve watchlistu + with mysql.cursor() as cur: + cur.execute("SELECT insured_to, uninsured_from FROM vzp_sledovani_pojisteni WHERE rc = %s", (RC,)) + existuje = cur.fetchone() + + if existuje: + print(f" Pacient již ve watchlistu: insured_to={existuje[0]}, uninsured_from={existuje[1]}") + print(f" Přeskakuji hledání zlomu.") + else: + with mysql.cursor() as cur: + cur.execute( + "SELECT MAX(k_datu) FROM vzp_stav_pojisteni WHERE rc = %s AND stav = '1'", + (RC,) + ) + r = cur.fetchone() + last_ok = r[0] if r and r[0] else None + + insured_to, uninsured_from = najdi_zlom(RC, last_ok) + + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_sledovani_pojisteni + (rc, prijmeni, jmeno, insured_to, uninsured_from, + aktualni_stav, prvni_detekce, posledni_kontrola) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + insured_to=VALUES(insured_to), + uninsured_from=VALUES(uninsured_from), + aktualni_stav=VALUES(aktualni_stav), + posledni_kontrola=VALUES(posledni_kontrola) + """, (RC, PRIJMENI, JMENO, insured_to, uninsured_from, + stav_hist, today, today)) + + print(f"\n{'=' * 50}") + print(f" VÝSLEDEK — {PRIJMENI} {JMENO} (RC: {RC})") + print(f"{'=' * 50}") + print(f" Stav k {K_DATU} : {stav_hist!r}") + print(f" Poslední den pojištěn : {insured_to or 'nezjištěno'}") + print(f" První den bez pojištění : {uninsured_from or 'nezjištěno'}") + print(f" Celkem VZP dotazů : {call_count}") + print(f"{'=' * 50}\n") + +mysql.close() +print(f"Hotovo. Celkem VZP dotazů: {call_count}")