Files
ordinaceprojekt/Insurance/FinalReconcilliation/seed_tracking.py
T
Vladimir Buzalka 9b6f89f437 notebookvb
2026-06-16 10:21:19 +02:00

244 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
seed_tracking.py
================
Naplní MongoDB databázi `ordinace`, kolekci `registrovani_tracking`, výchozím
snímkem registrovaných pacientů a jejich OVĚŘENÝM stavem u VZP.
Logika "v zakoupeném souboru pacientů":
- "Registrovaný v Medicusu" je jen stav v software.
- Skutečnost ověřujeme u pojišťovny: kdo je k danému dni registrující praktik
(odbornost 001) daného pacienta.
* praktik = Buzalková (IČP 09305001) -> v pořádku, v zakoupeném souboru
* praktik = někdo jiný / žádný -> NEBYL V ZAKOUPENÉM SOUBORU PACIENTŮ
Kategorie (plné podkategorie):
OK_BUZALKOVA praktik 001 je Buzalková (IČP 09305001)
JINY_PRAKTIK praktik 001 je jiné ZZZ
BEZ_PRAKTIKA_VZP pacient má u VZP záznam (jiná odbornost), ale praktika 001 ne
BEZ_ZAZNAMU_VZP VZP nevrátila žádný záznam (typicky jiná pojišťovna / neplatné RČ)
Schéma dokumentu (1 dokument na pacienta, _id = rodné číslo):
{
"_id": "8202...", "rc": "...", "prijmeni": "...", "jmeno": "...",
"pojistovna": {"kod": "111", "zkratka": "VZP"},
"vychozi_datum": "2025-01-01",
"aktualni": { ...snímek... },
"historie": [ { ...snímek..., "zmena": "výchozí snímek" }, ... ],
"created_at": ..., "updated_at": ...
}
Snímek (aktualni i položka historie):
{ "k_datu", "kategorie", "kategorie_popis", "v_zakoupenem_souboru" (bool),
"flag", "praktik_nazev", "praktik_icz", "praktik_icp",
"datum_zahajeni", "datum_ukonceni" }
Spuštění:
python seed_tracking.py # seed k 2025-01-01
python seed_tracking.py 2026-05-02 # aplikuje další snímek (appendne změny do historie)
"""
import sys
from pathlib import Path
from datetime import datetime, date, timezone
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
import pymongo
from Knihovny.mysql_db import connect_mysql
# ── KONFIGURACE ───────────────────────────────────────────────────────────────
MONGO_URI = "mongodb://192.168.1.76:27017"
MONGO_DB = "ordinace"
MONGO_COLL = "registrovani_tracking"
ICP_BUZALKOVA = "09305001"
KATEGORIE_POPIS = {
"OK_BUZALKOVA": "OK praktik je Buzalková (IČP 09305001)",
"JINY_PRAKTIK": "Registrován u jiného praktika",
"BEZ_PRAKTIKA_VZP": "U VZP bez praktika (odb. 001)",
"BEZ_ZAZNAMU_VZP": "VZP nevrátila žádný záznam (jiná pojišťovna / neplatné RČ)",
}
FLAG_MIMO_SOUBOR = "NEBYL V ZAKOUPENÉM SOUBORU PACIENTŮ"
def klasifikuj(praktik_001: dict | None, ma_nejaky_zaznam: bool) -> dict:
"""Vrátí snímek stavu (bez k_datu) na základě 001 záznamu z VZP."""
if praktik_001 and praktik_001.get("ICP") == ICP_BUZALKOVA:
kat = "OK_BUZALKOVA"
elif praktik_001:
kat = "JINY_PRAKTIK"
elif ma_nejaky_zaznam:
kat = "BEZ_PRAKTIKA_VZP"
else:
kat = "BEZ_ZAZNAMU_VZP"
v_souboru = (kat == "OK_BUZALKOVA")
nazev = (praktik_001 or {}).get("nazev_zzz")
icz = (praktik_001 or {}).get("ICZ")
od = (praktik_001 or {}).get("datum_zahajeni")
# Čitelný důvod, proč pacient NENÍ v zakoupeném souboru (kdo + od kdy)
if kat == "JINY_PRAKTIK":
flag_duvod = f"jiný praktik: {nazev} (IČZ {icz}) od {od}"
elif kat == "BEZ_PRAKTIKA_VZP":
flag_duvod = "u VZP bez registrujícího praktika (odb. 001)"
elif kat == "BEZ_ZAZNAMU_VZP":
flag_duvod = "VZP nevrátila žádný záznam (jiná pojišťovna / neplatné RČ / zaniklé pojištění)"
else:
flag_duvod = ""
return {
"kategorie": kat,
"kategorie_popis": KATEGORIE_POPIS[kat],
"v_zakoupenem_souboru": v_souboru,
"flag": "" if v_souboru else FLAG_MIMO_SOUBOR,
"flag_duvod": flag_duvod,
# "kdo" a "od kdy" registrujícího praktika dle VZP
"praktik_nazev": nazev,
"praktik_icz": icz,
"praktik_icp": (praktik_001 or {}).get("ICP"),
"praktik_od": od,
"datum_zahajeni": od,
"datum_ukonceni": (praktik_001 or {}).get("datum_ukonceni"),
}
def nacti_snimek_z_mysql(mysql, k_datu: str) -> dict:
"""
Vrátí {rc: {prijmeni, jmeno, pojistovna{}, praktik_001 | None, ma_zaznam}}
pro populaci registrovaných dotázaných k danému datu.
"""
cur = mysql.cursor()
# Populace = všechna dotázaná RČ (raw) k tomuto datu
cur.execute("SELECT rc FROM vzp_registrace_raw WHERE k_datu = %s", (k_datu,))
populace = [r[0] for r in cur.fetchall()]
# Parsované záznamy lékařů k tomuto datu
cur.execute("""
SELECT rc, prijmeni, jmeno, kod_odbornosti, ICP, ICZ, nazev_zzz,
poj_kod, poj_zkratka, datum_zahajeni, datum_ukonceni
FROM vzp_registrace_lekari
WHERE k_datu = %s
""", (k_datu,))
data: dict[str, dict] = {rc: {"prijmeni": None, "jmeno": None,
"pojistovna": {"kod": None, "zkratka": None},
"praktik_001": None, "ma_zaznam": False}
for rc in populace}
for (rc, prijmeni, jmeno, odb, icp, icz, nazev_zzz,
poj_kod, poj_zkr, dat_zah, dat_uk) in cur.fetchall():
d = data.setdefault(rc, {"prijmeni": None, "jmeno": None,
"pojistovna": {"kod": None, "zkratka": None},
"praktik_001": None, "ma_zaznam": False})
d["ma_zaznam"] = True
if prijmeni and not d["prijmeni"]:
d["prijmeni"] = prijmeni
if jmeno and not d["jmeno"]:
d["jmeno"] = jmeno
# Pojišťovnu vezmi z jakéhokoli záznamu (preferuj 001 níže)
if poj_kod and not d["pojistovna"]["kod"]:
d["pojistovna"] = {"kod": poj_kod, "zkratka": poj_zkr}
if odb == "001":
d["praktik_001"] = {
"ICP": icp, "ICZ": icz, "nazev_zzz": nazev_zzz,
"poj_kod": poj_kod, "poj_zkratka": poj_zkr,
"datum_zahajeni": str(dat_zah) if dat_zah else None,
"datum_ukonceni": str(dat_uk) if dat_uk else None,
}
# Pojišťovna z 001 má přednost
if poj_kod:
d["pojistovna"] = {"kod": poj_kod, "zkratka": poj_zkr}
return data
def apply_snapshot(coll, mysql, k_datu: str) -> dict:
"""
Klasifikuje populaci k danému datu a upsertne do Mongo.
Při změně kategorie/praktika oproti `aktualni` appendne do `historie`.
Vrátí statistiku.
"""
data = nacti_snimek_z_mysql(mysql, k_datu)
now = datetime.now(timezone.utc)
stats = {"novych": 0, "zmen": 0, "beze_zmeny": 0, "kategorie": {}}
for rc, d in data.items():
snimek = klasifikuj(d["praktik_001"], d["ma_zaznam"])
snimek_s_datem = {"k_datu": k_datu, **snimek}
stats["kategorie"][snimek["kategorie"]] = stats["kategorie"].get(snimek["kategorie"], 0) + 1
existing = coll.find_one({"_id": rc})
if existing is None:
doc = {
"_id": rc, "rc": rc,
"prijmeni": d["prijmeni"], "jmeno": d["jmeno"],
"pojistovna": d["pojistovna"],
"vychozi_datum": k_datu,
"aktualni": snimek_s_datem,
"historie": [{**snimek_s_datem, "zmena": "výchozí snímek"}],
"created_at": now, "updated_at": now,
}
coll.insert_one(doc)
stats["novych"] += 1
else:
akt = existing.get("aktualni", {})
zmena = (akt.get("kategorie") != snimek["kategorie"]
or akt.get("praktik_icp") != snimek["praktik_icp"])
update = {"aktualni": snimek_s_datem, "updated_at": now}
if d["prijmeni"]:
update["prijmeni"] = d["prijmeni"]
if d["jmeno"]:
update["jmeno"] = d["jmeno"]
ops = {"$set": update}
if zmena:
popis = (f"{akt.get('kategorie')}{snimek['kategorie']}")
ops["$push"] = {"historie": {**snimek_s_datem, "zmena": popis}}
stats["zmen"] += 1
else:
stats["beze_zmeny"] += 1
coll.update_one({"_id": rc}, ops)
return stats
def main():
k_datu = sys.argv[1] if len(sys.argv) > 1 else "2025-01-01"
mysql = connect_mysql()
client = pymongo.MongoClient(MONGO_URI, serverSelectionTimeoutMS=3000)
client.admin.command("ping")
coll = client[MONGO_DB][MONGO_COLL]
# Indexy pro běžné dotazy
coll.create_index("aktualni.kategorie")
coll.create_index("aktualni.v_zakoupenem_souboru")
coll.create_index("prijmeni")
print(f"Aplikuji snímek k {k_datu} do {MONGO_DB}.{MONGO_COLL} ...")
stats = apply_snapshot(coll, mysql, k_datu)
print(f"\nNových pacientů : {stats['novych']}")
print(f"Změn stavu : {stats['zmen']}")
print(f"Beze změny : {stats['beze_zmeny']}")
print("\nRozpad podle kategorií:")
for kat, n in sorted(stats["kategorie"].items(), key=lambda x: -x[1]):
print(f" {kat:18s} {n:5d} {KATEGORIE_POPIS[kat]}")
celkem = sum(stats["kategorie"].values())
mimo = celkem - stats["kategorie"].get("OK_BUZALKOVA", 0)
print(f"\nCelkem v populaci: {celkem}")
print(f" v zakoupeném souboru (Buzalková): {stats['kategorie'].get('OK_BUZALKOVA', 0)}")
print(f" NEBYL v zakoupeném souboru : {mimo}")
mysql.close()
client.close()
if __name__ == "__main__":
main()