Povolení cookies
+V ČPZP používáme cookies a jiné technologie za účelem poskytování našich služeb, vylepšení + vašeho uživatelského zážitku, analýzy používání + našich stránek a při cílení reklamy.
+diff --git a/Insurance/SeznamPojistencu/07_doplnit_zahajeni.py b/Insurance/SeznamPojistencu/07_doplnit_zahajeni.py new file mode 100644 index 0000000..fc83c9a --- /dev/null +++ b/Insurance/SeznamPojistencu/07_doplnit_zahajeni.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import sys as _sys +_sys.stdout.reconfigure(encoding="utf-8", errors="replace") +_sys.stderr.reconfigure(encoding="utf-8", errors="replace") +""" +07_doplnit_zahajeni.py +====================== +Pro nové pacienty (první dávka po 31.12.2024) kteří jsou již ukončeni +a nemají záznam v vzp_registrace_lekari pro naše ICP=09305001, +dotáže se VZP k datu jejich první dávky — tehdy tam ještě byli +a odpověď obsahuje datumZahajeni registrace u nás. + +Výsledek uloží do vzp_registrace_lekari (stejná tabulka jako kdojelekar). +""" + +import sys +import time +from datetime import date +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny")) +from mysql_db import connect_mysql +from vzpb2b_client import VZPB2BClient + +PFX_PATH = str(Path(__file__).resolve().parents[1] / "Certificates" / "picka.pfx") +PFX_PASSWORD = "Vlado7309208104+" +ICZ = "09305000" +NASA_ICP = "09305001" +API_PAUSE = 2 + +PRVNI_DAVKA = date(2024, 12, 1) + +# ── Načtení kandidátů ───────────────────────────────────────────────────────── +conn = connect_mysql() +cur = conn.cursor() + +# Noví pacienti (prvni_davka > 31.12.2024) bez záznamu v registrace pro naše ICP +cur.execute(""" + SELECT + s.cip, + MIN(s.prijmeni) AS prijmeni, + MIN(s.jmeno) AS jmeno, + MIN(DATE(CONCAT(s.davka_rok,'-',LPAD(s.davka_mesic,2,'0'),'-',LPAD(s.davka_den,2,'0')))) AS prvni_davka + FROM seznam_pojistencu_davky s + WHERE s.pojistovna = '111' + GROUP BY s.cip + HAVING prvni_davka > %s +""", (PRVNI_DAVKA,)) +vsichni_novi = {r[0]: (r[1], r[2], r[3]) for r in cur.fetchall()} + +# Kteří z nich už mají záznam v registrace pro naše ICP +cur.execute(""" + SELECT DISTINCT rc FROM vzp_registrace_lekari + WHERE ICP = %s AND kod_odbornosti = '001' AND ma_lekare = 1 +""", (NASA_ICP,)) +uz_maji = {r[0] for r in cur.fetchall()} + +kandidati = { + cip: info + for cip, info in vsichni_novi.items() + if cip not in uz_maji +} + +print(f"Noví pacienti celkem: {len(vsichni_novi)}") +print(f"Již mají datum zahájení: {len(uz_maji & vsichni_novi.keys())}") +print(f"Ke doplnění: {len(kandidati)}") + +if not kandidati: + print("Vše je kompletní.") + cur.close(); conn.close(); sys.exit(0) + +vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ) + +print() +sirka = max(len(f"{p} {j}") for p, j, _ in kandidati.values()) + 2 + +for i, (cip, (prijmeni, jmeno, prvni_davka)) in enumerate(kandidati.items(), 1): + print(f"[{i:>2}/{len(kandidati)}] {prijmeni+' '+jmeno:<{sirka}} ({cip}) k {prvni_davka}", end=" ", flush=True) + + try: + xml = vzp.registrace_lekare(cip, prvni_davka.isoformat(), odbornosti=["001"]) + time.sleep(API_PAUSE) + zaznamy = vzp.parse_registrace_lekare(xml) + except Exception as e: + print(f"CHYBA: {e}") + continue + + nas_zaznam = next( + (z for z in zaznamy + if z.get("ma_lekare") and z.get("ICP") == NASA_ICP and z.get("kod_odbornosti") == "001"), + None + ) + + if not nas_zaznam: + print("→ nenalezen k tomuto datu") + continue + + def to_date(s): + try: + return date.fromisoformat(s) if s else None + except ValueError: + return None + + poj_el = nas_zaznam + cur.execute(""" + INSERT INTO vzp_registrace_lekari + (rc, prijmeni, jmeno, k_datu, kod_odbornosti, ma_lekare, + ICZ, ICP, nazev_lekare, nazev_zzz, + poj_kod, poj_zkratka, + datum_registrace, datum_zahajeni, datum_ukonceni, stav_vyrizeni) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON DUPLICATE KEY UPDATE + datum_zahajeni = VALUES(datum_zahajeni), + datum_ukonceni = VALUES(datum_ukonceni), + nazev_lekare = VALUES(nazev_lekare), + nazev_zzz = VALUES(nazev_zzz) + """, ( + cip, prijmeni, jmeno, prvni_davka, "001", 1, + nas_zaznam.get("ICZ"), nas_zaznam.get("ICP"), + nas_zaznam.get("nazev_lekare"), nas_zaznam.get("nazev_zzz"), + nas_zaznam.get("poj_kod"), nas_zaznam.get("poj_zkratka"), + to_date(nas_zaznam.get("datum_registrace")), + to_date(nas_zaznam.get("datum_zahajeni")), + to_date(nas_zaznam.get("datum_ukonceni")), + nas_zaznam.get("stav_vyrizeni"), + )) + + dz = nas_zaznam.get("datum_zahajeni") or "?" + du = nas_zaznam.get("datum_ukonceni") or "?" + print(f"→ zahájení: {dz} ukončení: {du}") + +cur.close() +conn.close() +print(f"\nHotovo.") diff --git a/Insurance/SeznamPojistencu/99_report_excel.py b/Insurance/SeznamPojistencu/99_report_excel.py index f4d756b..a3ccf84 100644 --- a/Insurance/SeznamPojistencu/99_report_excel.py +++ b/Insurance/SeznamPojistencu/99_report_excel.py @@ -92,8 +92,8 @@ cur.execute(""" cip, MIN(prijmeni) AS prijmeni, MIN(jmeno) AS jmeno, - MIN(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-01'))) AS prvni_davka, - MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-01'))) AS posledni_davka, + MIN(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0')))) AS prvni_davka, + MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0')))) AS posledni_davka, COUNT(DISTINCT CONCAT(davka_rok,davka_mesic)) AS pocet_davek FROM seznam_pojistencu_davky WHERE pojistovna='111' @@ -105,7 +105,7 @@ seznam = {r[0]: {"prijmeni": r[1], "jmeno": r[2], # Nejnovější datum dávky (pro sloupec "v aktuální dávce") cur.execute(""" - SELECT MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-01'))) + SELECT MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0')))) FROM seznam_pojistencu_davky WHERE pojistovna='111' """) nejnovejsi_davka = cur.fetchone()[0] @@ -113,8 +113,8 @@ nejnovejsi_davka = cur.fetchone()[0] cur.execute(""" SELECT DISTINCT cip FROM seznam_pojistencu_davky WHERE pojistovna='111' - AND CONCAT(davka_rok,LPAD(davka_mesic,2,'0')) = ( - SELECT MAX(CONCAT(davka_rok,LPAD(davka_mesic,2,'0'))) + AND CONCAT(davka_rok,LPAD(davka_mesic,2,'0'),LPAD(davka_den,2,'0')) = ( + SELECT MAX(CONCAT(davka_rok,LPAD(davka_mesic,2,'0'),LPAD(davka_den,2,'0'))) FROM seznam_pojistencu_davky WHERE pojistovna='111' ) """) @@ -214,15 +214,25 @@ for cip, s in seznam.items(): "datum_prehl": datum_prehl, }) +DATUM_REGISTRACE_OD = date(2025, 1, 1) + radky.sort(key=lambda r: (r["prijmeni"], r["jmeno"])) -aktivni = [r for r in radky if r["stav"] == "aktivní"] -ukonceni = sorted([r for r in radky if r["stav"] == "ukončen"], - key=lambda r: r["ukonceni"] or date.min) +aktivni = [r for r in radky if r["stav"] == "aktivní"] +ukonceni = sorted([r for r in radky if r["stav"] == "ukončen"], + key=lambda r: r["ukonceni"] or date.min) nenalezeni = [r for r in radky if r["stav"] == "nenalezen"] +# Noví: datum_zahajeni registrace u nás >= 01.01.2025, řazeno chronologicky +novi = sorted( + [r for r in radky + if registrace.get(r["cip"], {}).get("zahajeni") is not None + and registrace[r["cip"]]["zahajeni"] >= DATUM_REGISTRACE_OD], + key=lambda r: (registrace[r["cip"]]["zahajeni"], r["prijmeni"], r["jmeno"]) +) print(f" Aktivní: {len(aktivni)}") print(f" Ukončení: {len(ukonceni)}") +print(f" Noví: {len(novi)}") print(f" Nenalezeni: {len(nenalezeni)}") # ── Excel ───────────────────────────────────────────────────────────────────── @@ -270,6 +280,48 @@ def zapsat_list(ws, nadpis, bg_title, seznam_radku, stav_bg): cell(ws, ri, 11, r.get("novy_lekar", ""), bg) cell(ws, ri, 12, r.get("datum_prehl"), bg, align="center", fmt=DATE_FMT) +C_REGISTRACE = "DAEEF3" # světle modrá +C_TITLE_REG = "17375E" + +SLOUPCE_REG = ["Příjmení", "Jméno", "ČIP", "Zahájení\nregistrace", + "Ukončení\nregistrace", "Počet\ndávek", "V aktuální\ndávce", + "Stav", "Po ukončení", "Nový lékař / poznámka", "Datum\npřehlášení"] +WIDTHS_REG = [24, 18, 13, 16, 14, 9, 10, 10, 13, 40, 13] + +PO_CLR_REG = {"prehlasil": "375623", "nenalezen": "C55A11", + "bez_lekare": "7F6000", "": "000000"} +PO_TXT_REG = {"prehlasil": "přehlásil se", "nenalezen": "nenalezen", + "bez_lekare": "bez lékaře", "": ""} + +def zapsat_registrace(ws, nadpis, seznam_radku): + ncols = len(SLOUPCE_REG) + ws.freeze_panes = "A3" + title_row(ws, 1, nadpis, ncols, C_TITLE_REG) + ws.row_dimensions[2].height = 30 + for col, h in enumerate(SLOUPCE_REG, 1): + hdr(ws.cell(2, col), h, bg=C_TITLE_REG) + autofit(ws, WIDTHS_REG) + + for ri, r in enumerate(seznam_radku, 3): + bg = C_REGISTRACE if ri % 2 == 0 else None + cell(ws, ri, 1, r["prijmeni"], bg) + cell(ws, ri, 2, r["jmeno"], bg) + cell(ws, ri, 3, r["cip"], bg, align="center") + zahajeni = registrace.get(r["cip"], {}).get("zahajeni") + cell(ws, ri, 4, zahajeni, bg, align="center", fmt=DATE_FMT) + cell(ws, ri, 5, r["ukonceni"], bg, align="center", fmt=DATE_FMT) + cell(ws, ri, 6, r["pocet_davek"],bg, align="right") + akt_txt = "✓" if r["v_aktualni"] else "–" + akt_clr = "375623" if r["v_aktualni"] else "C55A11" + cell(ws, ri, 7, akt_txt, bg, bold=True, align="center", color=akt_clr) + cell(ws, ri, 8, r["stav"], bg, align="center", bold=True, + color=("375623" if r["stav"] == "aktivní" else "C55A11")) + po = r.get("po_ukonceni", "") + cell(ws, ri, 9, PO_TXT_REG.get(po, po), bg, align="center", bold=bool(po), + color=PO_CLR_REG.get(po, "000000")) + cell(ws, ri, 10, r.get("novy_lekar", ""), bg) + cell(ws, ri, 11, r.get("datum_prehl"), bg, align="center", fmt=DATE_FMT) + wb = openpyxl.Workbook() # List 1 – Všichni @@ -292,10 +344,16 @@ zapsat_list(ws3, f"Ukončená registrace — celkem: {len(ukonceni)}", C_TITLE_UKO, ukonceni, C_UKONCEN) -# List 4 – Nenalezeni +# List 4 – Registrace (noví po 31.12.2024) +ws4 = wb.create_sheet("Registrace") +zapsat_registrace(ws4, + f"Noví pojištěnci — datum zahájení registrace od 1.1.2025 — celkem: {len(novi)}", + novi) + +# List 5 – Nenalezeni if nenalezeni: - ws4 = wb.create_sheet("Nenalezeni") - zapsat_list(ws4, + ws5 = wb.create_sheet("Nenalezeni") + zapsat_list(ws5, f"Bez dostatečných dat — celkem: {len(nenalezeni)}", C_TITLE_NEN, nenalezeni, C_NENALEZEN) diff --git a/Insurance/SeznamPojistencu/report_pojistenci.xlsx b/Insurance/SeznamPojistencu/report_pojistenci.xlsx index a155cc2..59fe2a0 100644 Binary files a/Insurance/SeznamPojistencu/report_pojistenci.xlsx and b/Insurance/SeznamPojistencu/report_pojistenci.xlsx differ diff --git a/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/05_klientela.py b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/05_klientela.py new file mode 100644 index 0000000..c27130f --- /dev/null +++ b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/05_klientela.py @@ -0,0 +1,135 @@ +""" +Stahování seznamu registrovaných pojištěnců ČPZP. + +Použij po 01_prihlaseni.py (ten uloží cpzp_cookies.json). + +Co dělá: + - Načte cookies z cpzp_cookies.json + - Otevře prohlížeč jednou, projde všechny zadané měsíce + - Pro každý měsíc vyplní formulář, klikne Hledat, stáhne soubor + - Přeskočí měsíce kde soubor v cílovém adresáři už existuje + - Uloží jako: YYYY-MM-DD f205MMRR.123 + +NASTAVENÍ: + OD_MESIC / OD_ROK — první měsíc rozsahu + DO_MESIC / DO_ROK — poslední měsíc rozsahu (včetně) +""" + +import glob +import json +import os +import sys +import time +from datetime import date + +from playwright.sync_api import sync_playwright + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from Knihovny.najdi_dropbox import get_dropbox_root + +OD_MESIC = 12 +OD_ROK = 2024 +DO_MESIC = 3 +DO_ROK = 2026 + +BASE_URL = "https://portal.cpzp.cz" +COOKIES_FILE = os.path.join(os.path.dirname(__file__), "..", "..", "StahováníZpráv", "205 ČPZP", "cpzp_cookies.json") +DEST_DIR = os.path.join( + get_dropbox_root(), + "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "205 ČPZP", +) + + +def mesice_v_rozsahu(od_m, od_r, do_m, do_r): + """Generuje (mesic, rok) od od_m/od_r do do_m/do_r včetně.""" + m, r = od_m, od_r + while (r, m) <= (do_r, do_m): + yield m, r + m += 1 + if m > 12: + m = 1 + r += 1 + + +def uz_stazeno(mesic: int, rok: int) -> bool: + """Vrátí True pokud soubor pro daný měsíc/rok už existuje v DEST_DIR.""" + mm = f"{mesic:02d}" + rr = str(rok)[-2:] + pattern = os.path.join(DEST_DIR, f"* f205{mm}{rr}.*") + return bool(glob.glob(pattern)) + + +def stahni_mesic(page, mesic: int, rok: int) -> bool: + """Stáhne soubor pro jeden měsíc. Vrátí True pokud staženo.""" + today = date.today().strftime("%Y-%m-%d") + + if uz_stazeno(mesic, rok): + print(f" [{mesic:02d}/{rok}] přeskočeno — soubor už existuje") + return False + + # Vyplň formulář + inputs = page.query_selector_all("input[type=text]") + if len(inputs) < 2: + print(f" [{mesic:02d}/{rok}] CHYBA — inputy nenalezeny") + return False + + inputs[0].fill(str(mesic)) + inputs[1].fill(str(rok)) + + page.get_by_text("Hledat", exact=True).click() + page.wait_for_load_state("networkidle") + + dl_selector = "a:has-text('Seznam registrovaných pojištěnců')" + if not page.query_selector(dl_selector): + print(f" [{mesic:02d}/{rok}] CHYBA — download odkaz nenalezen") + return False + + with page.expect_download() as dl_info: + page.click(dl_selector) + download = dl_info.value + + original_name = download.suggested_filename + dest_path = os.path.join(DEST_DIR, f"{today} {original_name}") + download.save_as(dest_path) + print(f" [{mesic:02d}/{rok}] OK — {os.path.basename(dest_path)}") + return True + + +def hlavni() -> None: + if not os.path.exists(COOKIES_FILE): + raise SystemExit(f"Soubor s cookies nenalezen: {COOKIES_FILE}\nNejdřív spusť 01_prihlaseni.py") + + with open(COOKIES_FILE, encoding="utf-8") as f: + cookies = json.load(f) + + os.makedirs(DEST_DIR, exist_ok=True) + + mesice = list(mesice_v_rozsahu(OD_MESIC, OD_ROK, DO_MESIC, DO_ROK)) + print(f"Celkem měsíců: {len(mesice)} ({OD_MESIC:02d}/{OD_ROK} – {DO_MESIC:02d}/{DO_ROK})") + + with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + context = browser.new_context() + context.add_cookies(cookies) + page = context.new_page() + + print("Otevírám stránku klientely...") + page.goto(f"{BASE_URL}/app/prohlizeni-klientely/") + page.wait_for_load_state("networkidle") + + if "frmPrihlasCert" in page.content(): + raise SystemExit("Cookies expirovala — nejdřív spusť 01_prihlaseni.py") + + stazeno = 0 + for mesic, rok in mesice: + if stahni_mesic(page, mesic, rok): + stazeno += 1 + time.sleep(2) + + browser.close() + + print(f"\nHotovo: {stazeno} staženo, {len(mesice) - stazeno} přeskočeno.") + + +if __name__ == "__main__": + hlavni() diff --git a/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_po_hledani.png b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_po_hledani.png new file mode 100644 index 0000000..55b195c Binary files /dev/null and b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_po_hledani.png differ diff --git a/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_pred_hledanim.html b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_pred_hledanim.html new file mode 100644 index 0000000..522683c --- /dev/null +++ b/Insurance/StahováníSeznamuPojištěnců/205 ČPZP/debug_pred_hledanim.html @@ -0,0 +1,756 @@ +
+ + + +Aktuální klientela je zobrazována vždy až po interní uzávěrce pro Kapitační centrum, tzn. s cca měsíčním zpožděním oproti aktuálnímu datu.
+ + + +| IČP | +Odbornost | +Číslo pojištěnce | +Příjmení | +Jméno | +Registrace od | +Registrace do | +
|---|---|---|---|---|---|---|
| 09305001 | +001 | +0105072528 | +Vinický | +Ondřej | +01.03.2026 | ++ |
| 09305001 | +001 | +0301214925 | +Štefanský | +Daniel | +01.05.2025 | ++ |
| 09305001 | +001 | +0657650510 | +Krehul | +Valeriia | +01.02.2026 | ++ |
| 09305001 | +001 | +435614435 | +Strnadová | +Vítězslava | +01.07.2025 | ++ |
| 09305001 | +001 | +446228471 | +Feoktistova | +Natalia | +01.09.2018 | ++ |
| 09305001 | +001 | +6504141149 | +Bečica | +Josef | +01.05.2010 | ++ |
| 09305001 | +001 | +6510130792 | +Šuhaj | +Petr | +01.06.2013 | ++ |
| 09305001 | +001 | +6758120446 | +Bečicová | +Markéta | +01.05.2010 | ++ |
| 09305001 | +001 | +6861010288 | +Štefanská | +Renáta | +01.03.2025 | ++ |
| 09305001 | +001 | +7909054780 | +Babáček | +Marek | +01.02.2017 | ++ |
| 09305001 | +001 | +8509170802 | +Neumann | +Jakub | +01.09.2015 | ++ |
| 09305001 | +001 | +8554125360 | +Grygarová | +Jana | +01.04.2011 | ++ |
| 09305001 | +001 | +9355042466 | +Bečicová | +Tereza | +01.03.2012 | ++ |
| 09305001 | +001 | +9355071297 | +Dobrohrušková | +Lucie | +01.11.2014 | ++ |
| 09305001 | +001 | +9651301253 | +Kut Citores | +Markéta | +01.11.2021 | ++ |