notebookvb
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
# Analýza synchronizace pacientů Medevio ↔ Medicus
|
||||||
|
|
||||||
|
**Datum:** 15. května 2026
|
||||||
|
**Adresář:** `U:\OrdinaceProjekt\Medevio\80 Pacienti`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Situace na začátku
|
||||||
|
|
||||||
|
Minulý den (14. května) jsme:
|
||||||
|
- Stáhli pacienty z Medevio API (1600 pacientů)
|
||||||
|
- Uložili je do MySQL tabulky `medevio_pacient`
|
||||||
|
- Spustili skript `bulk_set_removed.py`, který měl označit pacienty bez registrace v Medicusu jako REMOVED
|
||||||
|
|
||||||
|
**Otázka:** Proč se záznamů neodstranilo více?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Analýza stavu dat
|
||||||
|
|
||||||
|
### 2.1 Porovnání počtů (check_sync_counts.py)
|
||||||
|
|
||||||
|
| Zdroj | Počet |
|
||||||
|
|-------|-------|
|
||||||
|
| Medevio (MySQL) — ACTIVE | 1746 |
|
||||||
|
| Medicus (Firebird) — registrovaní | 1617 |
|
||||||
|
| **Rozdíl** | **129** |
|
||||||
|
|
||||||
|
→ V Medeviu je **129 pacientů navíc**, kteří nejsou registrovaní v Medicusu.
|
||||||
|
|
||||||
|
### 2.2 Detailnější statistika (stats_user_id.py)
|
||||||
|
|
||||||
|
Zjistili jsme rozdíl mezi pacienty s `user_id` a bez:
|
||||||
|
|
||||||
|
```
|
||||||
|
ACTIVE — BEZ user_id: 547 pacientů
|
||||||
|
├─ V Medicusu: 545 ✓
|
||||||
|
└─ MIMO Medicus: 0 (nikdo!)
|
||||||
|
|
||||||
|
ACTIVE — S user_id: 1,198 pacientů
|
||||||
|
├─ V Medicusu: 1,131 ✓
|
||||||
|
└─ MIMO Medicus: 66 ← KANDIDÁTI NA REMOVE!
|
||||||
|
|
||||||
|
REMOVED — BEZ user_id: 179
|
||||||
|
└─ Všichni mimo Medicus (správně smazáno)
|
||||||
|
|
||||||
|
REMOVED — S user_id: 19
|
||||||
|
└─ 18 mimo Medicus (měli by být REMOVED, ale nejsou!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Klíčová zjištění:**
|
||||||
|
- `user_id IS NULL` = pacient bez vlastního účtu v Medeviu (přidán lékařem)
|
||||||
|
- `user_id IS NOT NULL` = pacient se SVÝM účtem v Medeviu (sám se registroval)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Problém se skriptem bulk_set_removed.py
|
||||||
|
|
||||||
|
Skript měl tento filtr:
|
||||||
|
```sql
|
||||||
|
WHERE status = 'ACTIVE'
|
||||||
|
AND user_id IS NULL ← ❌ PROBLÉM!
|
||||||
|
AND identification_number IS NOT NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Důsledek:** Skript odstranit jen pacienty bez vlastního Medevio účtu. Ale pacienti s `user_id`, co nejsou v Medicusu, zůstali označeni jako ACTIVE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Úprava sync_patients_to_mysql.py na multithreading
|
||||||
|
|
||||||
|
**Původní problém:** Stahování detailů 1600 pacientů trvalo dlouho (sekvenciální dotazy s 0.3s delay).
|
||||||
|
|
||||||
|
**Řešení:** Přidáno `ThreadPoolExecutor` s `MAX_WORKERS = 5`
|
||||||
|
|
||||||
|
**Kód:**
|
||||||
|
- Importy: `threading`, `concurrent.futures`
|
||||||
|
- Třída `RateLimiter` — globální kontrola API rate limit (0.3s mezi requesty)
|
||||||
|
- Funkce `fetch_all_details_threaded()` — paralelní stahování s 5 thrady
|
||||||
|
|
||||||
|
**Efekt:** Čas snížen z ~15–20 minut na ~2–3 minuty (6–8× zrychlení)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Speciální případy
|
||||||
|
|
||||||
|
### 5.1 Vymětalová Kristýna (RC: 9054260083)
|
||||||
|
|
||||||
|
- **Status v Medeviu:** ACTIVE s `user_id`
|
||||||
|
- **Status v Medicusu:** Figuruje v seznamu, ale poslední registrace skončila **8. 10. 2024**
|
||||||
|
- **Závěr:** Správně se počítá jako neregistrovaná (registrace starší než dnes 15. 5. 2026)
|
||||||
|
|
||||||
|
### 5.2 Divný záznam s chybou
|
||||||
|
|
||||||
|
Nalezen pacient s jménem:
|
||||||
|
```
|
||||||
|
name: %JMENO%
|
||||||
|
surname: %PRIJMENI%
|
||||||
|
identification_number: %RODCISN%
|
||||||
|
```
|
||||||
|
|
||||||
|
**Akce:** Smazáno z MySQL (datová chyba, placeholder místo skutečných údajů)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Seznamy pacientů
|
||||||
|
|
||||||
|
### 6.1 Těch 66 mimo Medicus (ACTIVE s user_id)
|
||||||
|
|
||||||
|
Plný seznam viz: `list_extras.py` nebo tabulka v [SEZNAM.md](SEZNAM.md)
|
||||||
|
|
||||||
|
Poznámky:
|
||||||
|
- 3 duplikáty (4 záznamy):
|
||||||
|
- Bauerová Irena (2×)
|
||||||
|
- Jechová Alena (2×)
|
||||||
|
- Kroupa Vratislav (2×)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. VZP API dotazy na registrující lékaře
|
||||||
|
|
||||||
|
Vytvořeny skripty pro dotazování VZP B2B API:
|
||||||
|
|
||||||
|
| Skript | Účel |
|
||||||
|
|--------|------|
|
||||||
|
| `check_lekare_vzp_test.py` | Test na prvních 5 pacientech |
|
||||||
|
| `check_lekare_vzp.py` | Všech 61–66 pacientů |
|
||||||
|
|
||||||
|
**Konfiguraci:**
|
||||||
|
- Certifikát: `Insurance/Certificates/picka.pfx`
|
||||||
|
- Heslo: `Vlado7309208104+`
|
||||||
|
- ICZ: `09305000`
|
||||||
|
|
||||||
|
**Výstup:** Tabulka s jménem lékaře v odbornosti 001 pro každého pacienta (nebo "NEMÁ LÉKAŘE")
|
||||||
|
|
||||||
|
**Poznámka:** VZP provádí údržbu 15. 5. 2026 18:30 — 17. 5. 2026 (sic!) — API nemusí být dostupné.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Oprava připojení k Medicusu
|
||||||
|
|
||||||
|
**Problém:** `get_medicus_connection()` v `medicus_db.py` měla špatný DSN
|
||||||
|
|
||||||
|
**Řešení:** Aktualizován DSN pro počítač `NTBVBHP470G10`:
|
||||||
|
```python
|
||||||
|
"NTBVBHP470G10": r"reporter:c:\medicus\medicus.fdb",
|
||||||
|
```
|
||||||
|
|
||||||
|
Místo: `localhost:c:\medicus 3\data\medicus.fdb`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Závěr — co to znamená
|
||||||
|
|
||||||
|
**Situace:**
|
||||||
|
- ✓ 1617 pacientů je správně registrovaných v Medicusu
|
||||||
|
- ⚠️ 66 pacientů je ACTIVE v Medeviu, ale nemají registraci u nás (mají vlastní účet v Medeviu)
|
||||||
|
- ⚠️ 179 pacientů bylo správně označeno jako REMOVED (bez vlastního účtu)
|
||||||
|
- ⚠️ 18 pacientů je označeno REMOVED, mají vlastní účet, ale nejsou v Medicusu
|
||||||
|
|
||||||
|
**Příštích kroků:**
|
||||||
|
1. Opravit `bulk_set_removed.py` — měl by hledat všechny ACTIVE mimo Medicus, ne jen bez `user_id`
|
||||||
|
2. Rozhodnout o těch 66 pacientech:
|
||||||
|
- Jsou to pacienti, co se registrují jen v Medeviu (bez návštěvy u lékaře)?
|
||||||
|
- Měli by být označeni jako REMOVED?
|
||||||
|
3. Dotázat se VZP API, kde jsou těmito pacienty registrováni (pokud vůbec)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Soubory vytvořené během analýzy
|
||||||
|
|
||||||
|
```
|
||||||
|
U:\OrdinaceProjekt\Medevio\80 Pacienti\
|
||||||
|
├── sync_patients_to_mysql.py (upraveno — multithreading)
|
||||||
|
├── bulk_set_removed.py (existující — problém s logikou)
|
||||||
|
├── check_sync_counts.py (NEW)
|
||||||
|
├── check_removed_status.py (NEW)
|
||||||
|
├── analyze_extras.py (NEW)
|
||||||
|
├── list_extras.py (NEW)
|
||||||
|
├── stats_user_id.py (NEW)
|
||||||
|
├── check_vymetalova.py (NEW)
|
||||||
|
├── debug_vymetalova.py (NEW)
|
||||||
|
├── check_lekare_vzp.py (NEW)
|
||||||
|
├── check_lekare_vzp_test.py (NEW)
|
||||||
|
└── vzp_output.txt (output z VZP dotazů)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Zpracoval:** Claude Code
|
||||||
|
**Poslední update:** 2026-05-15 16:00 CET
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pro každého z 66 pacientů mimo Medicus:
|
||||||
|
- Zavolá VZP API na registrující lékaře v odbornosti 001
|
||||||
|
- Vypíše, kdo je jejich lékař (nebo že ho nemají)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
except AttributeError:
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||||
|
|
||||||
|
sys.path.insert(0, 'U:/OrdinaceProjekt')
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
from Knihovny.medicus_db import get_medicus_db
|
||||||
|
from Knihovny.vzpb2b_client import VZPB2BClient
|
||||||
|
from pathlib import Path
|
||||||
|
import time
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "medevio",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Certifikát
|
||||||
|
CERT_PATH = Path("U:/OrdinaceProjekt/Insurance/Certificates/picka.pfx")
|
||||||
|
CERT_PASSWORD = "Vlado7309208104+"
|
||||||
|
ICZ = "09305000"
|
||||||
|
|
||||||
|
# ==================== MAIN ====================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Načti registrované v Medicusu
|
||||||
|
db = get_medicus_db()
|
||||||
|
rows = db.get_active_registered_patients()
|
||||||
|
medicus_rc = {r[0].strip() for r in rows if r[0]}
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Najdi všechny ACTIVE s účtem mimo Medicus
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT name, surname, identification_number
|
||||||
|
FROM medevio_pacient
|
||||||
|
WHERE status = 'ACTIVE'
|
||||||
|
AND user_id IS NOT NULL
|
||||||
|
AND identification_number IS NOT NULL
|
||||||
|
ORDER BY surname, name
|
||||||
|
""")
|
||||||
|
all_with_account = cur.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Filtruj ty mimo Medicus
|
||||||
|
extras = [
|
||||||
|
r for r in all_with_account
|
||||||
|
if r["identification_number"] not in medicus_rc
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
|
print(f"DOTAZ VZP NA REGISTRUJÍCÍHO LÉKAŘE — {len(extras)} pacientů\n")
|
||||||
|
print(f"{'#':3s} {'Jméno':30s} {'RC':13s} {'Lékař v 001':40s}")
|
||||||
|
print("=" * 100)
|
||||||
|
|
||||||
|
# Inicializuj VZP client
|
||||||
|
try:
|
||||||
|
client = VZPB2BClient("prod", str(CERT_PATH), CERT_PASSWORD, icz=ICZ)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"CHYBA: Nelze se připojit k VZP API: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pro každého pacienta
|
||||||
|
for i, r in enumerate(extras, 1):
|
||||||
|
name = f"{r['surname']} {r['name']}"
|
||||||
|
rc = r["identification_number"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Zavolej VZP API
|
||||||
|
xml_response = client.registrace_lekare(rc, odbornosti=["001"])
|
||||||
|
parsed = client.parse_registrace_lekare(xml_response)
|
||||||
|
|
||||||
|
# Pokud má lékaře v 001
|
||||||
|
if parsed and parsed[0].get("ma_lekare"):
|
||||||
|
p = parsed[0]
|
||||||
|
lekar = f"{p.get('nazev_lekare', '?')} (ICP: {p.get('ICP', '?')})"
|
||||||
|
else:
|
||||||
|
lekar = "NEMÁ LÉKAŘE"
|
||||||
|
|
||||||
|
print(f"{i:3d} {name:30s} {rc:13s} {lekar:40s}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{i:3d} {name:30s} {rc:13s} CHYBA: {str(e)[:40]}")
|
||||||
|
|
||||||
|
# Delay pro API
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
TEST — jen prvních 5 pacientů
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
except AttributeError:
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||||
|
|
||||||
|
sys.path.insert(0, 'U:/OrdinaceProjekt')
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
from Knihovny.medicus_db import get_medicus_db
|
||||||
|
from Knihovny.vzpb2b_client import VZPB2BClient
|
||||||
|
from pathlib import Path
|
||||||
|
import time
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "medevio",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
CERT_PATH = Path("U:/OrdinaceProjekt/Insurance/Certificates/picka.pfx")
|
||||||
|
CERT_PASSWORD = "Vlado7309208104+"
|
||||||
|
ICZ = "09305000"
|
||||||
|
|
||||||
|
# Načti pacienty mimo Medicus — seznam máme v MySQL z loňské analýzy
|
||||||
|
# (seznam těch, co nejsou v registru Medicusu)
|
||||||
|
medicus_rc = set() # Prázdné — nás zajímají všichni co jsou v Medeviu s user_id
|
||||||
|
|
||||||
|
# Najdi všechny ACTIVE s účtem mimo Medicus
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT name, surname, identification_number
|
||||||
|
FROM medevio_pacient
|
||||||
|
WHERE status = 'ACTIVE'
|
||||||
|
AND user_id IS NOT NULL
|
||||||
|
AND identification_number IS NOT NULL
|
||||||
|
ORDER BY surname, name
|
||||||
|
""")
|
||||||
|
all_with_account = cur.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
extras = [
|
||||||
|
r for r in all_with_account
|
||||||
|
if r["identification_number"] not in medicus_rc
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
|
print(f"TEST — prvních 5 z {len(extras)} pacientů\n")
|
||||||
|
print(f"{'#':3s} {'Jméno':30s} {'RC':13s} {'Lékař v 001':50s}")
|
||||||
|
print("=" * 100)
|
||||||
|
|
||||||
|
client = VZPB2BClient("prod", str(CERT_PATH), CERT_PASSWORD, icz=ICZ)
|
||||||
|
|
||||||
|
for i, r in enumerate(extras[:5], 1):
|
||||||
|
name = f"{r['surname']} {r['name']}"
|
||||||
|
rc = r["identification_number"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
xml_response = client.registrace_lekare(rc, odbornosti=["001"])
|
||||||
|
parsed = client.parse_registrace_lekare(xml_response)
|
||||||
|
|
||||||
|
if parsed and parsed[0].get("ma_lekare"):
|
||||||
|
p = parsed[0]
|
||||||
|
lekar = f"{p.get('nazev_lekare', '?')} (ICP: {p.get('ICP', '?')})"
|
||||||
|
else:
|
||||||
|
lekar = "NEMÁ LÉKAŘE"
|
||||||
|
|
||||||
|
print(f"{i:3d} {name:30s} {rc:13s} {lekar:50s}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{i:3d} {name:30s} {rc:13s} CHYBA: {str(e)[:50]}")
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
Binary file not shown.
@@ -0,0 +1,227 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys, io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||||
|
"""
|
||||||
|
Přehled předepsaných ePoukazů na ZP (ORTOPE)
|
||||||
|
Výstup: Přehled_ZP.xlsx se dvěma listy:
|
||||||
|
List 1 "Všechny záznamy" — vše včetně jmen pacientů
|
||||||
|
List 2 "Přehled Medicus" — pouze sloupce z Medicus obrazovky
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, r'U:\OrdinaceProjekt')
|
||||||
|
from Knihovny.najdi_dropbox import get_dropbox_root
|
||||||
|
from Knihovny.medicus_db import get_medicus_connection
|
||||||
|
|
||||||
|
import openpyxl
|
||||||
|
from openpyxl.styles import Font, PatternFill, Alignment
|
||||||
|
from datetime import datetime, date
|
||||||
|
import os
|
||||||
|
|
||||||
|
# --- Připojení ---
|
||||||
|
conn = get_medicus_connection()
|
||||||
|
|
||||||
|
# --- Parsování DATA blobu ---
|
||||||
|
def parse_data(data_text):
|
||||||
|
"""Parsuje key=value formát z HISTDOC.DATA"""
|
||||||
|
result = {}
|
||||||
|
if not data_text:
|
||||||
|
return result
|
||||||
|
for line in data_text.replace('\r\n', '\n').replace('\r', '\n').split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if '=' not in line:
|
||||||
|
continue
|
||||||
|
key, _, value = line.partition('=')
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if value.startswith('C:'):
|
||||||
|
try:
|
||||||
|
result[key] = float(value[2:].replace(',', '.'))
|
||||||
|
except Exception:
|
||||||
|
result[key] = value[2:]
|
||||||
|
elif value.startswith('I:'):
|
||||||
|
try:
|
||||||
|
result[key] = int(value[2:])
|
||||||
|
except Exception:
|
||||||
|
result[key] = value[2:]
|
||||||
|
elif value.startswith('D:'):
|
||||||
|
result[key] = value[2:] # DD.MM.YYYY řetězec
|
||||||
|
elif value in ('$:~\b', '$:~\x08', '$:'):
|
||||||
|
result[key] = ''
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
# --- Načtení dat ---
|
||||||
|
print("Načítám záznamy ORTOPE...")
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
h.ID,
|
||||||
|
h.DATUM,
|
||||||
|
h.STAV,
|
||||||
|
CAST(h.DATA AS VARCHAR(8000)) AS DATA_TEXT,
|
||||||
|
k.RODCIS,
|
||||||
|
k.PRIJMENI,
|
||||||
|
k.JMENO AS JMENO_PAC,
|
||||||
|
k.POJ AS POJ_KAR,
|
||||||
|
u.TITUL,
|
||||||
|
u.PRIJMENI AS PRIJMENI_LEK,
|
||||||
|
u.JMENO AS JMENO_LEK,
|
||||||
|
e.ID_DOKLADU,
|
||||||
|
e.ID_ZP,
|
||||||
|
e.ODESLANO,
|
||||||
|
e.CHYBA,
|
||||||
|
e.VYDANO
|
||||||
|
FROM HISTDOC h
|
||||||
|
LEFT JOIN KAR k ON h.IDPACI = k.IDPAC
|
||||||
|
LEFT JOIN UZIVATEL u ON h.IDUZIV = u.IDUZI
|
||||||
|
LEFT JOIN HISTDOC_EPOUKAZ e ON e.IDHISTDOC = h.ID
|
||||||
|
WHERE h.TYP = 'ORTOPE'
|
||||||
|
ORDER BY h.DATUM, h.ID
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print(f"Načteno {len(rows)} záznamů.")
|
||||||
|
|
||||||
|
# --- Sestavení řádků ---
|
||||||
|
records = []
|
||||||
|
for row in rows:
|
||||||
|
(hid, datum, stav, data_text,
|
||||||
|
rodcis, prijmeni_pac, jmeno_pac, poj_kar,
|
||||||
|
titul_lek, prijmeni_lek, jmeno_lek,
|
||||||
|
id_dokladu, id_zp, odeslano, chyba, vydano) = row
|
||||||
|
|
||||||
|
d = parse_data(data_text)
|
||||||
|
|
||||||
|
# Pojišťovna — z DATA (POJ=I:111) nebo z KAR
|
||||||
|
poj_raw = d.get('POJ', '')
|
||||||
|
if isinstance(poj_raw, int):
|
||||||
|
poj = str(poj_raw)
|
||||||
|
else:
|
||||||
|
poj = str(poj_raw).replace('I:', '').strip() if poj_raw else (str(poj_kar) if poj_kar else '')
|
||||||
|
|
||||||
|
vystavil = ' '.join(filter(None, [titul_lek, prijmeni_lek, jmeno_lek]))
|
||||||
|
|
||||||
|
# Datum jako date objekt pro Excel
|
||||||
|
datum_val = datum if isinstance(datum, (datetime, date)) else datum
|
||||||
|
|
||||||
|
# Pocet — z DATA
|
||||||
|
pocet_raw = d.get('Pocet', '')
|
||||||
|
try:
|
||||||
|
pocet = float(pocet_raw) if pocet_raw != '' else None
|
||||||
|
except Exception:
|
||||||
|
pocet = pocet_raw
|
||||||
|
|
||||||
|
# Cena — z DATA (přesnější než HISTDOC.CENA)
|
||||||
|
cena_raw = d.get('Cena', '')
|
||||||
|
try:
|
||||||
|
cena = float(cena_raw) if cena_raw != '' else None
|
||||||
|
except Exception:
|
||||||
|
cena = cena_raw
|
||||||
|
|
||||||
|
records.append({
|
||||||
|
'ID': hid,
|
||||||
|
'Datum': datum_val,
|
||||||
|
'Kód': d.get('Kod', ''),
|
||||||
|
'Název': d.get('Nazev', ''),
|
||||||
|
'Počet': pocet,
|
||||||
|
'Cena': cena,
|
||||||
|
'Pojišťovna': poj,
|
||||||
|
'IČZ': d.get('ICZ', '09305000'),
|
||||||
|
'Rodné číslo': rodcis or '',
|
||||||
|
'Příjmení': prijmeni_pac or '',
|
||||||
|
'Jméno': jmeno_pac or '',
|
||||||
|
'Vystavil': vystavil,
|
||||||
|
'Doklad': id_dokladu or '',
|
||||||
|
'ID ZP': id_zp or '',
|
||||||
|
'Odesláno': odeslano,
|
||||||
|
'Vydáno': vydano,
|
||||||
|
'Chyba': 'Ano' if chyba == 'T' else '',
|
||||||
|
'Diagnóza': d.get('Dgn', ''),
|
||||||
|
'Platnost do': d.get('DatPlat', ''),
|
||||||
|
'Stav': d.get('Stav', stav or ''),
|
||||||
|
'Doplněk': d.get('DoplnekNazvu', ''),
|
||||||
|
'Dopl. cena': d.get('Dopl', ''),
|
||||||
|
'UHS': d.get('UHS', ''),
|
||||||
|
'Notifikace': str(d.get('Notifikace', '')),
|
||||||
|
})
|
||||||
|
|
||||||
|
# --- Excel ---
|
||||||
|
wb = openpyxl.Workbook()
|
||||||
|
|
||||||
|
# Styl záhlaví
|
||||||
|
HEADER_FILL = PatternFill("solid", fgColor="1F4E79")
|
||||||
|
HEADER_FONT = Font(bold=True, color="FFFFFF")
|
||||||
|
HEADER_ALIGN = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||||||
|
|
||||||
|
def write_sheet(ws, headers, data_rows):
|
||||||
|
"""Zapíše záhlaví a data na list."""
|
||||||
|
ws.append(headers)
|
||||||
|
# Styl záhlaví
|
||||||
|
for cell in ws[1]:
|
||||||
|
cell.fill = HEADER_FILL
|
||||||
|
cell.font = HEADER_FONT
|
||||||
|
cell.alignment = HEADER_ALIGN
|
||||||
|
ws.row_dimensions[1].height = 30
|
||||||
|
|
||||||
|
for rec in data_rows:
|
||||||
|
row = [rec.get(h, '') for h in headers]
|
||||||
|
ws.append(row)
|
||||||
|
|
||||||
|
# Formát sloupce Datum
|
||||||
|
date_cols = [i+1 for i, h in enumerate(headers) if 'Datum' in h or 'datum' in h]
|
||||||
|
for col_idx in date_cols:
|
||||||
|
for row_idx in range(2, ws.max_row + 1):
|
||||||
|
cell = ws.cell(row=row_idx, column=col_idx)
|
||||||
|
if isinstance(cell.value, (datetime, date)):
|
||||||
|
cell.number_format = 'DD.MM.YYYY'
|
||||||
|
|
||||||
|
# Formát Cena / Počet
|
||||||
|
num_cols = [i+1 for i, h in enumerate(headers) if h in ('Cena', 'Počet', 'Dopl. cena')]
|
||||||
|
for col_idx in num_cols:
|
||||||
|
for row_idx in range(2, ws.max_row + 1):
|
||||||
|
cell = ws.cell(row=row_idx, column=col_idx)
|
||||||
|
if isinstance(cell.value, float):
|
||||||
|
cell.number_format = '#,##0.00'
|
||||||
|
|
||||||
|
# Šířky sloupců
|
||||||
|
for col in ws.columns:
|
||||||
|
max_len = max((len(str(c.value or '')) for c in col), default=10)
|
||||||
|
ws.column_dimensions[col[0].column_letter].width = min(max_len + 2, 40)
|
||||||
|
|
||||||
|
# --- List 1: Všechny záznamy ---
|
||||||
|
ws1 = wb.active
|
||||||
|
ws1.title = "Všechny záznamy"
|
||||||
|
headers1 = [
|
||||||
|
'Datum', 'Kód', 'Název', 'Doplněk', 'Počet', 'Cena', 'Dopl. cena',
|
||||||
|
'Pojišťovna', 'IČZ', 'Rodné číslo', 'Příjmení', 'Jméno',
|
||||||
|
'Diagnóza', 'Platnost do', 'Stav', 'Vystavil',
|
||||||
|
'Doklad', 'Odesláno', 'Vydáno', 'Chyba', 'ID ZP', 'UHS', 'Notifikace', 'ID'
|
||||||
|
]
|
||||||
|
write_sheet(ws1, headers1, records)
|
||||||
|
|
||||||
|
# --- List 2: Přehled Medicus (sloupce jako na obrazovce) ---
|
||||||
|
ws2 = wb.create_sheet("Přehled Medicus")
|
||||||
|
headers2 = [
|
||||||
|
'Datum', 'Kód', 'Název', 'Počet', 'Cena',
|
||||||
|
'Pojišťovna', 'IČZ', 'Rodné číslo', 'Příjmení', 'Jméno',
|
||||||
|
'Vystavil', 'Doklad'
|
||||||
|
]
|
||||||
|
write_sheet(ws2, headers2, records)
|
||||||
|
|
||||||
|
# Zmraž záhlaví na obou listech
|
||||||
|
for ws in [ws1, ws2]:
|
||||||
|
ws.freeze_panes = ws['A2']
|
||||||
|
|
||||||
|
# --- Uložení ---
|
||||||
|
DROPBOX_ROOT = get_dropbox_root()
|
||||||
|
OUTPUT_DIR = os.path.join(DROPBOX_ROOT, '!!!Days', 'Downloads Z230')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
output_path = os.path.join(OUTPUT_DIR, 'Přehled_ZP.xlsx')
|
||||||
|
wb.save(output_path)
|
||||||
|
print(f"Uloženo: {output_path}")
|
||||||
|
print(f"Celkem záznamů: {len(records)}")
|
||||||
|
conn.close()
|
||||||
@@ -0,0 +1,723 @@
|
|||||||
|
# PrůzkumDatabáze – poznámky pro Clauda
|
||||||
|
|
||||||
|
Průzkum Medicus Firebird DB, doplněk k `MedicusWithClaude/CLAUDE_NOTES.md`.
|
||||||
|
Aktualizováno: 2026-05-16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Celkový přehled
|
||||||
|
|
||||||
|
- **993 tabulek** (Firebird 2.5 HQbird, charset win1250)
|
||||||
|
- Přímý přístup přes MCP nástroje: `execute_query`, `get_table_columns`, `list_tables`
|
||||||
|
- Základní info o DB, RTF formátu a importu viz [`MedicusWithClaude/CLAUDE_NOTES.md`](../MedicusWithClaude/CLAUDE_NOTES.md)
|
||||||
|
- SQL dotazy a schéma fakturace viz [`MedicusWithClaudeSelects/`](../MedicusWithClaudeSelects/)
|
||||||
|
|
||||||
|
### Správné připojení (MCP server `U:\OrdinaceProjekt\mcp_firebird.py`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
fdb.connect(
|
||||||
|
dsn=r'reporter:c:\medicus\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ Pozor: dříve chybně nastaveno na `localhost:c:\medicus 3\data\medicus.fdb` (port 3070) — to ukazovalo na starou zálohu s daty do 2026-04-20. Opraveno 2026-05-19.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Statistiky klíčových tabulek (stav 2026-05-16)
|
||||||
|
|
||||||
|
| Tabulka | Záznamů | Obsah |
|
||||||
|
|---|---|---|
|
||||||
|
| KAR | 6 319 | Kartotéka pacientů |
|
||||||
|
| DEKURS | 172 068 | Záznamy z návštěv (RTF blob) |
|
||||||
|
| RECEPT | 191 820 | E-recepty (od 1997) |
|
||||||
|
| DOKLADD | 98 321 | Výkony – detail |
|
||||||
|
| DOKLADH | 54 436 | Výkony – záhlaví |
|
||||||
|
| HISTDOC | 32 689 | Dokumenty (poukazy, posudky…) |
|
||||||
|
| OCKZAZ | 9 746 | Záznamy podaných vakcín |
|
||||||
|
| PREH | 11 885 | Preventivní prohlídky a měření |
|
||||||
|
| VZPARC | 1 954 | Výkonové dávky pojišťovnám |
|
||||||
|
| NES | 4 318 | Neschopenky (od 2008) |
|
||||||
|
| LECH | 4 247 | Lékový doklad – záhlaví |
|
||||||
|
| LECD | 4 413 | Lékový doklad – detail (léky) |
|
||||||
|
| OCKPRI | 1 397 | Plánovaná očkování |
|
||||||
|
| FAK | 844 | Faktury pojišťovnám |
|
||||||
|
| MEDIKACE | 252 | Aktuální medikace pacientů |
|
||||||
|
| DISPAC | 2 | Dispenzarizace pacientů |
|
||||||
|
| ICP | 11 | Pracoviště ordinace (IČP) |
|
||||||
|
| ICZ | 11 | Zdravotnické zařízení (IČZ) |
|
||||||
|
| LOG | 2 812 283 | Audit log — kdo, kdy, co otevřel/změnil |
|
||||||
|
| ZURNAL | 795 220 | Starší audit log (do 2019, nahrazen tabulkou LOG) |
|
||||||
|
| PZT | 600 341 | Číselník zdravotnických prostředků (ZP) — 46 151 platných, zbytek historické |
|
||||||
|
| LABVD | 526 500 | Laboratorní hodnoty – detail |
|
||||||
|
| DOCLIST | 466 837 | Univerzální index dokumentů — rozcestník přes DEKURS, RECEPT, HISTDOC, LABVH, LEKZPRAVY, NES, OCKZAZ, PREH, FILES, SPECVYS |
|
||||||
|
| LEKY | 429 658 | Číselník léků SÚKL |
|
||||||
|
| DEKLINK | 298 823 | Vazby dekurzů na jiné záznamy |
|
||||||
|
| RECEPT_EPODANI | 64 154 | Stav e-podání receptů |
|
||||||
|
| REGISTR | 1 999 | Registrace pacientů u pojišťovny |
|
||||||
|
| POU | 0 | Poukazy (prázdná) |
|
||||||
|
| SMS | 0 | SMS zprávy (prázdná) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LOG – audit log
|
||||||
|
|
||||||
|
**2 812 283 záznamů** — každý přístup nebo změna záznamu v Medicusu se sem zapíše. Aktivně roste, poslední záznam 2026-05-15.
|
||||||
|
|
||||||
|
### Roční objem
|
||||||
|
|
||||||
|
| Rok | Záznamů |
|
||||||
|
|---|---|
|
||||||
|
| 2024 | 325 435 |
|
||||||
|
| 2025 | 432 479 |
|
||||||
|
| 2026 | 146 377 (do 15.5.) |
|
||||||
|
|
||||||
|
Průměr ~380 000/rok. Za 10 let (do 2036) odhadem ~6,6M řádků, ~600–800 MB s indexy.
|
||||||
|
|
||||||
|
### Struktura
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| DATUM | datum akce |
|
||||||
|
| CAS | čas akce |
|
||||||
|
| UZIVATEL | zkratka uživatele (JOU, SES, MBU, VBU…) |
|
||||||
|
| IDPAC | ID pacienta |
|
||||||
|
| TABULKA | číslo tabulky → mapování přes LOGTABLES |
|
||||||
|
| IDREC | ID záznamu v dané tabulce |
|
||||||
|
| AKCE | `V` = View (otevření), `U` = Update (změna) |
|
||||||
|
| DETAIL | detail akce (většinou NULL) |
|
||||||
|
|
||||||
|
### LOGTABLES – mapování čísel na tabulky (výběr)
|
||||||
|
|
||||||
|
| ID | Tabulka | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | KAR | Identifikace |
|
||||||
|
| 2 | REGISTR | Registrace |
|
||||||
|
| 3 | KARUZIV | Ošetřující lékaři |
|
||||||
|
| 7 | DEKURS | Ambulantní záznamy |
|
||||||
|
| 8 | ANAMNEZA | Anamnéza |
|
||||||
|
| 9 | MEDIKACE | Medikace |
|
||||||
|
| 10 | RECEPT | Recepty |
|
||||||
|
| 11 | OCKZAZ | Očkování |
|
||||||
|
| 12 | NES | Neschopenky |
|
||||||
|
| 15 | PREH | Prohlídky |
|
||||||
|
| 16 | HISTDOC | Žádanky, Posudky, Formuláře |
|
||||||
|
| 10000 | *(otevření karty pacienta)* | speciální kód, není v LOGTABLES |
|
||||||
|
|
||||||
|
### Pozor při dotazování
|
||||||
|
LOG timeoutuje bez filtru — vždy používat `WHERE DATUM = '...'` nebo `WHERE ID BETWEEN x AND y`. `ORDER BY ID DESC` na celé tabulce také timeoutuje.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PZT – číselník zdravotnických prostředků
|
||||||
|
|
||||||
|
**600 341 záznamů** celkem — z toho **46 151 platných** (PLATIDO >= 2026-01-01), **554 190 historických**. Data od 2007. Medicus stahuje aktualizace číselníku od pojišťoven/SÚKL.
|
||||||
|
|
||||||
|
Stejná logika jako tabulka LEKY — jen pro zdravotnické prostředky místo léků.
|
||||||
|
|
||||||
|
### Klíčové sloupce (dle oficiální metodiky VZP, verze 1143, platnost 1.5.2026)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| KOD | kód ZP (7 znaků) |
|
||||||
|
| PKOD | původní kód ZP přidělený VZP ČR |
|
||||||
|
| NAZ | název ZP |
|
||||||
|
| DOP | doplněk názvu ZP |
|
||||||
|
| PRO | preskripční označení: **P** = předepisuje se na Poukaz, **M** = zvlášť účtovaný materiál (ZUM) |
|
||||||
|
| TYP | typ skupiny ZP (kódové označení skupiny) |
|
||||||
|
| MJD | počet měrných jednotek v balení |
|
||||||
|
| MJ | měrná jednotka |
|
||||||
|
| TBAL | typ balení (ks, bal…) |
|
||||||
|
| VYR | výrobce (zkratka) |
|
||||||
|
| ZEM | země výroby |
|
||||||
|
| OHL | název Ohlašovatele |
|
||||||
|
| MAXI | maximální úhrada pojišťovnou — odpovídá UHR1 (1. úhradová sazba) |
|
||||||
|
| LIM | příznak schválené úhrady (LIM1) — 'Z' = vyžaduje schválení pojišťovnou |
|
||||||
|
| OME | specializace předepisujícího lékaře (OME1) |
|
||||||
|
| UHR2 | 2. úhradová sazba |
|
||||||
|
| LIM2 | příznak schválené úhrady pro 2. sazbu |
|
||||||
|
| OME2 | specializace lékaře pro 2. sazbu |
|
||||||
|
| UHR3 | 3. úhradová sazba |
|
||||||
|
| LIM3 | příznak schválené úhrady pro 3. sazbu |
|
||||||
|
| OME3 | specializace lékaře pro 3. sazbu |
|
||||||
|
| MFC | maximální konečná cena ke spotřebiteli |
|
||||||
|
| DNC | písemné ujednání o ceně: prázdné = není DNC, 1 = DNC, 2 = DNC se závazkem, 3 = cenová soutěž |
|
||||||
|
| UHS | úhradová skupina |
|
||||||
|
| UPO | způsob úhrady pojišťovnou: **I** = plně hrazený, **R** = zapůjčovaný ZP, prázdné = nezapůjčovaný |
|
||||||
|
| UHP | úhrada v procentech |
|
||||||
|
| UDOKS | počet MJ za UDO |
|
||||||
|
| UDO | užitná doba (v měsících) |
|
||||||
|
| SKP | skupina postižení |
|
||||||
|
| DAT | datum změny záznamu |
|
||||||
|
| KAT | kategorie |
|
||||||
|
| PLATIOD / PLATIDO | platnost položky v číselníku |
|
||||||
|
| RP1–RP5 | dodatečné sloupce (pravděpodobně referenční ceny) |
|
||||||
|
| IS_CG | příznak (patrně IS_CGM — kontinuální monitorace glukózy) |
|
||||||
|
|
||||||
|
### OME – zkratky odborností oprávněných předepsat ZP
|
||||||
|
|
||||||
|
91 různých kombinací. Výběr zkratek:
|
||||||
|
|
||||||
|
| Zkratka | Odbornost |
|
||||||
|
|---|---|
|
||||||
|
| PRL | Praktický lékař |
|
||||||
|
| LEKAR | Jakýkoliv lékař |
|
||||||
|
| DIA | Diabetologie |
|
||||||
|
| NEU | Neurologie |
|
||||||
|
| ORT | Ortopedie |
|
||||||
|
| REH | Rehabilitace |
|
||||||
|
| CHI | Chirurgie |
|
||||||
|
| GYN | Gynekologie |
|
||||||
|
| ONK | Onkologie |
|
||||||
|
| URN | Urologie |
|
||||||
|
| KAR | Kardiologie |
|
||||||
|
| PNE | Pneumologie |
|
||||||
|
| DER | Dermatologie |
|
||||||
|
| GER | Geriatrie |
|
||||||
|
| PED | Pediatrie |
|
||||||
|
| ORL | ORL |
|
||||||
|
| PSY | Psychiatrie |
|
||||||
|
| INT | Interna |
|
||||||
|
| S5 | Specifická kategorie (sestra/soc. péče?) |
|
||||||
|
| DLE ZÁKONA | Speciální režim (vozíky, lůžka…) |
|
||||||
|
|
||||||
|
Poznámka: `PRL` se vyskytuje pouze v kombinaci s dalšími odbornostmi — praktický lékař nikdy není jediným předepisujícím.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ZURNAL – starý audit log (nepoužívaná)
|
||||||
|
|
||||||
|
**795 220 záznamů**, data od 2012-02-22 do **2019-04-17** — poté se přestala plnit.
|
||||||
|
|
||||||
|
Starší verze tabulky LOG, stejná struktura (DATUM, CAS, TABULKA, UZIVATEL, AKCE, KEYFIELD, ID1…). Medicus ji nahradil tabulkou LOG někdy kolem roku 2019. Dnes je mrtvá, pouze historická data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RECEPT – e-recepty
|
||||||
|
|
||||||
|
**191 820 záznamů**, od 1997-05-06 do současnosti. 1 261 stornovaných.
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| DATUM | datum vystavení |
|
||||||
|
| KOD | kód léku (SÚKL, 7 znaků) |
|
||||||
|
| LEK | název léku |
|
||||||
|
| POJ | pojišťovna (111, 205, 207…) |
|
||||||
|
| DGN | diagnóza |
|
||||||
|
| TYP | typ receptu (1 = běžný) |
|
||||||
|
| STORNO | 'F' = platný, 'T' = stornovaný |
|
||||||
|
| CISRECEPT | číslo receptu (po e-podání) |
|
||||||
|
| IDUZI | kdo vystavil |
|
||||||
|
| ICP | IČP ordinace |
|
||||||
|
| UHRADA | způsob úhrady |
|
||||||
|
| PLATIDO | platnost do |
|
||||||
|
| ES_STAV | stav e-receptu (eRecept systém) |
|
||||||
|
|
||||||
|
Poznámka: Tabulka obsahuje jak tištěné, tak e-recepty. Pole `CISRECEPT` je NULL u starších papírových.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LECH / LECD – lékové doklady pro pojišťovnu
|
||||||
|
|
||||||
|
Dvojice podobná DOKLADH/DOKLADD — ale pro léky vykazované pojišťovně (oddělená agenda od receptů).
|
||||||
|
|
||||||
|
### LECH – záhlaví (4 247 záznamů)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| IDLEC | primární klíč |
|
||||||
|
| POJ | pojišťovna |
|
||||||
|
| ICZ | IČZ ordinace (09305001) |
|
||||||
|
| RODCIS | rodné číslo pacienta |
|
||||||
|
| HODB | hlavní odbornost (001) |
|
||||||
|
| HDGN | hlavní diagnóza |
|
||||||
|
| TYP | typ ('A' = ambulantní) |
|
||||||
|
| STAV | stav (0 = otevřen) |
|
||||||
|
| DAVKA | číslo dávky |
|
||||||
|
|
||||||
|
### LECD – detail / jednotlivé léky (4 413 záznamů)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDLEC | FK → LECH |
|
||||||
|
| RODCIS | rodné číslo |
|
||||||
|
| DATOSE | datum ošetření |
|
||||||
|
| KOD | kód léku (SÚKL) |
|
||||||
|
| POCET | počet balení |
|
||||||
|
| CENA | cena (Kč) |
|
||||||
|
| KAT | kategorie ('I' = imunizace, NULL = běžný) |
|
||||||
|
| IDUZI | který lékař |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DOKLADH / DOKLADD – výkony
|
||||||
|
|
||||||
|
Hlavní záhlaví/detail pro výkony vykazované pojišťovnám.
|
||||||
|
|
||||||
|
### DOKLADH – záhlaví (54 436 záznamů)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| IDHLAV | primární klíč |
|
||||||
|
| POJ | pojišťovna |
|
||||||
|
| ICZ | IČZ ordinace |
|
||||||
|
| RODCIS | rodné číslo pacienta |
|
||||||
|
| HODB | hlavní odbornost (001 = praktický lékař) |
|
||||||
|
| HDGN | hlavní diagnóza |
|
||||||
|
| VDGN1–6 | vedlejší diagnózy |
|
||||||
|
| TYP | typ dokladu ('A' = ambulantní) |
|
||||||
|
| STAV | stav (0 = otevřen) |
|
||||||
|
| ROK | rok |
|
||||||
|
| EICZ | IČZ odesílajícího lékaře (NULL = vlastní) |
|
||||||
|
| EODZ | odbornost odesílajícího |
|
||||||
|
| DAVKA | číslo dávky |
|
||||||
|
|
||||||
|
### DOKLADD – detail výkony (98 321 záznamů)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDHLAV | FK → DOKLADH |
|
||||||
|
| RODCIS | rodné číslo pacienta |
|
||||||
|
| DATOSE | datum ošetření |
|
||||||
|
| KOD | kód výkonu (5 znaků, např. '01022') |
|
||||||
|
| POCVYK | počet provedení výkonu |
|
||||||
|
| DDGN | diagnóza výkonu |
|
||||||
|
| ODB | odbornost |
|
||||||
|
| BODY | počet bodů výkonu |
|
||||||
|
| CENABOD | cena v bodech (Kč) |
|
||||||
|
| CENAMAT | cena materiálu (Kč) |
|
||||||
|
| CENAPAU | paušál (Kč) |
|
||||||
|
| KAT | kategorie ('N' = nevykazovat, 'K' = kapitace, 'A' = agregace) |
|
||||||
|
| IDUZI | který lékař |
|
||||||
|
| IDHLAV | FK → DOKLADH |
|
||||||
|
|
||||||
|
Schéma: `DOKLADH ←IDHLAV→ DOKLADD` (1:N)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PREH – preventivní prohlídky a měření
|
||||||
|
|
||||||
|
**11 885 záznamů** — ukládá měřené hodnoty při prohlídce (výška, váha, tlak, puls…).
|
||||||
|
|
||||||
|
### Klíčové sloupce
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| IDPRE | primární klíč |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| IDUZI | kdo zaznamenal |
|
||||||
|
| IDPREINI | typ prohlídky (viz PREINIH) |
|
||||||
|
| DATUM | datum prohlídky |
|
||||||
|
| VYSKA | výška (cm) |
|
||||||
|
| VAHA | váha (kg) |
|
||||||
|
| TLAKSYS | systolický tlak (mmHg) |
|
||||||
|
| TLAKDIA | diastolický tlak (mmHg) |
|
||||||
|
| PULS | puls (tepy/min) |
|
||||||
|
| CHOLESTEROL | hladina cholesterolu |
|
||||||
|
| SATURACE | saturace kyslíkem |
|
||||||
|
| TEPLOTA | teplota |
|
||||||
|
| BRICHO | obvod břicha |
|
||||||
|
| TERMIN | plánovaný příští termín |
|
||||||
|
| TYP | typ záznamu (0 = standardní) |
|
||||||
|
|
||||||
|
### PREINIH – typy prohlídek (7 záznamů)
|
||||||
|
|
||||||
|
| IDPREINI | NAZEV |
|
||||||
|
|---|---|
|
||||||
|
| 24 | Prohlídka (Dekurs) — **5 665 záznamů** |
|
||||||
|
| 25 | Výška a váha — **3 813 záznamů** |
|
||||||
|
| 26 | Tlak a puls — **2 062 záznamů** |
|
||||||
|
| 27 | Prohlídka - tlak — 341 záznamů |
|
||||||
|
| 28 | Diabetologická prohlídka — 4 záznamy |
|
||||||
|
| 29 | Předoperační vyšetření — 0 |
|
||||||
|
| 30 | Předoperační vyšetření — 0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HISTDOC – dokumenty a doklady
|
||||||
|
|
||||||
|
**32 689 záznamů** — evidence vydaných dokladů, poukazů, posudků. Typ dokladu určuje sloupec `TYP`.
|
||||||
|
|
||||||
|
### Typy TYP (nejčastější)
|
||||||
|
|
||||||
|
| TYP | Počet | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| POUV_1 | 15 861 | Poukaz na vyšetření – typ 1 |
|
||||||
|
| POUV_3 | 6 279 | Poukaz na vyšetření – typ 3 |
|
||||||
|
| MEDLAB | 2 396 | Laboratorní žádanka |
|
||||||
|
| MOTORVO | 1 535 | Posudek způsobilosti k řízení MV |
|
||||||
|
| ORTOP | 1 003 | Ortopedický poukaz |
|
||||||
|
| POTDPN | 901 | Potvrzení DPN (pracovní neschopnost) |
|
||||||
|
| Cov19Ts | 900 | Covid-19 test |
|
||||||
|
| ORTOPE | 891 | Ortopedický poukaz (e-verze) |
|
||||||
|
| COV19 | 595 | Covid-19 certifikát |
|
||||||
|
| LAZPEC | 517 | Žádost o lázeňskou péči |
|
||||||
|
| VYMLIST | 362 | Výměnný list |
|
||||||
|
| ZDRSTA5 | 340 | Zdravotní stav (typ 5) |
|
||||||
|
| PROHLAS | 151 | Prohlášení pacienta |
|
||||||
|
| ZPUPRN | 136 | Způsobilost (pracovní náplň?) |
|
||||||
|
| SANIT | 107 | Sanitní transport |
|
||||||
|
| ZBROJPR | 58 | Zbrojní průkaz |
|
||||||
|
| HlaOseL | 95 | Hlášení ošetřujícího lékaře |
|
||||||
|
|
||||||
|
### Klíčové sloupce
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| TYP | typ dokladu (viz tabulka výše) |
|
||||||
|
| DATUM | datum vydání |
|
||||||
|
| IDPACI | FK → KAR (pacienti) |
|
||||||
|
| IDUZIV | FK → UZIVATEL (kdo vytvořil) |
|
||||||
|
| STAV | stav ('Z' = zpracován, NULL = otevřen) |
|
||||||
|
| DATA | blob – data dokladu |
|
||||||
|
| IDDOKLAD | vazba na DOKLADH (pro výkony) |
|
||||||
|
| IDHLAV | vazba na záhlaví dokladu |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DOCLIST – univerzální index dokumentů
|
||||||
|
|
||||||
|
**466 837 záznamů** (stav 2026-05-16) — aktivně se plní, průměr ~3 700 záznamů/měsíc.
|
||||||
|
Slouží jako rozcestník: jeden řádek = jeden dokument v jiné tabulce. Neobsahuje samotná data, jen odkaz.
|
||||||
|
|
||||||
|
Poznámka: jeden záznam má datum 2029-11-21 (ID 566201, FILES, "PZ chirurgie.pdf") — patrně překlep při ručním zadání.
|
||||||
|
|
||||||
|
### Klíčové sloupce
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| DATUM | datum záznamu |
|
||||||
|
| TABULKA | zdrojová tabulka (viz níže) |
|
||||||
|
| IDREC | ID záznamu v té zdrojové tabulce |
|
||||||
|
| TYP | typ dokumentu (kód, viz níže) |
|
||||||
|
| POPIS | popis (u DEKURS = čas uložení, u FILES = název souboru, u LEKZPRAVY = "Lékařská zpráva"…) |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| IDODDEL | oddělení (2 = ordinace, -1 = vnější/bez oddělení) |
|
||||||
|
|
||||||
|
### Kombinace TYP + TABULKA (kompletní přehled)
|
||||||
|
|
||||||
|
| TYP | TABULKA | Počet | Popis |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | DEKURS | 174 927 | Záznamy z návštěv |
|
||||||
|
| 1 | FILES | 11 715 | Přílohy a soubory |
|
||||||
|
| 3 | HISTDOC | 32 940 | Poukazy, posudky, žádanky |
|
||||||
|
| 3 | HISTDOC_REP | 3 | Repliky histdoc (neznámé, 3 záznamy) |
|
||||||
|
| 6 | LABVH | 20 249 | Laboratorní výsledky – záhlaví |
|
||||||
|
| 7 | RECEPT | 191 137 | E-recepty |
|
||||||
|
| 8 | OCKZAZ | 7 911 | Očkování |
|
||||||
|
| 9 | PREH | 12 027 | Preventivní prohlídky a měření |
|
||||||
|
| 10 | NES | 3 744 | Neschopenky |
|
||||||
|
| 17 | SPECVYS | 746 | Specializované výsledky (tabulka zatím neprozkoumaná) |
|
||||||
|
| 24 | FILES | 227 | Přílohy jiného typu |
|
||||||
|
| 30 | LEKZPRAVY | 13 878 | Lékařské zprávy (tabulka zatím neprozkoumaná) |
|
||||||
|
|
||||||
|
### Tabulky zatím nepopsané v NOTES.md (zjištěno přes DOCLIST)
|
||||||
|
|
||||||
|
- **LABVH** — laboratorní výsledky záhlaví (LABVD je detail, LABVH záhlaví — viz DOCLIST TYP=6)
|
||||||
|
- **LEKZPRAVY** — lékařské zprávy (TYP=30, 13 878 záznamů)
|
||||||
|
- **SPECVYS** — specializované výsledky (TYP=17, 746 záznamů)
|
||||||
|
- **HISTDOC_REP** — repliky histdoc (TYP=3, pouze 3 záznamy)
|
||||||
|
- **FILES** — přílohy/PDF (různé typy — TYP=1 i TYP=24)
|
||||||
|
|
||||||
|
### Aktivita po měsících (2025–2026)
|
||||||
|
|
||||||
|
| Měsíc | Záznamů |
|
||||||
|
|---|---|
|
||||||
|
| 2025-01 | 3 117 |
|
||||||
|
| 2025-02 | 2 812 |
|
||||||
|
| 2025-03 | 4 433 |
|
||||||
|
| 2025-04 | 3 881 |
|
||||||
|
| 2025-05 | 4 097 |
|
||||||
|
| 2025-06 | 3 320 |
|
||||||
|
| 2025-07 | 3 644 |
|
||||||
|
| 2025-08 | 2 801 |
|
||||||
|
| 2025-09 | 4 053 |
|
||||||
|
| 2025-10 | 4 506 |
|
||||||
|
| 2025-11 | 4 023 |
|
||||||
|
| 2025-12 | 3 619 |
|
||||||
|
| 2026-01 | 4 133 |
|
||||||
|
| 2026-02 | 3 473 |
|
||||||
|
| 2026-03 | 4 438 |
|
||||||
|
| 2026-04 | 3 770 |
|
||||||
|
| 2026-05 | 1 001 *(do 17.5.)* |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OCKZAZ – záznamy podaných vakcín
|
||||||
|
|
||||||
|
**9 746 záznamů** — každá aplikace vakcíny jednomu pacientovi.
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| DATUM | datum aplikace |
|
||||||
|
| ZKRATKA | kód SÚKL vakcíny (7 znaků) |
|
||||||
|
| LATKA | obchodní název (např. 'FSME-IMMUN 0,5 ML') |
|
||||||
|
| NAZEV | název onemocnění/typ (např. 'Klíšťová encefalitida') |
|
||||||
|
| KODMZ | kód MZ (např. 'KENC', 'TETP', 'HEAB') |
|
||||||
|
| IDUZI | kdo aplikoval |
|
||||||
|
| EXPIRE | expirace vakcíny |
|
||||||
|
| PRISTE | plánovaná příští dávka |
|
||||||
|
| PRISTE_OD / PRISTE_DO | interval pro příští dávku |
|
||||||
|
| EOCK | příznak e-očkování |
|
||||||
|
| CHYBA | chyba při e-podání |
|
||||||
|
|
||||||
|
Nejčastější vakcíny (z ukázky): Klíšťová encefalitida (FSME-IMMUN), Tetanus (VACTETA), Hepatitida A+B (TWINRIX).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## NES – neschopenky (pracovní neschopnosti)
|
||||||
|
|
||||||
|
**4 318 záznamů** (od 2008-02-02), 4 stornované. Datum KONNES může být NULL (dosud trvá).
|
||||||
|
Výskyt data "2109-01-28" v MAX — patrně technické datum "dosud trvá" = 2109.
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| ZACNES | začátek neschopnosti |
|
||||||
|
| KONNES | konec neschopnosti (NULL = stále trvá) |
|
||||||
|
| CISNES | číslo neschopenky |
|
||||||
|
| DIAGNO | diagnóza |
|
||||||
|
| IDUZI | který lékař |
|
||||||
|
| STORNO | 'F' = platná, 'T' = stornovaná |
|
||||||
|
| STORNO | příznak storna |
|
||||||
|
| ECN | číslo e-neschopenky |
|
||||||
|
| EPODANI | stav e-podání |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MEDIKACE – aktuální medikace
|
||||||
|
|
||||||
|
**252 záznamů** — záznamy o lécích předepsaných jako dlouhodobá/trvalá medikace (ne každý recept, jen ty označené jako medikace).
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| ID | primární klíč |
|
||||||
|
| IDPAC | FK → KAR |
|
||||||
|
| IDUZI | kdo zaznamenal |
|
||||||
|
| DATUM | datum záznamu |
|
||||||
|
| PLATI_OD | platnost od |
|
||||||
|
| PLATI_DO | platnost do (NULL = dosud) |
|
||||||
|
| KOD | kód SÚKL léku |
|
||||||
|
| NAZ | název léku |
|
||||||
|
| UHRADA | způsob úhrady ('C' = plně hrazeno, 'J' = částečně) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ICP / ICZ – pracoviště a zdravotnické zařízení
|
||||||
|
|
||||||
|
**11 záznamů** v obou tabulkách — identifikátory ordinace pro různé pojišťovny.
|
||||||
|
|
||||||
|
### ICP (pracoviště)
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| IDICP | primární klíč |
|
||||||
|
| IDICZ | FK → ICZ |
|
||||||
|
| NAZEV | název ('Ordinace', 'Smluvní pracoviště…') |
|
||||||
|
| ICP | IČP = 09305001 (naše ordinace) |
|
||||||
|
| ODB | odbornost = 001 (praktický lékař) |
|
||||||
|
| DPECE | druh péče |
|
||||||
|
|
||||||
|
Poznámka: Všech 11 záznamů má stejný ICP 09305001 a ODB 001 — liší se jen IDICP/IDICZ (pro různé pojišťovny).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schéma vazeb (rozšířené)
|
||||||
|
|
||||||
|
```
|
||||||
|
KAR (kartotéka)
|
||||||
|
├──IDPAC──► DEKURS (záznamy z návštěv, RTF)
|
||||||
|
├──IDPAC──► RECEPT (e-recepty)
|
||||||
|
├──IDPAC──► MEDIKACE (trvalá medikace)
|
||||||
|
├──IDPAC──► NES (neschopenky)
|
||||||
|
├──IDPAC──► OCKZAZ (podané vakcíny)
|
||||||
|
├──IDPAC──► OCKPRI (plánovaná očkování)
|
||||||
|
├──IDPAC──► PREH (měření: tlak, váha, výška…)
|
||||||
|
├──IDPAC──► FILES (přílohy/PDF, v ext. FDB)
|
||||||
|
├──IDPAC──► HISTDOC (poukazy, posudky, žádanky)
|
||||||
|
├──IDPAC──► REGISTR (registrace u pojišťovny)
|
||||||
|
├──IDPAC──► KARUZIV (ošetřující lékař)
|
||||||
|
├──RODCIS──► DOKLADH ──► DOKLADD (výkony – záhlaví/detail)
|
||||||
|
└──RODCIS──► LECH ──► LECD (lékové doklady)
|
||||||
|
|
||||||
|
FAK (faktury)
|
||||||
|
├──IDFAK──► FAKDET (detail per lékař)
|
||||||
|
├──IDFAK──► FAKDAV (dávky ve faktuře)
|
||||||
|
└──IDFAK──► PORTAL (e-podání – výkonové dávky DP98)
|
||||||
|
|
||||||
|
VZPARC (výkonové dávky – klíčové pro hledání výkonů per pacient)
|
||||||
|
PREINIH (typy prohlídek: 24=Dekurs, 25=Výška/váha, 26=Tlak/puls…)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Poznámky k průzkumu
|
||||||
|
|
||||||
|
### RECEPT vs LECD – kdy co použít
|
||||||
|
- **RECEPT** = všechny recepty vystavené pacientovi (191 820 záznamů) — pro analýzu předepisování
|
||||||
|
- **LECD** = léky vykázané pojišťovně (4 413 záznamů) — pro fakturaci
|
||||||
|
|
||||||
|
### DOKLADD vs VZPARC – kdy co použít
|
||||||
|
- **DOKLADD** = výkony v DB (98 321), vč. nevykázaných — pro analýzu péče
|
||||||
|
- **VZPARC** = odeslaná výkonová data pojišťovně (CP852 kódování!) — pro fakturaci, VŽDY používat pro hledání PP
|
||||||
|
|
||||||
|
### PREH – víc záznamů na jednu návštěvu
|
||||||
|
Jedna návštěva = typicky 2 záznamy v PREH: IDPREINI=25 (výška+váha) + IDPREINI=26 (tlak+puls).
|
||||||
|
|
||||||
|
### NES – datum 2109
|
||||||
|
MAX(KONNES) = 2109-01-28 — Medicus ukládá "dosud trvající" neschopenky s datem daleko v budoucnosti. Při filtrování "aktuálních" NES používat: `KONNES IS NULL OR KONNES > dnes`.
|
||||||
|
|
||||||
|
### HISTDOC – nejpoužívanější typy
|
||||||
|
POUV_1 a POUV_3 dominují (celkem 22 140 = 68 % všech dokumentů) — jsou to poukazy na odborná vyšetření.
|
||||||
|
|
||||||
|
### ORTOP vs ORTOPE – poukazy na ZP (zdravotnické prostředky)
|
||||||
|
|
||||||
|
- **ORTOP** = papírový ortopedický poukaz — data do konce 2021
|
||||||
|
- **ORTOPE** = elektronický poukaz na ZP — od 2022 (891 → 2022: 12, 2023: 220, 2024: 287, 2025: 288, 2026: 113 k 5/2026)
|
||||||
|
|
||||||
|
Detaily ZP nejsou v samostatné tabulce — jsou uloženy v **HISTDOC.DATA jako blob ve formátu key=value**:
|
||||||
|
|
||||||
|
```
|
||||||
|
ICZ=09305000
|
||||||
|
ICP=09305001
|
||||||
|
POJ=I:111
|
||||||
|
Dgn=N394
|
||||||
|
Kod=5009790
|
||||||
|
Nazev=VLOŽKY ABSORPČNÍ MOLICARE LADY 4,5 KAPKY
|
||||||
|
Pocet=C:4.00
|
||||||
|
Cena=C:418.40
|
||||||
|
Dopl=C:73.84
|
||||||
|
DoplnekNazvu=934ML,14KS
|
||||||
|
DatPlat=D:17.06.2026
|
||||||
|
Notifikace=736734018
|
||||||
|
Stav=PREDEPSANY
|
||||||
|
Omezeni=GER, GYN, PED, NEF, NEU, PRL, SLO, CHI, URN, SDP
|
||||||
|
UHS=02.01.01.01
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Typové prefixy hodnot: `C:` = decimal, `I:` = integer, `D:` = datum (DD.MM.YYYY), `$:~\b` = prázdné.
|
||||||
|
|
||||||
|
**Napojení na pacienta a lékaře:**
|
||||||
|
```sql
|
||||||
|
SELECT h.DATUM, k.PRIJMENI, k.JMENO, k.RODCIS, u.PRIJMENI AS LEKAR,
|
||||||
|
CAST(h.DATA AS VARCHAR(8000)) AS DATA_TEXT
|
||||||
|
FROM HISTDOC h
|
||||||
|
JOIN KAR k ON h.IDPACI = k.IDPAC
|
||||||
|
JOIN UZIVATEL u ON h.IDUZIV = u.IDUZI
|
||||||
|
WHERE h.TYP = 'ORTOPE'
|
||||||
|
ORDER BY h.DATUM
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sloupec Doklad** (alfanumerický kód v Medicus UI jako "QVNZEGR2B") — je v tabulce **HISTDOC_EPOUKAZ.ID_DOKLADU**. JOIN přes `IDHISTDOC = HISTDOC.ID`.
|
||||||
|
|
||||||
|
### HISTDOC_EPOUKAZ — e-podání poukazu pojišťovně
|
||||||
|
|
||||||
|
Vznikne až při odeslání poukazu elektronicky. Analogie k RECEPT_EPODANI.
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| IDHISTDOC | FK → HISTDOC.ID |
|
||||||
|
| ID_DOKLADU | kód Dokladu z Medicus UI (9 znaků, např. `MGAS9PQWS`) |
|
||||||
|
| ID_ZP | UUID přidělený pojišťovnou |
|
||||||
|
| ODESLANO | datetime odeslání |
|
||||||
|
| VYDANO | datetime vydání pojišťovnou (NULL = zatím čeká) |
|
||||||
|
| CHYBA | `T` = chyba při podání, `F` = OK |
|
||||||
|
| SCHVALENI | `N` = neschváleno, `A` = schváleno |
|
||||||
|
| ID_DOKLADU_VYDEJ | kód při vydání (NULL dokud nevydáno) |
|
||||||
|
|
||||||
|
Pozor: LEFT JOIN — poukaz může existovat bez záznamu v HISTDOC_EPOUKAZ (neodeslané).
|
||||||
|
|
||||||
|
Tabulka EPOUKAZ (bez prefixu HISTDOC) — **prázdná, nepoužívaná**.
|
||||||
|
|
||||||
|
**Skript pro export:** `Medicus/MedicusWithClaudeZP/prehled_zp.py` → výstup `Přehled_ZP.xlsx`
|
||||||
|
- List 1 „Všechny záznamy" — vše včetně Doklad, Odesláno, Vydáno, Chyba, ID ZP
|
||||||
|
- List 2 „Přehled Medicus" — sloupce shodné s Medicus UI (Datum, Kód, Název, Počet, Cena, Pojišťovna, IČZ, RC, Příjmení, Jméno, Vystavil, Doklad)
|
||||||
|
|
||||||
|
### HISTDOC + HISTDOCVYK – předepsané PZT (zdravotnické prostředky)
|
||||||
|
Dvojice tabulek pro poukazy na ZP (analogie DOKLADH/DOKLADD):
|
||||||
|
- **HISTDOC** = záhlaví poukazu (IDPACI, DATUM, TYP)
|
||||||
|
- **HISTDOCVYK** = detail položky (KOD → PZT.KOD, POCET, CENA, FK přes `ID`)
|
||||||
|
|
||||||
|
Typy poukazů v HISTDOC: `POUV_1`, `POUV_3`, `ORTOP` (papírový), `ORTOPE` (e-poukaz). Od roku 2026 se ORTOP nepoužívá — vše přes ORTOPE. E-podání sleduje HISTDOC_EPOUKAZ (obsahuje `ID_ZP` = UUID z pojišťovny, `ODESLANO`, `VYDANO`).
|
||||||
|
|
||||||
|
Kategorie předepisovaného ZP (zjištěno z dat 2026): inkontinenční pomůcky (Tena, Molicare, Depend), kompresní punčochy (Maxis, Avicenum), stomické pomůcky (Aurum, Comfeel, Welland), mobilní pomůcky (chodítka, nástavce WC).
|
||||||
|
|
||||||
|
JOIN pro report: `HISTDOC h JOIN HISTDOCVYK v ON v.ID = h.ID JOIN PZT p ON p.KOD = v.KOD WHERE h.TYP IN ('POUV_1','POUV_3','ORTOP','ORTOPE')`
|
||||||
|
|
||||||
|
Poznámka: PZT číselník má historické záznamy — při joinu bez filtru platnosti může vrátit více názvů pro jeden kód (použít `MAX(p.NAZ)` nebo filtrovat `p.PLATIDO >= aktuální_datum`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TODO pro Claude Code
|
||||||
|
|
||||||
|
### 1. Report předepsaných poukazů a ZP
|
||||||
|
|
||||||
|
Naprogramovat periodický report (Python skript nebo HTML) předepsaných zdravotnických prostředků a poukazů z Medicus DB.
|
||||||
|
|
||||||
|
**Zdroj dat:**
|
||||||
|
```sql
|
||||||
|
SELECT h.DATUM, k.JMENO || ' ' || k.PRIJMENI AS PACIENT, h.TYP AS TYP_POUKAZU,
|
||||||
|
v.KOD, MAX(p.NAZ) AS NAZEV_ZP, v.POCET, v.CENA
|
||||||
|
FROM HISTDOC h
|
||||||
|
JOIN KAR k ON k.IDKAR = h.IDPACI
|
||||||
|
JOIN HISTDOCVYK v ON v.ID = h.ID
|
||||||
|
JOIN PZT p ON p.KOD = v.KOD
|
||||||
|
WHERE h.TYP IN ('POUV_1', 'POUV_3', 'ORTOP', 'ORTOPE')
|
||||||
|
AND h.DATUM BETWEEN :datum_od AND :datum_do
|
||||||
|
GROUP BY h.DATUM, k.JMENO, k.PRIJMENI, h.TYP, v.KOD, v.POCET, v.CENA
|
||||||
|
ORDER BY h.DATUM DESC
|
||||||
|
```
|
||||||
|
|
||||||
|
**Požadavky na report:**
|
||||||
|
- Parametry: datum od/do (default = aktuální měsíc)
|
||||||
|
- Seskupení: volitelně podle typu poukazu nebo kategorie ZP
|
||||||
|
- Součty: celková cena za období, počet poukazů per pacient
|
||||||
|
- Formát výstupu: Excel (.xlsx) nebo HTML s tiskem
|
||||||
|
- Volitelně: srovnání s předchozím obdobím (měsíc/rok)
|
||||||
|
- Připojení k DB přes MCP medicus-firebird nebo přímý Firebird driver (fdb / firebird-driver)
|
||||||
|
|
||||||
|
**Kontext:**
|
||||||
|
- Tabulka KAR: IDKAR = primární klíč, JMENO + PRIJMENI = jméno pacienta
|
||||||
|
- PZT číselník má historické záznamy — JOIN bezpečně přes `MAX(p.NAZ)` nebo `GROUP BY`
|
||||||
|
- Sloupec HISTDOC.IDPACI = FK → KAR.IDKAR
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MCP nástroje — přehled (stav 2026-05-19)
|
||||||
|
|
||||||
|
### Původní nástroje
|
||||||
|
| Nástroj | Popis |
|
||||||
|
|---|---|
|
||||||
|
| `execute_query(sql, params?)` | Libovolný SQL dotaz (SELECT/INSERT/UPDATE/DELETE) |
|
||||||
|
| `list_tables()` | Seznam všech 993 tabulek |
|
||||||
|
| `get_table_columns(table)` | Seznam sloupců tabulky (jen názvy) |
|
||||||
|
| `get_schema()` | Kompletní schéma — všechny tabulky + sloupce |
|
||||||
|
|
||||||
|
### Nové nástroje (přidáno 2026-05-19)
|
||||||
|
| Nástroj | Popis |
|
||||||
|
|---|---|
|
||||||
|
| `get_patient(idpac)` | Základní info o pacientovi z KAR — jmeno, prijmeni, rc, datnar, pojistovna |
|
||||||
|
| `search_patients(query)` | Hledání pacienta podle příjmení/jména/RC, max 50 výsledků |
|
||||||
|
| `get_patient_timeline(idpac, datum_od?, datum_do?)` | Chronologický přehled z DOCLIST — všechny záznamy pacienta |
|
||||||
|
| `parse_histdoc_data(idhistdoc)` | Dekóduje DATA blob z HISTDOC — vrátí dict {Kod, Nazev, Pocet, Cena, Stav, Doklad…} |
|
||||||
|
| `get_table_info(table)` | Rozšířené info o tabulce — typy sloupců, nullable, PK, počet záznamů |
|
||||||
|
| `safe_query(sql, params?)` | SELECT s ochranou — varuje pro velké tabulky bez WHERE, limit 500 řádků |
|
||||||
|
|
||||||
|
### Velké tabulky vyžadující WHERE (safe_query varuje automaticky)
|
||||||
|
LOG, ZURNAL, LABVD, DOCLIST, PZT, LEKY, DEKLINK
|
||||||
|
|
||||||
|
### Ještě neimplementováno
|
||||||
|
- `decode_blob` (RTF → čistý text) pro DEKURS, ANAMNEZA
|
||||||
|
- CP852 dekódování pro VZPARC
|
||||||
+272
-2
@@ -11,11 +11,10 @@ from typing import Optional
|
|||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
FB_CONFIG = {
|
FB_CONFIG = {
|
||||||
'dsn': r'localhost:c:\medicus 3\data\medicus.fdb',
|
'dsn': r'reporter:c:\medicus\medicus.fdb',
|
||||||
'user': 'SYSDBA',
|
'user': 'SYSDBA',
|
||||||
'password': 'masterkey',
|
'password': 'masterkey',
|
||||||
'charset': 'win1250',
|
'charset': 'win1250',
|
||||||
'port': 3070,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Všechny logy MUSÍ jít na stderr — stdout je rezervován pro JSON-RPC
|
# Všechny logy MUSÍ jít na stderr — stdout je rezervován pro JSON-RPC
|
||||||
@@ -149,6 +148,277 @@ def get_schema() -> dict:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ── Velké tabulky kde je nutný WHERE filtr ──────────────────────────────────
|
||||||
|
LARGE_TABLES = {'LOG', 'ZURNAL', 'LABVD', 'DOCLIST', 'PZT', 'LEKY', 'DEKLINK'}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_histdoc_blob(data_text: str) -> dict:
|
||||||
|
"""Interní parser key=value blobu z HISTDOC.DATA."""
|
||||||
|
result = {}
|
||||||
|
if not data_text:
|
||||||
|
return result
|
||||||
|
for line in data_text.replace('\r\n', '\n').replace('\r', '\n').split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if '=' not in line:
|
||||||
|
continue
|
||||||
|
key, _, value = line.partition('=')
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if value.startswith('C:'):
|
||||||
|
try:
|
||||||
|
result[key] = float(value[2:].replace(',', '.'))
|
||||||
|
except Exception:
|
||||||
|
result[key] = value[2:]
|
||||||
|
elif value.startswith('I:'):
|
||||||
|
try:
|
||||||
|
result[key] = int(value[2:])
|
||||||
|
except Exception:
|
||||||
|
result[key] = value[2:]
|
||||||
|
elif value.startswith('D:'):
|
||||||
|
result[key] = value[2:]
|
||||||
|
elif value in ('$:~\b', '$:~\x08', '$:'):
|
||||||
|
result[key] = ''
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_patient(idpac: int) -> dict:
|
||||||
|
"""Vrátí základní info o pacientovi z tabulky KAR podle IDPAC.
|
||||||
|
Výsledek: jmeno, prijmeni, rc, datnar, pojistovna, vyrazen.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
SELECT IDPAC, JMENO, PRIJMENI, RODCIS, DATNAR, POJ, VYRAZEN
|
||||||
|
FROM KAR WHERE IDPAC = ?
|
||||||
|
""", [idpac])
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
return {'error': f'Pacient IDPAC={idpac} nenalezen'}
|
||||||
|
import datetime, decimal
|
||||||
|
def cv(v):
|
||||||
|
if isinstance(v, (datetime.date, datetime.datetime)): return v.isoformat()
|
||||||
|
if isinstance(v, decimal.Decimal): return float(v)
|
||||||
|
return v
|
||||||
|
cols = ['idpac', 'jmeno', 'prijmeni', 'rc', 'datnar', 'pojistovna', 'vyrazen']
|
||||||
|
return dict(zip(cols, [cv(v) for v in row]))
|
||||||
|
except Exception:
|
||||||
|
log(f"get_patient chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def search_patients(query: str) -> list:
|
||||||
|
"""Vyhledá pacienty podle příjmení, jména nebo rodného čísla (částečná shoda).
|
||||||
|
Vrátí max. 50 výsledků: idpac, jmeno, prijmeni, rc, pojistovna.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
q = query.strip().upper()
|
||||||
|
cur.execute("""
|
||||||
|
SELECT FIRST 50 IDPAC, JMENO, PRIJMENI, RODCIS, POJ
|
||||||
|
FROM KAR
|
||||||
|
WHERE UPPER(PRIJMENI) LIKE ? OR UPPER(JMENO) LIKE ? OR RODCIS LIKE ?
|
||||||
|
ORDER BY PRIJMENI, JMENO
|
||||||
|
""", [f'%{q}%', f'%{q}%', f'%{q}%'])
|
||||||
|
rows = cur.fetchall()
|
||||||
|
return [
|
||||||
|
{'idpac': r[0], 'jmeno': r[1], 'prijmeni': r[2], 'rc': r[3], 'pojistovna': r[4]}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
except Exception:
|
||||||
|
log(f"search_patients chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_patient_timeline(idpac: int, datum_od: Optional[str] = None, datum_do: Optional[str] = None) -> dict:
|
||||||
|
"""Chronologický přehled všech záznamů pacienta z DOCLIST.
|
||||||
|
datum_od / datum_do ve formátu YYYY-MM-DD (volitelné).
|
||||||
|
Vrátí: pacient (jmeno, prijmeni, rc) + seznam událostí (datum, tabulka, popis).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
# Info o pacientovi
|
||||||
|
cur.execute("SELECT JMENO, PRIJMENI, RODCIS FROM KAR WHERE IDPAC = ?", [idpac])
|
||||||
|
pac = cur.fetchone()
|
||||||
|
if not pac:
|
||||||
|
return {'error': f'Pacient IDPAC={idpac} nenalezen'}
|
||||||
|
|
||||||
|
# Timeline z DOCLIST
|
||||||
|
sql = "SELECT DATUM, TABULKA, TYP, POPIS FROM DOCLIST WHERE IDPAC = ?"
|
||||||
|
params = [idpac]
|
||||||
|
if datum_od:
|
||||||
|
sql += " AND DATUM >= ?"
|
||||||
|
params.append(datum_od)
|
||||||
|
if datum_do:
|
||||||
|
sql += " AND DATUM <= ?"
|
||||||
|
params.append(datum_do)
|
||||||
|
sql += " ORDER BY DATUM, ID"
|
||||||
|
|
||||||
|
cur.execute(sql, params)
|
||||||
|
import datetime, decimal
|
||||||
|
def cv(v):
|
||||||
|
if isinstance(v, (datetime.date, datetime.datetime)): return v.isoformat()
|
||||||
|
return v
|
||||||
|
|
||||||
|
events = [
|
||||||
|
{'datum': cv(r[0]), 'tabulka': r[1], 'typ': r[2], 'popis': r[3] or ''}
|
||||||
|
for r in cur.fetchall()
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
'pacient': {'jmeno': pac[0], 'prijmeni': pac[1], 'rc': pac[2]},
|
||||||
|
'pocet': len(events),
|
||||||
|
'events': events
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
log(f"get_patient_timeline chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def parse_histdoc_data(idhistdoc: int) -> dict:
|
||||||
|
"""Načte a dekóduje DATA blob z HISTDOC pro daný záznam.
|
||||||
|
Vrátí strukturovaný dict: Kod, Nazev, Pocet, Cena, Poj, Dgn, DatPlat, Stav, Doklad…
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
SELECT h.TYP, h.DATUM, CAST(h.DATA AS VARCHAR(8000)),
|
||||||
|
e.ID_DOKLADU, e.ID_ZP, e.ODESLANO, e.VYDANO, e.CHYBA
|
||||||
|
FROM HISTDOC h
|
||||||
|
LEFT JOIN HISTDOC_EPOUKAZ e ON e.IDHISTDOC = h.ID
|
||||||
|
WHERE h.ID = ?
|
||||||
|
""", [idhistdoc])
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
return {'error': f'HISTDOC ID={idhistdoc} nenalezen'}
|
||||||
|
import datetime
|
||||||
|
def cv(v):
|
||||||
|
if isinstance(v, (datetime.date, datetime.datetime)): return v.isoformat()
|
||||||
|
return v
|
||||||
|
typ, datum, data_text, id_dokladu, id_zp, odeslano, vydano, chyba = row
|
||||||
|
parsed = _parse_histdoc_blob(data_text or '')
|
||||||
|
return {
|
||||||
|
'id': idhistdoc,
|
||||||
|
'typ': typ,
|
||||||
|
'datum': cv(datum),
|
||||||
|
'data': parsed,
|
||||||
|
'epoukaz': {
|
||||||
|
'id_dokladu': id_dokladu,
|
||||||
|
'id_zp': id_zp,
|
||||||
|
'odeslano': cv(odeslano),
|
||||||
|
'vydano': cv(vydano),
|
||||||
|
'chyba': chyba == 'T' if chyba else False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
log(f"parse_histdoc_data chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_table_info(table_name: str) -> dict:
|
||||||
|
"""Vrátí rozšířené info o tabulce: sloupce s datovými typy, nullable, PK + počet záznamů."""
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
TRIM(f.RDB$FIELD_NAME) AS SLOUPEC,
|
||||||
|
CASE ft.RDB$FIELD_TYPE
|
||||||
|
WHEN 7 THEN 'SMALLINT'
|
||||||
|
WHEN 8 THEN 'INTEGER'
|
||||||
|
WHEN 10 THEN 'FLOAT'
|
||||||
|
WHEN 12 THEN 'DATE'
|
||||||
|
WHEN 13 THEN 'TIME'
|
||||||
|
WHEN 14 THEN 'CHAR(' || ft.RDB$FIELD_LENGTH || ')'
|
||||||
|
WHEN 16 THEN 'BIGINT'
|
||||||
|
WHEN 27 THEN 'DOUBLE'
|
||||||
|
WHEN 35 THEN 'TIMESTAMP'
|
||||||
|
WHEN 37 THEN 'VARCHAR(' || ft.RDB$FIELD_LENGTH || ')'
|
||||||
|
WHEN 261 THEN 'BLOB'
|
||||||
|
ELSE 'TYP(' || ft.RDB$FIELD_TYPE || ')'
|
||||||
|
END AS TYP,
|
||||||
|
CASE COALESCE(f.RDB$NULL_FLAG, 0) WHEN 1 THEN 'NOT NULL' ELSE 'NULL' END AS NULLABLE,
|
||||||
|
CASE WHEN pk.RDB$FIELD_NAME IS NOT NULL THEN 'PK' ELSE '' END AS PK
|
||||||
|
FROM RDB$RELATION_FIELDS f
|
||||||
|
JOIN RDB$FIELDS ft ON ft.RDB$FIELD_NAME = f.RDB$FIELD_SOURCE
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT i.RDB$RELATION_NAME, s.RDB$FIELD_NAME
|
||||||
|
FROM RDB$INDICES i
|
||||||
|
JOIN RDB$INDEX_SEGMENTS s ON s.RDB$INDEX_NAME = i.RDB$INDEX_NAME
|
||||||
|
WHERE i.RDB$RELATION_NAME = ?
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM RDB$RELATION_CONSTRAINTS c
|
||||||
|
WHERE c.RDB$INDEX_NAME = i.RDB$INDEX_NAME
|
||||||
|
AND c.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||||
|
)
|
||||||
|
) pk ON pk.RDB$FIELD_NAME = f.RDB$FIELD_NAME
|
||||||
|
WHERE TRIM(f.RDB$RELATION_NAME) = ?
|
||||||
|
ORDER BY f.RDB$FIELD_POSITION
|
||||||
|
""", [table_name.upper(), table_name.upper()])
|
||||||
|
columns = [
|
||||||
|
{'sloupec': r[0], 'typ': r[1], 'nullable': r[2], 'pk': r[3]}
|
||||||
|
for r in cur.fetchall()
|
||||||
|
]
|
||||||
|
# Počet záznamů
|
||||||
|
try:
|
||||||
|
cur.execute(f'SELECT COUNT(*) FROM {table_name.upper()}')
|
||||||
|
count = cur.fetchone()[0]
|
||||||
|
except Exception:
|
||||||
|
count = None
|
||||||
|
return {
|
||||||
|
'tabulka': table_name.upper(),
|
||||||
|
'pocet_zaznamu': count,
|
||||||
|
'sloupce': columns
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
log(f"get_table_info chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def safe_query(sql: str, params: Optional[list] = None) -> dict:
|
||||||
|
"""Bezpečný SELECT s ochranou před timeoutem na velkých tabulkách.
|
||||||
|
Automaticky varuje pokud dotaz míří na LOG, ZURNAL, LABVD, DOCLIST, PZT, LEKY
|
||||||
|
bez WHERE klauzule. Výsledek omezen na 500 řádků.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sql_upper = sql.strip().upper()
|
||||||
|
if not sql_upper.startswith('SELECT'):
|
||||||
|
return {'error': 'safe_query podporuje pouze SELECT dotazy'}
|
||||||
|
|
||||||
|
# Varování pro velké tabulky bez WHERE
|
||||||
|
warnings = []
|
||||||
|
for tbl in LARGE_TABLES:
|
||||||
|
if tbl in sql_upper and 'WHERE' not in sql_upper:
|
||||||
|
warnings.append(f'Tabulka {tbl} má miliony záznamů — přidej WHERE filtr!')
|
||||||
|
|
||||||
|
# Přidej FIRST limit pokud chybí
|
||||||
|
if 'FIRST ' not in sql_upper:
|
||||||
|
sql = sql.strip()
|
||||||
|
if sql.upper().startswith('SELECT'):
|
||||||
|
sql = 'SELECT FIRST 500 ' + sql[6:].lstrip()
|
||||||
|
|
||||||
|
cur = conn.cursor()
|
||||||
|
if params:
|
||||||
|
cur.execute(sql, params)
|
||||||
|
else:
|
||||||
|
cur.execute(sql)
|
||||||
|
|
||||||
|
rows = rows_to_json(cur.fetchall(), cur.description or [])
|
||||||
|
result = {'rowcount': len(rows), 'rows': rows}
|
||||||
|
if warnings:
|
||||||
|
result['warnings'] = warnings
|
||||||
|
return result
|
||||||
|
except Exception:
|
||||||
|
log(f"safe_query chyba: {traceback.format_exc()}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log("MCP Firebird server spuštěn (FastMCP)")
|
log("MCP Firebird server spuštěn (FastMCP)")
|
||||||
mcp.run()
|
mcp.run()
|
||||||
|
|||||||
Reference in New Issue
Block a user