Files
Vladimir Buzalka a9ef60212d notebookvb
2026-05-31 07:51:29 +02:00

236 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
fill_poukaz_dp.py
-----------------
Vyplní formulář "Poukaz na vyšetření / ošetření DP" (VZP-06dp/2024).
Vstup: PDF šablona (plochý formulář)
Výstup: vyplněný PDF
Použití:
python fill_poukaz_dp.py
Závislosti:
pip install pypdf reportlab
"""
from pathlib import Path
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from pypdf import PdfReader, PdfWriter
# ---------------------------------------------------------------------------
# NASTAVENÍ — sem doplňte cestu k šabloně a výstupnímu souboru
# ---------------------------------------------------------------------------
SABLONA = Path(__file__).parent / "Poukaz DP zdroj.pdf"
VYSTUP = Path(__file__).parent / "Poukaz DP vyplneny.pdf"
# ---------------------------------------------------------------------------
# DATA FORMULÁŘE — doplňte hodnoty
# ---------------------------------------------------------------------------
DATA = {
# ── Hlavička ─────────────────────────────────────────────────────────────
"kod_pojistovny": "111", # 3 číslice (VZP = 111)
"icp": "12345678", # IČP pracoviště
"odbornost": "001", # odbornost
"datum": "22.05.2026", # datum vystavení
"poradove_cislo": "42", # pořadové č. poukazu / nepřerušené DP
"platnost_do": "22.08.2026", # platnost poukazu
# ── Pacient ──────────────────────────────────────────────────────────────
"pacient": "Novák Jan",
"cislo_pojistence": "7001015432",
"zkladni_dg": "I10", # základní diagnóza (MKN kód)
"variabilni_symbol":"123456",
"ost_dg": "E11", # ostatní diagnózy
"kod_nahrady": "",
# ── Adresa a kontakty ────────────────────────────────────────────────────
"adresa_pacienta": "Dlouhá 12, 110 00 Praha 1, tel: 777 123 456",
"dalsi_prislusnici":"ne", # "ano" nebo "ne"
"kontaktni_osoba": "Nováková Marie (manželka), tel: 777 654 321",
# ── Klinické údaje ────────────────────────────────────────────────────────
"pecovatelska_sluzba": "ne", # "ano" nebo "ne"
"mobilita": "b) omezená: chodí s holí",
"smyslove_omezeni": "zraková vada brýle",
"sebeobsluha": "b) omezená: potřebuje pomoc při hygieně",
"medikace": "Metformin 1000 mg 1-0-1, Amlodipine 5 mg 1-0-0, inzulín Lantus 20j večer",
"dalsi_informace": "alergie: penicilin; inkontinence moči; byt 2. patro bez výtahu",
"cil_dp": "Edukace v aplikaci inzulínu, kontrola glykémie, péče o DM nohu",
# ── Požadované výkony (max 5 řádků) ─────────────────────────────────────
# Každý výkon: {"kod": "06101", "popis": "...", "popis2": "..."}
"pozadovano": [
{"kod": "06101", "popis": "Komplexní ošetřovatelská péče 1× denně, 5× týdně", "popis2": ""},
{"kod": "06129", "popis": "Aplikace inzulínu 1× denně, 7× týdně", "popis2": ""},
{"kod": "06111", "popis": "Odběr biologického materiálu 1× týdně", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
],
}
# ---------------------------------------------------------------------------
# Pomocná funkce — zkrátí text pokud je moc dlouhý
# ---------------------------------------------------------------------------
def _fit(text: str, max_chars: int) -> str:
return text[:max_chars] + "" if len(text) > max_chars else text
# ---------------------------------------------------------------------------
# Vytvoří overlay stránku s textem
# ---------------------------------------------------------------------------
def _vytvor_overlay(data: dict) -> BytesIO:
buf = BytesIO()
W, H = A4 # 595.3 × 841.9 pt
c = canvas.Canvas(buf, pagesize=A4)
c.setFont("Helvetica", 8)
def txt(x, y, text, size=8, bold=False):
if not text:
return
font = "Helvetica-Bold" if bold else "Helvetica"
c.setFont(font, size)
c.drawString(x, y, str(text))
# ── Hlavička ─────────────────────────────────────────────────────────────
txt(58, 795, data["kod_pojistovny"], size=8) # Kód pojišťovny
txt(200, 800, data["icp"], size=8) # IČP
txt(200, 782, data["odbornost"], size=8) # Odbornost
txt(310, 800, data["datum"], size=8) # Datum
txt(425, 800, data["poradove_cislo"], size=8) # Pořadové č.
txt(470, 765, data["platnost_do"], size=8) # Platnost do
# ── Pacient ──────────────────────────────────────────────────────────────
txt(195, 726, _fit(data["pacient"], 40), size=8) # Pacient
txt(115, 709, data["cislo_pojistence"], size=8) # Č. pojištěnce
txt(390, 709, data["zkladni_dg"], size=8) # Základní dg.
txt(115, 692, data["variabilni_symbol"], size=8) # Variabilní symbol
txt(390, 692, data["ost_dg"], size=8) # Ost. dg.
txt(390, 675, data["kod_nahrady"], size=8) # Kód náhrady
# ── Adresa pacienta ───────────────────────────────────────────────────────
txt(335, 634, _fit(data["adresa_pacienta"], 70), size=8)
# Další příslušníci — podtrhne zvolenou možnost
if data["dalsi_prislusnici"].lower() == "ano":
txt(310, 600, "ano", size=8, bold=True)
else:
txt(323, 600, "ne", size=8, bold=True)
# ── Kontaktní osoba ───────────────────────────────────────────────────────
txt(420, 573, _fit(data["kontaktni_osoba"], 50), size=8)
# Pacient v péči pečovatelské služby
if data["pecovatelska_sluzba"].lower() == "ano":
txt(310, 536, "ano", size=8, bold=True)
else:
txt(323, 536, "ne", size=8, bold=True)
# ── Mobilita ──────────────────────────────────────────────────────────────
mob = data["mobilita"]
if mob.lower().startswith("a"):
txt(230, 521, "✓ plná", size=8, bold=True)
else:
# odstraní "b) omezená:" prefix pokud tam je
detail = mob.replace("b) omezená:", "").replace("b)omezená:", "").strip()
txt(280, 504, _fit(detail, 60), size=8)
# ── Smyslové omezení ──────────────────────────────────────────────────────
txt(280, 479, _fit(data["smyslove_omezeni"], 65), size=8)
# ── Sebeobsluha ───────────────────────────────────────────────────────────
sebo = data["sebeobsluha"]
if sebo.lower().startswith("a"):
txt(390, 452, "✓ plná", size=8, bold=True)
else:
detail = sebo.replace("b) omezená:", "").replace("b)omezená:", "").strip()
txt(280, 437, _fit(detail, 60), size=8)
# ── Medikace (max 3 řádky) ────────────────────────────────────────────────
med_lines = _rozlom(data["medikace"], 80)
for i, line in enumerate(med_lines[:3]):
txt(58, 413 - i * 14, line, size=8)
# ── Další informace (max 3 řádky) ─────────────────────────────────────────
info_lines = _rozlom(data["dalsi_informace"], 80)
for i, line in enumerate(info_lines[:3]):
txt(58, 370 - i * 14, line, size=8)
# ── Cíl DP (max 2 řádky) ──────────────────────────────────────────────────
cil_lines = _rozlom(data["cil_dp"], 80)
for i, line in enumerate(cil_lines[:2]):
txt(58, 321 - i * 14, line, size=8)
# ── Požadované výkony ─────────────────────────────────────────────────────
# Souřadnice každého řádku (y pro kód, y pro 1. text, y pro 2. text)
radky = [
(240, 263, 243), # řádek 1
(197, 210, 192), # řádek 2
(152, 163, 145), # řádek 3
(110, 120, 102), # řádek 4
( 68, 78, 60), # řádek 5
]
for i, vykon in enumerate(data["pozadovano"][:5]):
y_kod, y_txt1, y_txt2 = radky[i]
txt(58, y_kod, vykon.get("kod", ""), size=8)
txt(285, y_txt1, _fit(vykon.get("popis", ""), 75), size=8)
txt(285, y_txt2, _fit(vykon.get("popis2", ""), 75), size=8)
c.save()
buf.seek(0)
return buf
def _rozlom(text: str, sirka: int) -> list[str]:
"""Rozlomí dlouhý text na řádky po max `sirka` znacích (na hranici slova)."""
words = text.split()
lines, current = [], ""
for w in words:
if len(current) + len(w) + 1 <= sirka:
current = (current + " " + w).strip()
else:
if current:
lines.append(current)
current = w
if current:
lines.append(current)
return lines
# ---------------------------------------------------------------------------
# Hlavní funkce
# ---------------------------------------------------------------------------
def vyplnit_poukaz(data: dict, sablona: Path, vystup: Path):
if not sablona.exists():
raise FileNotFoundError(f"Šablona nenalezena: {sablona}")
overlay_buf = _vytvor_overlay(data)
reader = PdfReader(sablona)
writer = PdfWriter()
overlay = PdfReader(overlay_buf)
# Stránka 1 — překryjeme overlayem
page1 = reader.pages[0]
page1.merge_page(overlay.pages[0])
writer.add_page(page1)
# Stránka 2 — beze změny
if len(reader.pages) > 1:
writer.add_page(reader.pages[1])
vystup.parent.mkdir(parents=True, exist_ok=True)
with open(vystup, "wb") as f:
writer.write(f)
print(f"✓ Hotovo: {vystup}")
# ---------------------------------------------------------------------------
if __name__ == "__main__":
vyplnit_poukaz(DATA, SABLONA, VYSTUP)