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)
|
||||
Reference in New Issue
Block a user