notebookvb

This commit is contained in:
Vladimir Buzalka
2026-04-29 06:51:47 +02:00
parent a1b9c93506
commit a9c143ba24
141 changed files with 30711 additions and 0 deletions
@@ -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()
@@ -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 11301134 |
| `TOKS` | dokladd + vykony | Kódy 1511815121 |
| `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`)
@@ -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.")