diff --git a/MedicusWithClaudeKomplexniReport/010 Můj skript b/MedicusWithClaudeKomplexniReport/010 Můj skript new file mode 100644 index 0000000..e8dda84 --- /dev/null +++ b/MedicusWithClaudeKomplexniReport/010 Můj skript @@ -0,0 +1,379 @@ +import os +import fdb +import csv,time,pandas as pd +import openpyxl + + +PathToSaveCSV=r"z:\Dropbox\Ordinace\Reporty" +timestr = time.strftime("%Y-%m-%d %H-%M-%S ") +CSVname="Pacienti.xlsx" + +# ================= DELETE OLD REPORTS (KEEP TODAY) ================== +from datetime import datetime + +today = datetime.now().strftime("%Y-%m-%d") + +for fname in os.listdir(PathToSaveCSV): + if fname.endswith("Pacienti.xlsx"): + file_date = fname[:10] # first 10 chars = YYYY-MM-DD + if file_date != today: # delete only older files + try: + os.remove(os.path.join(PathToSaveCSV, fname)) + print(f"🗑️ Deleted old report: {fname}") + except Exception as e: + print(f"⚠️ Could not delete {fname}: {e}") + + +con = fdb.connect( + host='192.168.1.10', database=r'm:\MEDICUS\data\medicus.FDB', + user='sysdba', password='masterkey',charset='WIN1250') + +#Server=192.168.1.10 +#Path=M:\Medicus\Data\Medicus.fdb + +# Create a Cursor object that operates in the context of Connection con: +cur = con.cursor() + +# import openpyxl module +import openpyxl +import xlwings as xw +wb = openpyxl.Workbook() +sheet = wb.active +# wb.save("sample.xlsx") + + +#Načtení očkování registrovaných pacientů +cur.execute("select rodcis,prijmeni,jmeno,ockzaz.datum,kodmz,ockzaz.poznamka,latka,nazev,expire from registr join kar on registr.idpac=kar.idpac join ockzaz on registr.idpac=ockzaz.idpac where datum_zruseni is null and kar.vyrazen!='A' and kar.rodcis is not null and idicp!=0 order by ockzaz.datum desc") +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.title="Očkování" +sheet.append(["Rodne cislo","Prijmeni","Jmeno","Datum ockovani","Kod MZ","Sarze","Latka","Nazev","Expirace"]) +#nacteno jsou ockovani +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + + + + +#Načtení registrovaných pacientů +cur.execute("select rodcis,prijmeni,jmeno,datum_registrace,registr.idpac,poj from registr join kar on registr.idpac=kar.idpac where kar.vyrazen!='A' and kar.rodcis is not null and idicp!=0 and datum_zruseni is null") +nacteno=cur.fetchall() +print(len(nacteno)) + +wb.create_sheet('Registrovani',0) +sheet=wb['Registrovani'] + +sheet.append(["Rodne cislo","Prijmeni","Jmeno","Datum registrace","ID pacienta","Pojistovna"]) +#nacteno jsou registrovani +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + + +#Načtení receptů +cur.execute("""select +kar.rodcis, +TRIM(kar.prijmeni) ||' '|| substring(kar.jmeno from 1 for 1) ||'.' as jmeno, +recept.datum, +TRIM(recept.lek) ||' '|| trim(recept.dop) as lek, +recept.expori AS Poc, +CASE + WHEN recept.opakovani is null THEN 1 + ELSE recept.opakovani + END AS OP, +recept.uhrada, +recept.dsig, +recept.NOTIFIKACE_KONTAKT as notifikace, +recept_epodani.erp, +recept_epodani.vystavitel_jmeno, +recept.atc, +recept.CENAPOJ, +recept.cenapac +from recept LEFT Join RECEPT_EPODANI on recept.id_epodani=recept_epodani.id +LEFT join kar on recept.idpac=kar.idpac +order by datum desc,erp desc""" +) +nacteno=cur.fetchall() +print(len(nacteno)) + +wb.create_sheet('Recepty',0) +sheet=wb['Recepty'] + +sheet.title="Recepty" +sheet.append(["Rodné číslo","Jméno","Datum vystavení","Název leku","Poč.","Op.","Úhr.","Da signa","Notifikace","eRECEPT","Vystavil","ATC","Cena pojišťovna","Cena pacient"]) +#nacteno jsou ockovani +for row in nacteno: + try: + sheet.append(row) + except: + continue + +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Načtení vykony vsech +cur.execute("select dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,dokladd.pocvyk,dokladd.ddgn,dokladd.body,vykony.naz " + "from kar join dokladd on kar.rodcis=dokladd.rodcis join vykony on dokladd.kod=vykony.kod where (datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null) order by dokladd.datose desc,dokladd.rodcis") + +wb.create_sheet('Vykony',0) +sheet=wb['Vykony'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum vykonu","Kod","Pocet","Dg.","Body","Nazev"]) +#nacteno jsou ockovani +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Načtení neschopenek + +import datetime +def pocet_dni(zacnes,konnes,pracne): + dnes=datetime.date.today() + if pracne=='A': + return (dnes-zacnes).days + if pracne=='N' and zacnes is not None and konnes is not None and zacnes<=konnes: + return (konnes-zacnes).days + else: + return "NA" + +cur.execute("select nes.idpac, " + "kar.rodcis, " + "TRIM(prijmeni) ||', '|| TRIM(jmeno), " + "nes.datnes, " + "nes.ecn, " + "nes.zacnes, " + "nes.pracne, " + "nes.konnes, " + "nes.diagno, " + "nes.kondia, " + "nes.updated " + "from nes " + "left join kar on nes.idpac=kar.idpac where nes.datnes<=current_date " + "order by datnes desc") + + +tmpnacteno_vse=[] +nacteno_vse=cur.fetchall() + +cur.execute("select nes.idpac, " + "kar.rodcis, " + "TRIM(prijmeni) ||', '|| TRIM(jmeno), " + "nes.datnes, " + "nes.ecn, " + "nes.zacnes, " + "nes.pracne, " + "nes.konnes, " + "nes.diagno, " + "nes.kondia, " + "nes.updated " + "from nes " + "left join kar on nes.idpac=kar.idpac where nes.datnes<=current_date and pracne='A'" + "order by datnes desc") + +tmpnacteno_aktivni=[] +nacteno_aktivni=cur.fetchall() + +for row in nacteno_vse: + tmpnacteno_vse.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],pocet_dni(row[5],row[7],row[6]),row[8],row[9],row[10])) + +for row in nacteno_aktivni: + (tmpnacteno_aktivni.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],pocet_dni(row[5],row[7],row[6]),row[8],row[9],row[10]))) + +wb.create_sheet('Neschopenky všechny',0) +sheet=wb["Neschopenky všechny"] +sheet.append(["ID pac","Rodne cislo","Jmeno","Datum neschopenky","Číslo neschopenky","Zacatek","Aktivní?","Konec","Pocet dni","Diagnoza zacatel","Diagnoza konec","Aktualizovano"]) +for row in tmpnacteno_vse: + sheet.append(row) + +wb.create_sheet('Neschopenky aktivní',0) +sheet=wb["Neschopenky aktivní"] +sheet.append(["ID pac","Rodne cislo","Jmeno","Datum neschopenky","Číslo neschopenky","Zacatek","Aktivní?","Konec","Pocet dni","Diagnoza zacatel","Diagnoza konec","Aktualizovano"]) +for row in tmpnacteno_aktivni: + sheet.append(row) + +#Načtení preventivni prohlidky +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=1022 or dokladd.kod=1021) " +"order by datose desc") + +wb.create_sheet('Preventivni prohlidky',0) +sheet=wb['Preventivni prohlidky'] + + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni INR +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=01443) " +"order by datose desc") + +wb.create_sheet('INR',0) +sheet=wb['INR'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni CRP +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=02230 or dokladd.kod=09111) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('CRP',0) +sheet=wb['CRP'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + + +#Nacteni Holter +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=17129) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('Holter',0) +sheet=wb['Holter'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni prostata +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=01130 or dokladd.kod=01131 or dokladd.kod=01132 or dokladd.kod=01133 or dokladd.kod=01134) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('Prostata',0) +sheet=wb['Prostata'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni TOKS +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and " +"(dokladd.kod=15118 or dokladd.kod=15119 or dokladd.kod=15120 or dokladd.kod=15121) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('TOKS',0) +sheet=wb['TOKS'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni COVID +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and " +"(dokladd.kod=01306) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('COVID',0) +sheet=wb['COVID'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + +#Nacteni Streptest +cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body " +"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where " +"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and " +"(dokladd.kod=02220) " +"order by datose desc,dokladd.rodcis,dokladd.kod") + +wb.create_sheet('Streptest',0) +sheet=wb['Streptest'] + +nacteno=cur.fetchall() +print(len(nacteno)) + +sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"]) + +for row in nacteno: + sheet.append(row) + + +# autofilter +for ws in wb.worksheets: + # Get the maximum number of rows and columns + max_row = ws.max_row + max_column = ws.max_column + ws.auto_filter.ref = f"A1:{openpyxl.utils.get_column_letter(max_column)}{max_row}" + # ws.auto_filter.ref = ws.dimensions + + + + + +wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname)) + + +# Tento modul je pouze na autofit jednotlivych sloupcu na vsech listech workbooku +file = os.path.join(PathToSaveCSV ,timestr+CSVname) +with xw.App(visible=False) as app: + wb = xw.Book(file) + for sheet in range(len(wb.sheets)): + ws = wb.sheets[sheet] + ws.autofit() + + # centrování receptů + sheet = wb.sheets['Recepty'] + for sloupec in ["C:C", "E:E", "F:F", "G:G", "I:I", "M:M", "N:N"]: + sheet.range(sloupec).api.HorizontalAlignment = 3 # 3 = Center + + + wb.save() + wb.close() + + diff --git a/MedicusWithClaudeKomplexniReport/CLAUDE_NOTES.md b/MedicusWithClaudeKomplexniReport/CLAUDE_NOTES.md new file mode 100644 index 0000000..ef07410 --- /dev/null +++ b/MedicusWithClaudeKomplexniReport/CLAUDE_NOTES.md @@ -0,0 +1,188 @@ +# MedicusWithClaudeKomplexniReport – CLAUDE_NOTES + +## Co skript dělá + +`komplexni_report.py` generuje komplexní Excel přehled ordinace. +Soubor se ukládá do `u:\Dropbox\!!!Days\Downloads Z230\YYYY-MM-DD_HH-MM-SS_Pacienti.xlsx`. +Předchozí verze (`*Pacienti.xlsx`) se před zápisem automaticky smažou. + +## Spuštění + +``` +C:\Python\python.exe komplexni_report.py +``` + +Trvá cca **10 minut** (kvůli xlwings autofit přes celý Excel). +Spouští se automaticky v noci → nevadí. + +## Připojení k DB + +```python +fdb.connect( + host='localhost', + database=r'c:\MEDICUS 3\data\medicus.FDB', + user='sysdba', password='masterkey', charset='WIN1250' +) +``` + +## Závislosti + +``` +pip install fdb openpyxl xlwings extract-msg beautifulsoup4 python-dateutil +``` + +--- + +## Listy v Excelu (pořadí) + +| List | Zdroj | Popis | +|---|---|---| +| `Registrovani` | registr + kar | Aktivní registrovaní pacienti | +| `Očkování` | ockzaz + registr + kar | Záznamy o očkování registrovaných | +| `Recepty` | recept + recept_epodani + kar | Všechny recepty, eRECEPT čísla, ceny | +| `Vykony` | dokladd + kar + vykony | Všechny výkony (s platným číselníkem) | +| `Neschopenky všechny` | nes + kar | Všechny neschopenky | +| `Neschopenky aktivní` | nes + kar | Pouze aktivní (pracne='A') | +| `Preventivni prohlidky` | dokladd + vykony | Kódy 1021, 1022 | +| `INR` | dokladd + vykony | Kód 1443 | +| `CRP` | dokladd + vykony | Kódy 2230, 9111 | +| `Holter` | dokladd + vykony | Kód 17129 | +| `Prostata` | dokladd + vykony | Kódy 1130–1134 | +| `TOKS` | dokladd + vykony | Kódy 15118–15121 | +| `COVID` | dokladd + vykony | Kód 1306 | +| `Streptest` | dokladd + vykony | Kód 2220 | +| `Posudky řidičák` | HISTDOC (TYP=MOTORVO) + KAR | Ruční posudky k řízení MV | +| `ePosudky registr` | HISTDOC (TYP=EPOSMRO) + HISTDOC_EPOSUDEK + KAR | Elektronická podání do centrálního registru | + +--- + +## Pomocné funkce + +### `sanitize(val)` +Opraví znaky neplatné pro Excel: +- `µ` → `u` +- řídící znaky (ord < 32, kromě tab/LF/CR) → `_` +- náhradní znaky Unicode (0xFFFE, 0xFFFF, surrogáty) → `_` + +Použito ve všech listech kde hrozí problematická data z DB. + +### `fmt(val)` +Vrátí `''` pro None, jinak zavolá `sanitize()`. + +### `add_vykony_sheet(sheet_name, kody)` +Helper pro listy s výkony. Přijme název listu a seznam kódů výkonů. +SQL: `dokladd JOIN kar JOIN vykony WHERE kod IN (...) AND platnost kódu platí`. +Řazení: datum DESC, rodcis, kod. + +### `pocet_dni(zacnes, konnes, pracne)` +Výpočet délky neschopenky: +- `pracne='A'` (aktivní) → dny od začátku do dnes +- `pracne='N'` → dny od začátku do konce +- jinak → `"NA"` + +### `parse_data(data_str)` +Parsuje `key=value` text z pole `HISTDOC.DATA` do slovníku. +Každý řádek = jeden klíč/hodnota oddělené `=`. + +### `parse_date(val)` +Převede formát `D:DD.MM.YYYY` (jak ho ukládá Medicus) na `datetime.date`. + +### `style_header(ws)` / `autofit_ws(ws)` +Styl záhlaví (modrý fill, bílý tučný text, centrování) a šířky sloupců (max 50 znaků). +Používají se jen na listech s posudky (ostatní listy řeší xlwings). + +--- + +## Listy s posudky – detail + +### `Posudky řidičák` (MOTORVO) + +Data jsou uložena v `HISTDOC.DATA` jako `key=value` text. +Parsovaná pole: + +| Sloupec | Zdroj v DATA | +|---|---| +| PorCislo | `PorCislo` nebo `HISTDOC.PORCISLO` | +| DatumVyd | `DatumVyd` (formát `D:DD.MM.YYYY`) | +| DatKonec | `DatKonec` (formát `D:DD.MM.YYYY`) | +| DruhProh | `DruhProh` | +| Posouzeni | odvozeno z `Posouzeni`, `Posouzeni2`, `ZpusobPodminka` | +| ZpusobPodminka | `ZpusobPodminka` | +| SkupinaPodminka | `SkupinaPodminka` | +| Skupiny | `ZpusobJe` | + +**Logika Posouzeni:** +- `Posouzeni2 = T` → `nezpůsobilý` +- `ZpusobPodminka = B:1` → `způsobilý s podmínkou` +- `Posouzeni = T` → `způsobilý` + +**Sloupec ePosudek:** +`ANO` pokud existuje záznam v HISTDOC s `TYP='EPOSMRO'` pro stejného pacienta (IDPACI) a stejné datum. +Párování: `(IDPACI, DATUM)` – přímá FK vazba mezi MOTORVO a EPOSMRO neexistuje. +Buňka s ANO je zelená (fill + font). + +**Zebra pruhování:** liché řádky bílé, sudé světle modré (`DCE6F1`). + +### `ePosudky registr` (EPOSMRO) + +Elektronická podání do centrálního registru způsobilosti. +Stát tuto funkci zavedl přibližně od aktualizace Medicusu (03/2026). + +Data parsovaná z `HISTDOC.DATA`: + +| Sloupec | Zdroj v DATA | +|---|---| +| DatumVyd | `DatumVystaveni` | +| DatKonec | `PlatnostDo` | +| DruhProhlidky | `DruhProhlidkyNazev` | +| DruhPosudku | `DruhPosudkuNazev` | +| Vysledek | `VysledekNazev` | +| StavPosudku | `StavPosudkuNazev` | +| TypAkce | `TypAkceNazev` | + +Stavová pole z `HISTDOC_EPOSUDEK`: +- `ID_PODANI` – ID podání do registru +- `ODESLANO` – timestamp odeslání +- `STATUS_ODESL` – stav odpovědi z registru (`O` = odesláno) + +**Zneplatnění:** `StavPosudku = zneplatneny` = lékař aktivně odvolal způsobilost +(např. pacient prodělal mrtvici, epileptický záchvat atp.). +Zneplatnění je samostatný EPOSMRO záznam, ne modifikace původního. + +--- + +## xlwings – závěrečný krok + +Po `wb.save()` se soubor otevře přes xlwings (vyžaduje plný Excel): +1. `sheet.autofit()` na všech listech – správné šířky sloupců +2. Na listu `Recepty`: centrování sloupců C, E, F, G, I, M, N +3. `wb_xw.save()` + zavření + +xlwings je nutný pro spolehlivý autofit (openpyxl ho neumí přesně). +Trvá ~10 minut, spouští se v noci. + +--- + +## Pořadí zpracování (pro debugování) + +``` +DB connect +→ smazání starých souborů +→ SQL dotazy (Registrovani, Očkování, Recepty, Výkony, Neschopenky) +→ add_vykony_sheet × 8 +→ MOTORVO + EPOSMRO listy (s parsováním DATA) +→ autofilter na všech listech +→ con.close() + wb.save() +→ xlwings autofit + centrování +→ Hotovo. +``` + +Print výstup v konzoli ukazuje počty řádků každého listu – užitečné pro kontrolu. + +--- + +## Rozšíření v budoucnu + +- Přidat další typy posudků (pracovní, vstupní, sportovní...) ze `VS_POSUDKY` +- Případně sledovat stav podání EPOSMRO v čase (datum odeslání vs. datum posudku) +- Automatické spouštění přes Windows Task Scheduler (jako `faktury_report.py`) diff --git a/MedicusWithClaudeKomplexniReport/komplexni_report.py b/MedicusWithClaudeKomplexniReport/komplexni_report.py new file mode 100644 index 0000000..4a11b2a --- /dev/null +++ b/MedicusWithClaudeKomplexniReport/komplexni_report.py @@ -0,0 +1,410 @@ +import os +import time +import fdb +import openpyxl +import xlwings as xw +from datetime import datetime, date +from openpyxl.utils import get_column_letter +from openpyxl.styles import Font, PatternFill, Alignment + +# --- Konfigurace --- +PathToSaveCSV = r"u:\Dropbox\!!!Days\Downloads Z230" +timestr = time.strftime("%Y-%m-%d_%H-%M-%S_") +output_path = os.path.join(PathToSaveCSV, timestr + "Pacienti.xlsx") + +# --- Smazání předchozích verzí --- +for fname in os.listdir(PathToSaveCSV): + if fname.endswith("Pacienti.xlsx"): + try: + os.remove(os.path.join(PathToSaveCSV, fname)) + except Exception as e: + print(f"Nelze smazat {fname}: {e}") + +# --- Připojení k DB --- +con = fdb.connect( + host='localhost', database=r'c:\MEDICUS 3\data\medicus.FDB', + user='sysdba', password='masterkey', charset='WIN1250' +) +cur = con.cursor() + +wb = openpyxl.Workbook() + +# ===================== +# Pomocné funkce +# ===================== + +# Styly pro posudky +HEADER_FILL = PatternFill('solid', fgColor='2F5496') +HEADER_FONT = Font(bold=True, color='FFFFFF') +ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1') +GREEN_FILL = PatternFill('solid', fgColor='C6EFCE') +GREEN_FONT = Font(bold=True, color='276221') + +def style_header(ws): + for cell in ws[1]: + cell.fill = HEADER_FILL + cell.font = HEADER_FONT + cell.alignment = Alignment(horizontal='center') + +def autofit_ws(ws): + for col in ws.columns: + max_len = max((len(str(cell.value)) if cell.value is not None else 0) for cell in col) + ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 2, 50) + +def sanitize(val): + """Nahradí znaky neplatné pro Excel: µ → u, ostatní → _""" + if not isinstance(val, str): + return val + result = [] + for ch in val: + if ch == 'µ': + result.append('u') + elif ord(ch) < 32 and ch not in '\t\n\r': + result.append('_') + elif ord(ch) in (0xFFFE, 0xFFFF) or 0xD800 <= ord(ch) <= 0xDFFF: + result.append('_') + else: + result.append(ch) + return ''.join(result) + +def fmt(val): + return '' if val is None else sanitize(val) + +def parse_data(data_str): + """Parsuje key=value text z HISTDOC.DATA do slovníku.""" + result = {} + if not data_str: + return result + for line in data_str.splitlines(): + if '=' in line: + key, _, val = line.partition('=') + result[key.strip()] = val.strip() + return result + +def parse_date(val): + """Převede 'D:DD.MM.YYYY' na datetime.date.""" + if val and val.startswith('D:'): + try: + return datetime.strptime(val[2:], '%d.%m.%Y').date() + except ValueError: + return val + return val + +VYKONY_CONDITION = """ + (datose >= vykony.platiod AND datose <= vykony.platido) + OR (datose >= vykony.platiod AND vykony.platido IS NULL) +""" +VYKONY_HEADERS = ["Rodne cislo", "Jmeno", "Datum vykonu", "Kod", "Název", "Dg.", "Body"] + +def add_vykony_sheet(sheet_name, kody): + """Přidá list s výkony filtrovanými podle seznamu kódů.""" + kod_list = ", ".join(str(k) for k in kody) + cur.execute(f""" + SELECT dokladd.rodcis, + TRIM(prijmeni) || ', ' || TRIM(jmeno), + dokladd.datose, dokladd.kod, vykony.naz, dokladd.ddgn, dokladd.body + FROM dokladd + LEFT JOIN kar ON dokladd.rodcis = kar.rodcis + JOIN vykony ON dokladd.kod = vykony.kod + WHERE ({VYKONY_CONDITION}) + AND dokladd.kod IN ({kod_list}) + ORDER BY datose DESC, dokladd.rodcis, dokladd.kod + """) + rows = cur.fetchall() + print(f"{sheet_name}: {len(rows)}") + ws = wb.create_sheet(sheet_name) + ws.append(VYKONY_HEADERS) + for row in rows: + ws.append(list(row)) + +# ===================== +# List: Registrovaní +# ===================== +cur.execute(""" + SELECT rodcis, prijmeni, jmeno, datum_registrace, registr.idpac, poj + FROM registr + JOIN kar ON registr.idpac = kar.idpac + WHERE kar.vyrazen != 'A' + AND kar.rodcis IS NOT NULL + AND idicp != 0 + AND datum_zruseni IS NULL +""") +rows = cur.fetchall() +print(f"Registrovaní: {len(rows)}") +ws = wb.active +ws.title = 'Registrovani' +ws.append(["Rodne cislo", "Prijmeni", "Jmeno", "Datum registrace", "ID pacienta", "Pojistovna"]) +for row in rows: + ws.append(list(row)) + +# ===================== +# List: Očkování +# ===================== +cur.execute(""" + SELECT rodcis, prijmeni, jmeno, ockzaz.datum, kodmz, ockzaz.poznamka, latka, nazev, expire + FROM registr + JOIN kar ON registr.idpac = kar.idpac + JOIN ockzaz ON registr.idpac = ockzaz.idpac + WHERE datum_zruseni IS NULL + AND kar.vyrazen != 'A' + AND kar.rodcis IS NOT NULL + AND idicp != 0 + ORDER BY ockzaz.datum DESC +""") +rows = cur.fetchall() +print(f"Očkování: {len(rows)}") +ws = wb.create_sheet("Očkování") +ws.append(["Rodne cislo", "Prijmeni", "Jmeno", "Datum ockovani", "Kod MZ", "Sarze", "Latka", "Nazev", "Expirace"]) +for row in rows: + ws.append(list(row)) + +# ===================== +# List: Recepty +# ===================== +cur.execute(""" + SELECT kar.rodcis, + TRIM(kar.prijmeni) || ' ' || SUBSTRING(kar.jmeno FROM 1 FOR 1) || '.' AS jmeno, + recept.datum, + TRIM(recept.lek) || ' ' || TRIM(recept.dop) AS lek, + recept.expori AS Poc, + CASE WHEN recept.opakovani IS NULL THEN 1 ELSE recept.opakovani END AS OP, + recept.uhrada, + recept.dsig, + recept.NOTIFIKACE_KONTAKT AS notifikace, + recept_epodani.erp, + recept_epodani.vystavitel_jmeno, + recept.atc, + recept.CENAPOJ, + recept.cenapac + FROM recept + LEFT JOIN RECEPT_EPODANI ON recept.id_epodani = recept_epodani.id + LEFT JOIN kar ON recept.idpac = kar.idpac + ORDER BY datum DESC, erp DESC +""") +rows = cur.fetchall() +print(f"Recepty: {len(rows)}") +ws = wb.create_sheet("Recepty") +ws.append(["Rodné číslo", "Jméno", "Datum vystavení", "Název leku", "Poč.", "Op.", "Úhr.", + "Da signa", "Notifikace", "eRECEPT", "Vystavil", "ATC", "Cena pojišťovna", "Cena pacient"]) +for row in rows: + ws.append([sanitize(v) if isinstance(v, str) else v for v in row]) + +# ===================== +# List: Výkony všechny +# ===================== +cur.execute(f""" + SELECT dokladd.rodcis, + TRIM(prijmeni) || ', ' || TRIM(jmeno), + dokladd.datose, dokladd.kod, dokladd.pocvyk, dokladd.ddgn, dokladd.body, vykony.naz + FROM kar + JOIN dokladd ON kar.rodcis = dokladd.rodcis + JOIN vykony ON dokladd.kod = vykony.kod + WHERE {VYKONY_CONDITION} + ORDER BY dokladd.datose DESC, dokladd.rodcis +""") +rows = cur.fetchall() +print(f"Výkony: {len(rows)}") +ws = wb.create_sheet("Vykony") +ws.append(["Rodne cislo", "Jmeno", "Datum vykonu", "Kod", "Pocet", "Dg.", "Body", "Nazev"]) +for row in rows: + ws.append(list(row)) + +# ===================== +# Listy: Neschopenky +# ===================== +def pocet_dni(zacnes, konnes, pracne): + dnes = date.today() + if pracne == 'A': + return (dnes - zacnes).days if zacnes else "NA" + if pracne == 'N' and zacnes and konnes and zacnes <= konnes: + return (konnes - zacnes).days + return "NA" + +def nes_row(r): + return (r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], + pocet_dni(r[5], r[7], r[6]), r[8], r[9], r[10]) + +NES_HEADERS = ["ID pac", "Rodne cislo", "Jmeno", "Datum neschopenky", "Číslo neschopenky", + "Zacatek", "Aktivní?", "Konec", "Pocet dni", "Diagnoza zacatel", "Diagnoza konec", "Aktualizovano"] + +cur.execute(""" + SELECT nes.idpac, kar.rodcis, + TRIM(prijmeni) || ', ' || TRIM(jmeno), + nes.datnes, nes.ecn, nes.zacnes, nes.pracne, nes.konnes, + nes.diagno, nes.kondia, nes.updated + FROM nes + LEFT JOIN kar ON nes.idpac = kar.idpac + WHERE nes.datnes <= CURRENT_DATE + ORDER BY datnes DESC +""") +vse = cur.fetchall() +aktivni = [r for r in vse if r[6] == 'A'] +print(f"Neschopenky: {len(vse)} celkem, {len(aktivni)} aktivních") + +ws = wb.create_sheet("Neschopenky všechny") +ws.append(NES_HEADERS) +for r in vse: + ws.append(list(nes_row(r))) + +ws = wb.create_sheet("Neschopenky aktivní") +ws.append(NES_HEADERS) +for r in aktivni: + ws.append(list(nes_row(r))) + +# ===================== +# Výkonové listy – jednotlivé typy výkonů +# ===================== +add_vykony_sheet('Preventivni prohlidky', [1022, 1021]) +add_vykony_sheet('INR', [1443]) +add_vykony_sheet('CRP', [2230, 9111]) +add_vykony_sheet('Holter', [17129]) +add_vykony_sheet('Prostata', [1130, 1131, 1132, 1133, 1134]) +add_vykony_sheet('TOKS', [15118, 15119, 15120, 15121]) +add_vykony_sheet('COVID', [1306]) +add_vykony_sheet('Streptest', [2220]) + +# ===================== +# List: Posudky řidičák – MOTORVO (ruční) +# ===================== +cur.execute("SELECT IDPACI, DATUM FROM HISTDOC WHERE TYP = 'EPOSMRO'") +eposmro_keys = set((r[0], r[1]) for r in cur.fetchall()) + +cur.execute(""" + SELECT h.ID, h.DATUM, h.IDPACI, + k.PRIJMENI, k.JMENO, k.RODCIS, + h.DATA, h.PORCISLO, h.STAV, h.PRINTED, h.IDUZIV, h.CREATED + FROM HISTDOC h + JOIN KAR k ON k.IDPAC = h.IDPACI + WHERE h.TYP = 'MOTORVO' + ORDER BY h.ID DESC +""") +motorvo_rows = cur.fetchall() +print(f"MOTORVO: {len(motorvo_rows)}") + +motorvo_headers = [ + 'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS', + 'PorCislo', 'DatumVyd', 'DatKonec', 'DruhProh', + 'Posouzeni', 'ZpusobPodminka', 'SkupinaPodminka', 'Skupiny', + 'ePosudek', 'STAV', 'PRINTED', 'IDUZIV', 'CREATED' +] +ws = wb.create_sheet("Posudky řidičák") +ws.append(motorvo_headers) + +epos_col_idx = motorvo_headers.index('ePosudek') + 1 + +for i, row in enumerate(motorvo_rows, start=2): + (hid, datum, idpac, prijmeni, jmeno, rodcis, + data_blob, porcislo, stav, printed, iduziv, created) = row + data = parse_data(data_blob) + + if data.get('Posouzeni2') == 'T': + posouzeni = 'nezpůsobilý' + elif data.get('ZpusobPodminka') == 'B:1': + posouzeni = 'způsobilý s podmínkou' + elif data.get('Posouzeni') == 'T': + posouzeni = 'způsobilý' + else: + posouzeni = '' + + ws.append([ + hid, fmt(datum), idpac, fmt(prijmeni), fmt(jmeno), fmt(rodcis), + fmt(porcislo or data.get('PorCislo', '')), + parse_date(data.get('DatumVyd', '')), + parse_date(data.get('DatKonec', '')), + fmt(data.get('DruhProh', '')), + posouzeni, + fmt(data.get('ZpusobPodminka', '')), + fmt(data.get('SkupinaPodminka', '')), + fmt(data.get('ZpusobJe', '')), + 'ANO' if (idpac, datum) in eposmro_keys else 'NE', + fmt(stav), fmt(printed), fmt(iduziv), fmt(created), + ]) + + if i % 2 == 0: + for cell in ws[i]: + cell.fill = ZEBRA_FILL + cell = ws.cell(row=i, column=epos_col_idx) + if cell.value == 'ANO': + cell.fill = GREEN_FILL + cell.font = GREEN_FONT + +style_header(ws) +ws.freeze_panes = 'A2' +autofit_ws(ws) + +# ===================== +# List: Posudky řidičák – EPOSMRO (elektronická podání) +# ===================== +cur.execute(""" + SELECT h.ID, h.DATUM, h.IDPACI, + k.PRIJMENI, k.JMENO, k.RODCIS, + h.DATA, h.STAV, h.CREATED, + e.ID_PODANI, e.ODESLANO, e.STATUS + FROM HISTDOC h + JOIN KAR k ON k.IDPAC = h.IDPACI + LEFT JOIN HISTDOC_EPOSUDEK e ON e.ID_HISTDOC = h.ID + WHERE h.TYP = 'EPOSMRO' + ORDER BY h.ID DESC +""") +epos_rows = cur.fetchall() +print(f"EPOSMRO: {len(epos_rows)}") + +ws = wb.create_sheet("ePosudky registr") +ws.append([ + 'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS', + 'DatumVyd', 'DatKonec', 'DruhProhlidky', 'DruhPosudku', + 'Vysledek', 'StavPosudku', 'TypAkce', + 'STAV', 'CREATED', 'ID_PODANI', 'ODESLANO', 'STATUS_ODESL' +]) + +for i, row in enumerate(epos_rows, start=2): + (hid, datum, idpac, prijmeni, jmeno, rodcis, + data_blob, stav, created, id_podani, odeslano, status_odesl) = row + data = parse_data(data_blob) + + ws.append([ + hid, fmt(datum), idpac, fmt(prijmeni), fmt(jmeno), fmt(rodcis), + parse_date(data.get('DatumVystaveni', '')), + parse_date(data.get('PlatnostDo', '')), + fmt(data.get('DruhProhlidkyNazev', '')), + fmt(data.get('DruhPosudkuNazev', '')), + fmt(data.get('VysledekNazev', '')), + fmt(data.get('StavPosudkuNazev', '')), + fmt(data.get('TypAkceNazev', '')), + fmt(stav), fmt(created), fmt(id_podani), fmt(odeslano), fmt(status_odesl), + ]) + + if i % 2 == 0: + for cell in ws[i]: + cell.fill = ZEBRA_FILL + +style_header(ws) +ws.freeze_panes = 'A2' +autofit_ws(ws) + +# ===================== +# Autofilter na všech listech +# ===================== +for ws in wb.worksheets: + ws.auto_filter.ref = f"A1:{get_column_letter(ws.max_column)}{ws.max_row}" + +# ===================== +# Uložení +# ===================== +con.close() +wb.save(output_path) +print(f"Uloženo: {output_path}") + +# ===================== +# xlwings: autofit + centrování Recepty +# ===================== +with xw.App(visible=False) as app: + wb_xw = xw.Book(output_path) + for sheet in wb_xw.sheets: + sheet.autofit() + for sloupec in ["C:C", "E:E", "F:F", "G:G", "I:I", "M:M", "N:N"]: + wb_xw.sheets['Recepty'].range(sloupec).api.HorizontalAlignment = 3 + wb_xw.save() + wb_xw.close() + +print("Hotovo.") diff --git a/MedicusWithClaudePosudek/CLAUDE_NOTES.md b/MedicusWithClaudePosudek/CLAUDE_NOTES.md new file mode 100644 index 0000000..4805c54 --- /dev/null +++ b/MedicusWithClaudePosudek/CLAUDE_NOTES.md @@ -0,0 +1,143 @@ +# MedicusWithClaudePosudek – poznámky pro Clauda + +## O co jde + +Lékařské posudky vystavované MUDr. Buzalkovou. Prozatím řešíme posudky k řízení motorových vozidel. + +Nový zákon ukládá povinnost odesílat posudky k řízení do **centrálního registru** – tuto funkci Medicus přidal v aktualizaci z konce března 2026. + +--- + +## Tabulky + +### HISTDOC – hlavní tabulka pro všechny posudky + +Všechny posudky jsou záznamy v `HISTDOC`, lišící se hodnotou sloupce `TYP`. + +Klíčové sloupce: +| Sloupec | Popis | +|---|---| +| `ID` | primární klíč | +| `TYP` | typ dokumentu (viz níže) | +| `DATUM` | datum vystavení posudku | +| `IDPACI` | FK → KAR.IDPAC (pacient) | +| `DATA` | obsah posudku – text ve formátu key=value (viz níže) | +| `PORCISLO` | pořadové číslo posudku (= PorCislo v DATA) | +| `STAV` | stav záznamu (Z = zavřeno) | +| `PRINTED` | T/F – byl vytištěn | +| `IDUZIV` | FK → UZIVATEL.IDUZI – kdo vystavil (4 = MUDr. Buzalková) | +| `CREATED` | timestamp vytvoření záznamu | + +**Vazba:** žádná přímá vazba na jiné tabulky (vyšetření, dekurz apod.) – posudek je svébytný dokument. + +### TYP hodnoty relevantní pro posudky řidičů + +| TYP | Popis | Počet (k 2026-03-31) | +|---|---|---| +| `MOTORVO` | ruční posudek k řízení motorových vozidel | 1530 | +| `EPOSMRO` | elektronické podání posudku do centrálního registru | 2 | + +Ostatní typy posudků v HISTDOC (pro referenci): +- `ZBROJPR`, `ZBROJP2` – zbrojní průkaz +- `ZPUPRN` – způsobilost pro práci +- `ZDRSTA3`–`ZDRSTA5`, `ZDRSTAV`, `ZDRINF` – zdravotní stav (různé varianty) +- ... (celkem desítky typů) + +### HISTDOC_EPOSUDEK – evidence odeslání do registru + +Doplňková tabulka k EPOSMRO záznamům v HISTDOC. + +| Sloupec | Popis | +|---|---| +| `ID_HISTDOC` | FK → HISTDOC.ID (záznam EPOSMRO) | +| `ID_PODANI` | UUID přidělené centrálním registrem | +| `ODESLANO` | timestamp odeslání | +| `STATUS` | O = odesláno | +| `VERZE` | verze záznamu (base64 interní hodnota) | + +### VS_POSUDKY – prázdná, zatím nepoužívaná + +Sloupce: ID, IDPAC, DATA (BLOB), DATUM, POSTYPE. Pravděpodobně připravena pro budoucí využití. + +--- + +## Workflow: ruční posudek → elektronické podání + +1. Lékař v Medicusu vyplní posudek → vznikne `HISTDOC` TYP=`MOTORVO` +2. Medicus automaticky odešle do centrálního registru → vznikne `HISTDOC` TYP=`EPOSMRO` + záznam v `HISTDOC_EPOSUDEK` +3. Oba záznamy mají stejné `IDPACI` + `DATUM` → podle toho je párujeme + +Příklad (pacient Vráček, 30.3.2026): +- HISTDOC ID=34743, TYP=MOTORVO, CREATED=13:12 +- HISTDOC ID=34746, TYP=EPOSMRO, CREATED=13:21 +- HISTDOC_EPOSUDEK: STATUS=O, ODESLANO=13:21 + +--- + +## Formát DATA (key=value) – MOTORVO + +``` +JmenoPac=Radomil Vráček +DatNar=D:27.03.1956 +Prukaz=207069669 ← číslo řidičského průkazu +DatKonec=D:30.03.2028 ← platnost posudku do +DatumVyd=D:30.03.2026 ← datum vydání +Bydliste=K Šafránce 507/16, 19000 Praha 9-Střížkov +DruhProh=periodická ← druh prohlídky +Posouzeni=T ← T = způsobilý (F = nezpůsobilý?) +Posouzeni2=F ← T = nezpůsobilý (druhá volba) +ZpusobJe=B:0 ← skupiny bez podmínky +ZpusobPodminka=B:1 ← B:1 = má podmínku +SkupinaPodminka=sk. B brýle +PorCislo=2600037 +KonecDleZakona=D +DatumPrevzeti=D:30.03.2026 +``` + +**Výsledek posouzení** (kombinace Posouzeni + Posouzeni2 + ZpusobPodminka): +- `Posouzeni=T` + `Posouzeni2=F` + `ZpusobPodminka=B:0` → způsobilý +- `Posouzeni=T` + `Posouzeni2=F` + `ZpusobPodminka=B:1` → způsobilý s podmínkou +- `Posouzeni=T` + `Posouzeni2=T` → nezpůsobilý + +## Formát DATA (key=value) – EPOSMRO + +``` +Lekar=MUDr. Michaela Buzalková +KRZPID=130153584 ← ID lékaře v registru +ICO=68366370 +ICP=09305001 +Pacient=Radomil Vráček +RID=8705636888 ← číslo řidičáku +DatumNarozeni=D:27.03.1956 +StavPosudkuKodVerze=zneplatneny|1.0.0 +StavPosudkuNazev=Zneplatněný ← stav posudku v registru +TypAkceNazev=vytvoření +TypAkceKodVerze=akce_ro_1|1.0.0 +DruhProhlidkyNazev=pravidelná +DruhProhlidkyKodVerze=Pravidelna|1.0.0 +DruhPosudkuNazev=řidičské oprávnění pro seniory +DruhPosudkuKodVerze=SenioriRo|1.0.0 +SkupinaZadatelRidicNazev=skupina 1 +SkupinyRidicskehoOpravneniSeznam=B +HarmonizovaneNarodniKody=$:~HNK1:011:01.01 Brýle5:01.012:HK1:B0: ← kódy omezení (brýle) +VysledekKodVerze=ZpusobilySPodminkou|1.0.0 +VysledekNazev=způsobilý s podmínkou +DatumVystaveni=D:30.03.2026 +PlatnostDo=D:30.03.2028 +``` + +**StavPosudku = "Zneplatněný"** neznamená chybu – jde o akci, kdy lékař odvolá způsobilost pacienta (např. po mrtvici, epileptickém záchvatu apod.). Medicus pak odešle do registru zneplatnění existujícího posudku. + +--- + +## Soubory v projektu + +- `posudky_report.py` – generuje Excel s listy MOTORVO a EPOSMRO +- `CLAUDE_NOTES.md` – tento soubor + +## Report (posudky_report.py) + +- Výstup: `u:\Dropbox\!!!Days\Downloads Z230\YYYY-MM-DD_HH-MM-SS_Přehled posudků řidičák.xlsx` +- Maže předchozí verzi před zápisem nové +- List MOTORVO: 1530 záznamů, sloupec `ePosudek` = ANO (zeleně) / NE podle toho, zda byl odeslán ePosudek (párování IDPACI + DATUM) +- List EPOSMRO: 2 záznamy, detail elektronického podání diff --git a/MedicusWithClaudePosudek/posudky_report.py b/MedicusWithClaudePosudek/posudky_report.py new file mode 100644 index 0000000..51f4e7e --- /dev/null +++ b/MedicusWithClaudePosudek/posudky_report.py @@ -0,0 +1,237 @@ +import fdb +import openpyxl +from openpyxl.styles import Font, PatternFill, Alignment +from openpyxl.utils import get_column_letter +from datetime import datetime +import os +import sys + +# --- Připojení --- +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +# --- Výstupní soubor --- +output_dir = r'u:\Dropbox\!!!Days\Downloads Z230' +now = datetime.now() +filename = now.strftime('%Y-%m-%d_%H-%M-%S') + '_Přehled posudků řidičák.xlsx' +output_path = os.path.join(output_dir, filename) + +# --- Smazání předchozích verzí --- +for f in os.listdir(output_dir): + if f.endswith('_Přehled posudků řidičák.xlsx'): + os.remove(os.path.join(output_dir, f)) + +wb = openpyxl.Workbook() + +# ===================== +# Pomocné funkce +# ===================== + +HEADER_FILL = PatternFill('solid', fgColor='2F5496') +HEADER_FONT = Font(bold=True, color='FFFFFF') +ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1') +GREEN_FILL = PatternFill('solid', fgColor='C6EFCE') +GREEN_FONT = Font(bold=True, color='276221') + +def style_header(ws): + for cell in ws[1]: + cell.fill = HEADER_FILL + cell.font = HEADER_FONT + cell.alignment = Alignment(horizontal='center') + +def autofit(ws): + for col in ws.columns: + max_len = max((len(str(cell.value)) if cell.value is not None else 0) for cell in col) + ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 2, 50) + +def fmt(val): + if val is None: + return '' + return val + +def parse_data(data_str): + """Parsuje key=value text z HISTDOC.DATA do slovníku.""" + result = {} + if not data_str: + return result + for line in data_str.splitlines(): + if '=' in line: + key, _, val = line.partition('=') + result[key.strip()] = val.strip() + return result + +def parse_date(val): + """Převede 'D:DD.MM.YYYY' na datetime.date, nebo vrátí původní hodnotu.""" + if val and val.startswith('D:'): + try: + return datetime.strptime(val[2:], '%d.%m.%Y').date() + except ValueError: + return val + return val + +# ===================== +# List 1 – MOTORVO (ruční posudky k řízení) +# ===================== + +ws1 = wb.active +ws1.title = 'MOTORVO' + +# Množina (IDPACI, DATUM) kde existuje EPOSMRO +cur.execute(""" + SELECT IDPACI, DATUM FROM HISTDOC WHERE TYP = 'EPOSMRO' +""") +eposmro_keys = set((r[0], r[1]) for r in cur.fetchall()) + +cur.execute(""" + SELECT h.ID, h.DATUM, h.IDPACI, + k.PRIJMENI, k.JMENO, k.RODCIS, + h.DATA, h.PORCISLO, h.STAV, h.PRINTED, h.IDUZIV, h.CREATED + FROM HISTDOC h + JOIN KAR k ON k.IDPAC = h.IDPACI + WHERE h.TYP = 'MOTORVO' + ORDER BY h.ID DESC +""") +raw_rows = cur.fetchall() + +headers = [ + 'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS', + 'PorCislo', 'DatumVyd', 'DatKonec', 'DruhProh', + 'Posouzeni', 'ZpusobPodminka', 'SkupinaPodminka', + 'Skupiny', + 'ePosudek', + 'STAV', 'PRINTED', 'IDUZIV', 'CREATED' +] +ws1.append(headers) + +for i, row in enumerate(raw_rows, start=2): + (hid, datum, idpac, prijmeni, jmeno, rodcis, + data_blob, porcislo, stav, printed, iduziv, created) = row + + data = parse_data(data_blob) + + posouzeni = '' + if data.get('Posouzeni') == 'T': + if data.get('Posouzeni2') == 'T': + posouzeni = 'nezpůsobilý' + elif data.get('ZpusobPodminka') == 'B:1': + posouzeni = 'způsobilý s podmínkou' + else: + posouzeni = 'způsobilý' + + skupiny = data.get('SkupinyRidicskehoOpravneniSeznam', '') + if not skupiny: + # MOTORVO nemá SkupinyRidicskehoOpravneniSeznam, zkusíme ZpusobJe + skupiny = data.get('ZpusobJe', '') + + ws1.append([ + hid, + fmt(datum), + idpac, + fmt(prijmeni), + fmt(jmeno), + fmt(rodcis), + fmt(porcislo or data.get('PorCislo', '')), + parse_date(data.get('DatumVyd', '')), + parse_date(data.get('DatKonec', '')), + fmt(data.get('DruhProh', '')), + posouzeni, + fmt(data.get('ZpusobPodminka', '')), + fmt(data.get('SkupinaPodminka', '')), + fmt(skupiny), + 'ANO' if (idpac, datum) in eposmro_keys else 'NE', + fmt(stav), + fmt(printed), + fmt(iduziv), + fmt(created), + ]) + + if i % 2 == 0: + for cell in ws1[i]: + cell.fill = ZEBRA_FILL + + # Sloupec ePosudek – zvýraznit ANO zeleně + epos_col = headers.index('ePosudek') + 1 + cell = ws1.cell(row=i, column=epos_col) + if cell.value == 'ANO': + cell.fill = GREEN_FILL + cell.font = GREEN_FONT + +style_header(ws1) +ws1.freeze_panes = 'A2' +autofit(ws1) + +# ===================== +# List 2 – EPOSMRO (elektronická podání do registru) +# ===================== + +ws2 = wb.create_sheet('EPOSMRO') + +cur.execute(""" + SELECT h.ID, h.DATUM, h.IDPACI, + k.PRIJMENI, k.JMENO, k.RODCIS, + h.DATA, h.STAV, h.CREATED, + e.ID_PODANI, e.ODESLANO, e.STATUS + FROM HISTDOC h + JOIN KAR k ON k.IDPAC = h.IDPACI + LEFT JOIN HISTDOC_EPOSUDEK e ON e.ID_HISTDOC = h.ID + WHERE h.TYP = 'EPOSMRO' + ORDER BY h.ID DESC +""") +epos_rows = cur.fetchall() + +headers2 = [ + 'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS', + 'DatumVyd', 'DatKonec', 'DruhProhlidky', 'DruhPosudku', + 'Vysledek', 'StavPosudku', 'TypAkce', + 'STAV', 'CREATED', + 'ID_PODANI', 'ODESLANO', 'STATUS_ODESL' +] +ws2.append(headers2) + +for i, row in enumerate(epos_rows, start=2): + (hid, datum, idpac, prijmeni, jmeno, rodcis, + data_blob, stav, created, + id_podani, odeslano, status_odesl) = row + + data = parse_data(data_blob) + + ws2.append([ + hid, + fmt(datum), + idpac, + fmt(prijmeni), + fmt(jmeno), + fmt(rodcis), + parse_date(data.get('DatumVystaveni', '')), + parse_date(data.get('PlatnostDo', '')), + fmt(data.get('DruhProhlidkyNazev', '')), + fmt(data.get('DruhPosudkuNazev', '')), + fmt(data.get('VysledekNazev', '')), + fmt(data.get('StavPosudkuNazev', '')), + fmt(data.get('TypAkceNazev', '')), + fmt(stav), + fmt(created), + fmt(id_podani), + fmt(odeslano), + fmt(status_odesl), + ]) + + if i % 2 == 0: + for cell in ws2[i]: + cell.fill = ZEBRA_FILL + +style_header(ws2) +ws2.freeze_panes = 'A2' +autofit(ws2) + +# ===================== +# Uložení +# ===================== + +conn.close() +wb.save(output_path) +sys.stdout.buffer.write(f'Ulozeno: {output_path}\n'.encode('utf-8')) +sys.stdout.buffer.write(f'MOTORVO: {len(raw_rows)} radku, EPOSMRO: {len(epos_rows)} radku\n'.encode('utf-8'))