z230
This commit is contained in:
@@ -10,18 +10,67 @@ z eRecept SÚKL API a jejich uložení do relační databáze MySQL.
|
||||
| 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 i samostatně |
|
||||
| `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 |
|
||||
|
||||
```
|
||||
LékovýZáznamWithClaude/
|
||||
├── 05UlozitOdpoved.py
|
||||
├── 06UlozitDoMySQL.py
|
||||
├── 07StahnoutVsechny.py
|
||||
├── LEKOVY_ZAZNAM_DB.md
|
||||
├── 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)
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -29,9 +78,6 @@ LékovýZáznamWithClaude/
|
||||
## Typické spuštění
|
||||
|
||||
```bash
|
||||
# Čistý start — jednou (DROP + CREATE schéma, importuje odpoved_lekovy_zaznam.xml)
|
||||
python 06UlozitDoMySQL.py
|
||||
|
||||
# Hromadné stažení všech registrovaných pacientů
|
||||
python 07StahnoutVsechny.py
|
||||
|
||||
@@ -40,6 +86,9 @@ 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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -275,7 +324,7 @@ Lékaři, kteří pacientovi předepisovali (ze všech ordinací).
|
||||
| `prijmeni` | VARCHAR(35) | |
|
||||
| `jmena` | VARCHAR(24) | |
|
||||
| `icz` | CHAR(8) | IČZ zdravotnického zařízení |
|
||||
| `icp` | CHAR(8) | IČP pracoviště |
|
||||
| `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) | |
|
||||
@@ -374,10 +423,96 @@ 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)
|
||||
|
||||
```
|
||||
@@ -385,10 +520,15 @@ requests
|
||||
requests-pkcs12
|
||||
pymysql
|
||||
fdb
|
||||
zeep
|
||||
mysql-connector-python
|
||||
playwright
|
||||
openpyxl
|
||||
```
|
||||
|
||||
```bash
|
||||
pip install requests requests-pkcs12 pymysql fdb
|
||||
# Instalace (nebo použít setup.ps1)
|
||||
pip install requests requests-pkcs12 pymysql fdb openpyxl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Reimport vsech XML souboru z xml_archive do MySQL — bez volani API.
|
||||
|
||||
Pouziti:
|
||||
python reimport_z_xml.py # vsechna XML z 2026-04-11
|
||||
python reimport_z_xml.py xml_archive/2026-04-11 # konkretni adresar
|
||||
python reimport_z_xml.py xml_archive # vsechny podadresare rekurzivne
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
import fdb
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
|
||||
# Windows konzole
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(errors="replace")
|
||||
|
||||
# ── Konfigurace ───────────────────────────────────────────────────────────────
|
||||
XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11"
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
ICP = "09305001"
|
||||
ODB = "001"
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Nacteni parsovaci logiky z 06UlozitDoMySQL.py
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"m06", Path(__file__).parent / "06UlozitDoMySQL.py"
|
||||
)
|
||||
_m06 = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(_m06)
|
||||
|
||||
parsuj_xml = _m06.parsuj_xml
|
||||
uloz = _m06.uloz
|
||||
inicializuj_schema = _m06.inicializuj_schema
|
||||
|
||||
|
||||
def nacti_pacienty_z_fb():
|
||||
"""Vrati slovnik {(prijmeni_upper, datnar): idpac} ze vsech pacientu v Medicusu."""
|
||||
conn = fdb.connect(**FB)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
dnes = date.today().isoformat()
|
||||
cur.execute("""
|
||||
SELECT KAR.IDPAC, KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR
|
||||
FROM KAR
|
||||
WHERE KAR.vyrazen = 'N'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND r.datum <= ?
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
|
||||
AND r.priznak IN ('V','D','A')
|
||||
AND i.icp = ?
|
||||
AND i.odb = ?
|
||||
)
|
||||
""", (dnes, dnes, ICP, ODB))
|
||||
result = {}
|
||||
for row in cur.fetchall():
|
||||
idpac, prijmeni, jmeno, datnar = row
|
||||
klic = (prijmeni.strip().upper(), datnar)
|
||||
result[klic] = {"idpac": idpac, "prijmeni": prijmeni.strip(), "jmeno": jmeno.strip(), "datnar": datnar}
|
||||
print(f"Firebird: nacteno {len(result)} registrovanych pacientu")
|
||||
return result
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def upsert_pacient(cur, pac):
|
||||
cur.execute("""
|
||||
INSERT INTO pacient (idpac, prijmeni, jmena, datum_narozeni)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijmeni = VALUES(prijmeni),
|
||||
jmena = VALUES(jmena)
|
||||
""", (pac["idpac"], pac["prijmeni"], pac["jmeno"], pac["datnar"]))
|
||||
cur.execute("SELECT id FROM pacient WHERE idpac = %s", (pac["idpac"],))
|
||||
return cur.fetchone()["id"]
|
||||
|
||||
|
||||
def main():
|
||||
# Adresar z argumentu nebo default
|
||||
adresar = Path(sys.argv[1]) if len(sys.argv) > 1 else XML_ADRESAR
|
||||
if not adresar.is_dir():
|
||||
sys.exit(f"Adresar neexistuje: {adresar}")
|
||||
|
||||
# Najdi vsechna XML rekurzivne
|
||||
xml_soubory = sorted(adresar.rglob("*.xml"))
|
||||
if not xml_soubory:
|
||||
sys.exit(f"Zadne XML soubory nalezeny v: {adresar}")
|
||||
print(f"Nalezeno {len(xml_soubory)} XML souboru v: {adresar}")
|
||||
|
||||
# Nacti pacienty z Firebirdu
|
||||
fb_pacienti = nacti_pacienty_z_fb()
|
||||
|
||||
# Pripoj se k MySQL a inicializuj schema
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
inicializuj_schema(conn)
|
||||
|
||||
ok = chyba = preskoceno = 0
|
||||
p_celkem = v_celkem = 0
|
||||
|
||||
for i, xml_path in enumerate(xml_soubory, 1):
|
||||
rel = xml_path.relative_to(Path(__file__).parent)
|
||||
try:
|
||||
zprava, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path)
|
||||
except Exception as e:
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA parsovani: {e}")
|
||||
chyba += 1
|
||||
continue
|
||||
|
||||
# Zjisti prijmeni a datum narozeni z XML odpovedi
|
||||
pac_prijmeni = (zprava.get("pacient_prijmeni") or "").upper()
|
||||
pac_datnar = zprava.get("pacient_datum_narozeni") # string YYYY-MM-DD nebo None
|
||||
|
||||
# Prevod na date objekt pro porovnani s Firebirdem
|
||||
if pac_datnar and isinstance(pac_datnar, str):
|
||||
try:
|
||||
from datetime import datetime
|
||||
pac_datnar_d = datetime.strptime(pac_datnar[:10], "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
pac_datnar_d = None
|
||||
elif hasattr(pac_datnar, "year"):
|
||||
pac_datnar_d = pac_datnar
|
||||
else:
|
||||
pac_datnar_d = None
|
||||
|
||||
klic = (pac_prijmeni, pac_datnar_d)
|
||||
fb_pac = fb_pacienti.get(klic)
|
||||
|
||||
if not fb_pac:
|
||||
# Pacient neni registrovan — uloz bez idpac (bude ignorovan pri hromadnem behu)
|
||||
# Zkus najit v MySQL podle jmena a data
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT id FROM pacient WHERE prijmeni = %s AND datum_narozeni = %s",
|
||||
(zprava.get("pacient_prijmeni"), pac_datnar)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
pacient_id = row["id"]
|
||||
else:
|
||||
preskoceno += 1
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} PRESKOCENO (neni v registru)")
|
||||
continue
|
||||
else:
|
||||
with conn.cursor() as cur:
|
||||
pacient_id = upsert_pacient(cur, fb_pac)
|
||||
conn.commit()
|
||||
|
||||
try:
|
||||
stats = uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici,
|
||||
pacient_id=pacient_id, xml_soubor=str(rel))
|
||||
conn.commit()
|
||||
p_celkem += stats["predpisy_novych"]
|
||||
v_celkem += stats["vydeji_novych"]
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} OK "
|
||||
f"{stats['predpisy_novych']:3}p {stats['vydeji_novych']:3}v")
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA ukladani: {e}")
|
||||
chyba += 1
|
||||
|
||||
print()
|
||||
print(f"Hotovo: {ok} OK, {chyba} chyb, {preskoceno} preskoceno")
|
||||
print(f"Celkem vlozeno: {p_celkem} predpisu, {v_celkem} vydejuu")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user