notebookvb

This commit is contained in:
2026-04-11 11:56:54 +02:00
parent 24635b955d
commit c2d94b2362
10 changed files with 507 additions and 35 deletions
@@ -0,0 +1,77 @@
import uuid
from datetime import datetime, timezone
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
# --- Konfigurace ---
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
# HTTP Basic Auth - UUID lékaře (jednoznačný v ČR) + osobní heslo
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
PRACOVISTE = "00214235367"
ENDPOINTS = [
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar2",
]
# --- Pacient ---
PRIJMENI = "Buzalka"
JMENA = "Vladimír"
DATUM_NAROZENI = "1973-09-20"
POCET_ZNAKU_ATC = 7
POCET_MESICU = 60
def nacist_lekovy_zaznam():
sess = Session()
sess.mount("https://", Pkcs12Adapter(
pkcs12_filename=PFX_FILE,
pkcs12_password=PFX_PASSWORD
))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
f'<Doklad>'
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
f'<Pacient><Totoznost><Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni></Totoznost></Pacient>'
f'</Doklad>'
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy><Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano><SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
f'</NacistLekovyZaznamLekarDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistLekovyZaznam"',
"User-Agent": "Medicus"
}
for url in ENDPOINTS:
print(f"\n--- POST: {url} ---")
try:
resp = sess.post(url, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
print(resp.text)
except Exception as e:
print(f"CHYBA: {e}")
if __name__ == "__main__":
nacist_lekovy_zaznam()
@@ -0,0 +1,99 @@
import uuid
from datetime import datetime, timezone, timedelta
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
# --- Konfigurace ---
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
PRACOVISTE = "00214235367"
ENDPOINT = "https://cuer-soap.erecept.sukl.cz/"
# --- Filtr ---
CP_PACIENTA = "7309208104" # rodné číslo pacienta (bez lomítka)
DATUM_OD = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
DATUM_DO = datetime.now().strftime("%Y-%m-%d")
LIMIT = 100
def seznam_predpisu():
sess = Session()
sess.mount("https://", Pkcs12Adapter(
pkcs12_filename=PFX_FILE,
pkcs12_password=PFX_PASSWORD
))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
filtr_pacient = ""
if CP_PACIENTA:
filtr_pacient = f"<CP_Pacienta>{CP_PACIENTA}</CP_Pacienta>"
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
'<SeznamPredpisuDotaz xmlns="http://www.sukl.cz/erp/201704">'
'<Doklad>'
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
f'<Filtr>'
f'<DatumOd>{DATUM_OD}</DatumOd>'
f'<DatumDo>{DATUM_DO}</DatumDo>'
f'<Limit>{LIMIT}</Limit>'
f'{filtr_pacient}'
f'</Filtr>'
'</Doklad>'
f'<Zprava>'
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
f'<Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano>'
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
f'</Zprava>'
'</SeznamPredpisuDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"SeznamPredpisu"',
"User-Agent": "Medicus"
}
print(f"=== SOAP REQUEST ===\n{soap_body}\n===================\n")
print(f"Datum od: {DATUM_OD}, do: {DATUM_DO}, limit: {LIMIT}")
if CP_PACIENTA:
print(f"Filtr pacienta (RC): {CP_PACIENTA}")
print(f"ID zprávy: {id_zpravy}")
print(f"Endpoint: {ENDPOINT}\n")
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=30)
print(f"HTTP status: {resp.status_code}")
output_file = f"seznam_predpisu_{id_zpravy}.xml"
with open(output_file, "wb") as f:
f.write(resp.content)
print(f"Odpověď uložena do: {output_file}")
text = resp.text
if "Fault" in text or "fault" in text:
print("\n!!! SOAP Fault v odpovědi !!!")
print(text[:2000])
else:
print("Dotaz proběhl úspěšně.")
print(f"Velikost odpovědi: {len(resp.content):,} bytů")
print("\n--- Odpověď (prvních 3000 znaků) ---")
print(text[:3000])
if __name__ == "__main__":
seznam_predpisu()
@@ -0,0 +1,66 @@
import uuid
from datetime import datetime, timezone
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
sess = Session()
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+01:00")
# Přesně tělo z Medicusu
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
'<Doklad><Pristupujici>'
'<Uzivatel>E08C89C6-2B1A-4EBA-8ED9-4E3E63618379</Uzivatel>'
'<Pracoviste>00214235367</Pracoviste>'
'</Pristupujici>'
'<PocetZnakuATC>7</PocetZnakuATC>'
'<PocetMesicu>60</PocetMesicu>'
'<Pacient><Totoznost><Jmeno>'
'<Prijmeni>Buzalka</Prijmeni>'
'<Jmena>Vladim\u00edr</Jmena>'
'</Jmeno>'
'<DatumNarozeni>1973-09-20</DatumNarozeni>'
'</Totoznost></Pacient></Doklad>'
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy>'
'<Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano>'
'<SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
'</NacistLekovyZaznamLekarDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistLekovyZaznam"',
"User-Agent": "Medicus"
}
endpoints = [
"https://cuer-soap.erecept.sukl.cz/",
"https://rlpo-soap.erecept.sukl.cz/",
"https://common-soap.erecept.sukl.cz/",
"https://lz-soap.erecept.sukl.cz/",
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar",
]
for ep in endpoints:
print(f"\n--- {ep} ---")
try:
resp = sess.post(ep, data=soap_body.encode("utf-8"), headers=headers, timeout=10)
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
print(resp.text[:300])
except Exception as e:
print(f"CHYBA: {e}")
@@ -0,0 +1,64 @@
import uuid
import xml.dom.minidom
from datetime import datetime, timezone
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar"
NAMESPACE = "http://www.sukl.cz/erp/201704"
def nacist_ciselnik_chyb():
sess = Session()
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
f'<CisChybDotaz xmlns="{NAMESPACE}">'
f'<Zprava>'
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
f'<Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano>'
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
f'</Zprava>'
f'</CisChybDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistCiselnikChyb"',
"User-Agent": "Medicus"
}
print(f"Volám: {ENDPOINT}")
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů\n")
if resp.status_code == 200:
# Hezky formátovaný výpis
dom = xml.dom.minidom.parseString(resp.content)
pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
print(pretty)
# Uložit do souboru
fname = f"ciselnik_chyb_{id_zpravy[:8]}.xml"
with open(fname, "w", encoding="utf-8") as f:
f.write(pretty)
print(f"\nUloženo: {fname}")
else:
print(resp.text)
if __name__ == "__main__":
nacist_ciselnik_chyb()
@@ -0,0 +1,393 @@
# NacistLekovyZaznam — Funkční SOAP klient pro IS eRecept SÚKL
## Status
**OVĚŘENO FUNKČNÍ** — 5. dubna 2026
Odpověď serveru: `HTTP 200`, velikost: ~227 KB reálných dat
Schéma ověřeno proti: `Cuer2Schema.xsd` verze `202501A` (dokumentace SÚKL 2025-04-24)
---
## Co tato operace dělá
`NacistLekovyZaznam` je SOAP operace IS eRecept (SÚKL), která vrátí kompletní **lékový záznam pacienta** — tedy seznam všech předpisů a výdejů léků napříč všemi lékaři a lékárnami v ČR za zadané období.
Typické použití: lékař si před konzultací zobrazí, co pacient aktuálně bere, co mu bylo předepsáno a vydáno v lékárně.
---
## Klíčové informace (těžce dohledané)
### Endpoint (produkce)
```
https://lekar-soap.erecept.sukl.cz/cuer/Lekar2
```
> **Pozor:** Endpoint má na konci číslici `2` — tj. `/cuer/Lekar2`, nikoli `/cuer/Lekar`.
> Tato operace **není** dostupná na starším endpointu `/cuer/Lekar`.
> Poprvé zdokumentováno v: `eRecept_lekovy_zaznam_1v3.docx` (dokumentace SÚKL ze dne 2024-01-18).
### Endpoint (testovací prostředí)
```
https://lekar-soap.test-erecept.sukl.cz/cuer/Lekar2
```
### SOAPAction
```
"NacistLekovyZaznam"
```
### XML namespace
```
http://www.sukl.cz/erp/201912
```
> Namespace pochází z prosince 2019 a **nemění se** ani v novějších verzích rozhraní.
> Verze rozhraní se předává v elementu `<Verze>` uvnitř zprávy, ne změnou namespace.
> SÚKL zachovává zpětnou kompatibilitu — namespace zůstává `201912` i pro verze `202401A`, `202501A` atd.
---
## Další operace dostupné na stejném endpointu `/cuer/Lekar2`
Dle `CUERLekarService.wsdl` (2025-04-24) jsou na tomto endpointu celkem **4 operace**:
| Operace | SOAPAction | Popis |
|---|---|---|
| `NacistLekovyZaznam` | `NacistLekovyZaznam` | ✅ lékový záznam pacienta |
| `OverDuplicity` | `OverDuplicity` | kontrola duplicitních předpisů |
| `ZjistitPoznamkyHvlp` | `ZjistitPoznamkyHvlp` | poznámky k HVLP přípravkům |
| `GetAppInfo` | `GetAppInfo` | info o verzi API |
| `AppPing` | `AppPing` | test spojení |
---
## Autentizace (dvojitá)
IS eRecept vyžaduje **dvě vrstvy** autentizace současně:
### 1. Klientský certifikát (TLS mutual auth)
- Soubor: `AMBSUKL214235369G_31DEC2024.pfx` (certifikát lékaře vydaný SÚKL)
- Formát: PKCS#12 (`.pfx`)
- Knihovna: `requests-pkcs12``Pkcs12Adapter`
- Platnost certifikátu: do 31. 12. 2024 (při expiraci je nutné zažádat SÚKL o nový)
### 2. HTTP Basic Auth
- Uživatel: UUID lékaře (přiděluje SÚKL, jednoznačný v celé ČR)
- Heslo: osobní heslo lékaře do portálu eRecept
---
## Struktura SOAP dotazu
```xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">
<Doklad>
<Pristupujici>
<Uzivatel>E08C89C6-2B1A-4EBA-8ED9-4E3E63618379</Uzivatel> <!-- UUID lékaře, povinný -->
<Pracoviste>00214235367</Pracoviste> <!-- IČP pracoviště, povinný -->
</Pristupujici>
<PocetZnakuATC>7</PocetZnakuATC> <!-- POUZE hodnoty 5 nebo 7 (enum!), povinný -->
<PocetMesicu>60</PocetMesicu> <!-- max 99, VOLITELNÝ -->
<Pacient>
<Totoznost>
<Jmeno>
<Prijmeni>Buzalka</Prijmeni>
<Jmena>Vladimír</Jmena>
</Jmeno>
<DatumNarozeni>1973-09-20</DatumNarozeni>
</Totoznost>
</Pacient>
<!-- Zastupce — volitelný, pouze pokud dotazuje zastupující lékař -->
<!-- <Zastupce>
<DruhDokladu>OP</DruhDokladu> 12 znaky, volitelný -->
<!-- <CisloDokladu>123456789</CisloDokladu> 19 znaků, volitelný -->
<!-- </Zastupce> -->
</Doklad>
<Zprava>
<ID_Zpravy>dabda2ad-df61-41db-bd46-a969eced026b</ID_Zpravy> <!-- UUID, generovat pro každý dotaz -->
<Verze>202501A</Verze> <!-- aktuální verze protokolu -->
<Odeslano>2026-04-05T10:00:00+00:00</Odeslano> <!-- UTC timestamp -->
<SW_Klienta>MEDICUS_____</SW_Klienta> <!-- identifikátor SW, přesně 12 znaků -->
</Zprava>
</NacistLekovyZaznamLekarDotaz>
</soapenv:Body>
</soapenv:Envelope>
```
### Parametry dotazu (ověřeno proti Cuer2Schema.xsd verze 202501A)
| Element | Povinný | Omezení | Poznámka |
|---|---|---|---|
| `Uzivatel` | ✅ ano | UUID formát | UUID lékaře přidělené SÚKL |
| `Pracoviste` | ✅ ano | — | IČP pracoviště lékaře |
| `PocetZnakuATC` | ✅ ano | **pouze 5 nebo 7** | enum — jiné hodnoty server odmítne |
| `PocetMesicu` | ❌ volitelný | max 99 | počet měsíců do minulosti |
| `Prijmeni` | ✅ ano | — | příjmení pacienta |
| `Jmena` | ✅ ano | — | jméno/jména pacienta |
| `DatumNarozeni` | ✅ ano | `YYYY-MM-DD` | datum narození pacienta |
| `Zastupce` | ❌ volitelný | — | pouze pro zastupujícího lékaře |
| `ID_Zpravy` | ✅ ano | UUID formát | nové UUID pro každý dotaz |
| `Verze` | ✅ ano | — | aktuálně `202501A` |
| `Odeslano` | ✅ ano | ISO 8601 | čas odeslání s časovou zónou |
| `SW_Klienta` | ✅ ano | přesně 12 znaků | doplnit mezerami zleva nebo zprava |
---
## Struktura odpovědi (ověřeno proti Cuer2Schema.xsd verze 202501A)
```
NacistLekovyZaznamOdpoved
├── Doklad
│ ├── Pacient potvrzení identity pacienta
│ │ ├── Jmeno (Prijmeni, Jmena) volitelný
│ │ └── DatumNarozeni volitelný
│ │
│ ├── PredepisujiciSeznam volitelný — lékaři kteří předepisovali
│ │ └── Predepisujici[]
│ │ ├── Lekar Kod (UUID) + Jmeno — volitelný
│ │ ├── ICZ 8 číslic — volitelný
│ │ ├── ICP 8 číslic — povinný
│ │ ├── PZS volitelný
│ │ │ ├── Nazev max 200 znaků
│ │ │ ├── Telefon max 20 znaků — volitelný
│ │ │ └── Adresa
│ │ └── Telefon max 20 znaků — volitelný
│ │
│ ├── VydavajiciSeznam volitelný — lékárníci kteří vydávali
│ │ └── Vydavajici[]
│ │ ├── Lekarnik Kod (UUID) + Jmeno
│ │ └── PZS Nazev + Adresa
│ │
│ ├── PredpisSeznam volitelný — všechny předpisy
│ │ └── Predpis[]
│ │ ├── ID_LP_Predpis UUID předpisu — povinný
│ │ ├── KodPredepisujiciho odkaz do PredepisujiciSeznam — povinný
│ │ ├── DatumVystaveni datum vystavení
│ │ ├── Mnozstvi int, 19999
│ │ ├── Navod max 80 znaků (dávkování)
│ │ ├── Opakovani int — volitelný
│ │ ├── HVLPReg volitelný (registrovaný HVLP)
│ │ ├── HVLPNereg volitelný (neregistrovaný HVLP)
│ │ ├── IPLP volitelný (individuálně připravovaný LP)
│ │ ├── INN volitelný (generický název)
│ │ └── ModryPruh boolean — volitelný
│ │
│ ├── VydejSeznam volitelný — výdeje z lékáren
│ │ └── Vydej[]
│ │ ├── ID_LP_Vydej UUID výdeje
│ │ ├── ID_LP_Predpis UUID předpisu — volitelný (odkaz na předpis)
│ │ ├── KodVydavajiciho odkaz do VydavajiciSeznam — povinný
│ │ ├── DatumVydeje datum výdeje
│ │ ├── Mnozstvi decimal, 0.019999.99
│ │ ├── Navod max 80 znaků
│ │ ├── Exspirace datum — volitelný
│ │ ├── Sarze max 50 znaků
│ │ ├── SerioveCislo max 20 znaků — volitelný
│ │ ├── Pozn max 1000 znaků — volitelný
│ │ ├── HVLPReg volitelný
│ │ ├── HVLPNereg volitelný
│ │ └── IPLP volitelný
│ │
│ └── DuplicitaSeznam volitelný — detekované duplicity výdejů
│ └── Duplicita[]
│ └── ID_LP[]
│ ├── ID_LP_Vydej UUID — volitelný
│ └── ID_LP_Predpis UUID — volitelný
└── Zprava
├── ID_Zpravy echo zpět
├── Verze
├── Odeslano
└── ...
```
> **Poznámka k odkazům:** `KodPredepisujiciho` v předpisu odpovídá `Lekar.Kod` v `PredepisujiciSeznam`.
> Stejně tak `KodVydavajiciho` odpovídá `Lekarnik.Kod` v `VydavajiciSeznam`.
> Struktura je záměrně normalizovaná — lékaři a lékárny jsou uloženi jednou, předpisy a výdeje se na ně odkazují.
Reálná velikost odpovědi: **~227 KB** (pacient s 60měsíční historií).
---
## HTTP hlavičky
```python
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistLekovyZaznam"', # uvozovky jsou součástí hodnoty!
"User-Agent": "Medicus"
}
```
---
## Závislosti (Python)
```
requests
requests-pkcs12
```
Instalace:
```bash
pip install requests requests-pkcs12
```
---
## Jak dohledat správný endpoint (postup pro budoucí operace)
Endpoint `/cuer/Lekar2` byl dohledán tímto postupem:
1. Prohledání veškeré dokumentace SÚKL (20172025) fulltext hledáním výrazu `soap:address`
2. Klíčový dokument: `eRecept_lekovy_zaznam_1v3.docx` (2024-01-18):
> *"Pro webové služby, které používá lékař a klinický farmaceut je nová verze rozhraní 202401A (původní 201912A): `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2`"*
3. Ověřeno živým voláním — HTTP 200, reálná data
### Mapa endpointů IS eRecept (produkce)
| Endpoint | Určen pro | Operace |
|---|---|---|
| `https://lekar-soap.erecept.sukl.cz/cuer/Lekar` | lékař | CUER operace (předpis, zrušení…) |
| `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2` | lékař | **NacistLekovyZaznam**, OverDuplicity, ZjistitPoznamkyHvlp |
| `https://lekar-soap.erecept.sukl.cz/rlpo/Lekar` | lékař | RLPO operace |
| `https://lekarnik-soap.erecept.sukl.cz/cuer/Lekarnik` | lékárník | CUER operace (výdej…) |
| `https://lekarnik-soap.erecept.sukl.cz/cuer/Lekarnik2` | lékárník | nové CUER operace lékárníka |
| `https://cuer-soap.erecept.sukl.cz/` | lékárna, ZP | doplatky, limity pojištěnce |
---
## Ztotožnění pacienta a chybové kódy
Zdroj: živé volání `NacistCiselnikChyb` na `https://lekar-soap.erecept.sukl.cz/cuer/Lekar` — ověřeno 5. 4. 2026.
### Jak funguje ztotožnění pacienta (Registr obyvatel)
Server každý dotaz porovná s **Registrem obyvatel (ROB)**. Lze použít dvě sady údajů:
| Sada | Prvky | Poznámka |
|---|---|---|
| **A — jméno** | Prijmeni + Jmena + DatumNarozeni | základní, stačí pokud je pacient jednoznačný |
| **B — doklad** | DruhDokladu + CisloDokladu | jednoznačné vždy, řeší jmenovce |
Obě sady lze kombinovat. Pokud jsou uvedeny obě, musí být konzistentní (jinak C017).
### Jak přesně probíhá ztotožnění pacienta v ROB
> Zdroj: oficiální dokumentace SÚKL k IS eRecept
IS eRecept nejprve prohledá svůj interní **kmen pacientů** (vlastní databáze dříve ztotožněných osob). Teprve při neúspěchu volá **Registr obyvatel (ROB)** — což může trvat až několik sekund. U každého pacienta s korektními údaji by se to mělo stát pouze jednou. IS eRecept průběžně přebírá změny z ROB (např. přejmenování po svatbě se projeví automaticky).
Způsoby ztotožnění probíhají v tomto pořadí:
#### 1. ECD — elektronicky čitelný doklad
Hledání dle `DruhDokladu` + `CisloDokladu`. Pokud zadáno, má přednost.
#### 2. JPDN — jméno, příjmení, datum narození
Pokud nalezena právě jedna osoba → hotovo (`ROB=JPDN`).
Pokud nalezeno více jmenovců → přechod na JPDNA.
Pokud nenalezena žádná → upozornění (měkká chyba).
#### 3. JPDNA — jméno, příjmení, datum narození + adresa trvalého pobytu
ROB umí automaticky opravit záměnu čísla popisného a orientačního.
Pokud nalezena právě jedna osoba → hotovo (`ROB=JPDNA`).
Pokud nenalezena žádná → upozornění, předpis se uloží s kódem **C023**.
> **Důležité:** Adresu není nutné uvádět, pokud proběhne úspěšné vyhledání bez ní. V takovém případě se na předpisu použije adresa dohledaná v ROB.
> **Cizinci:** Lze uvést zahraniční adresu, nebo adresu hotelu/lázní v ČR.
> **⚠️ Testování:** Ztotožňování reálných osob smí probíhat **pouze na produkci**. O každém ztotožnění je záznam v základních registrech — pacient může obdržet výpis do datové schránky. Na testovacím prostředí jsou dostupné testovací identity na: https://www.szrcr.cz/cs/sluzby/spravci-a-vyvojari/vyvojari-agendovych-informacnich-systemu#testdata (mění se denně mezi 6:006:30).
---
### Hodnoty DruhDokladu
⚠️ **Oprava předchozí dokumentace** — povolených hodnot je více než jen `ID` a `P`:
| Hodnota | Doklad |
|---|---|
| `ID` | občanský průkaz (nový formát) |
| `OP` | občanský průkaz (starší označení) |
| `P` | cestovní pas |
| `IR` | povolení k pobytu |
| `VS` | vízový štítek |
| `PS` | pobytový štítek |
`CisloDokladu` — pouze číslice, max 9 znaků.
---
### Chybové kódy — ztotožnění pacienta (skupina C)
| Kód | Typ | Popis | Co udělat |
|---|---|---|---|
| **C010** | tvrdá | Jméno + příjmení + datum narození nenalezeno v ROB | Zkontrolovat překlep, záměnu jména a příjmení |
| **C011** | tvrdá | Druh a číslo dokladu nenalezeno v ROB | Zkontrolovat číslo a platnost dokladu |
| **C012** | tvrdá | Adresa neodpovídá — více jmenovců | Doplnit `DruhDokladu` + `CisloDokladu` |
| **C014** | tvrdá | Není uvedeno ani jméno/datum, ani číslo dokladu | Doplnit alespoň jednu sadu |
| **C015** | tvrdá | Více jmenovců v ROB, nelze dohledat adresu | Doplnit adresu **nebo** číslo dokladu |
| **C016** | měkká | Pacient mladší 33 dní | Předpis uložen, ale nebude v lékovém záznamu |
| **C017** | tvrdá | Jméno/datum neodpovídá osobě nalezené dle čísla dokladu | Opravit jméno nebo ho neuvádět |
| **C018** | tvrdá | Nelze dohledat, chybí adresa | Doplnit adresu nebo číslo dokladu |
| **C019** | tvrdá | Datum narození v budoucnosti | Opravit datum |
| **C020** | tvrdá | Chybí jméno, příjmení nebo datum narození | Doplnit kompletní sadu |
| **C022** | tvrdá | Pacient dle ROB již zemřel | — |
| **C023** | měkká | Předpis uložen, ale kvůli chybě ztotožnění **nebude v lékovém záznamu** | Opravit identifikaci a provést změnu předpisu |
| **C024** | měkká | Registr LP prohledán, pacient nenalezen v ROB (možný cizinec) | — |
| **C025** | měkká | Výdej uložen, ale kvůli chybě ztotožnění nebude v lékovém záznamu | — |
> **Měkká chyba** = operace proběhla, ale s upozorněním. **Tvrdá chyba** = operace odmítnuta.
### Chybové kódy — doklad totožnosti
| Kód | Popis |
|---|---|
| **L076** | Neznámý druh dokladu — viz tabulka povolených hodnot výše |
### Chybové kódy — systémové
| Kód | Popis |
|---|---|
| **I005** | Registr obyvatel nedostupný (mimo kontrolu SÚKL) — zkusit znovu |
| **I007** | Chyba dotazu na ROB — vstupní data nemají správný formát |
---
### Proč Medicus žádá o občanský průkaz
Typický scénář: lékař pošle dotaz pouze se jménem + datem narození. Pokud existuje více jmenovců v ROB (méně než 1 % obyvatel), server vrátí **C015**. Medicus tento kód rozpozná a zobrazí dialog *"zadejte číslo občanského průkazu"*. Lékař zadá číslo, Medicus pošle dotaz znovu s `DruhDokladu=ID` + `CisloDokladu=...` → server jednoznačně identifikuje pacienta metodou **ECD**.
### Identifikace pro úhradové mechanismy (ZP)
Oddělená od ztotožnění v ROB — probíhá přes **kód ZP + číslo pojištěnce**. IS eRecept číslo pojištěnce **neověřuje** — není garantováno, že je správné. Pokud lékárna zjistí chybu kódu pojišťovny, může ji opravit operací `ZmenitPojistovnuPredpisu`.
### Skript s občankou
`NacistLekovyZaznam_FUNKCNI_OBCANKA.py` — varianta s `DruhDokladu` + `CisloDokladu` v dotazu.
---
## Soubory skriptů
| Skript | Popis |
|---|---|
| `NacistLekovyZaznam_FUNKCNI.py` | základní dotaz — jméno + datum narození |
| `NacistLekovyZaznam_FUNKCNI_OBCANKA.py` | dotaz s číslem občanského průkazu |
| `NacistCiselnikChyb.py` | stáhne kompletní číselník chybových kódů ze serveru |
---
## Zdroje dokumentace SÚKL
| Soubor | Datum | Obsah |
|---|---|---|
| `eRecept_lekovy_zaznam_1v1.docx` | 2020-05-28 | původní popis lékového záznamu |
| `eRecept_lekovy_zaznam_1v3.docx` | 2024-01-18 | endpointy Lekar2/Lekarnik2, verze 202401A |
| `CUERLekarService.wsdl` | 2025-04-24 | nejnovější definice rozhraní, seznam operací |
| `Cuer2Schema.xsd` | 2025-04-24 | kompletní XSD schéma dotazu i odpovědi, verze 202501A |
| `CuerSchema.xsd` | 2025-04-24 | sdílené typy (Zprava, Pristupujici, adresy…) |
@@ -0,0 +1,77 @@
import uuid
from datetime import datetime, timezone
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
# --- Konfigurace ---
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
# HTTP Basic Auth - UUID lékaře (jednoznačný v ČR) + osobní heslo
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
PRACOVISTE = "00214235367"
ENDPOINTS = [
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar2",
]
# --- Pacient ---
PRIJMENI = "Buzalka"
JMENA = "Vladimír"
DATUM_NAROZENI = "1973-09-20"
POCET_ZNAKU_ATC = 7
POCET_MESICU = 60
def nacist_lekovy_zaznam():
sess = Session()
sess.mount("https://", Pkcs12Adapter(
pkcs12_filename=PFX_FILE,
pkcs12_password=PFX_PASSWORD
))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
f'<Doklad>'
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
f'<Pacient><Totoznost><Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni></Totoznost></Pacient>'
f'</Doklad>'
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy><Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano><SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
f'</NacistLekovyZaznamLekarDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistLekovyZaznam"',
"User-Agent": "Medicus"
}
for url in ENDPOINTS:
print(f"\n--- POST: {url} ---")
try:
resp = sess.post(url, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
print(resp.text)
except Exception as e:
print(f"CHYBA: {e}")
if __name__ == "__main__":
nacist_lekovy_zaznam()
@@ -0,0 +1,93 @@
import uuid
from datetime import datetime, timezone
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
# --- Konfigurace ---
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASSWORD = "Vlado7309208104++"
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS = "Buzalka@Vladimir2025"
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
PRACOVISTE = "00214235367"
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar2"
NAMESPACE = "http://www.sukl.cz/erp/201912"
# --- Pacient ---
PRIJMENI = "Buzalka"
JMENA = "Vladimír"
DATUM_NAROZENI = "1973-09-20"
# --- Doklad totožnosti ---
# DruhDokladu: "ID" = občanský průkaz, "P" = cestovní pas
# CisloDokladu: číslo dokladu, max 9 znaků (jen číslice)
DRUH_DOKLADU = "ID"
CISLO_DOKLADU = "" # <-- sem vyplnit číslo občanského průkazu pacienta
POCET_ZNAKU_ATC = 7 # pouze 5 nebo 7
POCET_MESICU = 60 # max 99, volitelné
def nacist_lekovy_zaznam_s_obcankou():
if not CISLO_DOKLADU:
print("CHYBA: Není vyplněno CISLO_DOKLADU!")
return
sess = Session()
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
sess.auth = (API_USER, API_PASS)
id_zpravy = str(uuid.uuid4())
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
soap_body = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
'<soapenv:Body>'
f'<NacistLekovyZaznamLekarDotaz xmlns="{NAMESPACE}">'
f'<Doklad>'
f'<Pristupujici>'
f'<Uzivatel>{UZIVATEL}</Uzivatel>'
f'<Pracoviste>{PRACOVISTE}</Pracoviste>'
f'</Pristupujici>'
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
f'<Pacient>'
f'<Totoznost>'
f'<Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni>'
f'<DruhDokladu>{DRUH_DOKLADU}</DruhDokladu>'
f'<CisloDokladu>{CISLO_DOKLADU}</CisloDokladu>'
f'</Totoznost>'
f'</Pacient>'
f'</Doklad>'
f'<Zprava>'
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
f'<Verze>202501A</Verze>'
f'<Odeslano>{odeslano}</Odeslano>'
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
f'</Zprava>'
f'</NacistLekovyZaznamLekarDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistLekovyZaznam"',
"User-Agent": "Medicus"
}
print(f"Pacient: {PRIJMENI} {JMENA}, nar. {DATUM_NAROZENI}")
print(f"Doklad: {DRUH_DOKLADU} {CISLO_DOKLADU}")
print(f"Volám: {ENDPOINT}\n")
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů\n")
print(resp.text)
if __name__ == "__main__":
nacist_lekovy_zaznam_s_obcankou()