""" Export prehledu lekoveho zaznamu pacienta do Excelu. Nastaveni: RODNE_CISLO ... rodne cislo pacienta (s lomitkem i bez) DATUM_OD ... predpisy od tohoto data ve formatu DD.MM.RRRR (None = vsechny) VYSTUP_DIR ... slozka kam se ulozi Excel (None = stejna slozka jako skript) """ from datetime import datetime from pathlib import Path import sys import fdb import pymysql import pymysql.cursors from openpyxl import Workbook from openpyxl.styles import (Font, PatternFill, Alignment, Border, Side, GradientFill) from openpyxl.utils import get_column_letter # Kody odbornosti dle SUKL / VZP (posledni 3 cislice ICP) ODBORNOST = { "001": "Praktický lékař", "002": "Pediatr (prakt.)", "003": "Chirurgie", "004": "Ortopedie", "005": "ORL", "006": "Gynekologie", "007": "Urologie", "008": "Neurologie", "009": "Psychiatrie", "010": "Oftalmologie", "011": "Zubní lékařství", "012": "Dermatovenerologie", "013": "Infekční lékařství", "014": "Radiodiagnostika", "015": "Stomatochirurgie", "016": "Čelistní ortopedie", "017": "Dětská psychiatrie", "018": "Pneumologie", "019": "Anesteziologie", "020": "Rehabilitace", "021": "Radiodiagnostika", "022": "Radioterapie", "023": "Nukleární medicína", "024": "Klin. biochemie", "025": "Alergologie/imunologie", "026": "Hematologie", "027": "Soudní lékařství", "028": "Soudní psychiatrie", "029": "Lékařská genetika", "031": "Gastroenterologie", "032": "Nefrologie", "033": "Kardiologie", "034": "Endokrinologie/diab.", "035": "Revmatologie", "040": "Vnitřní lékařství", "041": "Geriatrie", "042": "Klin. farmakologie", "043": "Diabetologie", "044": "Endokrinologie", "045": "Hepatologie", "052": "Dětská neurologie", "060": "Dětská chirurgie", "065": "Plastická chirurgie", "066": "Cévní chirurgie", "067": "Kardiochirurgie", "072": "Foniatrie", "074": "Neurochirurgie", "077": "Maxilofaciální chir.", "079": "Hrudní chirurgie", "082": "Urologie", "083": "Andrologie", "085": "Proktologie", "091": "Gynekolog. onkologie", "092": "Reprodukční medicína", "096": "Léčebná rehabilitace", "097": "Fyzioterapie", "101": "Vnitřní lékařství", "102": "Kardiologie", "104": "Kardiologie", "105": "Gastroenterologie", "106": "Hepatologie", "107": "Nefrologie", "108": "Nefrologie", "110": "Diabetologie", "111": "Endokrinologie", "114": "Pneumologie", "115": "Ftizeologie", "121": "Endokrinologie", "122": "Diabetologie", "129": "Andrologie", "143": "Psychiatrie", "144": "Psychoterapie", "145": "Adiktologie", "148": "Dětská psychiatrie", "155": "Oční onkologie", "156": "Hematologie", "157": "Hemostáza", "160": "Neurologie", "162": "Epileptologie", "163": "Dětská neurologie", "164": "Neurorehabilit.", "168": "Klin. neurofyziologie", "169": "Revmatologie", "174": "Ortoped. protetika", "181": "Infektologie", "183": "Tropická medicína", "185": "Mikrobiologie", "188": "Virologie", "200": "Stomatologie", "201": "Stomatochirurgie", "202": "Maxilofaciální chir.", "203": "Parodontologie", "204": "Ortodoncie", "205": "Zubní protetika", "206": "Dětská stomatologie", "220": "Pediatrie", "221": "Neonatologie", "222": "Dětská endokrinol.", "223": "Dětská gastroenterol.", "234": "Dětská hematologie", "239": "Dětská nefrologie", "243": "Dětská pneumologie", "245": "Dětská psychiatrie", "246": "Dětská revmatologie", "247": "Dětská kardiologie", "250": "Dětská neurologie", "251": "Dětská neurologie", "258": "Dětská onkologie", "261": "Dětská chirurgie", "262": "Dětská ortopedie", "263": "Urologie", "264": "Dětská stomatologie", "271": "Dětská klin. biochem.", "272": "Alergologie", "273": "Dětská alergologie", "281": "Dětská dermatologie", "282": "Dětská radiologie", "283": "Dětská neurochir.", "289": "Dětská kardiochir.", "291": "Dětská onkol. chir.", "294": "Dětská oftalmologie", "295": "Dětská gynekologie", "300": "Onkologie", "301": "Klin. onkologie", "302": "Radiodiagnostika", "303": "Radioterapie", "304": "Nukleární medicína", "305": "Nukleární kardiologie", "316": "Klin. genetika", "319": "Soudní lékařství", "321": "Cytologie", "324": "Klin. onkologie", "333": "Onkologie", "501": "Zubní lékařství", "502": "Čelistní ortopedie", "503": "Stomatochirurgie", "508": "Parodontologie", "509": "Ortodoncie", "510": "Dětská stomatologie", "513": "Zubní protetika", "535": "Orální medicína", "555": "Stomatologie", "558": "Zubní lékařství", "559": "Stomatologie", "560": "Stomatologie", "562": "Stomatologie", "571": "Stomatologie", "574": "Stomatologie", "580": "Stomatologie", "581": "Stomatologie", "582": "Stomatologie", "584": "Stomatologie", "590": "Lékárenství", "600": "Onkologie", "601": "Klin. onkologie", "603": "Onkologie", "606": "Radioterapie", "607": "Nukleární medicína", "615": "Onkologie", "700": "Chirurgie", "701": "Cévní chirurgie", "702": "Hrudní chirurgie", "704": "Kardiochirurgie", "705": "Chirurgie", "706": "Plastická chirurgie", "719": "Dětská chirurgie", "721": "Ortopedie", "722": "Ortopedie", "723": "Ortopedie", "801": "Fyzioterapie", "802": "Ergoterapie", "852": "Fyzioterapie", "853": "Fyzioterapie", "858": "Fyzioterapie", "860": "Fyzioterapie", "862": "Fyzioterapie", "873": "Fyzioterapie", "880": "Rehabilitace", "881": "Endokrinologie", "885": "Rehabilitace", "889": "Rehabilitace", "890": "Rehabilitace", } def odbornost_z_icp(icp): """Vrati nazev odbornosti z ICP kodu (posledni 3 cislice).""" if not icp or len(icp) < 3: return "" return ODBORNOST.get(icp[-3:], f"odb. {icp[-3:]}") # ── NASTAVENÍ ───────────────────────────────────────────────────────────────── RODNE_CISLO = "440802/018" DATUM_OD = "01.01.2025" # None = vsechny predpisy VYSTUP_DIR = None # None = stejny adresar jako skript # ───────────────────────────────────────────────────────────────────────────── FB = dict( dsn = r"localhost:c:\medicus 3\data\medicus.fdb", user = "SYSDBA", password = "masterkey", charset = "win1250", ) DB = dict( host = "192.168.1.76", user = "root", password = "Vlado9674+", database = "medicus", charset = "utf8mb4", cursorclass = pymysql.cursors.DictCursor, ) # ── Barvy ───────────────────────────────────────────────────────────────────── C_HEADER_BG = "1F4E79" # tmave modra — hlavicka tabulky C_HEADER_FG = "FFFFFF" # bila — text hlavicky C_TITLE_BG = "2E75B6" # stredni modra — nadpis sekce C_TITLE_FG = "FFFFFF" C_INFO_BG = "DEEAF1" # svetle modra — info o pacientovi C_ROW_ODD = "FFFFFF" # bila C_ROW_EVEN = "EBF3FB" # velmi svetle modra — striped C_NEVYZV_BG = "FCE4D6" # lososova — nevyzvednuto C_BORDER = "B8CCE4" def thin_border(): s = Side(style="thin", color=C_BORDER) return Border(left=s, right=s, top=s, bottom=s) def header_fill(color): return PatternFill("solid", fgColor=color) def parse_datum(s, nazev): try: return datetime.strptime(s, "%d.%m.%Y").date() except (ValueError, TypeError): sys.exit(f"Spatny format data '{nazev}': '{s}'") def najdi_v_firebirdu(rc): rc = rc.replace("/", "").replace(" ", "") conn = fdb.connect(**FB) try: cur = conn.cursor() cur.execute("SELECT KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR FROM KAR WHERE KAR.RODCIS = ?", (rc,)) row = cur.fetchone() if not row: sys.exit(f"Rodne cislo '{rc}' nenalezeno v Medicusu.") return {"prijmeni": row[0].strip(), "jmeno": row[1].strip(), "datnar": row[2]} finally: conn.close() def nacti_data(prijmeni, datum_narozeni, datum_od): conn = pymysql.connect(**DB) try: with conn.cursor() as cur: cur.execute( "SELECT id, prijmeni, jmena, datum_narozeni FROM pacient " "WHERE prijmeni = %s AND datum_narozeni = %s", (prijmeni, datum_narozeni) ) pac = cur.fetchone() if not pac: sys.exit(f"Pacient '{prijmeni}' nar. {datum_narozeni} nema zaznam v MySQL.") # Lekari cur.execute(""" SELECT pr.prijmeni, pr.jmena, pr.icp, pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto, COUNT(*) AS pocet FROM zprava z JOIN predpis p ON p.zprava_id = z.id JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho WHERE z.pacient_id = %s GROUP BY pr.lekar_kod, pr.prijmeni, pr.jmena, pr.icp, pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto ORDER BY pocet DESC """, (pac["id"],)) lekari = cur.fetchall() # Predpisy podminka = "AND p.datum_vystaveni >= %s" if datum_od else "" params = (pac["id"], datum_od) if datum_od else (pac["id"],) cur.execute(f""" SELECT p.datum_vystaveni, COALESCE(v.nazev, p.nazev) AS vydany_lek, v.nazev IS NULL AS nevyzvednuto, p.atc, p.navod, pr.prijmeni AS lek_prijmeni, pr.jmena AS lek_jmena, pr.icp, pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto FROM zprava z JOIN predpis p ON p.zprava_id = z.id JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis WHERE z.pacient_id = %s {podminka} ORDER BY p.datum_vystaveni DESC """, params) predpisy = cur.fetchall() return pac, lekari, predpisy finally: conn.close() def nastav_sirky(ws, sirky): for col, width in sirky.items(): ws.column_dimensions[col].width = width def autofit(ws, min_width=5, max_width=60, padding=2): """Autofit sloupcu a radku podle obsahu.""" col_widths = {} for row in ws.iter_rows(): for cell in row: if cell.value is None: continue # Preskoc mergnuté bunky — jejich sirka se pocita ze zakladni bunky if isinstance(cell, type(cell)) and hasattr(cell, 'column'): text = str(cell.value) # Tučný text je trochu širší factor = 1.15 if (cell.font and cell.font.bold) else 1.0 width = len(text) * factor + padding col = get_column_letter(cell.column) col_widths[col] = max(col_widths.get(col, min_width), width) for col, width in col_widths.items(): ws.column_dimensions[col].width = min(max(width, min_width), max_width) # Autofit výšky řádků (wrap_text obsah) for row in ws.iter_rows(): max_lines = 1 for cell in row: if cell.value and cell.alignment and cell.alignment.wrap_text: col_w = ws.column_dimensions[get_column_letter(cell.column)].width or 10 lines = max(1, int(len(str(cell.value)) / max(col_w, 1)) + 1) max_lines = max(max_lines, lines) row_num = row[0].row if max_lines > 1: ws.row_dimensions[row_num].height = max(ws.row_dimensions[row_num].height or 15, max_lines * 14) def zapis_nadpis_sekce(ws, row, text, n_cols): ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=n_cols) cell = ws.cell(row=row, column=1, value=text) cell.font = Font(name="Arial", bold=True, size=11, color=C_TITLE_FG) cell.fill = header_fill(C_TITLE_BG) cell.alignment = Alignment(horizontal="left", vertical="center", indent=1) ws.row_dimensions[row].height = 20 return row + 1 def zapis_hlavicku(ws, row, hlavicka, n_cols=None): for col, text in enumerate(hlavicka, 1): cell = ws.cell(row=row, column=col, value=text) cell.font = Font(name="Arial", bold=True, size=10, color=C_HEADER_FG) cell.fill = header_fill(C_HEADER_BG) cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) cell.border = thin_border() ws.row_dimensions[row].height = 28 return row + 1 def zapis_radek(ws, row, hodnoty, highlight=False): bg = C_NEVYZV_BG if highlight else (C_ROW_EVEN if row % 2 == 0 else C_ROW_ODD) fill = header_fill(bg) for col, val in enumerate(hodnoty, 1): cell = ws.cell(row=row, column=col, value=val) cell.font = Font(name="Arial", size=10) cell.fill = fill cell.alignment = Alignment(vertical="center", wrap_text=True) cell.border = thin_border() ws.row_dimensions[row].height = 18 return row + 1 def vytvor_excel(pac, lekari, predpisy, datum_od, fb_pac): wb = Workbook() ws = wb.active ws.title = "Lekovy zaznam" # ── Záhlaví — info o pacientovi ────────────────────────────────────────── n_cols = 8 ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=n_cols) title_cell = ws.cell(row=1, column=1, value=f"LÉKOVÝ ZÁZNAM — {pac['prijmeni'].upper()} {fb_pac['jmeno'].upper()}") title_cell.font = Font(name="Arial", bold=True, size=14, color=C_HEADER_FG) title_cell.fill = header_fill(C_HEADER_BG) title_cell.alignment = Alignment(horizontal="left", vertical="center", indent=1) ws.row_dimensions[1].height = 32 info = [ ("Datum narození:", pac["datum_narozeni"].strftime("%d.%m.%Y")), ("Datum tisku:", datetime.today().strftime("%d.%m.%Y")), ("Předpisy od:", datum_od.strftime("%d.%m.%Y") if datum_od else "vše"), ] for i, (label, val) in enumerate(info, 2): ws.merge_cells(start_row=i, start_column=1, end_row=i, end_column=2) ws.merge_cells(start_row=i, start_column=3, end_row=i, end_column=n_cols) lbl = ws.cell(row=i, column=1, value=label) lbl.font = Font(name="Arial", bold=True, size=10) lbl.fill = header_fill(C_INFO_BG) lbl.alignment = Alignment(vertical="center", indent=1) val_cell = ws.cell(row=i, column=3, value=val) val_cell.font = Font(name="Arial", size=10) val_cell.fill = header_fill(C_INFO_BG) val_cell.alignment = Alignment(vertical="center") ws.row_dimensions[i].height = 16 row = len(info) + 3 # prázdný řádek # ── Tabulka lékařů ─────────────────────────────────────────────────────── row = zapis_nadpis_sekce(ws, row, "PŘEDEPISUJÍCÍ LÉKAŘI", n_cols) row = zapis_hlavicku(ws, row, ["#", "Lékař", "Odbornost", "Pracoviště", "Ulice", "PSČ", "Město", "Předpisů"]) for i, r in enumerate(lekari, 1): adresa_ulice = r.get("ulice") or "" row = zapis_radek(ws, row, [ i, f"{r['prijmeni']} {r['jmena']}", odbornost_z_icp(r.get("icp")), r.get("pzs_nazev") or "", adresa_ulice, r.get("psc") or "", r.get("mesto") or "", r["pocet"], ]) row += 1 # prázdný řádek # ── Tabulka předpisů ───────────────────────────────────────────────────── od_text = datum_od.strftime("%d.%m.%Y") if datum_od else "vše" row = zapis_nadpis_sekce(ws, row, f"VŠECHNY PŘEDPISY (od {od_text}) — celkem {len(predpisy)}", n_cols) row = zapis_hlavicku(ws, row, ["#", "Datum", "Vydaný lék", "ATC", "Návod", "Lékař", "Odbornost", "Pracoviště a adresa"]) for i, r in enumerate(predpisy, 1): nevyzv = bool(r["nevyzvednuto"]) adresa = (f"{r.get('pzs_nazev') or ''}, {r.get('ulice') or ''}, " f"{r.get('psc') or ''} {r.get('mesto') or ''}").strip(", ") row = zapis_radek(ws, row, [ i, r["datum_vystaveni"].strftime("%d.%m.%Y") if r["datum_vystaveni"] else "", r["vydany_lek"], r.get("atc") or "", r.get("navod") or "", f"{r['lek_prijmeni']} {r['lek_jmena']}", odbornost_z_icp(r.get("icp")), adresa, ], highlight=nevyzv) # ── Autofit sloupců a řádků ─────────────────────────────────────────────── autofit(ws, min_width=5, max_width=60) # Zmraz záhlaví ws.freeze_panes = "A2" return wb def main(): datum_od = parse_datum(DATUM_OD, "DATUM_OD") if DATUM_OD else None fb_pac = najdi_v_firebirdu(RODNE_CISLO) prijmeni = fb_pac["prijmeni"] datum_narozeni = fb_pac["datnar"] print(f"Nacitam data: {prijmeni} {fb_pac['jmeno']} nar. {datum_narozeni} ...") pac, lekari, predpisy = nacti_data(prijmeni, datum_narozeni, datum_od) print(f" {len(lekari)} lekaru, {len(predpisy)} predpisu") wb = vytvor_excel(pac, lekari, predpisy, datum_od, fb_pac) vyst = Path(VYSTUP_DIR) if VYSTUP_DIR else Path(__file__).parent zaklad = vyst / f"LZ_{prijmeni}_{fb_pac['jmeno']}_{datum_narozeni}.xlsx" if not zaklad.exists(): soubor = zaklad else: i = 2 while True: soubor = vyst / f"LZ_{prijmeni}_{fb_pac['jmeno']}_{datum_narozeni}_v{i}.xlsx" if not soubor.exists(): break i += 1 wb.save(soubor) print(f"Ulozeno: {soubor}") if __name__ == "__main__": main()