notebookvb
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/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")
|
||||
"""
|
||||
01_parse_seznam.py
|
||||
==================
|
||||
Najde a rozparsuje dávky VZP – Seznam registrovaných pojištěnců (III-1.1.2).
|
||||
Formát souboru: Fxxxmmur.nnn, pevná šířka, kódování CP852.
|
||||
|
||||
Záhlaví (typ H, délka 20):
|
||||
HTYP C 1 0 typ věty 'H'
|
||||
HICP N 8 1 IČP lékaře
|
||||
HPUP N 5 9 počet uznávaných pojištěnců
|
||||
HROK N 2 14 poslední dvojčíslí roku
|
||||
HMES N 2 16 měsíc
|
||||
HDEN N 2 18 den
|
||||
|
||||
Registrace (typ I, délka 82):
|
||||
ITYP C 1 0 typ věty 'I'
|
||||
IPOR N 4 1 pořadové číslo
|
||||
IVS N 2 5 věková skupina
|
||||
IPRI C 30 7 příjmení
|
||||
IJME C 24 37 jméno
|
||||
ICIP C 10 61 číslo pojištěnce
|
||||
IDUOD D 8 71 datum uznání registrace (DDMMRRRR)
|
||||
ICPO C 3 79 kód pojišťovny
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from datetime import date, timedelta
|
||||
|
||||
DAVKY_DIR = Path(r"U:\Dropbox\Ordinace\Dokumentace_ke_zpracování\Zúčtovací zprávy\111 VZP Podání")
|
||||
ENCODING = "cp852"
|
||||
|
||||
|
||||
def parse_davku(path: Path) -> dict:
|
||||
"""Vrátí dict s klíči 'hlavicka' a 'pojistenci'."""
|
||||
lines = path.read_bytes().splitlines()
|
||||
hlavicka = None
|
||||
pojistenci = []
|
||||
|
||||
for raw in lines:
|
||||
line = raw.decode(ENCODING, errors="replace")
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line[0] == "H":
|
||||
hlavicka = {
|
||||
"icp": line[1:9].strip(),
|
||||
"pocet": int(line[9:14].strip() or 0),
|
||||
"rok": 2000 + int(line[14:16]),
|
||||
"mesic": int(line[16:18]),
|
||||
"den": int(line[18:20]),
|
||||
}
|
||||
|
||||
elif line[0] == "I":
|
||||
if len(line) < 82:
|
||||
continue
|
||||
dat_raw = line[71:79] # DDMMRRRR
|
||||
try:
|
||||
datum = date(int(dat_raw[4:8]), int(dat_raw[2:4]), int(dat_raw[0:2]))
|
||||
except ValueError:
|
||||
datum = None
|
||||
|
||||
pojistenci.append({
|
||||
"por": int(line[1:5].strip() or 0),
|
||||
"vs": line[5:7].strip(),
|
||||
"prijmeni": line[7:37].strip(),
|
||||
"jmeno": line[37:61].strip(),
|
||||
"cip": line[61:71].strip(),
|
||||
"datum_od": datum,
|
||||
"pojistovna": line[79:82].strip(),
|
||||
})
|
||||
|
||||
return {"hlavicka": hlavicka, "pojistenci": pojistenci, "soubor": path.name}
|
||||
|
||||
|
||||
def najdi_davky(adresar: Path) -> list[Path]:
|
||||
"""Vrátí seřazený seznam souborů odpovídajících vzoru Fxxx*.nnn."""
|
||||
return sorted(
|
||||
[p for p in adresar.iterdir()
|
||||
if re.search(r"F\d{3}.*\.\d{3}$", p.name, re.IGNORECASE)],
|
||||
key=lambda p: p.name
|
||||
)
|
||||
|
||||
|
||||
def tiskni_davku(d: dict) -> None:
|
||||
h = d["hlavicka"]
|
||||
if h:
|
||||
print(f"\n=== {d['soubor']} ===")
|
||||
print(f" IČP: {h['icp']} | datum: {h['den']:02d}.{h['mesic']:02d}.{h['rok']} | pojištěnců: {h['pocet']}")
|
||||
print(f" {'#':>4} {'Příjmení':<30} {'Jméno':<24} {'ČIP':<10} {'Datum od':>10} Pojiš.")
|
||||
print(f" {'-'*4} {'-'*30} {'-'*24} {'-'*10} {'-'*10} {'-'*6}")
|
||||
else:
|
||||
print(f"\n=== {d['soubor']} === (záhlaví chybí)")
|
||||
|
||||
for p in d["pojistenci"]:
|
||||
datum_str = p["datum_od"].strftime("%d.%m.%Y") if p["datum_od"] else "?"
|
||||
print(f" {p['por']:>4} {p['prijmeni']:<30} {p['jmeno']:<24} {p['cip']:<10} {datum_str:>10} {p['pojistovna']}")
|
||||
|
||||
|
||||
# ── MAIN ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
davky = najdi_davky(DAVKY_DIR)
|
||||
print(f"Nalezeno dávek: {len(davky)}")
|
||||
|
||||
for cesta in davky:
|
||||
data = parse_davku(cesta)
|
||||
tiskni_davku(data)
|
||||
|
||||
print(f"\nCelkem dávek: {len(davky)}")
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/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")
|
||||
"""
|
||||
02_import_do_db.py
|
||||
==================
|
||||
Vytvoří tabulku seznam_pojistencu_davky v medevio DB a naimportuje
|
||||
všechny dávky F111*.nnn ze složky Podání.
|
||||
|
||||
Spuštění:
|
||||
python 02_import_do_db.py # import všech dávek
|
||||
python 02_import_do_db.py --reset # smaže a znovu vytvoří tabulku před importem
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
|
||||
from mysql_db import connect_mysql
|
||||
|
||||
DAVKY_DIR = Path(r"U:\Dropbox\Ordinace\Dokumentace_ke_zpracování\Zúčtovací zprávy\111 VZP Podání")
|
||||
ENCODING = "cp852"
|
||||
|
||||
CREATE_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS seznam_pojistencu_davky (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
soubor VARCHAR(60) NOT NULL COMMENT 'Název souboru dávky',
|
||||
icp VARCHAR(8) NOT NULL COMMENT 'IČP lékaře (HICP)',
|
||||
davka_rok SMALLINT NOT NULL COMMENT 'Rok dávky (HROK)',
|
||||
davka_mesic TINYINT NOT NULL COMMENT 'Měsíc dávky (HMES)',
|
||||
davka_den TINYINT NOT NULL COMMENT 'Den pořízení seznamu (HDEN)',
|
||||
por SMALLINT NOT NULL COMMENT 'Pořadové číslo v dávce (IPOR)',
|
||||
vs VARCHAR(2) NOT NULL COMMENT 'Věková skupina (IVS)',
|
||||
prijmeni VARCHAR(30) NOT NULL COMMENT 'Příjmení pojištěnce (IPRI)',
|
||||
jmeno VARCHAR(24) NOT NULL COMMENT 'Jméno pojištěnce (IJME)',
|
||||
cip VARCHAR(10) NOT NULL COMMENT 'Číslo pojištěnce (ICIP)',
|
||||
datum_od DATE NULL COMMENT 'Datum uznání registrace (IDUOD)',
|
||||
pojistovna VARCHAR(3) NOT NULL COMMENT 'Kód pojišťovny (ICPO)',
|
||||
vytvoreno TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_soubor_por (soubor, por)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci
|
||||
COMMENT='VZP seznam registrovaných pojištěnců III-1.1.2';
|
||||
"""
|
||||
|
||||
|
||||
def parse_davku(path: Path) -> dict:
|
||||
lines = path.read_bytes().splitlines()
|
||||
hlavicka = None
|
||||
pojistenci = []
|
||||
|
||||
for raw in lines:
|
||||
line = raw.decode(ENCODING, errors="replace")
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line[0] == "H":
|
||||
hlavicka = {
|
||||
"icp": line[1:9].strip(),
|
||||
"pocet": int(line[9:14].strip() or 0),
|
||||
"rok": 2000 + int(line[14:16]),
|
||||
"mesic": int(line[16:18]),
|
||||
"den": int(line[18:20]),
|
||||
}
|
||||
|
||||
elif line[0] == "I":
|
||||
if len(line) < 82:
|
||||
continue
|
||||
dat_raw = line[71:79] # DDMMRRRR
|
||||
try:
|
||||
datum = date(int(dat_raw[4:8]), int(dat_raw[2:4]), int(dat_raw[0:2]))
|
||||
except ValueError:
|
||||
datum = None
|
||||
|
||||
pojistenci.append({
|
||||
"por": int(line[1:5].strip() or 0),
|
||||
"vs": line[5:7].strip(),
|
||||
"prijmeni": line[7:37].strip(),
|
||||
"jmeno": line[37:61].strip(),
|
||||
"cip": line[61:71].strip(),
|
||||
"datum_od": datum,
|
||||
"pojistovna": line[79:82].strip(),
|
||||
})
|
||||
|
||||
return {"hlavicka": hlavicka, "pojistenci": pojistenci, "soubor": path.name}
|
||||
|
||||
|
||||
def najdi_davky(adresar: Path) -> list[Path]:
|
||||
return sorted(
|
||||
[p for p in adresar.iterdir()
|
||||
if re.search(r"F\d{3}.*\.\d{3}$", p.name, re.IGNORECASE)],
|
||||
key=lambda p: p.name
|
||||
)
|
||||
|
||||
|
||||
def import_davku(cur, davka: dict) -> tuple[int, int]:
|
||||
"""Vrátí (vloženo, přeskočeno duplicit)."""
|
||||
h = davka["hlavicka"]
|
||||
if not h:
|
||||
print(f" SKIP {davka['soubor']} — chybí záhlaví")
|
||||
return 0, 0
|
||||
|
||||
rows = [
|
||||
(davka["soubor"], h["icp"], h["rok"], h["mesic"], h["den"],
|
||||
p["por"], p["vs"], p["prijmeni"], p["jmeno"],
|
||||
p["cip"], p["datum_od"], p["pojistovna"])
|
||||
for p in davka["pojistenci"]
|
||||
]
|
||||
|
||||
# INSERT IGNORE přeskočí duplicity (unikátní klíč soubor+por) bez chyby
|
||||
affected = cur.executemany(
|
||||
"""INSERT IGNORE INTO seznam_pojistencu_davky
|
||||
(soubor, icp, davka_rok, davka_mesic, davka_den,
|
||||
por, vs, prijmeni, jmeno, cip, datum_od, pojistovna)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
|
||||
rows
|
||||
)
|
||||
vlozeno = affected if affected else 0
|
||||
preskoceno = len(rows) - vlozeno
|
||||
return vlozeno, preskoceno
|
||||
|
||||
|
||||
# ── MAIN ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(description="Import VZP dávek do MySQL")
|
||||
parser.add_argument("--reset", action="store_true",
|
||||
help="Smaže a znovu vytvoří tabulku před importem")
|
||||
args = parser.parse_args()
|
||||
|
||||
conn = connect_mysql()
|
||||
cur = conn.cursor()
|
||||
|
||||
if args.reset:
|
||||
print("DROP TABLE seznam_pojistencu_davky ...")
|
||||
cur.execute("DROP TABLE IF EXISTS seznam_pojistencu_davky")
|
||||
|
||||
print("Vytváření tabulky (pokud neexistuje) ...")
|
||||
cur.execute(CREATE_SQL)
|
||||
|
||||
davky = najdi_davky(DAVKY_DIR)
|
||||
print(f"Nalezeno dávek: {len(davky)}\n")
|
||||
|
||||
celkem_vlozeno = celkem_preskoceno = 0
|
||||
|
||||
for cesta in davky:
|
||||
davka = parse_davku(cesta)
|
||||
h = davka["hlavicka"]
|
||||
if h:
|
||||
print(f"{davka['soubor']} ({h['den']:02d}.{h['mesic']:02d}.{h['rok']}, {len(davka['pojistenci'])} záznamů)")
|
||||
else:
|
||||
print(f"{davka['soubor']} (záhlaví chybí)")
|
||||
|
||||
vlozeno, preskoceno = import_davku(cur, davka)
|
||||
celkem_vlozeno += vlozeno
|
||||
celkem_preskoceno += preskoceno
|
||||
print(f" → vloženo: {vlozeno}, duplicit přeskočeno: {preskoceno}")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
print(f"\nHotovo. Celkem vloženo: {celkem_vlozeno}, přeskočeno: {celkem_preskoceno}")
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/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")
|
||||
"""
|
||||
03_porovnani_davek.py
|
||||
=====================
|
||||
Porovná všechny po sobě jdoucí dávky VZP a vytvoří Excel se třemi listy:
|
||||
1. Přehled – souhrnná tabulka přechodů (kolik odešlo / přibylo)
|
||||
2. Odešli – detail všech, kdo v dané dávce chyběli oproti předchozí
|
||||
3. Přibylo – detail všech, kdo v dané dávce přibyly oproti předchozí
|
||||
|
||||
Pro data s více soubory se použijí unikátní CIPy (dávky jsou totožné).
|
||||
"""
|
||||
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
|
||||
from mysql_db import connect_mysql
|
||||
|
||||
OUTPUT = Path(__file__).parent / "porovnani_davek.xlsx"
|
||||
|
||||
# ── Barvy ─────────────────────────────────────────────────────────────────────
|
||||
CLR_HEADER = "1F4E79" # tmavě modrá
|
||||
CLR_SUBHDR = "2E75B6" # střední modrá
|
||||
CLR_ODEŠLI = "FCE4D6" # světle lososová
|
||||
CLR_PŘIBYLO = "E2EFDA" # světle zelená
|
||||
CLR_ZEBRA = "F2F2F2" # šedá pro zebra řádky
|
||||
CLR_SUMMARY = "DEEAF1" # světle modrá pro souhrn
|
||||
|
||||
# ── Styly ─────────────────────────────────────────────────────────────────────
|
||||
def hdr_font(white=True):
|
||||
return Font(bold=True, color="FFFFFF" if white else "000000", size=11)
|
||||
|
||||
def cell_border():
|
||||
thin = Side(style="thin", color="BFBFBF")
|
||||
return Border(left=thin, right=thin, top=thin, bottom=thin)
|
||||
|
||||
def set_header(cell, text, bg=CLR_HEADER, white=True):
|
||||
cell.value = text
|
||||
cell.font = hdr_font(white)
|
||||
cell.fill = PatternFill("solid", fgColor=bg)
|
||||
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||||
cell.border = cell_border()
|
||||
|
||||
def set_cell(cell, value, bg=None, bold=False, align="left", num_fmt=None):
|
||||
cell.value = value
|
||||
cell.font = Font(bold=bold, size=10)
|
||||
cell.alignment = Alignment(horizontal=align, vertical="center")
|
||||
cell.border = cell_border()
|
||||
if bg:
|
||||
cell.fill = PatternFill("solid", fgColor=bg)
|
||||
if num_fmt:
|
||||
cell.number_format = num_fmt
|
||||
|
||||
# ── Načtení dat z DB ──────────────────────────────────────────────────────────
|
||||
|
||||
print("Připojuji se k DB ...")
|
||||
conn = connect_mysql()
|
||||
cur = conn.cursor()
|
||||
|
||||
# Unikátní CIPy per datum + jméno (z prvního souboru daného data)
|
||||
cur.execute("""
|
||||
SELECT davka_rok, davka_mesic, davka_den, cip,
|
||||
MIN(prijmeni) AS prijmeni, MIN(jmeno) AS jmeno
|
||||
FROM seznam_pojistencu_davky
|
||||
GROUP BY davka_rok, davka_mesic, davka_den, cip
|
||||
ORDER BY davka_rok, davka_mesic, davka_den
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
|
||||
# Seskupení do dict: datum -> {cip: (prijmeni, jmeno)}
|
||||
davky: dict[date, dict[str, tuple]] = defaultdict(dict)
|
||||
for rok, mes, den, cip, pri, jme in rows:
|
||||
d = date(rok, mes, den)
|
||||
davky[d][cip] = (pri, jme)
|
||||
|
||||
data = sorted(davky.keys())
|
||||
print(f"Nalezeno {len(data)} unikátních datumů dávek.")
|
||||
|
||||
# ── Porovnání ────────────────────────────────────────────────────────────────
|
||||
|
||||
prehled = [] # (dat_od, dat_do, stav_od, odešlo, přibylo, stav_do)
|
||||
odešli = [] # (dat_do, cip, prijmeni, jmeno)
|
||||
přibylo = [] # (dat_do, cip, prijmeni, jmeno)
|
||||
|
||||
for i in range(1, len(data)):
|
||||
d1, d2 = data[i-1], data[i]
|
||||
cip1 = set(davky[d1])
|
||||
cip2 = set(davky[d2])
|
||||
|
||||
vyšli = cip1 - cip2
|
||||
vstou = cip2 - cip1
|
||||
|
||||
prehled.append((d1, d2, len(cip1), len(vyšli), len(vstou), len(cip2)))
|
||||
|
||||
for cip in sorted(vyšli):
|
||||
pri, jme = davky[d1][cip]
|
||||
odešli.append((d2, cip, pri, jme))
|
||||
|
||||
for cip in sorted(vstou):
|
||||
pri, jme = davky[d2][cip]
|
||||
přibylo.append((d2, cip, pri, jme))
|
||||
|
||||
# ── Excel ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
# ── List 1: Přehled ───────────────────────────────────────────────────────────
|
||||
ws = wb.active
|
||||
ws.title = "Přehled"
|
||||
ws.freeze_panes = "A3"
|
||||
|
||||
# Nadpis
|
||||
ws.merge_cells("A1:F1")
|
||||
t = ws["A1"]
|
||||
t.value = "Porovnání po sobě jdoucích dávek VZP – seznam registrovaných pojištěnců"
|
||||
t.font = Font(bold=True, size=13, color="FFFFFF")
|
||||
t.fill = PatternFill("solid", fgColor=CLR_HEADER)
|
||||
t.alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws.row_dimensions[1].height = 28
|
||||
|
||||
# Záhlaví sloupců
|
||||
headers = ["Datum od", "Datum do", "Stav\nna začátku", "Odešlo", "Přibylo", "Stav\nna konci"]
|
||||
for col, h in enumerate(headers, 1):
|
||||
set_header(ws.cell(2, col), h)
|
||||
ws.row_dimensions[2].height = 32
|
||||
|
||||
# Data
|
||||
for row_i, (d1, d2, stav_od, vyšli, vstou, stav_do) in enumerate(prehled, 3):
|
||||
bg = CLR_ZEBRA if row_i % 2 == 0 else None
|
||||
set_cell(ws.cell(row_i, 1), d1.strftime("%d.%m.%Y"), bg, align="center")
|
||||
set_cell(ws.cell(row_i, 2), d2.strftime("%d.%m.%Y"), bg, align="center")
|
||||
set_cell(ws.cell(row_i, 3), stav_od, bg, align="right")
|
||||
set_cell(ws.cell(row_i, 4), -vyšli, CLR_ODEŠLI if vyšli else bg, bold=bool(vyšli), align="right")
|
||||
set_cell(ws.cell(row_i, 5), vstou, CLR_PŘIBYLO if vstou else bg, bold=bool(vstou), align="right")
|
||||
set_cell(ws.cell(row_i, 6), stav_do, bg, align="right")
|
||||
|
||||
# Souhrnný řádek
|
||||
sr = len(prehled) + 3
|
||||
ws.merge_cells(f"A{sr}:B{sr}")
|
||||
sc = ws.cell(sr, 1)
|
||||
sc.value = "CELKEM pohybů"
|
||||
sc.font = Font(bold=True, size=10)
|
||||
sc.fill = PatternFill("solid", fgColor=CLR_SUMMARY)
|
||||
sc.alignment = Alignment(horizontal="center", vertical="center")
|
||||
sc.border = cell_border()
|
||||
|
||||
celk_odešlo = sum(p[3] for p in prehled)
|
||||
celk_přibylo = sum(p[4] for p in prehled)
|
||||
set_cell(ws.cell(sr, 3), "", CLR_SUMMARY)
|
||||
set_cell(ws.cell(sr, 4), -celk_odešlo, CLR_SUMMARY, bold=True, align="right")
|
||||
set_cell(ws.cell(sr, 5), celk_přibylo, CLR_SUMMARY, bold=True, align="right")
|
||||
set_cell(ws.cell(sr, 6), "", CLR_SUMMARY)
|
||||
|
||||
# Šířky sloupců
|
||||
for col, w in zip(range(1, 7), [14, 14, 14, 10, 10, 12]):
|
||||
ws.column_dimensions[get_column_letter(col)].width = w
|
||||
|
||||
# ── List 2: Odešli ────────────────────────────────────────────────────────────
|
||||
ws2 = wb.create_sheet("Odešli")
|
||||
ws2.freeze_panes = "A3"
|
||||
|
||||
ws2.merge_cells("A1:D1")
|
||||
t2 = ws2["A1"]
|
||||
t2.value = f"Pacienti, kteří odešli – celkem {len(odešli)} pohybů"
|
||||
t2.font = Font(bold=True, size=13, color="FFFFFF")
|
||||
t2.fill = PatternFill("solid", fgColor="C55A11")
|
||||
t2.alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws2.row_dimensions[1].height = 28
|
||||
|
||||
for col, h in enumerate(["Datum dávky", "Číslo pojištěnce", "Příjmení", "Jméno"], 1):
|
||||
set_header(ws2.cell(2, col), h, bg="C55A11")
|
||||
ws2.row_dimensions[2].height = 24
|
||||
|
||||
for row_i, (dat, cip, pri, jme) in enumerate(odešli, 3):
|
||||
bg = CLR_ODEŠLI if row_i % 2 == 0 else None
|
||||
set_cell(ws2.cell(row_i, 1), dat.strftime("%d.%m.%Y"), bg, align="center")
|
||||
set_cell(ws2.cell(row_i, 2), cip, bg, align="center")
|
||||
set_cell(ws2.cell(row_i, 3), pri, bg)
|
||||
set_cell(ws2.cell(row_i, 4), jme, bg)
|
||||
|
||||
for col, w in zip(range(1, 5), [14, 16, 28, 22]):
|
||||
ws2.column_dimensions[get_column_letter(col)].width = w
|
||||
|
||||
# ── List 3: Přibylo ───────────────────────────────────────────────────────────
|
||||
ws3 = wb.create_sheet("Přibylo")
|
||||
ws3.freeze_panes = "A3"
|
||||
|
||||
ws3.merge_cells("A1:D1")
|
||||
t3 = ws3["A1"]
|
||||
t3.value = f"Pacienti, kteří přibylo – celkem {len(přibylo)} pohybů"
|
||||
t3.font = Font(bold=True, size=13, color="FFFFFF")
|
||||
t3.fill = PatternFill("solid", fgColor="375623")
|
||||
t3.alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws3.row_dimensions[1].height = 28
|
||||
|
||||
for col, h in enumerate(["Datum dávky", "Číslo pojištěnce", "Příjmení", "Jméno"], 1):
|
||||
set_header(ws3.cell(2, col), h, bg="375623")
|
||||
ws3.row_dimensions[2].height = 24
|
||||
|
||||
for row_i, (dat, cip, pri, jme) in enumerate(přibylo, 3):
|
||||
bg = CLR_PŘIBYLO if row_i % 2 == 0 else None
|
||||
set_cell(ws3.cell(row_i, 1), dat.strftime("%d.%m.%Y"), bg, align="center")
|
||||
set_cell(ws3.cell(row_i, 2), cip, bg, align="center")
|
||||
set_cell(ws3.cell(row_i, 3), pri, bg)
|
||||
set_cell(ws3.cell(row_i, 4), jme, bg)
|
||||
|
||||
for col, w in zip(range(1, 5), [14, 16, 28, 22]):
|
||||
ws3.column_dimensions[get_column_letter(col)].width = w
|
||||
|
||||
# ── Uložení ───────────────────────────────────────────────────────────────────
|
||||
wb.save(OUTPUT)
|
||||
print(f"\nExcel uložen: {OUTPUT}")
|
||||
print(f" Přehled: {len(prehled)} přechodů")
|
||||
print(f" Odešli: {len(odešli)} pohybů")
|
||||
print(f" Přibylo: {len(přibylo)} pohybů")
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/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")
|
||||
"""
|
||||
04_najdi_zlomy.py
|
||||
=================
|
||||
Pro pacienty z seznam_pojistencu_davky, kteří NEMAJÍ záznam v vzp_registrace_lekari
|
||||
(skript kdojelekar je nezachytil), najde bod zlomu registrace u naší ambulance.
|
||||
|
||||
Algoritmus:
|
||||
1. Dotaz VZP dnes — je pacient stále registrován u nás (ICP=09305001)?
|
||||
ANO → datum_ukonceni z odpovědi = výsledek
|
||||
2. NE → hledáme rokem dozadu (od dnes, −1 rok, −2 roky …)
|
||||
dokud nenajdeme rok kdy BYL registrován → tím ohraničíme interval [lo, hi]
|
||||
3. V intervalu [lo, hi] binární hledání na den přesně
|
||||
(nebo pokud datum_ukonceni z VZP odpovědi není 3000, použijeme ho přímo)
|
||||
|
||||
Výsledek se uloží do tabulky seznam_pojistencu_zlomy a vytiskne na konzoli.
|
||||
"""
|
||||
|
||||
import time
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
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
|
||||
|
||||
# ── Konfigurace ───────────────────────────────────────────────────────────────
|
||||
PFX_PATH = str(Path(__file__).resolve().parents[1] / "Certificates" / "picka.pfx")
|
||||
PFX_PASSWORD = "Vlado7309208104+"
|
||||
ICZ = "09305000"
|
||||
NASA_ICP = "09305001"
|
||||
API_PAUSE = 2 # sekundy mezi VZP dotazy
|
||||
|
||||
CREATE_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS seznam_pojistencu_zlomy (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
cip VARCHAR(12) NOT NULL,
|
||||
prijmeni VARCHAR(30) NOT NULL,
|
||||
jmeno VARCHAR(24) NOT NULL,
|
||||
posledni_davka DATE NOT NULL COMMENT 'Poslední měsíc kdy byl v dávce',
|
||||
zlom_datum DATE NULL COMMENT 'Poslední den registrace u nás (NULL=stále aktivní)',
|
||||
zlom_zdroj VARCHAR(60) NULL COMMENT 'Jak byl zlom určen',
|
||||
stav VARCHAR(20) NOT NULL COMMENT 'aktivní / ukončen / nenalezen',
|
||||
dotazeno_dne DATE NOT NULL,
|
||||
UNIQUE KEY uq_cip (cip)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Zlomy registrace pro pacienty bez záznamu v kdojelekar';
|
||||
"""
|
||||
|
||||
# ── VZP klient ────────────────────────────────────────────────────────────────
|
||||
vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ)
|
||||
|
||||
|
||||
def je_registrovan(rc: str, k_datu: date) -> tuple[bool, date | None]:
|
||||
"""
|
||||
Vrátí (registrován_u_nas: bool, datum_ukonceni: date|None).
|
||||
datum_ukonceni = None pokud nelze parsovat nebo pacient není u nás.
|
||||
"""
|
||||
xml = vzp.registrace_lekare(rc, k_datu.isoformat(), odbornosti=["001"])
|
||||
time.sleep(API_PAUSE)
|
||||
try:
|
||||
zaznamy = vzp.parse_registrace_lekare(xml)
|
||||
except Exception as e:
|
||||
print(f" [CHYBA parsování] {e}")
|
||||
return False, None
|
||||
|
||||
for z in zaznamy:
|
||||
if z.get("ma_lekare") and z.get("ICP") == NASA_ICP and z.get("kod_odbornosti") == "001":
|
||||
du_str = z.get("datum_ukonceni")
|
||||
try:
|
||||
du = date.fromisoformat(du_str) if du_str else None
|
||||
except ValueError:
|
||||
du = None
|
||||
return True, du
|
||||
|
||||
return False, None
|
||||
|
||||
|
||||
def najdi_zlom(rc: str, posledni_davka: date) -> tuple[date | None, str, str]:
|
||||
"""
|
||||
Vrátí (zlom_datum, zlom_zdroj, stav).
|
||||
zlom_datum = poslední den kdy byl registrován (None = stále aktivní).
|
||||
"""
|
||||
today = date.today()
|
||||
|
||||
# ── Krok 1: dotaz dnes ────────────────────────────────────────────────────
|
||||
print(f" [dnes {today}]", end=" ", flush=True)
|
||||
reg, du = je_registrovan(rc, today)
|
||||
|
||||
if reg:
|
||||
if du and du.year < 3000:
|
||||
print(f"registrován, ukončení {du}")
|
||||
return du, "VZP datum_ukonceni (dnes)", "ukončen"
|
||||
else:
|
||||
print("registrován, bez data ukončení → stále aktivní")
|
||||
return None, "VZP dnes aktivní", "aktivní"
|
||||
|
||||
print("NENÍ registrován")
|
||||
|
||||
# ── Krok 2: hledání po rocích dozadu ─────────────────────────────────────
|
||||
# lo = víme, že tam BYL (posledni_davka)
|
||||
# hi = víme, že tam NENÍ (today)
|
||||
lo: date = posledni_davka
|
||||
hi: date = today
|
||||
|
||||
probe = today.replace(year=today.year - 1)
|
||||
while probe >= posledni_davka:
|
||||
print(f" [rok {probe}]", end=" ", flush=True)
|
||||
reg_p, du_p = je_registrovan(rc, probe)
|
||||
if reg_p:
|
||||
lo = probe
|
||||
print(f"registrován")
|
||||
if du_p and du_p.year < 3000:
|
||||
print(f" → datum_ukonceni z VZP: {du_p}")
|
||||
return du_p, f"VZP datum_ukonceni (dotaz {probe})", "ukončen"
|
||||
break
|
||||
else:
|
||||
hi = probe
|
||||
print("není")
|
||||
try:
|
||||
probe = probe.replace(year=probe.year - 1)
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
# Ani v posledni_davka není registrován — neobvyklé
|
||||
print(f" ! Ani k datu {posledni_davka} není registrován — zkouším přímo")
|
||||
reg_lo, du_lo = je_registrovan(rc, posledni_davka)
|
||||
if not reg_lo:
|
||||
return None, "nenalezen ani k datu poslední dávky", "nenalezen"
|
||||
lo = posledni_davka
|
||||
if du_lo and du_lo.year < 3000:
|
||||
return du_lo, f"VZP datum_ukonceni ({posledni_davka})", "ukončen"
|
||||
|
||||
# ── Krok 3: binární hledání v intervalu [lo, hi] ─────────────────────────
|
||||
print(f" Binární hledání: {lo} … {hi}")
|
||||
iterace = 0
|
||||
while (hi - lo).days > 1:
|
||||
iterace += 1
|
||||
mid = lo + timedelta(days=(hi - lo).days // 2)
|
||||
print(f" [{iterace}. iterace: {mid}]", end=" ", flush=True)
|
||||
reg_m, du_m = je_registrovan(rc, mid)
|
||||
if reg_m:
|
||||
lo = mid
|
||||
print("registrován")
|
||||
if du_m and du_m.year < 3000:
|
||||
print(f" → datum_ukonceni z VZP: {du_m}")
|
||||
return du_m, f"VZP datum_ukonceni (binární {mid})", "ukončen"
|
||||
else:
|
||||
hi = mid
|
||||
print("není")
|
||||
|
||||
print(f" → Zlom: poslední den registrace = {lo}")
|
||||
return lo, f"binární hledání ({iterace} kroků)", "ukončen"
|
||||
|
||||
|
||||
# ── Načtení pacientů ──────────────────────────────────────────────────────────
|
||||
|
||||
conn = connect_mysql()
|
||||
cur = conn.cursor()
|
||||
cur.execute(CREATE_SQL)
|
||||
|
||||
# Unikátní CIP v seznamu (VZP, pojišťovna 111)
|
||||
cur.execute("SELECT DISTINCT cip FROM seznam_pojistencu_davky WHERE pojistovna='111'")
|
||||
vsechny_cip = {r[0] for r in cur.fetchall()}
|
||||
|
||||
# CIP které jsou v registrace_lekari (u nás, odb 001)
|
||||
cur.execute("""
|
||||
SELECT DISTINCT rc FROM vzp_registrace_lekari
|
||||
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
|
||||
""")
|
||||
zname_cip = {r[0] for r in cur.fetchall()}
|
||||
|
||||
# Nespárované
|
||||
nesparovane_cip = vsechny_cip - zname_cip
|
||||
|
||||
# Doplním jméno a posledni_davka
|
||||
cur.execute("""
|
||||
SELECT cip, MIN(prijmeni), MIN(jmeno),
|
||||
MAX(DATE(CONCAT(davka_rok, '-', LPAD(davka_mesic,2,'0'), '-01')))
|
||||
FROM seznam_pojistencu_davky
|
||||
WHERE pojistovna='111'
|
||||
GROUP BY cip
|
||||
""")
|
||||
info = {r[0]: (r[1], r[2], r[3]) for r in cur.fetchall()}
|
||||
|
||||
pacienti = [
|
||||
(cip, *info[cip])
|
||||
for cip in sorted(nesparovane_cip)
|
||||
if cip in info
|
||||
]
|
||||
|
||||
print(f"Pacientů ke zpracování: {len(pacienti)}\n")
|
||||
print("=" * 70)
|
||||
|
||||
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
|
||||
|
||||
vysledky = []
|
||||
for cip, prijmeni, jmeno, posledni_davka in pacienti:
|
||||
print(f"\n{prijmeni} {jmeno} (CIP: {cip}, poslední dávka: {posledni_davka})")
|
||||
try:
|
||||
zlom, zdroj, stav = najdi_zlom(cip, posledni_davka)
|
||||
except Exception as e:
|
||||
print(f" CHYBA: {e}")
|
||||
zlom, zdroj, stav = None, f"chyba: {e}", "chyba"
|
||||
|
||||
vysledky.append((cip, prijmeni, jmeno, posledni_davka, zlom, zdroj, stav))
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO seznam_pojistencu_zlomy
|
||||
(cip, prijmeni, jmeno, posledni_davka, zlom_datum, zlom_zdroj, stav, dotazeno_dne)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
posledni_davka=VALUES(posledni_davka),
|
||||
zlom_datum=VALUES(zlom_datum),
|
||||
zlom_zdroj=VALUES(zlom_zdroj),
|
||||
stav=VALUES(stav),
|
||||
dotazeno_dne=VALUES(dotazeno_dne)
|
||||
""", (cip, prijmeni, jmeno, posledni_davka, zlom, zdroj, stav, date.today()))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# ── Výsledky ──────────────────────────────────────────────────────────────────
|
||||
print("\n" + "=" * 70)
|
||||
print(f"\n{'Příjmení':<25} {'Jméno':<20} {'CIP':<12} {'Poslední dávka':<15} {'Zlom':<12} Stav")
|
||||
print("-" * 95)
|
||||
for cip, pri, jme, pd, zd, zdroj, stav in vysledky:
|
||||
zlom_str = str(zd) if zd else "—"
|
||||
print(f"{pri:<25} {jme:<20} {cip:<12} {str(pd):<15} {zlom_str:<12} {stav}")
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/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")
|
||||
"""
|
||||
06_nasledny_lekar.py
|
||||
====================
|
||||
Pro všechny ukončené pacienty (103) se dotáže VZP ke dni ukonceni+1
|
||||
na jejich nového praktického lékaře (odbornost 001).
|
||||
|
||||
Výsledek je jeden ze tří stavů:
|
||||
nenalezen → pacient u VZP neexistuje (zemřel / přestal být pojištěný)
|
||||
bez_lekare → pacient existuje, ale nemá GP (dosud se nepřehlásil)
|
||||
prehlasil → přehlásil se k novému lékaři (ukládáme ICP, jméno, datum)
|
||||
|
||||
Ukládá do tabulky seznam_pojistencu_nasledny_lekar.
|
||||
Přeskočí pacienty, kteří tam už jsou (resumovatelný běh).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import date, timedelta
|
||||
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"
|
||||
API_PAUSE = 2
|
||||
|
||||
CREATE_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS seznam_pojistencu_nasledny_lekar (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
cip VARCHAR(12) NOT NULL,
|
||||
prijmeni VARCHAR(60) NOT NULL,
|
||||
jmeno VARCHAR(40) NOT NULL,
|
||||
datum_ukonceni DATE NOT NULL COMMENT 'Datum ukončení u nás',
|
||||
datum_dotazu DATE NOT NULL COMMENT 'Dotaz k datu ukonceni+1',
|
||||
stav_vzp VARCHAR(20) NOT NULL COMMENT 'nenalezen / bez_lekare / prehlasil',
|
||||
stav_vyrizeni VARCHAR(10) NULL COMMENT 'stavVyrizeniPozadavku z VZP',
|
||||
novy_icp VARCHAR(20) NULL,
|
||||
novy_icz VARCHAR(20) NULL,
|
||||
novy_nazev VARCHAR(200) NULL COMMENT 'nazevSZZ — jméno lékaře',
|
||||
novy_ordinace VARCHAR(200) NULL COMMENT 'nazevICP — název ordinace',
|
||||
datum_prehlaseni DATE NULL COMMENT 'datumRegistrace u nového lékaře',
|
||||
dotazeno_dne DATE NOT NULL,
|
||||
UNIQUE KEY uq_cip (cip)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
COMMENT='Stav pojištěnce ke dni ukončení registrace u naší ordinace';
|
||||
"""
|
||||
|
||||
NS = "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1"
|
||||
|
||||
|
||||
def parse_nasledny(xml_text: str) -> dict:
|
||||
"""
|
||||
Vrátí dict s klíči: stav_vzp, stav_vyrizeni, novy_icp, novy_icz,
|
||||
novy_nazev, novy_ordinace, datum_prehlaseni.
|
||||
"""
|
||||
try:
|
||||
root = ET.fromstring(xml_text)
|
||||
except ET.ParseError:
|
||||
return {"stav_vzp": "chyba_xml", "stav_vyrizeni": None,
|
||||
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
|
||||
"novy_ordinace": None, "datum_prehlaseni": None}
|
||||
|
||||
def find(el, tag):
|
||||
e = el.find(f"{{{NS}}}{tag}")
|
||||
return e.text.strip() if e is not None and e.text else None
|
||||
|
||||
stav_vyrizeni = find(root, "stavVyrizeniPozadavku")
|
||||
|
||||
# Hledám odbornost 001 s jiným lékařem
|
||||
odbornosti = root.findall(f".//{{{NS}}}odbornost")
|
||||
for odb in odbornosti:
|
||||
# Vnější element — má ICZ
|
||||
icz = find(odb, "ICZ")
|
||||
icp = find(odb, "ICP")
|
||||
if not icp:
|
||||
continue
|
||||
# Ověřím odbornost (vnořený subelement)
|
||||
sub = odb.find(f"{{{NS}}}odbornost")
|
||||
if sub is None:
|
||||
continue
|
||||
kod = find(sub, "kod")
|
||||
if kod != "001":
|
||||
continue
|
||||
# Nalezen nový GP
|
||||
dr = date.fromisoformat(find(odb, "datumRegistrace")) \
|
||||
if find(odb, "datumRegistrace") else None
|
||||
return {
|
||||
"stav_vzp": "prehlasil",
|
||||
"stav_vyrizeni": stav_vyrizeni,
|
||||
"novy_icp": icp,
|
||||
"novy_icz": icz,
|
||||
"novy_nazev": find(odb, "nazevSZZ"),
|
||||
"novy_ordinace": find(odb, "nazevICP"),
|
||||
"datum_prehlaseni": dr,
|
||||
}
|
||||
|
||||
# Žádná odbornost 001 nenalezena
|
||||
if stav_vyrizeni == "0":
|
||||
stav = "nenalezen"
|
||||
elif stav_vyrizeni == "1":
|
||||
stav = "bez_lekare"
|
||||
else:
|
||||
# Zkusím ještě — pokud jsou nějaké záznamy ale ne 001, je to bez_lekare
|
||||
stav = "nenalezen" if not odbornosti else "bez_lekare"
|
||||
|
||||
return {"stav_vzp": stav, "stav_vyrizeni": stav_vyrizeni,
|
||||
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
|
||||
"novy_ordinace": None, "datum_prehlaseni": None}
|
||||
|
||||
|
||||
# ── Načtení ukončených pacientů ───────────────────────────────────────────────
|
||||
|
||||
conn = connect_mysql()
|
||||
cur = conn.cursor()
|
||||
cur.execute(CREATE_SQL)
|
||||
|
||||
# Ukončení z vzp_registrace_lekari
|
||||
cur.execute("""
|
||||
SELECT r.rc, r.prijmeni, r.jmeno, r.datum_ukonceni
|
||||
FROM vzp_registrace_lekari r
|
||||
INNER JOIN (
|
||||
SELECT rc, MAX(k_datu) mk
|
||||
FROM vzp_registrace_lekari
|
||||
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
|
||||
GROUP BY rc
|
||||
) l ON r.rc=l.rc AND r.k_datu=l.mk
|
||||
WHERE r.kod_odbornosti='001' AND r.ICP='09305001'
|
||||
AND r.datum_ukonceni < '3000-01-01' AND r.datum_ukonceni < CURDATE()
|
||||
""")
|
||||
pacienti = [(r[0], r[1] or "", r[2] or "", r[3]) for r in cur.fetchall()]
|
||||
|
||||
# Doplním ze zlomů (13 nespárovaných)
|
||||
cur.execute("""
|
||||
SELECT cip, prijmeni, jmeno, zlom_datum
|
||||
FROM seznam_pojistencu_zlomy
|
||||
WHERE stav='ukončen' AND zlom_datum IS NOT NULL AND zlom_datum < CURDATE()
|
||||
""")
|
||||
for r in cur.fetchall():
|
||||
if r[0] not in {p[0] for p in pacienti}:
|
||||
pacienti.append((r[0], r[1], r[2], r[3]))
|
||||
|
||||
# Přeskočím již zpracované
|
||||
cur.execute("SELECT cip FROM seznam_pojistencu_nasledny_lekar")
|
||||
hotovi = {r[0] for r in cur.fetchall()}
|
||||
ke_zpracovani = [(c, p, j, d) for c, p, j, d in pacienti if c not in hotovi]
|
||||
|
||||
print(f"Ukončených celkem: {len(pacienti)}")
|
||||
print(f"Již zpracováno: {len(hotovi)}")
|
||||
print(f"Ke zpracování: {len(ke_zpracovani)}")
|
||||
|
||||
if not ke_zpracovani:
|
||||
print("Vše již zpracováno.")
|
||||
cur.close(); conn.close(); sys.exit(0)
|
||||
|
||||
vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ)
|
||||
|
||||
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
|
||||
print()
|
||||
sirka = max(len(f"{p} {j}") for _, p, j, _ in ke_zpracovani) + 2
|
||||
|
||||
for i, (cip, pri, jme, du) in enumerate(ke_zpracovani, 1):
|
||||
datum_dotazu = du + timedelta(days=1)
|
||||
jmeno_str = f"{pri} {jme}"
|
||||
print(f"[{i:>3}/{len(ke_zpracovani)}] {jmeno_str:<{sirka}} ({cip}) k {datum_dotazu}", end=" ", flush=True)
|
||||
|
||||
try:
|
||||
xml = vzp.registrace_lekare(cip, datum_dotazu.isoformat(), odbornosti=["001"])
|
||||
time.sleep(API_PAUSE)
|
||||
vysl = parse_nasledny(xml)
|
||||
except Exception as e:
|
||||
print(f"CHYBA: {e}")
|
||||
vysl = {"stav_vzp": "chyba", "stav_vyrizeni": str(e),
|
||||
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
|
||||
"novy_ordinace": None, "datum_prehlaseni": None}
|
||||
|
||||
# Výpis
|
||||
stav = vysl["stav_vzp"]
|
||||
if stav == "prehlasil":
|
||||
print(f"→ přehlásil se: {vysl['novy_nazev']} (ICP {vysl['novy_icp']}, od {vysl['datum_prehlaseni']})")
|
||||
elif stav == "bez_lekare":
|
||||
print("→ bez nového lékaře")
|
||||
elif stav == "nenalezen":
|
||||
print("→ nenalezen (zemřel / nepojištěný)")
|
||||
else:
|
||||
print(f"→ {stav}")
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO seznam_pojistencu_nasledny_lekar
|
||||
(cip, prijmeni, jmeno, datum_ukonceni, datum_dotazu, stav_vzp,
|
||||
stav_vyrizeni, novy_icp, novy_icz, novy_nazev, novy_ordinace,
|
||||
datum_prehlaseni, dotazeno_dne)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
datum_ukonceni=VALUES(datum_ukonceni),
|
||||
datum_dotazu=VALUES(datum_dotazu),
|
||||
stav_vzp=VALUES(stav_vzp), stav_vyrizeni=VALUES(stav_vyrizeni),
|
||||
novy_icp=VALUES(novy_icp), novy_icz=VALUES(novy_icz),
|
||||
novy_nazev=VALUES(novy_nazev), novy_ordinace=VALUES(novy_ordinace),
|
||||
datum_prehlaseni=VALUES(datum_prehlaseni),
|
||||
dotazeno_dne=VALUES(dotazeno_dne)
|
||||
""", (cip, pri, jme, du, datum_dotazu, stav,
|
||||
vysl["stav_vyrizeni"], vysl["novy_icp"], vysl["novy_icz"],
|
||||
vysl["novy_nazev"], vysl["novy_ordinace"], vysl["datum_prehlaseni"],
|
||||
date.today()))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# ── Souhrn ────────────────────────────────────────────────────────────────────
|
||||
print(f"\nHotovo. Zpracováno {len(ke_zpracovani)} pacientů.")
|
||||
@@ -0,0 +1,303 @@
|
||||
#!/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")
|
||||
"""
|
||||
05_report_excel.py
|
||||
==================
|
||||
Kompletní Excel report všech VZP pojištěnců z dávek.
|
||||
Kombinuje čtyři zdroje:
|
||||
- seznam_pojistencu_davky → první/poslední dávka, zda v aktuální
|
||||
- vzp_registrace_lekari → datum_ukonceni od VZP (989 pacientů)
|
||||
- seznam_pojistencu_zlomy → bod zlomu pro 13 nespárovaných
|
||||
- seznam_pojistencu_nasledny_lekar → nový lékař / nenalezen po ukončení
|
||||
|
||||
Listy:
|
||||
1. Všichni pojištěnci – kompletní přehled, řazeno příjmení
|
||||
2. Aktivní – stále registrováni
|
||||
3. Ukončení – registrace ukončena + nový lékař / osud
|
||||
4. Nenalezeni – bez dostatečných dat
|
||||
"""
|
||||
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
|
||||
from mysql_db import connect_mysql
|
||||
|
||||
TODAY = date.today()
|
||||
OUTPUT = Path(__file__).parent / "report_pojistenci.xlsx"
|
||||
|
||||
# ── Barvy ─────────────────────────────────────────────────────────────────────
|
||||
C_HDR_DARK = "1F4E79"
|
||||
C_HDR_MED = "2E75B6"
|
||||
C_AKTIVNI = "E2EFDA" # zelená
|
||||
C_UKONCEN = "FCE4D6" # lososová
|
||||
C_NENALEZEN = "FFF2CC" # žlutá
|
||||
C_ZEBRA = "F2F2F2"
|
||||
C_TITLE_AKT = "375623"
|
||||
C_TITLE_UKO = "C55A11"
|
||||
C_TITLE_NEN = "7F6000"
|
||||
|
||||
THIN = Side(style="thin", color="BFBFBF")
|
||||
|
||||
def border():
|
||||
return Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||
|
||||
def hdr(cell, text, bg=C_HDR_DARK, fg="FFFFFF", size=10):
|
||||
cell.value = text
|
||||
cell.font = Font(bold=True, color=fg, size=size)
|
||||
cell.fill = PatternFill("solid", fgColor=bg)
|
||||
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||||
cell.border = border()
|
||||
|
||||
def cell(ws, row, col, value, bg=None, bold=False, align="left", fmt=None, color="000000"):
|
||||
c = ws.cell(row, col, value)
|
||||
c.font = Font(bold=bold, size=10, color=color)
|
||||
c.alignment = Alignment(horizontal=align, vertical="center")
|
||||
c.border = border()
|
||||
if bg:
|
||||
c.fill = PatternFill("solid", fgColor=bg)
|
||||
if fmt:
|
||||
c.number_format = fmt
|
||||
return c
|
||||
|
||||
def title_row(ws, row, text, ncols, bg, fg="FFFFFF", height=26):
|
||||
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=ncols)
|
||||
c = ws.cell(row, 1)
|
||||
c.value = text
|
||||
c.font = Font(bold=True, size=13, color=fg)
|
||||
c.fill = PatternFill("solid", fgColor=bg)
|
||||
c.alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws.row_dimensions[row].height = height
|
||||
|
||||
def autofit(ws, widths):
|
||||
for i, w in enumerate(widths, 1):
|
||||
ws.column_dimensions[get_column_letter(i)].width = w
|
||||
|
||||
# ── Načtení dat ───────────────────────────────────────────────────────────────
|
||||
print("Načítám data z DB ...")
|
||||
conn = connect_mysql()
|
||||
cur = conn.cursor()
|
||||
|
||||
# 1. Ze seznam_pojistencu_davky: rozsah přítomnosti + počet dávek
|
||||
cur.execute("""
|
||||
SELECT
|
||||
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,
|
||||
COUNT(DISTINCT CONCAT(davka_rok,davka_mesic)) AS pocet_davek
|
||||
FROM seznam_pojistencu_davky
|
||||
WHERE pojistovna='111'
|
||||
GROUP BY cip
|
||||
""")
|
||||
seznam = {r[0]: {"prijmeni": r[1], "jmeno": r[2],
|
||||
"prvni": r[3], "posledni": r[4], "pocet_davek": r[5]}
|
||||
for r in cur.fetchall()}
|
||||
|
||||
# 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')))
|
||||
FROM seznam_pojistencu_davky WHERE pojistovna='111'
|
||||
""")
|
||||
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')))
|
||||
FROM seznam_pojistencu_davky WHERE pojistovna='111'
|
||||
)
|
||||
""")
|
||||
v_aktualni = {r[0] for r in cur.fetchall()}
|
||||
|
||||
# 2. Z vzp_registrace_lekari: nejnovější záznam na pacienta (u nás, odb 001)
|
||||
cur.execute("""
|
||||
SELECT r.rc, r.datum_zahajeni, r.datum_ukonceni, r.k_datu
|
||||
FROM vzp_registrace_lekari r
|
||||
INNER JOIN (
|
||||
SELECT rc, MAX(k_datu) AS max_k
|
||||
FROM vzp_registrace_lekari
|
||||
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
|
||||
GROUP BY rc
|
||||
) latest ON r.rc = latest.rc AND r.k_datu = latest.max_k
|
||||
WHERE r.kod_odbornosti='001' AND r.ICP='09305001' AND r.ma_lekare=1
|
||||
""")
|
||||
registrace = {r[0]: {"zahajeni": r[1], "ukonceni": r[2], "k_datu": r[3]}
|
||||
for r in cur.fetchall()}
|
||||
|
||||
# 3. Ze seznam_pojistencu_zlomy (13 nespárovaných)
|
||||
cur.execute("SELECT cip, zlom_datum, zlom_zdroj, stav FROM seznam_pojistencu_zlomy")
|
||||
zlomy = {r[0]: {"ukonceni": r[1], "zdroj": r[2], "stav": r[3]}
|
||||
for r in cur.fetchall()}
|
||||
|
||||
# 4. Následný lékař po ukončení
|
||||
cur.execute("""
|
||||
SELECT cip, stav_vzp, novy_nazev, novy_ordinace, novy_icp, datum_prehlaseni
|
||||
FROM seznam_pojistencu_nasledny_lekar
|
||||
""")
|
||||
nasledni = {r[0]: {"stav_vzp": r[1], "novy_nazev": r[2],
|
||||
"novy_ordinace": r[3], "novy_icp": r[4],
|
||||
"datum_prehlaseni": r[5]}
|
||||
for r in cur.fetchall()}
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# ── Sestavení řádků ───────────────────────────────────────────────────────────
|
||||
print(f"Pacientů celkem: {len(seznam)}")
|
||||
|
||||
radky = []
|
||||
for cip, s in seznam.items():
|
||||
v_akt = cip in v_aktualni
|
||||
|
||||
if cip in registrace:
|
||||
reg = registrace[cip]
|
||||
du = reg["ukonceni"]
|
||||
zdroj = f"vzp_registrace ({reg['k_datu']})"
|
||||
elif cip in zlomy:
|
||||
z = zlomy[cip]
|
||||
du = z["ukonceni"]
|
||||
zdroj = z["zdroj"]
|
||||
else:
|
||||
du = None
|
||||
zdroj = "—"
|
||||
|
||||
if du is None:
|
||||
stav = "aktivní"
|
||||
elif du.year >= 3000:
|
||||
stav = "aktivní"
|
||||
du = None # nezobrazujeme 3000-01-01
|
||||
elif du >= TODAY:
|
||||
stav = "aktivní"
|
||||
else:
|
||||
stav = "ukončen"
|
||||
|
||||
if zdroj == "—" and not v_akt:
|
||||
stav = "nenalezen"
|
||||
|
||||
# Následný lékař
|
||||
nl = nasledni.get(cip)
|
||||
if nl:
|
||||
po_ukonceni = nl["stav_vzp"] # prehlasil / nenalezen / bez_lekare
|
||||
novy_lekar = nl["novy_nazev"] or nl["novy_ordinace"] or ""
|
||||
if nl["novy_icp"]:
|
||||
novy_lekar += f" (ICP {nl['novy_icp']})"
|
||||
datum_prehl = nl["datum_prehlaseni"]
|
||||
else:
|
||||
po_ukonceni = ""
|
||||
novy_lekar = ""
|
||||
datum_prehl = None
|
||||
|
||||
radky.append({
|
||||
"cip": cip,
|
||||
"prijmeni": s["prijmeni"],
|
||||
"jmeno": s["jmeno"],
|
||||
"prvni": s["prvni"],
|
||||
"posledni": s["posledni"],
|
||||
"pocet_davek": s["pocet_davek"],
|
||||
"v_aktualni": v_akt,
|
||||
"ukonceni": du,
|
||||
"zdroj": zdroj,
|
||||
"stav": stav,
|
||||
"po_ukonceni": po_ukonceni,
|
||||
"novy_lekar": novy_lekar,
|
||||
"datum_prehl": datum_prehl,
|
||||
})
|
||||
|
||||
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)
|
||||
nenalezeni = [r for r in radky if r["stav"] == "nenalezen"]
|
||||
|
||||
print(f" Aktivní: {len(aktivni)}")
|
||||
print(f" Ukončení: {len(ukonceni)}")
|
||||
print(f" Nenalezeni: {len(nenalezeni)}")
|
||||
|
||||
# ── Excel ─────────────────────────────────────────────────────────────────────
|
||||
SLOUPCE_ALL = ["Příjmení", "Jméno", "ČIP", "První\ndávka", "Poslední\ndávka",
|
||||
"Počet\ndávek", "V aktuální\ndávce", "Ukončení\nregistrace",
|
||||
"Stav", "Po ukončení", "Nový lékař / poznámka", "Datum\npřehlášení"]
|
||||
WIDTHS_ALL = [24, 18, 13, 12, 13, 9, 10, 14, 10, 13, 40, 13]
|
||||
|
||||
SLOUPCE = SLOUPCE_ALL # alias pro funkci zapsat_list
|
||||
WIDTHS = WIDTHS_ALL
|
||||
NCOLS = len(SLOUPCE)
|
||||
DATE_FMT = "DD.MM.YYYY"
|
||||
|
||||
def zapsat_list(ws, nadpis, bg_title, seznam_radku, stav_bg):
|
||||
ws.freeze_panes = "A3"
|
||||
title_row(ws, 1, nadpis, NCOLS, bg_title)
|
||||
ws.row_dimensions[2].height = 30
|
||||
for col, h in enumerate(SLOUPCE, 1):
|
||||
hdr(ws.cell(2, col), h)
|
||||
autofit(ws, WIDTHS)
|
||||
|
||||
PO_CLR = {"prehlasil": "375623", "nenalezen": "C55A11",
|
||||
"bez_lekare": "7F6000", "": "000000"}
|
||||
PO_TXT = {"prehlasil": "přehlásil se", "nenalezen": "nenalezen",
|
||||
"bez_lekare": "bez lékaře", "": ""}
|
||||
|
||||
for ri, r in enumerate(seznam_radku, 3):
|
||||
bg = stav_bg 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")
|
||||
cell(ws, ri, 4, r["prvni"], bg, align="center", fmt=DATE_FMT)
|
||||
cell(ws, ri, 5, r["posledni"], 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["ukonceni"], bg, align="center", fmt=DATE_FMT)
|
||||
cell(ws, ri, 9, r["stav"], bg, align="center", bold=True,
|
||||
color=("375623" if r["stav"]=="aktivní" else
|
||||
"C55A11" if r["stav"]=="ukončen" else "7F6000"))
|
||||
po = r.get("po_ukonceni", "")
|
||||
cell(ws, ri, 10, PO_TXT.get(po, po), bg, align="center", bold=bool(po),
|
||||
color=PO_CLR.get(po, "000000"))
|
||||
cell(ws, ri, 11, r.get("novy_lekar", ""), bg)
|
||||
cell(ws, ri, 12, r.get("datum_prehl"), bg, align="center", fmt=DATE_FMT)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
# List 1 – Všichni
|
||||
ws1 = wb.active
|
||||
ws1.title = "Všichni pojištěnci"
|
||||
zapsat_list(ws1,
|
||||
f"VZP pojištěnci — kompletní přehled ({TODAY.strftime('%d.%m.%Y')}) | "
|
||||
f"celkem: {len(radky)} | aktivní: {len(aktivni)} | ukončení: {len(ukonceni)}",
|
||||
C_HDR_DARK, radky, C_ZEBRA)
|
||||
|
||||
# List 2 – Aktivní
|
||||
ws2 = wb.create_sheet("Aktivní")
|
||||
zapsat_list(ws2,
|
||||
f"Aktivní pojištěnci ({TODAY.strftime('%d.%m.%Y')}) — celkem: {len(aktivni)}",
|
||||
C_TITLE_AKT, aktivni, C_AKTIVNI)
|
||||
|
||||
# List 3 – Ukončení
|
||||
ws3 = wb.create_sheet("Ukončení")
|
||||
zapsat_list(ws3,
|
||||
f"Ukončená registrace — celkem: {len(ukonceni)}",
|
||||
C_TITLE_UKO, ukonceni, C_UKONCEN)
|
||||
|
||||
# List 4 – Nenalezeni
|
||||
if nenalezeni:
|
||||
ws4 = wb.create_sheet("Nenalezeni")
|
||||
zapsat_list(ws4,
|
||||
f"Bez dostatečných dat — celkem: {len(nenalezeni)}",
|
||||
C_TITLE_NEN, nenalezeni, C_NENALEZEN)
|
||||
|
||||
wb.save(OUTPUT)
|
||||
print(f"\nExcel uložen: {OUTPUT}")
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user