Files
recept/LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md
T
2026-04-13 16:45:07 +02:00

544 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lékový záznam eRecept → MySQL
Pipeline pro hromadné stažení lékových záznamů všech registrovaných pacientů
z eRecept SÚKL API a jejich uložení do relační databáze MySQL.
---
## Soubory
| Soubor | Co dělá |
|--------|---------|
| `05UlozitOdpoved.py` | Stáhne XML pro **jednoho** pacienta (ruční test/ladění) |
| `06UlozitDoMySQL.py` | DDL schématu, parsování XML, import do MySQL — používá se jako **knihovna**, ne spouštět přímo! |
| `07StahnoutVsechny.py` | **Hlavní skript** — načte pacienty z Medicusu, stáhne lékové záznamy, uloží XML i DB záznamy |
| `reimport_z_xml.py` | Reimport XML ze zálohy bez volání API — viz sekce níže |
```
recept/
├── setup.ps1 ← vytvoří .venv, nainstaluje závislosti, Playwright chromium
├── requirements.txt ← seznam Python závislostí
├── .venv/ ← virtuální prostředí (Python 3.x)
├── LékovýZáznamWithClaude/
│ ├── 05UlozitOdpoved.py
│ ├── 06UlozitDoMySQL.py
│ ├── 07StahnoutVsechny.py
│ ├── reimport_z_xml.py
│ ├── LEKOVY_ZAZNAM_DB.md ← tento soubor
│ ├── Logs/ ← log každého běhu (UTF-8, YYYY-MM-DD_HH-MM-SS.log)
│ ├── Tests/ ← starší vývojové skripty
│ └── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/Prijmeni_Jmena_datnar.xml)
└── Dotazy/
├── prehled_pacienta.py ← konzolový přehled pacienta
├── prehled_pacienta_excel.py ← export přehledu pacienta do Excelu
└── DOTAZY.md ← dokumentace dotazovacích skriptů
```
> **⚠️ NIKDY nespouštět `06UlozitDoMySQL.py` přímo** — zavolá `vytvor_schema()`,
> která provede `DROP TABLE` a smaže celou databázi.
> Pro import dat vždy použít `07StahnoutVsechny.py` nebo `reimport_z_xml.py`.
---
## Nastavení prostředí (jednorázově)
```powershell
# PowerShell — spustit jednou po naklonování projektu
cd U:\recept
.\setup.ps1
```
`setup.ps1` provede:
1. Vytvoří `.venv` s Python interpretem z `C:\Python\python.exe`
2. Nainstaluje všechny závislosti z `requirements.txt`
3. Nainstaluje Playwright Chromium (pro případné automatizace)
Po nastavení aktivace:
```powershell
.venv\Scripts\Activate.ps1
```
### requirements.txt
```
requests
requests-pkcs12
pymysql
fdb
zeep
mysql-connector-python
playwright
openpyxl
```
---
## Typické spuštění
```bash
# Hromadné stažení všech registrovaných pacientů
python 07StahnoutVsechny.py
# Pouze vybraná příjmení (testování / rodina)
python 07StahnoutVsechny.py --prijmeni Buzalka,Buzalková,Kusinová
# Dávkování po částech
python 07StahnoutVsechny.py --offset 100 --limit 50
# Reimport ze zálohy XML (bez volání API) — viz níže
python reimport_z_xml.py
```
---
## Autentizace (eRecept SÚKL, ostrý provoz)
| Parametr | Hodnota |
|----------|---------|
| Endpoint | `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2` |
| mTLS certifikát | `AMBSUKL214235369G_31DEC2024.pfx` (platnost do 31. 12. 2026) |
| HTTP Basic user | UUID lékaře `e08c89c6-2b1a-4eba-8ed9-4e3e63618379` |
| SOAP operace | `NacistLekovyZaznam` |
| XML namespace | `http://www.sukl.cz/erp/201912` |
| Verze zprávy | `202501A` |
Certifikát = identifikace **ordinace**, UUID+heslo = identifikace **lékaře jako osoby**.
---
## Zdroj pacientů — Medicus (Firebird)
Pacienti se načítají přímo z `medicus.fdb` jako registrovaní pacienti ordinace:
```
DSN: localhost:c:\medicus 3\data\medicus.fdb
User: SYSDBA / masterkey
Charset: win1250
IČP: 09305001 (odbornost 001 — praktický lékař)
```
Podmínky registrace: `vyrazen = 'N'`, `registr.priznak IN ('V','D','A')`,
registrace platná k dnešnímu datu.
---
## Logika přírůstkového stahování
```
první stažení pacienta → PocetMesicu = 60 (maximum, 5 let)
opakované stažení → ceil(dny od posledního stažení / 30) + 1
```
- Překryv 1 měsíce zajistí, že nepřijdeme o nic na hranici období.
- `INSERT IGNORE` na `id_lp_predpis` / `id_lp_vydej` zabrání duplikátům.
- Pauza mezi voláními API: **náhodně 1020 sekund**.
---
## Ošetření chyb API
Pacienti, kteří nejsou v eReceptu ztotožněni (nikdy nebyli v lékárně s e-receptem),
vrátí SOAP Fault `Z002`. Skript:
1. Zachytí chybu (HTTP 500 nebo SOAP Fault v těle odpovědi)
2. Uloží text chyby do `pacient.poznamka`
3. Pokračuje dalším pacientem
Při příštím úspěšném stažení se `poznamka` automaticky vymaže.
```sql
-- přehled pacientů s chybou
SELECT prijmeni, jmena, datum_narozeni, poznamka
FROM pacient
WHERE poznamka IS NOT NULL;
```
---
## XML archiv
Každá odpověď API se uloží jako soubor:
```
xml_archive/YYYY-MM-DD/Prijmeni_Jmena_YYYY-MM-DD.xml
```
Cesta je zároveň uložena v `zprava.xml_soubor`.
Účel: možnost re-parsování při budoucích změnách schématu bez nutnosti znovu volat API.
---
## Výstup do konzole a logů
Konzole zobrazuje jen jeden řádek na pacienta:
```
[ 1/1621] Abohamda Horia OK 168p 252v 247 KB
[ 5/1621] Alakbarov Farid CHYBA Z002 - Lekovy zaznam ne...
```
Kompletní detaily (počty nových záznamů, ID zprávy, doba čekání) jsou v:
```
Logs/YYYY-MM-DD_HH-MM-SS.log
```
---
## Databázové schéma — `medicus` (MySQL)
Všechny délky a datové typy jsou přesně dle XSD, **ne odhady**.
Lék je denormalizován přímo do řádku předpisu/výdeje.
### Relační diagram
```
pacient (1)
└── zprava (N) -- každé volání API = 1 zpráva
├── predpis (N)
│ └── predpis_slozka (N) -- složky IPLP z předpisu
└── vydej (N)
└── vydej_slozka (N) -- složky IPLP z výdeje
vydej.id_lp_predpis → predpis.id_lp_predpis (párování výdeje s předpisem)
predpis.kod_predepisujiciho → predepisujici.lekar_kod
vydej.kod_vydavajiciho → vydavajici.lekarnik_kod
```
### Tabulka `pacient`
Zrcadlo registrovaných pacientů z Medicusu. Aktualizuje se při každém běhu `07`.
| Sloupec | Typ | Poznámka |
|---------|-----|----------|
| `id` | INT PK | |
| `idpac` | INT UNIQUE | IDPAC z tabulky KAR v Medicusu |
| `prijmeni` | VARCHAR(35) | |
| `jmena` | VARCHAR(24) | |
| `datum_narozeni` | DATE | |
| `aktivni` | TINYINT(1) | 0 = přeskočit při hromadném běhu |
| `poznamka` | VARCHAR(500) | poslední chyba API; NULL = OK |
### Tabulka `zprava`
Jeden řádek = jedno volání API (jeden pacient, jeden čas).
| Sloupec | Typ | Poznámka |
|---------|-----|----------|
| `id_zpravy` | CHAR(36) UNIQUE | UUID z eReceptu |
| `pacient_id` | INT FK → pacient | |
| `verze` | VARCHAR(20) | verze zprávy (202501A) |
| `odeslano` | DATETIME | čas odeslání dotazu |
| `aplikace` | VARCHAR(512) | SW SÚKL serveru |
| `id_podani` | CHAR(36) | UUID podání |
| `prijato` | DATETIME | čas přijetí odpovědi |
| `pacient_prijmeni` | VARCHAR(35) | z XML odpovědi |
| `pacient_jmena` | VARCHAR(24) | z XML odpovědi |
| `pacient_datum_narozeni` | DATE | z XML odpovědi |
| `xml_soubor` | VARCHAR(255) | relativní cesta k archivu |
| `stazeno` | DATETIME | automaticky při INSERT |
### Tabulka `predpis`
Dle `lz_nacteni_predepsany_lp_erp_type`.
| Sloupec | Typ | NOT NULL | Poznámka |
|---------|-----|----------|----------|
| `id_lp_predpis` | CHAR(36) UNIQUE | ✓ | UUID z eReceptu |
| `zprava_id` | INT FK | ✓ | |
| `kod_predepisujiciho` | VARCHAR(36) | ✓ | UUID lékaře |
| `datum_vystaveni` | DATE | ✓ | |
| `mnozstvi` | SMALLINT | ✓ | 19999 |
| `navod` | VARCHAR(80) | ✓ | |
| `opakovani` | INT | | |
| `modry_pruh` | TINYINT(1) | | návykové látky |
| `typ_leku` | ENUM | | HVLPReg / HVLPNereg / IPLP / INN |
| `lek_kod` | CHAR(7) | | kód SÚKL (jen HVLP) |
| `atc` | VARCHAR(7) | | ATC kód |
| `nazev` | VARCHAR(200) | | |
| `forma` | VARCHAR(27) | | |
| `sila` | VARCHAR(24) | | |
| `cesta_podani` | VARCHAR(15) | | POR, INH, … |
| `baleni` | VARCHAR(22) | | string, např. "100 ks" |
| `postup_pripravy` | VARCHAR(4000) | | receptura IPLP |
### Tabulka `predpis_slozka`
Složky IPLP předpisů (lékař typicky nevyplňuje, kvalitní data spíše u výdeje).
| Sloupec | Typ | NOT NULL |
|---------|-----|----------|
| `predpis_id` | INT FK | ✓ |
| `mnozstvi` | DECIMAL(15,6) | ✓ |
| `jednotka` | ENUM('g','ks') | ✓ |
| `nazev` | VARCHAR(200) | ✓ |
| `surovina` | CHAR(7) | |
| `hvlp_reg` | CHAR(7) | |
### Tabulka `vydej`
Dle `lz_nacteni_vydany_lp_erp_type`.
| Sloupec | Typ | NOT NULL | Poznámka |
|---------|-----|----------|----------|
| `id_lp_vydej` | CHAR(36) UNIQUE | ✓ | UUID výdeje |
| `zprava_id` | INT FK | ✓ | |
| `id_lp_predpis` | CHAR(36) FK | | NULL = výdej bez e-předpisu |
| `kod_vydavajiciho` | VARCHAR(36) | ✓ | UUID lékárníka |
| `datum_vydeje` | DATE | ✓ | |
| `mnozstvi` | DECIMAL(6,2) | ✓ | desetinné — např. 0.5 balení |
| `navod` | VARCHAR(80) | ✓ | |
| `exspirace` | DATE | | exspirace šarže |
| `sarze` | VARCHAR(50) | ✓ | |
| `seriove_cislo` | VARCHAR(20) | | léky s el. sledováním |
| `pozn` | VARCHAR(1000) | | poznámka lékárníka |
| `typ_leku` | ENUM | | HVLPReg / HVLPNereg / IPLP |
| `lek_kod` | CHAR(7) | | kód SÚKL nebo KodVZP (IPLP) |
| `atc` | VARCHAR(7) | | jen HVLP |
| `nazev` | VARCHAR(146) | | |
| `forma` | VARCHAR(27) | | |
| `sila` | VARCHAR(24) | | |
| `cesta_podani` | VARCHAR(15) | | |
| `postup_pripravy` | VARCHAR(4000) | | receptura IPLP |
### Tabulka `vydej_slozka`
Jako `predpis_slozka`, navíc `hrazeno_zp`. Data lékáren — kvalita závisí na lékárně.
| Sloupec | Typ | NOT NULL | Poznámka |
|---------|-----|----------|----------|
| `vydej_id` | INT FK | ✓ | |
| `mnozstvi` | DECIMAL(15,6) | ✓ | |
| `jednotka` | ENUM('g','ks') | ✓ | |
| `nazev` | VARCHAR(200) | ✓ | |
| `hrazeno_zp` | DECIMAL(9,2) | | částka hrazená ZP |
| `surovina` | CHAR(7) | | |
| `hvlp_reg` | CHAR(7) | | |
### Tabulka `predepisujici`
Lékaři, kteří pacientovi předepisovali (ze všech ordinací).
| Sloupec | Typ | Poznámka |
|---------|-----|----------|
| `lekar_kod` | CHAR(36) UNIQUE | UUID lékaře = predpis.kod_predepisujiciho |
| `prijmeni` | VARCHAR(35) | |
| `jmena` | VARCHAR(24) | |
| `icz` | CHAR(8) | IČZ zdravotnického zařízení |
| `icp` | CHAR(8) | IČP pracoviště — **poslední 3 číslice = kód odbornosti** (001 = prakt. lékař, 272 = alergologie…) |
| `pzs_nazev` | VARCHAR(200) | název zdravotnického zařízení |
| `ulice` | VARCHAR(150) | |
| `mesto` | VARCHAR(100) | |
| `psc` | CHAR(5) | |
| `telefon` | VARCHAR(20) | |
### Tabulka `vydavajici`
Lékárníci / lékárny, kde byl výdej.
| Sloupec | Typ | Poznámka |
|---------|-----|----------|
| `lekarnik_kod` | CHAR(36) UNIQUE | UUID lékárníka = vydej.kod_vydavajiciho |
| `prijmeni` | VARCHAR(35) | |
| `jmena` | VARCHAR(24) | |
| `pzs_nazev` | VARCHAR(200) | název lékárny |
| `ulice` | VARCHAR(150) | |
| `mesto` | VARCHAR(100) | |
| `psc` | CHAR(5) | |
| `telefon` | VARCHAR(20) | |
---
## Typy léků v Predpis i Vydej
Každý předpis / výdej obsahuje právě **jeden** z těchto elementů:
| Typ | Popis | Klíčová pole |
|-----|-------|-------------|
| `HVLPReg` | Registrovaný hromadně vyráběný LP | Kod (SÚKL), ATC, Nazev, Forma, Sila, Baleni |
| `HVLPNereg` | Neregistrovaný HVLP | stejná struktura jako HVLPReg |
| `IPLP` | Individuálně připravovaný LP (magistraliter) | Nazev, PostupPripravy, Slozka[] |
| `INN` | Předpis účinnou látkou (genericky) | Nazev, Forma, Sila, Baleni |
#### IPLP — dvojí uložení receptury
- **Předpis**: lékař zadal recepturu jako volný text v `PostupPripravy`. Složky typicky nevyplněny.
- **Výdej**: lékárna zaznamenala strukturované složky (`Slozka` s množstvím, jednotkou, názvem suroviny). Kvalita dat závisí na lékárně.
---
## Užitečné analytické dotazy
```sql
-- nejčastěji předepisované ATC skupiny za posledních 12 měsíců
SELECT atc, nazev, COUNT(*) AS pocet, MAX(datum_vystaveni) AS naposledy
FROM predpis
WHERE datum_vystaveni >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
GROUP BY atc, nazev
ORDER BY pocet DESC;
-- co bylo předepsáno ale nevyzvednuto (non-compliance)
SELECT pac.prijmeni, pac.jmena, p.datum_vystaveni, p.nazev, p.atc, p.navod
FROM predpis p
JOIN zprava z ON z.id = p.zprava_id
JOIN pacient pac ON pac.id = z.pacient_id
LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
WHERE v.id_lp_vydej IS NULL
ORDER BY p.datum_vystaveni DESC;
-- lékový záznam konkrétního pacienta (předpisy + výdeje)
SELECT p.datum_vystaveni, p.typ_leku, p.nazev, p.atc, p.navod,
v.datum_vydeje, v.mnozstvi AS vydano
FROM pacient pac
JOIN zprava z ON z.pacient_id = pac.id
JOIN predpis p ON p.zprava_id = z.id
LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
WHERE pac.prijmeni = 'Buzalka' AND pac.jmena = 'Vladimír'
ORDER BY p.datum_vystaveni DESC;
-- IPLP magistraliter — kompletní receptury s frekvencí (napříč pacienty)
SELECT p.nazev, p.postup_pripravy, COUNT(*) AS pocet_predpisu
FROM predpis p
WHERE p.typ_leku = 'IPLP'
GROUP BY p.nazev, p.postup_pripravy
ORDER BY pocet_predpisu DESC;
-- nejčastěji používané suroviny v magistrech
SELECT nazev, jednotka, COUNT(*) AS pocet
FROM vydej_slozka
GROUP BY nazev, jednotka
ORDER BY pocet DESC;
-- generická záměna: co předepsal lékař vs. co lékárna vydala
SELECT pac.prijmeni, pac.jmena,
p.datum_vystaveni, p.nazev AS predepsano, p.atc,
v.nazev AS vydano, v.datum_vydeje
FROM pacient pac
JOIN zprava z ON z.pacient_id = pac.id
JOIN predpis p ON p.zprava_id = z.id
JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
WHERE p.nazev <> v.nazev;
-- pacienti s chybou API (neztotožněni)
SELECT prijmeni, jmena, datum_narozeni, poznamka
FROM pacient
WHERE poznamka IS NOT NULL
ORDER BY prijmeni;
-- lékaři dle odbornosti — kolik předpisů pochází od které speciality
SELECT RIGHT(pr.icp, 3) AS odb_kod, COUNT(*) AS pocet_predpisu
FROM predpis p
JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho
WHERE pr.icp IS NOT NULL
GROUP BY RIGHT(pr.icp, 3)
ORDER BY pocet_predpisu DESC;
-- lékový záznam pacienta dle rodného čísla (přes Firebird → MySQL)
-- krok 1: z Medicusu zjistit příjmení a datum narozeni pro RC 7309208104
-- krok 2:
SELECT pac.prijmeni, pac.jmena, pac.datum_narozeni,
p.datum_vystaveni,
COALESCE(v.nazev, p.nazev) AS vydany_lek,
v.nazev IS NULL AS nevyzvednuto,
p.atc, p.navod,
pr.prijmeni AS lekar, RIGHT(pr.icp, 3) AS odb_kod
FROM pacient pac
JOIN zprava z ON z.pacient_id = pac.id
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 pac.prijmeni = 'Buzalka' AND pac.datum_narozeni = '1973-09-20'
ORDER BY p.datum_vystaveni DESC;
```
---
## Reimport ze zálohy XML (`reimport_z_xml.py`)
Slouží k opětovnému naplnění MySQL databáze z lokálních XML souborů **bez volání eRecept API**.
Použití: obnova po neúmyslném smazání databáze, migrace na nový server, re-parsování při změně schématu.
### Jak funguje
1. Načte všechny registrované pacienty z Firebirdu (ICP `09305001`, odbornost `001`)
2. Pro každý XML soubor v archivu:
- Naparsuje XML (volá `parsuj_xml()` z `06UlozitDoMySQL.py`)
- Dohledá pacienta v Firebirdu dle příjmení + data narození z XML
- Pokud je registrovaný → `upsert` pacienta do MySQL (INSERT ON DUPLICATE KEY UPDATE)
- Zavolá `uloz()` — INSERT IGNORE, takže duplicity se ignorují
3. Výpis průběhu: `[ 1/1177] Buzalka_Vladimir_1973-09-20.xml OK 12p 18v`
### Spuštění
```bash
# Výchozí adresář: xml_archive/2026-04-11
python reimport_z_xml.py
# Konkrétní podadresář
python reimport_z_xml.py xml_archive/2026-04-11
# Celý archiv rekurzivně (všechna data)
python reimport_z_xml.py xml_archive
```
### Konfigurace v souboru
```python
XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11" # výchozí adresář
ICP = "09305001" # IČP ordinace pro filtr registrovaných pacientů
ODB = "001" # odbornost (001 = praktický lékař)
```
### Poznámky
- Pacienti, kteří nejsou v Firebirdu registrováni pod daným ICP/ODB, se přeskočí
(pokud ale existují v MySQL z předchozího importu, data se aktualizují)
- Firebird slouží jako autoritativní zdroj identit — `idpac` z KAR se propíše do MySQL `pacient.idpac`
- `INSERT IGNORE` zajistí idempotentnost — opakované spuštění nepřidá duplikáty
---
## Dotazovací skripty (`Dotazy/`)
Viz samostatnou dokumentaci: [`Dotazy/DOTAZY.md`](../Dotazy/DOTAZY.md)
Stručný přehled:
| Skript | Co dělá |
|--------|---------|
| `prehled_pacienta.py` | Konzolový výpis lékového záznamu pacienta (lékaři + předpisy) |
| `prehled_pacienta_excel.py` | Totéž, ale exportuje do formátovaného souboru Excel (.xlsx) |
Pacient se identifikuje **rodným číslem** (nastavení `RODNE_CISLO` v záhlaví skriptu).
Oba skripty zobrazují **vydaný lék** (ne předepsaný), **odbornost lékaře** a příznak `*NV` pro nevyzvednuto.
---
## Závislosti (Python)
```
requests
requests-pkcs12
pymysql
fdb
zeep
mysql-connector-python
playwright
openpyxl
```
```bash
# Instalace (nebo použít setup.ps1)
pip install requests requests-pkcs12 pymysql fdb openpyxl
```
---
## XSD zdroje
Schéma verze `202501A`, soubory v `Dokumentace/2025-04-24/WSDL_XSD/NEPRIORITNI_WEBOVE_SLUZBY/`:
| Soubor | Obsah |
|--------|-------|
| `Cuer2Schema.xsd` | `NacistLekovyZaznamOdpoved`, `lz_nacteni_predepsany_lp_erp_type`, `lz_nacteni_vydany_lp_erp_type`, `slozka_iplp_*` |
| `CuerSchema.xsd` | `hvlp_type`, `zprava_odpoved_type`, `zprava_type`, `jmeno_osoby_type`, `jednotka` |