Přidán podprojekt Recepty (eRecept SÚKL)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
# Dotazy — přehled lékového záznamu pacienta
|
||||
|
||||
Skripty pro zobrazení a export lékového záznamu konkrétního pacienta z MySQL databáze `medicus`.
|
||||
Pacient se identifikuje **rodným číslem** — to se vyhledá v lokální Firebird databázi Medicusu,
|
||||
odkud se získá příjmení a datum narození, a teprve těmito dvěma hodnotami se najde pacient v MySQL.
|
||||
|
||||
---
|
||||
|
||||
## Soubory
|
||||
|
||||
| Soubor | Co dělá |
|
||||
|--------|---------|
|
||||
| `prehled_pacienta.py` | Konzolový výpis — lékaři + předpisy pacienta |
|
||||
| `prehled_pacienta_excel.py` | Export do formátovaného souboru Excel (.xlsx) |
|
||||
|
||||
---
|
||||
|
||||
## Nastavení (obě skripty)
|
||||
|
||||
Na začátku každého souboru jsou tři proměnné:
|
||||
|
||||
```python
|
||||
RODNE_CISLO = "440802/018" # rodné číslo — funguje s lomítkem i bez: "4408020183"
|
||||
DATUM_OD = "01.01.2025" # předpisy od tohoto data; None = všechny předpisy
|
||||
VYSTUP_DIR = None # pouze excel: složka výstupu; None = stejná jako skript
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spuštění
|
||||
|
||||
```bash
|
||||
# Konzolový výpis
|
||||
.venv\Scripts\python.exe Dotazy\prehled_pacienta.py
|
||||
|
||||
# Export do Excelu
|
||||
.venv\Scripts\python.exe Dotazy\prehled_pacienta_excel.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zdroje dat
|
||||
|
||||
### 1. Firebird — Medicus (`medicus.fdb`)
|
||||
|
||||
Slouží výhradně k identifikaci pacienta podle rodného čísla.
|
||||
|
||||
```
|
||||
DSN: localhost:c:\medicus 3\data\medicus.fdb
|
||||
User: SYSDBA / masterkey
|
||||
Charset: win1250
|
||||
Tabulka: KAR
|
||||
```
|
||||
|
||||
Dotaz:
|
||||
```sql
|
||||
SELECT KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR
|
||||
FROM KAR WHERE KAR.RODCIS = ?
|
||||
```
|
||||
|
||||
Rodné číslo se normalizuje před dotazem — odstraní se lomítko a mezery:
|
||||
```python
|
||||
rc = rc.replace("/", "").replace(" ", "").strip()
|
||||
```
|
||||
|
||||
### 2. MySQL — databáze `medicus`
|
||||
|
||||
Obsahuje lékové záznamy stažené z eReceptu SÚKL.
|
||||
|
||||
```
|
||||
Host: 192.168.1.76
|
||||
User: root
|
||||
DB: medicus
|
||||
```
|
||||
|
||||
Pacient se vyhledá podle příjmení a data narození (získaných z Firebirdu):
|
||||
```sql
|
||||
SELECT id, prijmeni, jmena, datum_narozeni
|
||||
FROM pacient
|
||||
WHERE prijmeni = %s AND datum_narozeni = %s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Co se zobrazuje
|
||||
|
||||
### Část 1 — Předepisující lékaři
|
||||
|
||||
Všichni lékaři, kteří pacientovi za celou dobu předepsali alespoň jeden lék,
|
||||
seřazeni sestupně podle počtu předpisů.
|
||||
|
||||
Sloupce: `#` | `Lékař` | `Odbornost` | `Pracoviště a adresa` | `Předpisů`
|
||||
|
||||
```sql
|
||||
SELECT pr.prijmeni, pr.jmena,
|
||||
pr.icp,
|
||||
CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa,
|
||||
COUNT(*) AS pocet_predpisu
|
||||
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_predpisu DESC
|
||||
```
|
||||
|
||||
### Část 2 — Všechny předpisy
|
||||
|
||||
Předpisy od `DATUM_OD`, seřazené sestupně dle data vystavení.
|
||||
|
||||
Zobrazuje se **vydaný lék** (z tabulky `vydej`), nikoli předepsaný název.
|
||||
Pokud lék nebyl vyzvednut, zobrazí se předepsaný název s příznakem `*NV`.
|
||||
|
||||
Sloupce: `#` | `Datum` | `Vydaný lék` | `ATC` | `Návod` | `Lékař` | `Odbornost` | `Adresa`
|
||||
|
||||
```sql
|
||||
SELECT p.datum_vystaveni,
|
||||
COALESCE(v.nazev, p.nazev) AS vydany_lek,
|
||||
v.nazev IS NULL AS nevyzvednuto,
|
||||
p.atc,
|
||||
p.navod,
|
||||
pr.prijmeni,
|
||||
pr.jmena,
|
||||
pr.icp,
|
||||
CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa
|
||||
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
|
||||
AND p.datum_vystaveni >= %s -- pouze pokud DATUM_OD není None
|
||||
ORDER BY p.datum_vystaveni DESC
|
||||
```
|
||||
|
||||
Klíčový princip `COALESCE(v.nazev, p.nazev)`:
|
||||
- `v.nazev` — název léku, který lékárna **skutečně vydala** (může být jiná značka než předepsaná)
|
||||
- `p.nazev` — název léku, který lékař **předepsal** (zobrazí se jen pokud výdej neexistuje → `*NV`)
|
||||
|
||||
---
|
||||
|
||||
## Odbornost lékaře
|
||||
|
||||
Odbornost se odvozuje z posledních 3 číslic pole `predepisujici.icp` (IČP pracoviště).
|
||||
|
||||
```
|
||||
ICP: 09305001 → kód odbornosti: 001 → všeobecné praktické lékařství
|
||||
ICP: 08006272 → kód odbornosti: 272 → alergologie
|
||||
ICP: 08075603 → kód odbornosti: 603 → onkologie
|
||||
```
|
||||
|
||||
Funkce:
|
||||
```python
|
||||
def odbornost_z_icp(icp):
|
||||
if not icp or len(icp) < 3:
|
||||
return ""
|
||||
return ODBORNOST.get(icp[-3:], f"odb. {icp[-3:]}")
|
||||
```
|
||||
|
||||
Pro neznámé kódy se zobrazí `odb. XXX` (XXX = třímístný kód).
|
||||
|
||||
### Zdroj dat — tabulky `vzp_pracoviste` + `odbornost` (MySQL)
|
||||
|
||||
Slovník `ODBORNOST` se **načítá dynamicky při startu skriptu** z MySQL:
|
||||
|
||||
```python
|
||||
def _nacti_odbornosti():
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
with conn.cursor(pymysql.cursors.Cursor) as cur:
|
||||
cur.execute("""
|
||||
SELECT vp.icp, o.nazev
|
||||
FROM vzp_pracoviste vp
|
||||
JOIN odbornost o ON o.kod = vp.odbornost
|
||||
WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do
|
||||
ORDER BY vp.platnost_od DESC
|
||||
""")
|
||||
result = {}
|
||||
for icp, nazev in cur.fetchall():
|
||||
result.setdefault(icp, nazev)
|
||||
return result
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
ODBORNOST = _nacti_odbornosti()
|
||||
```
|
||||
|
||||
- `vzp_pracoviste` — oficiální číselník VZP (stahován týdně z VZP Point přes `import_vzp_pracoviste.py`), obsahuje ~52 000 záznamů s přímou vazbou ICP → kód odbornosti
|
||||
- `odbornost` — číselník názvů odborností importovaný z Firebird tabulky `odborn` (360 aktuálně platných kódů)
|
||||
- Vyhledávání probíhá podle **plného 8-znakového ICP** — spolehlivé i pro pracoviště, která změnila odbornost
|
||||
- Slovník obsahuje ~43 000 aktuálně platných ICP kódů
|
||||
|
||||
---
|
||||
|
||||
## Excel export (`prehled_pacienta_excel.py`)
|
||||
|
||||
Soubor se ukládá do stejné složky jako skript (nebo do `VYSTUP_DIR`).
|
||||
|
||||
### Pojmenování souborů
|
||||
|
||||
```
|
||||
LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}.xlsx ← základní
|
||||
LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}_v2.xlsx ← pokud základní existuje
|
||||
LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}_v3.xlsx ← atd.
|
||||
```
|
||||
|
||||
Versioning zabrání přepsání dříve exportovaných souborů.
|
||||
|
||||
### Vzhled a formátování
|
||||
|
||||
| Prvek | Barva | Popis |
|
||||
|-------|-------|-------|
|
||||
| Záhlaví (jméno pacienta) | `#1F4E79` tmavě modrá | tučné, 14pt |
|
||||
| Záhlaví tabulky | `#1F4E79` tmavě modrá | bílý text, 10pt |
|
||||
| Nadpis sekce | `#2E75B6` střední modrá | bílý text, 11pt |
|
||||
| Info o pacientovi | `#DEEAF1` světle modrá | datum narozeni, datum tisku, předpisy od |
|
||||
| Sudé řádky | `#EBF3FB` velmi světle modrá | střídání řádků |
|
||||
| Liché řádky | `#FFFFFF` bílá | |
|
||||
| Nevyzvednuto | `#FCE4D6` lososová | zvýraznění celého řádku |
|
||||
| Ohraničení | `#B8CCE4` světle modrá | tenká linka |
|
||||
|
||||
- Font: **Arial** ve všech buňkách
|
||||
- Automatická šířka sloupců a výška řádků (`autofit`)
|
||||
- Zmrazení prvního řádku (`freeze_panes = "A2"`)
|
||||
- 8 sloupců: `#` | `Lékař/Datum` | `Odbornost/Vydaný lék` | `Pracoviště/ATC` | … | `Předpisů/Pracoviště a adresa`
|
||||
|
||||
### Tabulka lékařů (8 sloupců)
|
||||
|
||||
`#` | `Lékař` | `Odbornost` | `Pracoviště` | `Ulice` | `PSČ` | `Město` | `Předpisů`
|
||||
|
||||
### Tabulka předpisů (8 sloupců)
|
||||
|
||||
`#` | `Datum` | `Vydaný lék` | `ATC` | `Návod` | `Lékař` | `Odbornost` | `Pracoviště a adresa`
|
||||
|
||||
---
|
||||
|
||||
## Závislosti
|
||||
|
||||
```
|
||||
pymysql ← MySQL klient
|
||||
fdb ← Firebird klient
|
||||
openpyxl ← Excel export (pouze prehled_pacienta_excel.py)
|
||||
```
|
||||
|
||||
Všechny jsou součástí `requirements.txt` a nainstalují se přes `setup.ps1`.
|
||||
|
||||
---
|
||||
|
||||
## Typické chybové situace
|
||||
|
||||
| Chyba | Příčina | Řešení |
|
||||
|-------|---------|--------|
|
||||
| `Rodne cislo nenalezeno v Medicusu` | RC není v tabulce KAR | Zkontrolovat číslo, ověřit v Medicusu |
|
||||
| `Pacient nema zaznam v MySQL` | Lékový záznam nebyl stažen | Spustit `07StahnoutVsechny.py` nebo `reimport_z_xml.py` |
|
||||
| `PermissionError` při ukládání xlsx | Soubor je otevřen v Excelu | Zavřít Excel a spustit znovu — verzování uloží jako `_v2` |
|
||||
| Odbornost zobrazena jako `odb. XXX` | Kód není ve slovníku | Informativní stav — kód je platný, jen není pojmenován |
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Prehled lekoveho zaznamu pacienta z MySQL.
|
||||
|
||||
Zobrazuje:
|
||||
1. Lekare, kteri pacientovi predepsali leky (sestupne podle poctu predpisu)
|
||||
2. Vsechny predpisy od zadaneho data — s nazvem VYDANEHO leku (ne predepsaneho)
|
||||
|
||||
Nastaveni:
|
||||
RODNE_CISLO ... rodne cislo pacienta (cifry, bez lomitka)
|
||||
DATUM_OD ... predpisy od tohoto data ve formatu DD.MM.RRRR (None = vsechny)
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
import sys
|
||||
import fdb
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
|
||||
|
||||
|
||||
def odbornost_z_icp(icp):
|
||||
"""Vrati nazev odbornosti podle ICP (plny 8-znakovy kod) z tabulky vzp_pracoviste."""
|
||||
if not icp:
|
||||
return ""
|
||||
return ODBORNOST.get(icp, f"odb. {icp[-3:]}")
|
||||
|
||||
# ── NASTAVENÍ ─────────────────────────────────────────────────────────────────
|
||||
RODNE_CISLO = "440802/018" # funguje s lomitkem i bez: 7309208104 nebo 730920/8104
|
||||
DATUM_OD = "01.01.2025" # None = vsechny predpisy
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
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,
|
||||
)
|
||||
def _nacti_odbornosti():
|
||||
"""Nacteni odbornosti z MySQL: vzp_pracoviste JOIN odbornost (aktualne platne ICP)."""
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
with conn.cursor(pymysql.cursors.Cursor) as cur:
|
||||
cur.execute("""
|
||||
SELECT vp.icp, o.nazev
|
||||
FROM vzp_pracoviste vp
|
||||
JOIN odbornost o ON o.kod = vp.odbornost
|
||||
WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do
|
||||
ORDER BY vp.platnost_od DESC
|
||||
""")
|
||||
result = {}
|
||||
for icp, nazev in cur.fetchall():
|
||||
result.setdefault(icp, nazev)
|
||||
return result
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
ODBORNOST = _nacti_odbornosti()
|
||||
|
||||
|
||||
|
||||
SEP = "-" * 110
|
||||
SEP2 = "-" * 165
|
||||
|
||||
|
||||
def parse_datum(s, nazev):
|
||||
try:
|
||||
return datetime.strptime(s, "%d.%m.%Y").date()
|
||||
except (ValueError, TypeError):
|
||||
sys.exit(f"Nespravny format data '{nazev}': '{s}'. Pouzijte DD.MM.RRRR.")
|
||||
|
||||
|
||||
def normalizuj_rc(rc):
|
||||
"""Odstrani lomitko a mezery z rodneho cisla."""
|
||||
return rc.replace("/", "").replace(" ", "").strip()
|
||||
|
||||
|
||||
def najdi_v_firebirdu(rodne_cislo):
|
||||
"""Vrati prijmeni, jmeno a datum narozeni z Medicusu podle rodneho cisla."""
|
||||
rc = normalizuj_rc(rodne_cislo)
|
||||
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 '{rodne_cislo}' nenalezeno v Medicusu.")
|
||||
return {"prijmeni": row[0].strip(), "jmeno": row[1].strip(), "datnar": row[2]}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def najdi_pacienty(cur, prijmeni, datum_narozeni):
|
||||
cur.execute(
|
||||
"SELECT id, prijmeni, jmena, datum_narozeni "
|
||||
"FROM pacient WHERE prijmeni = %s AND datum_narozeni = %s",
|
||||
(prijmeni, datum_narozeni),
|
||||
)
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def tiskni_lekare(cur, pacient_id, prijmeni, jmena, datum_narozeni):
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT pr.prijmeni, pr.jmena,
|
||||
pr.icp,
|
||||
CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa,
|
||||
COUNT(*) AS pocet_predpisu
|
||||
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_predpisu DESC
|
||||
""",
|
||||
(pacient_id,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
print(f"\n{SEP}")
|
||||
print(f" PACIENT: {prijmeni} {jmena} | nar. {datum_narozeni.strftime('%d.%m.%Y')}")
|
||||
print(SEP)
|
||||
print(f"\nPREDEPISUJICI LEKARI:")
|
||||
print(f"{'#':<4} {'Lekar':<30} {'Odbornost':<25} {'Pracoviste a adresa':<50} {'Predpisu':>8}")
|
||||
print(SEP)
|
||||
for i, r in enumerate(rows, 1):
|
||||
lekar = f"{r['prijmeni']} {r['jmena']}"
|
||||
odb = odbornost_z_icp(r['icp'])
|
||||
print(f"{i:<4} {lekar:<30} {odb:<25} {r['adresa']:<50} {r['pocet_predpisu']:>8}")
|
||||
if not rows:
|
||||
print(" Zadne predpisy nenalezeny.")
|
||||
|
||||
|
||||
def tiskni_predpisy(cur, pacient_id, datum_od):
|
||||
podminka = "AND p.datum_vystaveni >= %s" if datum_od else ""
|
||||
params = (pacient_id, datum_od) if datum_od else (pacient_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,
|
||||
pr.jmena,
|
||||
pr.icp,
|
||||
CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa
|
||||
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,
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
od_text = f"od {datum_od.strftime('%d.%m.%Y')}" if datum_od else "vse"
|
||||
print(f"\nVSECHNY PREDPISY ({od_text}) — celkem {len(rows)}:")
|
||||
print(f"{'#':<4} {'Datum':<12} {'Vydany lek':<30} {'ATC':<8} {'Navod':<20} {'Lekar':<25} {'Odbornost':<22} Adresa")
|
||||
print(SEP2)
|
||||
for i, r in enumerate(rows, 1):
|
||||
datum = r["datum_vystaveni"].strftime("%d.%m.%Y")
|
||||
lekar = f"{r['prijmeni']} {r['jmena']}"
|
||||
lek = (r["vydany_lek"] or "")[:28]
|
||||
if r["nevyzvednuto"]:
|
||||
lek = f"{lek} *NV"
|
||||
navod = (r["navod"] or "")[:19]
|
||||
atc = (r["atc"] or "")
|
||||
odb = odbornost_z_icp(r["icp"])[:21]
|
||||
print(f"{i:<4} {datum:<12} {lek:<30} {atc:<8} {navod:<20} {lekar:<25} {odb:<22} {r['adresa']}")
|
||||
if not rows:
|
||||
print(" Zadne predpisy nenalezeny.")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
datum_od = parse_datum(DATUM_OD, "DATUM_OD") if DATUM_OD else None
|
||||
|
||||
# 1. Najdi pacienta v Medicusu (Firebird) podle rodneho cisla
|
||||
fb_pac = najdi_v_firebirdu(RODNE_CISLO)
|
||||
prijmeni = fb_pac["prijmeni"]
|
||||
datum_narozeni = fb_pac["datnar"]
|
||||
|
||||
print(f"\nFirebird: nalezen {prijmeni} {fb_pac['jmeno']} nar. {datum_narozeni}")
|
||||
|
||||
# 2. Dotaz do MySQL
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
pacienti = najdi_pacienty(cur, prijmeni, datum_narozeni)
|
||||
|
||||
if not pacienti:
|
||||
print(f"Pacient '{prijmeni}' nar. {datum_narozeni} nema zaznam v MySQL (lekovy zaznam nebyl stazeny).")
|
||||
return
|
||||
|
||||
for pac in pacienti:
|
||||
tiskni_lekare(cur, pac["id"], pac["prijmeni"], pac["jmena"], pac["datum_narozeni"])
|
||||
tiskni_predpisy(cur, pac["id"], datum_od)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,354 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
def odbornost_z_icp(icp):
|
||||
"""Vrati nazev odbornosti podle ICP (plny 8-znakovy kod) z tabulky vzp_pracoviste."""
|
||||
if not icp:
|
||||
return ""
|
||||
return ODBORNOST.get(icp, f"odb. {icp[-3:]}")
|
||||
|
||||
|
||||
# ── NASTAVENÍ ─────────────────────────────────────────────────────────────────
|
||||
RODNE_CISLO = "370315041"
|
||||
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,
|
||||
)
|
||||
def _nacti_odbornosti():
|
||||
"""Nacteni odbornosti z MySQL: vzp_pracoviste JOIN odbornost (aktualne platne ICP)."""
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
with conn.cursor(pymysql.cursors.Cursor) as cur:
|
||||
cur.execute("""
|
||||
SELECT vp.icp, o.nazev
|
||||
FROM vzp_pracoviste vp
|
||||
JOIN odbornost o ON o.kod = vp.odbornost
|
||||
WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do
|
||||
ORDER BY vp.platnost_od DESC
|
||||
""")
|
||||
result = {}
|
||||
for icp, nazev in cur.fetchall():
|
||||
result.setdefault(icp, nazev)
|
||||
return result
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
ODBORNOST = _nacti_odbornosti()
|
||||
|
||||
|
||||
|
||||
# ── 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()
|
||||
Reference in New Issue
Block a user