236 lines
12 KiB
Python
236 lines
12 KiB
Python
"""
|
||
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)
|