z230
This commit is contained in:
@@ -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í
|
||||
@@ -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'))
|
||||
Reference in New Issue
Block a user