notebook vb

This commit is contained in:
2026-03-25 17:32:12 +01:00
parent 5674de1247
commit 82719baba8
7 changed files with 1086 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
"""
001_pruzkum_PN_tabulek.py
=========================
Průzkum Medicus DB hledáme tabulky a strukturu relacionou s PN
(pracovní neschopnost).
Spustit na Windows:
python "001_pruzkum_PN_tabulek.py"
Výstup: přehled tabulek s PN v názvu + jejich sloupce + ukázka dat.
"""
import fdb
import datetime
# ── Připojení ──────────────────────────────────────────────────────────────────
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA',
password='masterkey',
charset='win1250'
)
cur = conn.cursor()
# ── 1. Všechny tabulky s "PN" v názvu ─────────────────────────────────────────
print("=" * 60)
print("TABULKY obsahující 'PN' v názvu")
print("=" * 60)
cur.execute("""
SELECT TRIM(rdb$relation_name)
FROM rdb$relations
WHERE rdb$view_blr IS NULL
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
AND UPPER(rdb$relation_name) CONTAINING 'PN'
ORDER BY rdb$relation_name
""")
pn_tables = [row[0] for row in cur.fetchall()]
if pn_tables:
for t in pn_tables:
print(f" {t}")
else:
print(" (žádná)")
# ── 2. Sloupce každé PN tabulky + ukázka dat ──────────────────────────────────
for table in pn_tables:
print(f"\n{'' * 60}")
print(f"TABULKA: {table}")
print(f"{'' * 60}")
# Sloupce
cur.execute("""
SELECT TRIM(rf.rdb$field_name),
f.rdb$field_type,
f.rdb$field_length,
rf.rdb$null_flag
FROM rdb$relation_fields rf
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
WHERE TRIM(rf.rdb$relation_name) = ?
ORDER BY rf.rdb$field_position
""", (table,))
cols = cur.fetchall()
# field_type kódy Firebirdu (nejběžnější)
type_map = {
7: 'SMALLINT', 8: 'INTEGER', 10: 'FLOAT', 12: 'DATE',
13: 'TIME', 14: 'CHAR', 16: 'BIGINT', 27: 'DOUBLE',
35: 'TIMESTAMP', 37: 'VARCHAR', 261: 'BLOB',
}
col_names = []
for c in cols:
name, ftype, flen, nullable = c
type_str = type_map.get(ftype, f'type#{ftype}')
if flen and ftype in (14, 37):
type_str += f'({flen})'
null_str = '' if nullable else ' NOT NULL'
print(f" {name:<25} {type_str}{null_str}")
col_names.append(name)
# Počet řádků
try:
cur.execute(f'SELECT COUNT(*) FROM {table}')
pocet = cur.fetchone()[0]
print(f"\n --> Celkem řádků: {pocet}")
except Exception as e:
print(f"\n --> COUNT selhalo: {e}")
continue
# Ukázka prvních 5 řádků
if pocet > 0:
try:
cur.execute(f'SELECT FIRST 5 * FROM {table}')
rows = cur.fetchall()
print(f"\n Ukázka (max 5 řádků):")
header = " | ".join(f"{n[:12]:>12}" for n in col_names)
print(f" {header}")
print(f" {'-' * len(header)}")
for row in rows:
vals = []
for v in row:
if isinstance(v, (datetime.date, datetime.datetime)):
vals.append(v.isoformat()[:12])
elif isinstance(v, bytes):
vals.append(f'<blob {len(v)}B>')
elif v is None:
vals.append('NULL')
else:
vals.append(str(v)[:12])
print(f" {' | '.join(f'{v:>12}' for v in vals)}")
except Exception as e:
print(f" Ukázka selhala: {e}")
# ── 3. Hledáme i tabulky s "NESCHOP" nebo "NESCHOPENK" ────────────────────────
print(f"\n{'=' * 60}")
print("TABULKY s 'NESCHOP' nebo 'PRACOV' nebo 'SICK' v názvu")
print("=" * 60)
cur.execute("""
SELECT TRIM(rdb$relation_name)
FROM rdb$relations
WHERE rdb$view_blr IS NULL
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
AND (
UPPER(rdb$relation_name) CONTAINING 'NESCHOP'
OR UPPER(rdb$relation_name) CONTAINING 'PRACOV'
OR UPPER(rdb$relation_name) CONTAINING 'SICK'
)
ORDER BY rdb$relation_name
""")
extra = [row[0] for row in cur.fetchall()]
if extra:
for t in extra:
print(f" {t}")
else:
print(" (žádná)")
# ── 4. Rychlý přehled tabulky s datem "OD/DO" nebo "ZACATEK/KONEC" ──────────
print(f"\n{'=' * 60}")
print("SLOUPCE: hledáme 'DATPN', 'DATDO', 'DATUM_DO' v celé DB")
print("=" * 60)
cur.execute("""
SELECT TRIM(rf.rdb$relation_name), TRIM(rf.rdb$field_name)
FROM rdb$relation_fields rf
JOIN rdb$relations r ON rf.rdb$relation_name = r.rdb$relation_name
WHERE (r.rdb$system_flag IS NULL OR r.rdb$system_flag = 0)
AND r.rdb$view_blr IS NULL
AND (
UPPER(rf.rdb$field_name) CONTAINING 'DATPN'
OR UPPER(rf.rdb$field_name) = 'DATDO'
OR UPPER(rf.rdb$field_name) CONTAINING 'DATUM_DO'
OR UPPER(rf.rdb$field_name) CONTAINING 'DAT_DO'
OR UPPER(rf.rdb$field_name) CONTAINING 'PN'
)
ORDER BY rf.rdb$relation_name, rf.rdb$field_name
""")
hits = cur.fetchall()
if hits:
for tab, col in hits:
print(f" {tab:<30} {col}")
else:
print(" (nic nenalezeno)")
cur.close()
conn.close()
print(f"\n{'=' * 60}")
print("Průzkum dokončen.")
+184
View File
@@ -0,0 +1,184 @@
"""
002_detail_NES_NP_tabulky.py
============================
Průzkum klíčových tabulek neschopenky:
- NES → hlavní tabulka pracovní neschopnosti
- NP → pravděpodobně neschopenka podání
- SOC_NEPRITOMNOST → sociální nepřítomnost
Spustit na Windows:
python "002_detail_NES_NP_tabulky.py"
"""
import fdb
import datetime
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA',
password='masterkey',
charset='win1250'
)
cur = conn.cursor()
def serialize(v):
if isinstance(v, (datetime.date, datetime.datetime)):
return v.isoformat()
if isinstance(v, datetime.time):
return v.isoformat()
if isinstance(v, bytes):
# Pro BLOB zkusíme dekódovat jako text
try:
return v.decode('cp1250')[:80]
except Exception:
return f'<blob {len(v)}B>'
if v is None:
return 'NULL'
return str(v)
def inspect_table(table):
print(f"\n{'=' * 70}")
print(f" TABULKA: {table}")
print(f"{'=' * 70}")
# Sloupce
cur.execute("""
SELECT TRIM(rf.rdb$field_name),
f.rdb$field_type,
f.rdb$field_length
FROM rdb$relation_fields rf
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
WHERE TRIM(rf.rdb$relation_name) = ?
ORDER BY rf.rdb$field_position
""", (table,))
cols = cur.fetchall()
type_map = {
7: 'SMALLINT', 8: 'INTEGER', 10: 'FLOAT', 12: 'DATE',
13: 'TIME', 14: 'CHAR', 16: 'BIGINT', 27: 'DOUBLE',
35: 'TIMESTAMP', 37: 'VARCHAR', 261: 'BLOB',
}
col_names = [c[0] for c in cols]
print("\nSloupce:")
for c in cols:
name, ftype, flen = c
type_str = type_map.get(ftype, f'type#{ftype}')
if flen and ftype in (14, 37):
type_str += f'({flen})'
print(f" {name:<30} {type_str}")
# Počet řádků
cur.execute(f'SELECT COUNT(*) FROM {table}')
pocet = cur.fetchone()[0]
print(f"\nCelkem řádků: {pocet}")
if pocet == 0:
return col_names
# Ukázka 5 nejnovějších (pokud má datum sloupec)
date_cols = [c[0] for c in cols if c[1] in (12, 35)] # DATE nebo TIMESTAMP
order = f'ORDER BY {date_cols[0]} DESC' if date_cols else ''
try:
cur.execute(f'SELECT FIRST 5 * FROM {table} {order}')
rows = cur.fetchall()
print(f"\nUkázka (5 {'nejnovějších' if order else 'prvních'}):")
for row in rows:
print()
for name, val in zip(col_names, row):
s = serialize(val)
if s != 'NULL':
print(f" {name:<30} {s}")
except Exception as e:
print(f" Ukázka selhala: {e}")
return col_names
# ── Prozkoumáme klíčové tabulky ───────────────────────────────────────────────
for tbl in ['NES', 'NP', 'SOC_NEPRITOMNOST']:
inspect_table(tbl)
# ── Speciálně: aktivní PN ─────────────────────────────────────────────────────
print(f"\n\n{'=' * 70}")
print(" POKUS: aktivní záznamy v NES (konec NULL nebo v budoucnosti)")
print(f"{'=' * 70}")
dnes = datetime.date.today()
# Nejdřív zjistíme sloupce NES
cur.execute("""
SELECT TRIM(rf.rdb$field_name), f.rdb$field_type
FROM rdb$relation_fields rf
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
WHERE TRIM(rf.rdb$relation_name) = 'NES'
ORDER BY rf.rdb$field_position
""")
nes_cols = [(r[0], r[1]) for r in cur.fetchall()]
nes_col_names = [c[0] for c in nes_cols]
print(f"\nVšechny sloupce NES: {', '.join(nes_col_names)}")
# Hledáme sloupce s datumem konce PN
date_cols_nes = [c[0] for c in nes_cols if c[1] in (12, 35)]
print(f"Datumové sloupce: {date_cols_nes}")
# Zkusíme různé kandidátní sloupce pro "datum do"
candidates_do = [c for c in nes_col_names if 'DO' in c or 'KONEC' in c or 'END' in c.upper()]
candidates_od = [c for c in nes_col_names if 'OD' in c or 'ZACATEK' in c or 'VZNIK' in c or 'START' in c.upper()]
candidates_pac = [c for c in nes_col_names if 'PAC' in c or 'IDPAC' in c]
print(f"Kandidáti DATUM_DO: {candidates_do}")
print(f"Kandidáti DATUM_OD: {candidates_od}")
print(f"Kandidáti IDPAC: {candidates_pac}")
# Ukázka posledních 10 záznamů NES seřazená dle prvního datumového sloupce
if date_cols_nes:
try:
cur.execute(f'SELECT FIRST 10 * FROM NES ORDER BY {date_cols_nes[0]} DESC')
rows = cur.fetchall()
print(f"\nPosledních 10 záznamů NES (dle {date_cols_nes[0]} DESC):")
for row in rows:
print()
for name, val in zip(nes_col_names, row):
s = serialize(val)
if s != 'NULL':
print(f" {name:<30} {s}")
except Exception as e:
print(f"Selhalo: {e}")
# ── NP tabulka stejný rozbor ────────────────────────────────────────────────
print(f"\n\n{'=' * 70}")
print(" POKUS: aktivní záznamy v NP")
print(f"{'=' * 70}")
cur.execute("""
SELECT TRIM(rf.rdb$field_name), f.rdb$field_type
FROM rdb$relation_fields rf
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
WHERE TRIM(rf.rdb$relation_name) = 'NP'
ORDER BY rf.rdb$field_position
""")
np_cols = [(r[0], r[1]) for r in cur.fetchall()]
np_col_names = [c[0] for c in np_cols]
print(f"\nVšechny sloupce NP: {', '.join(np_col_names)}")
date_cols_np = [c[0] for c in np_cols if c[1] in (12, 35)]
print(f"Datumové sloupce: {date_cols_np}")
if date_cols_np:
try:
cur.execute(f'SELECT FIRST 10 * FROM NP ORDER BY {date_cols_np[0]} DESC')
rows = cur.fetchall()
print(f"\nPosledních 10 záznamů NP (dle {date_cols_np[0]} DESC):")
for row in rows:
print()
for name, val in zip(np_col_names, row):
s = serialize(val)
if s != 'NULL':
print(f" {name:<30} {s}")
except Exception as e:
print(f"Selhalo: {e}")
cur.close()
conn.close()
print(f"\n{'=' * 70}")
print("Hotovo.")
+208
View File
@@ -0,0 +1,208 @@
"""
003_aktivni_PN_seznam.py
========================
Vypíše seznam pacientů s aktivní pracovní neschopností (PN) k dnešnímu datu.
SQL logika převzata přímo z Medicusu (report "Přehled práce neschopných pacientů"):
- ZACNES <= dnes
- (KONNES >= dnes) OR (KONNES IS NULL)
- PRACNE = 'A'
- STORNO <> 'T'
Spouštění: tlačítkem z Medicusu nebo přímo pythonu.
"""
import sys, os, traceback
_LOG = r"C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\003_error.log"
def _log_exception(exc_type, exc_value, exc_tb):
with open(_LOG, "w", encoding="utf-8") as f:
traceback.print_exception(exc_type, exc_value, exc_tb, file=f)
sys.excepthook = _log_exception
import fdb
import datetime
import tkinter as tk
from tkinter import ttk
# ── Připojení ─────────────────────────────────────────────────────────────────
DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
USER = 'SYSDBA'
PASSWORD = 'masterkey'
CHARSET = 'win1250'
# ── SQL ───────────────────────────────────────────────────────────────────────
SQL = """
SELECT
kar.PRIJMENI,
kar.JMENO,
kar.RODCIS,
nes.ZACNES,
nes.KONNES,
nes.DIAGNO,
COALESCE(nes.ECN, nes.CISNES) AS CISNES
FROM NES nes
JOIN KAR kar ON kar.IDPAC = nes.IDPAC
WHERE
nes.ZACNES <= ?
AND (nes.KONNES >= ? OR nes.KONNES IS NULL)
AND nes.PRACNE = 'A'
AND nes.STORNO <> 'T'
ORDER BY kar.PRIJMENI, kar.JMENO
"""
def nacti_data():
dnes = datetime.date.today()
conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET)
try:
cur = conn.cursor()
cur.execute(SQL, (dnes, dnes))
rows = cur.fetchall()
return dnes, rows
finally:
conn.close()
def delka_PN(zacnes, konnes, dnes):
"""Počet dní PN (od začátku do dnes nebo do konce)."""
do = konnes if konnes else dnes
return (do - zacnes).days + 1
def zobraz_okno(dnes, rows):
root = tk.Tk()
root.title(f"Aktivní pracovní neschopnost {dnes.strftime('%d.%m.%Y')}")
w, h = 900, 550
root.update_idletasks()
sw = root.winfo_screenwidth()
sh = root.winfo_screenheight()
x = (sw - w) // 2
y = (sh - h) // 2
root.geometry(f"{w}x{h}+{x}+{y}")
# ── Záhlaví ───────────────────────────────────────────────────────────────
hlavicka = tk.Frame(root, bg="#2c5f8a", pady=8)
hlavicka.pack(fill=tk.X)
tk.Label(
hlavicka,
text=f"Pracovní neschopnost aktivní k {dnes.strftime('%d.%m.%Y')} ({len(rows)} pacientů)",
font=("Segoe UI", 13, "bold"),
fg="white", bg="#2c5f8a"
).pack()
# ── Tabulka ───────────────────────────────────────────────────────────────
cols = ("Příjmení", "Jméno", "Rodné číslo", "Začátek", "Konec", "Dní", "Diagnóza", "Č. neschopenky")
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
scroll_y = tk.Scrollbar(frame, orient=tk.VERTICAL)
scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
scroll_x = tk.Scrollbar(frame, orient=tk.HORIZONTAL)
scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
tree = ttk.Treeview(
frame,
columns=cols,
show="headings",
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set
)
scroll_y.config(command=tree.yview)
scroll_x.config(command=tree.xview)
# Šířky sloupců
widths = [120, 100, 110, 85, 85, 50, 80, 150]
for col, w in zip(cols, widths):
tree.column(col, width=w, anchor=tk.W)
# ── Řazení kliknutím na záhlaví ───────────────────────────────────────────
_sort_state = {} # col -> ascending True/False
def sort_by(col):
ascending = not _sort_state.get(col, False)
_sort_state[col] = ascending
# Index sloupce pro čtení hodnoty
col_idx = cols.index(col)
data = [(tree.set(k, col), k) for k in tree.get_children("")]
# Číselné řazení pro "Dní"
if col == "Dní":
data.sort(key=lambda t: int(t[0]) if t[0].isdigit() else 0, reverse=not ascending)
else:
data.sort(key=lambda t: t[0].lower(), reverse=not ascending)
for idx, (_, k) in enumerate(data):
tree.move(k, "", idx)
# Šipka v záhlaví
for c in cols:
arrow = ("" if ascending else "") if c == col else ""
tree.heading(c, text=c + arrow, command=lambda c=c: sort_by(c))
# Obarvi znovu střídavě
for idx, (_, k) in enumerate(data):
current_tags = list(tree.item(k, "tags"))
dlouha = "dlouha" in current_tags
if dlouha:
tree.item(k, tags=("dlouha",))
else:
tree.item(k, tags=("even" if idx % 2 == 0 else "odd",))
for col in cols:
tree.heading(col, text=col, command=lambda c=col: sort_by(c))
# Střídavé barvy řádků
tree.tag_configure("even", background="#f0f4f8")
tree.tag_configure("odd", background="white")
tree.tag_configure("dlouha", foreground="#c0392b") # červeně > 90 dní
for i, row in enumerate(rows):
prijmeni, jmeno, rodcis, zacnes, konnes, diagno, cisnes = row
dni = delka_PN(zacnes, konnes, dnes)
konec_str = konnes.strftime("%d.%m.%Y") if konnes else "trvá"
diagno_str = (diagno or "").strip()
cisnes_str = (cisnes or "").strip()
tag = "dlouha" if dni > 90 else ("even" if i % 2 == 0 else "odd")
tree.insert("", tk.END, values=(
prijmeni,
jmeno,
rodcis,
zacnes.strftime("%d.%m.%Y"),
konec_str,
dni,
diagno_str,
cisnes_str,
), tags=(tag,))
tree.pack(fill=tk.BOTH, expand=True)
# ── Výchozí řazení: Dní vzestupně ─────────────────────────────────────────
sort_by("Dní")
# ── Legenda + zavřít ──────────────────────────────────────────────────────
spodek = tk.Frame(root, pady=6)
spodek.pack(fill=tk.X)
tk.Label(
spodek,
text="Červeně = PN delší než 90 dní",
font=("Segoe UI", 9),
fg="#c0392b"
).pack(side=tk.LEFT, padx=15)
tk.Button(
spodek,
text="Zavřít",
command=root.destroy,
width=12,
font=("Segoe UI", 10)
).pack(side=tk.RIGHT, padx=15)
root.mainloop()
# ── Hlavní tok ────────────────────────────────────────────────────────────────
if __name__ == "__main__":
try:
dnes, rows = nacti_data()
zobraz_okno(dnes, rows)
except Exception:
with open(_LOG, "w", encoding="utf-8") as f:
traceback.print_exc(file=f)
raise
+291
View File
@@ -0,0 +1,291 @@
# PNWithClaude Dokumentace projektu
> **Účel:** Python skripty spouštěné tlačítkem z Medicusu, které rozšiřují možnosti
> vestavěných reportů. Vznikly díky SQL loggeru/debuggeru v Medicusu, který odhalil
> strukturu databáze.
---
## Prostředí
| Položka | Hodnota |
|---|---|
| Python | 3.12.9 (64-bit) |
| Python exe | `C:\Users\vlado\PycharmProjects\Medicus\.venv\Scripts\python.exe` |
| Pythonw exe | `C:\Users\vlado\PycharmProjects\Medicus\.venv\Scripts\pythonw.exe` |
| Projekt | `C:\Users\vlado\PycharmProjects\Medicus\` |
| Skripty | `C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\` |
| Databáze | Firebird `localhost:c:\medicus 3\data\medicus.fdb` |
### Klíčové Python balíčky
- `fdb` Firebird databázový driver
- `tkinter` GUI (součást Pythonu, není třeba instalovat)
---
## Připojení k databázi
```python
import fdb
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA',
password='masterkey',
charset='win1250'
)
```
> ⚠️ Charset `win1250` je kritický bez něj jsou česká písmena rozbitá.
---
## Konvence pojmenování skriptů
```
NNN_popisne_jmeno.py
```
- `NNN` = třímístné číslo (001, 002, 003, ...)
- Příklad: `003_aktivni_PN_seznam.py`
---
## Jak nastavit tlačítko v Medicusu
Medicus → **Konfigurace → Externí programy** (nebo Nástroje → Nastavení)
### Pole v dialogu „Externí program"
| Pole | Hodnota |
|---|---|
| **Příkazový řádek** | `"C:\Users\vlado\PycharmProjects\Medicus\.venv\Scripts\pythonw.exe" "C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\NNN_skript.py"` |
| **Program** | `C:\Users\vlado\PycharmProjects\Medicus\.venv\Scripts\pythonw.exe` |
| **Spustit v** | *(nechat prázdné)* |
| **Popis** | Název tlačítka v Medicusu |
| **Pacient** | ☐ nezaškrtávat (pokud skript nepotřebuje aktuálního pacienta) |
> ⚠️ **`pythonw.exe`** místo `python.exe` = žádné černé konzolové okno!
>
> ⚠️ **„Spustit v" nechat prázdné** Medicus hlásí chybu pokud složka
> „neexistuje" (i když existuje), ale hlavně to nepotřebujeme,
> protože používáme absolutní cesty všude.
### Jak přidat tlačítko na lištu
Po uložení externího programu → pravým tlačítkem na lištu → přizpůsobit →
najít nový program → přetáhnout na lištu.
### Předávání dat z Medicusu skriptu
Medicus umí předat proměnné přes příkazový řádek, např.:
```
"pythonw.exe" "skript.py" "%RODCISN%" "%JMENO%" "%PRIJMENI%"
```
Dostupné proměnné (ukázka ze stávající konfigurace laboratoře):
- `%JMENO%` jméno pacienta
- `%PRIJMENI%` příjmení
- `%RODCISN%` rodné číslo (bez lomítka)
- `%POJ%` pojišťovna
- `%DGN%` diagnóza
V Pythonu pak čteš: `sys.argv[1]`, `sys.argv[2]`, atd.
---
## Jak zjistit SQL dotazy Medicusu SQL Logger
1. Medicus → **Nástroje → SQL Monitor** (nebo podobně v menu)
2. Zapnout logging
3. Provést akci v Medicusu (otevřít záložku, spustit report)
4. Zkopírovat SQL z logu
Log se ukládá do: `C:\Medicus 3\Debug\Monitor_DDMMYY_HHMMSS.log`
> 💡 **Zlatý důl!** SQL logger odhalí přesnou strukturu tabulek a podmínky
> filtrování, které Medicus interně používá.
---
## Struktura databáze klíčové tabulky
### Pacienti
| Tabulka | Popis |
|---|---|
| `KAR` | Kartotéka pacientů `IDPAC`, `PRIJMENI`, `JMENO`, `RODCIS` |
### Pracovní neschopnost (PN)
| Tabulka | Popis |
|---|---|
| `NES` | **Hlavní tabulka neschopenek** |
| `HPN` | Elektronická podání na ČSSZ (eNeschopenka) komunikační vrstva, 5693 záznamů |
| `HPN_NOTIFIKACE` | Notifikace z ČSSZ |
| `HPN_NOTIFIKACE_DETAIL` | Detail notifikací, stavy podání (`STAV_PODANI`) |
| `HPN_PODANI` | Podání na ČSSZ |
| `HOSPNES` | Hospitalizační neschopenky |
### Klíčové sloupce tabulky `NES`
| Sloupec | Popis |
|---|---|
| `IDPAC` | ID pacienta (JOIN s KAR) |
| `ZACNES` | Začátek PN (DATE) |
| `KONNES` | Konec PN (DATE) NULL = stále trvá |
| `DIAGNO` | Diagnóza (MKN-10 kód) |
| `CISNES` | Číslo neschopenky (starý formát) |
| `ECN` | eČíslo neschopenky (nový elektronický formát) |
| `PRACNE` | Pracovní neschopnost = `'A'` |
| `STORNO` | Storno záznamu = `'T'` (True) |
| `STATDPNKOD` | Stav DPN kód |
| `VERZE_DPN` | Verze DPN |
---
## SQL aktivní PN k dnešnímu datu
```sql
SELECT
kar.PRIJMENI,
kar.JMENO,
kar.RODCIS,
nes.ZACNES,
nes.KONNES,
nes.DIAGNO,
COALESCE(nes.ECN, nes.CISNES) AS CISNES -- preferuj eČíslo, fallback na staré
FROM NES nes
JOIN KAR kar ON kar.IDPAC = nes.IDPAC
WHERE
nes.ZACNES <= CAST('TODAY' AS DATE)
AND (nes.KONNES >= CAST('TODAY' AS DATE) OR nes.KONNES IS NULL)
AND nes.PRACNE = 'A'
AND nes.STORNO <> 'T'
ORDER BY kar.PRIJMENI, kar.JMENO
```
> 💡 `COALESCE(nes.ECN, nes.CISNES)` starší neschopenky mají jen `CISNES`,
> novější elektronické mají `ECN`. Takhle dostaneme vždy správné číslo.
---
## Šablona nového skriptu
```python
"""
NNN_nazev_skriptu.py
====================
Popis co skript dělá.
"""
import sys, os, traceback
_LOG = r"C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\NNN_error.log"
def _log_exception(exc_type, exc_value, exc_tb):
with open(_LOG, "w", encoding="utf-8") as f:
traceback.print_exception(exc_type, exc_value, exc_tb, file=f)
sys.excepthook = _log_exception
import fdb
import datetime
import tkinter as tk
from tkinter import ttk
DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
USER = 'SYSDBA'
PASSWORD = 'masterkey'
CHARSET = 'win1250'
SQL = """
SELECT ...
FROM ...
WHERE ...
"""
def nacti_data():
conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET)
try:
cur = conn.cursor()
cur.execute(SQL)
return cur.fetchall()
finally:
conn.close()
def zobraz_okno(rows):
root = tk.Tk()
root.title("Název okna")
# Centrování na střed monitoru
w, h = 900, 550
root.update_idletasks()
sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")
# ... GUI kód ...
root.mainloop()
if __name__ == "__main__":
try:
rows = nacti_data()
zobraz_okno(rows)
except Exception:
with open(_LOG, "w", encoding="utf-8") as f:
traceback.print_exc(file=f)
raise
```
---
## Řazení v ttk.Treeview vzorový kód
```python
_sort_state = {} # uchovává směr řazení pro každý sloupec
def sort_by(col):
ascending = not _sort_state.get(col, False)
_sort_state[col] = ascending
data = [(tree.set(k, col), k) for k in tree.get_children("")]
if col == "Dní": # číselné sloupce
data.sort(key=lambda t: int(t[0]) if t[0].isdigit() else 0, reverse=not ascending)
else: # textové sloupce
data.sort(key=lambda t: t[0].lower(), reverse=not ascending)
for idx, (_, k) in enumerate(data):
tree.move(k, "", idx)
# Šipka v záhlaví aktivního sloupce
for c in cols:
arrow = ("" if ascending else "") if c == col else ""
tree.heading(c, text=c + arrow, command=lambda c=c: sort_by(c))
# Připoj řazení na záhlaví
for col in cols:
tree.heading(col, text=col, command=lambda c=col: sort_by(c))
# Výchozí řazení při spuštění
sort_by("Dní")
```
---
## Ladění problémů
### Skript nefunguje z Medicusu, ale z PyCharmu ano
1. Přepnout dočasně na `python.exe` (ne `pythonw.exe`) uvidíš konzoli
2. Zkontrolovat `NNN_error.log` v adresáři skriptů
3. Nejčastější příčiny:
- `__file__` je prázdný → používej **absolutní cesty** pro log soubory
- Chybí `fbclient.dll` v PATH → testuj test skriptem
- Pole „Spustit v" v Medicusu → **nechat prázdné**
### Test skript pro ověření prostředí
Viz `test_spusteni.py` testuje Python, fdb import, tkinter a DB spojení.
Výstup: `Python: OK`, `fdb: OK`, `tkinter: OK`, `DB spojeni: OK`
### Černé konzolové okno
Použít `pythonw.exe` místo `python.exe` v konfiguraci Medicusu.
---
## Seznam skriptů
| Číslo | Soubor | Popis |
|---|---|---|
| 001 | `001_pruzkum_PN_tabulek.py` | Průzkum struktury DB tabulky a sloupce s PN |
| 003 | `003_aktivni_PN_seznam.py` | **Aktivní PN k dnešnímu datu** tlačítko na liště |
> Číslo 002 přeskočeno (bylo pracovní/testovací stadium).
---
*Projekt vznikl: březen 2026 | Medicus 3 Komfort | Firebird DB | Python 3.12*
+8
View File
@@ -0,0 +1,8 @@
Python: C:\Users\vlado\PycharmProjects\Medicus\.venv\Scripts\python.exe
Verze: 3.12.9 (tags/v3.12.9:fdb8142, Feb 4 2025, 15:27:58) [MSC v.1942 64 bit (AMD64)]
Cwd: C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude
__file__: C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\test_spusteni.py
fdb: OK
tkinter: OK
DB spojeni: OK
+42
View File
@@ -0,0 +1,42 @@
"""
Testovací skript - zjistí proč nejde 003.
Zapíše výsledek do test_spusteni.log
"""
import sys
import os
import traceback
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_spusteni.log")
lines = []
lines.append(f"Python: {sys.executable}")
lines.append(f"Verze: {sys.version}")
lines.append(f"Cwd: {os.getcwd()}")
lines.append(f"__file__: {os.path.abspath(__file__)}")
lines.append("")
try:
import fdb
lines.append("fdb: OK")
except Exception as e:
lines.append(f"fdb CHYBA: {e}")
lines.append(traceback.format_exc())
try:
import tkinter as tk
lines.append("tkinter: OK")
except Exception as e:
lines.append(f"tkinter CHYBA: {e}")
try:
conn = __import__("fdb").connect(
dsn=r"localhost:c:\medicus 3\data\medicus.fdb",
user="SYSDBA", password="masterkey", charset="win1250"
)
conn.close()
lines.append("DB spojeni: OK")
except Exception as e:
lines.append(f"DB CHYBA: {e}")
with open(LOG, "w", encoding="utf-8") as f:
f.write("\n".join(lines))