diff --git a/MedicusWithClaudeFaktury/decode_portal_data.py b/MedicusWithClaudeFaktury/decode_portal_data.py
new file mode 100644
index 0000000..38fdfa0
--- /dev/null
+++ b/MedicusWithClaudeFaktury/decode_portal_data.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+import sys, io, base64, re
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+# Nacti poslednich 5 PORTAL zaznamu s DATA
+cur.execute("""
+ SELECT FIRST 5 ID, ODESLANO, IDFAK, ID_PODANI, DATA
+ FROM PORTAL
+ WHERE DATA IS NOT NULL
+ ORDER BY ID DESC
+""")
+rows = cur.fetchall()
+
+for row in rows:
+ pid, odeslano, idfak, id_podani, data = row
+ print(f"\n{'='*70}")
+ print(f"PORTAL.ID={pid} ODESLANO={odeslano} IDFAK={idfak} ID_PODANI={id_podani}")
+
+ if data is None:
+ print("DATA: NULL")
+ continue
+
+ # data je string (win1250)
+ if isinstance(data, bytes):
+ data = data.decode('win1250', errors='replace')
+
+ # Parsuj XML obalku
+ print(f"DATA (prvnich 300 zn): {data[:300]}")
+
+ # Najdi BASE64 obsah uvnitr ...
+ m = re.search(r']*Format="BASE64"[^>]*>(.*?)', data, re.DOTALL)
+ if m:
+ b64_raw = m.group(1).strip()
+ # Dekoduj
+ try:
+ b64_clean = re.sub(r'\s+', '', b64_raw)
+ # Padding
+ b64_clean += '=' * (4 - len(b64_clean) % 4)
+ html_bytes = base64.b64decode(b64_clean)
+ html = html_bytes.decode('iso-8859-2', errors='replace')
+
+ # Extrahuj text
+ text = re.sub(r'', '', html, flags=re.DOTALL)
+ text = re.sub(r'', '', text, flags=re.DOTALL)
+ text = re.sub(r'', '', text, flags=re.DOTALL)
+ text = re.sub(r'<[^>]+>', ' ', text)
+ text = re.sub(r' ', ' ', text)
+ text = re.sub(r'&', '&', text)
+ text = re.sub(r'\s+', ' ', text).strip()
+
+ print(f"\nDEKÓDOVANÝ HTML protokol:")
+ print(text[:2000])
+ except Exception as e:
+ print(f"Chyba dekódování: {e}")
+ else:
+ # Zkus plain text odpoved (ne BASE64)
+ m2 = re.search(r']*>(.*?)', data, re.DOTALL)
+ if m2:
+ print(f"\nObsah Soubor (plain): {m2.group(1)[:500]}")
+
+conn.close()
diff --git a/MedicusWithClaudeFaktury/explore_ed_bookofsubmissions.py b/MedicusWithClaudeFaktury/explore_ed_bookofsubmissions.py
new file mode 100644
index 0000000..86a8f73
--- /dev/null
+++ b/MedicusWithClaudeFaktury/explore_ed_bookofsubmissions.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+import sys, io
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+for table in ['ED_BOOKOFSUBMISSIONS', 'ED_BOOKOFSUBMISSIONATTACH', 'ED_MAILBOXMESSAGE', 'ED_STORAGE']:
+ print(f"\n{'='*70}")
+ print(f"TABULKA: {table}")
+ print('='*70)
+
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ ORDER BY rf.RDB$FIELD_POSITION
+ """)
+ cols = [r[0].strip() for r in cur.fetchall()]
+ print(f"Sloupce: {cols}")
+
+ cur.execute(f"SELECT COUNT(*) FROM {table}")
+ print(f"Počet: {cur.fetchone()[0]}")
+
+ # BLOB sloupce vynech
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ AND f.RDB$FIELD_TYPE = 261
+ """)
+ blob_cols = {r[0].strip() for r in cur.fetchall()}
+ safe = [c for c in cols if c not in blob_cols]
+
+ # Najdi razeni
+ order_col = next((c for c in ['CREATEDATE','SENTDATE','CREATED','ODESLANO','DATUM','ID'] if c in cols), cols[0])
+
+ try:
+ cur.execute(f"SELECT FIRST 10 {', '.join(safe)} FROM {table} ORDER BY {order_col} DESC")
+ rows = cur.fetchall()
+ print(f"\nPosledních 10 (ORDER BY {order_col} DESC):")
+ for row in rows:
+ print(f" {dict(zip(safe, row))}")
+ except Exception as e:
+ print(f"Chyba SELECT: {e}")
+ # Zkus bez order
+ try:
+ cur.execute(f"SELECT FIRST 5 {', '.join(safe)} FROM {table}")
+ rows = cur.fetchall()
+ for row in rows:
+ print(f" {dict(zip(safe, row))}")
+ except Exception as e2:
+ print(f"Chyba i bez ORDER: {e2}")
+
+conn.close()
+print("\nHotovo.")
diff --git a/MedicusWithClaudeFaktury/explore_hpn.py b/MedicusWithClaudeFaktury/explore_hpn.py
new file mode 100644
index 0000000..9bef67c
--- /dev/null
+++ b/MedicusWithClaudeFaktury/explore_hpn.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+import sys, io
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+print("=" * 60)
+print("1. Sloupce HPN")
+print("=" * 60)
+cur.execute("""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = 'HPN'
+ ORDER BY rf.RDB$FIELD_POSITION
+""")
+col_names = [r[0].strip() for r in cur.fetchall()]
+print(f" Sloupce: {col_names}")
+
+print()
+print("=" * 60)
+print("2. Počet záznamů a max datum")
+print("=" * 60)
+cur.execute("SELECT COUNT(*) FROM HPN")
+print(f" Celkem: {cur.fetchone()[0]} záznamů")
+
+# Najdi datumove sloupce
+date_candidates = [c for c in col_names if any(x in c for x in ['DAT', 'ODE', 'VYT', 'CAS'])]
+print(f" Datumové sloupce: {date_candidates}")
+for dc in date_candidates[:3]:
+ try:
+ cur.execute(f"SELECT MIN({dc}), MAX({dc}) FROM HPN")
+ mn, mx = cur.fetchone()
+ print(f" {dc}: {mn} .. {mx}")
+ except Exception as e:
+ print(f" {dc}: {e}")
+
+print()
+print("=" * 60)
+print("3. Posledních 20 záznamů (bez BLOBů)")
+print("=" * 60)
+
+# Zjisti BLOB sloupce
+cur.execute("""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE
+ WHERE rf.RDB$RELATION_NAME = 'HPN'
+ AND f.RDB$FIELD_TYPE = 261
+""")
+blob_cols = {r[0].strip() for r in cur.fetchall()}
+print(f" BLOB sloupce: {blob_cols}")
+
+safe_cols = [c for c in col_names if c not in blob_cols]
+
+# Zjisti razeni - zkus VYTVORENO nebo ODESLANO nebo ID
+order_col = next((c for c in ['VYTVORENO', 'ODESLANO', 'DATUM', 'ID'] if c in col_names), col_names[0])
+
+cur.execute(f"SELECT FIRST 20 {', '.join(safe_cols)} FROM HPN ORDER BY {order_col} DESC")
+rows = cur.fetchall()
+for row in rows:
+ d = dict(zip(safe_cols, row))
+ print(f" {d}")
+
+print()
+print("=" * 60)
+print("4. Záznamy od 2026-02-01")
+print("=" * 60)
+for dc in date_candidates[:2]:
+ try:
+ cur.execute(f"SELECT COUNT(*) FROM HPN WHERE {dc} >= '2026-02-01'")
+ cnt = cur.fetchone()[0]
+ if cnt > 0:
+ print(f" {dc} >= 2026-02-01: {cnt} záznamů")
+ cur.execute(f"SELECT FIRST 5 {', '.join(safe_cols)} FROM HPN WHERE {dc} >= '2026-02-01' ORDER BY {dc} DESC")
+ for row in cur.fetchall():
+ print(f" {dict(zip(safe_cols, row))}")
+ break
+ except Exception as e:
+ print(f" {dc}: {e}")
+
+print()
+print("=" * 60)
+print("5. HPN_NOTIFIKACE_DETAIL – sloupce a ukazka")
+print("=" * 60)
+cur.execute("""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = 'HPN_NOTIFIKACE_DETAIL'
+ ORDER BY rf.RDB$FIELD_POSITION
+""")
+hnd_cols = [r[0].strip() for r in cur.fetchall()]
+print(f" Sloupce: {hnd_cols}")
+cur.execute("SELECT COUNT(*) FROM HPN_NOTIFIKACE_DETAIL")
+print(f" Záznamy: {cur.fetchone()[0]}")
+
+conn.close()
+print("\nHotovo.")
diff --git a/MedicusWithClaudeFaktury/explore_hpn_podani.py b/MedicusWithClaudeFaktury/explore_hpn_podani.py
new file mode 100644
index 0000000..e082e9d
--- /dev/null
+++ b/MedicusWithClaudeFaktury/explore_hpn_podani.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+import sys, io
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+print("=" * 60)
+print("1. Sloupce HPN_PODANI")
+print("=" * 60)
+cur.execute("""
+ SELECT rf.RDB$FIELD_NAME, rf.RDB$FIELD_POSITION
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = 'HPN_PODANI'
+ ORDER BY rf.RDB$FIELD_POSITION
+""")
+col_names = [r[0].strip() for r in cur.fetchall()]
+print(f" Sloupce: {col_names}")
+
+print()
+print("=" * 60)
+print("2. Ukazka HPN_PODANI (bez BLOB ODPOVED)")
+print("=" * 60)
+safe_cols = [c for c in col_names if c != 'ODPOVED']
+cur.execute(f"SELECT FIRST 10 {', '.join(safe_cols)} FROM HPN_PODANI ORDER BY ODESLANO DESC")
+rows = cur.fetchall()
+for row in rows:
+ print(dict(zip(safe_cols, row)))
+
+print()
+print("=" * 60)
+print("3. Hledej tabulky s POJ nebo ZP sloupcem + data od 2026-02")
+print("=" * 60)
+
+# Najdi vsechny tabulky ktere maji sloupec POJ nebo ZP
+cur.execute("""
+ SELECT DISTINCT rf.RDB$RELATION_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
+ WHERE r.RDB$SYSTEM_FLAG = 0
+ AND (TRIM(rf.RDB$FIELD_NAME) = 'POJ' OR TRIM(rf.RDB$FIELD_NAME) = 'ZP')
+ ORDER BY rf.RDB$RELATION_NAME
+""")
+poj_tables = [r[0].strip() for r in cur.fetchall()]
+print(f" Tabulky s POJ/ZP: {len(poj_tables)}")
+
+# Z techto tabulek vyber ty ktere maji ODESLANO nebo DATUM a data od 2026-02
+hits = []
+for table in poj_tables:
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%ODESLAN%'
+ OR TRIM(rf.RDB$FIELD_NAME) LIKE '%VYTVOR%'
+ OR TRIM(rf.RDB$FIELD_NAME) = 'DATUM')
+ """)
+ date_cols = [r[0].strip() for r in cur.fetchall()]
+ for dc in date_cols:
+ try:
+ cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-02-01'")
+ cnt = cur.fetchone()[0]
+ if cnt > 0:
+ hits.append((table, dc, cnt))
+ except Exception:
+ pass
+
+hits.sort(key=lambda x: -x[2])
+for table, dc, cnt in hits:
+ print(f" {table:<40} {dc}: {cnt} od 2026-02")
+
+print()
+print("=" * 60)
+print("4. Hledej tabulky s PODACI_CISLO nebo PODAC nebo ID_PODANI")
+print("=" * 60)
+cur.execute("""
+ SELECT DISTINCT rf.RDB$RELATION_NAME, rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
+ WHERE r.RDB$SYSTEM_FLAG = 0
+ AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%PODACI%'
+ OR TRIM(rf.RDB$FIELD_NAME) = 'ID_PODANI'
+ OR TRIM(rf.RDB$FIELD_NAME) LIKE '%PODANI%')
+ ORDER BY rf.RDB$RELATION_NAME
+""")
+for r in cur.fetchall():
+ print(f" {r[0].strip():<40} sloupec: {r[1].strip()}")
+
+print()
+print("=" * 60)
+print("5. PORTAL - co tam chybi od 2026-02?")
+print("=" * 60)
+cur.execute("""
+ SELECT FIRST 5 ID, IDFAK, ODESLANO, STAV, ID_PODANI, DAVKA_ROK, BB_DAVKA, BB_FAKTURA
+ FROM PORTAL
+ ORDER BY ODESLANO DESC
+""")
+for row in cur.fetchall():
+ print(f" {row}")
+
+conn.close()
+print("\nHotovo.")
diff --git a/MedicusWithClaudeFaktury/faktury_report.py b/MedicusWithClaudeFaktury/faktury_report.py
index ec2fdbd..0c22a18 100644
--- a/MedicusWithClaudeFaktury/faktury_report.py
+++ b/MedicusWithClaudeFaktury/faktury_report.py
@@ -262,6 +262,224 @@ for i, row in enumerate(fak_rows, start=2):
else:
cell.value = ''
+# =====================
+# List 5 – ED_PODANI (ED_BOOKOFSUBMISSIONS)
+# =====================
+
+ws5 = wb.create_sheet('ED_PODANI')
+
+REQUESTTYPE_MAP = {0: 'Registrační', 1: 'Výkonová'}
+HICCODE_MAP = {
+ '111': 'VZP', '201': 'VoZP', '205': 'ČPZP',
+ '207': 'OZP', '209': 'ZPŠ', '211': 'ZPMV',
+ '212': '212', '213': '213', '217': '217', '228': '228', '333': '333',
+}
+
+cur.execute('''
+ SELECT
+ ID, CREATED, SENTDATE, HICCODE, REQUESTTYPE,
+ SUBMISSIONID, INVOICENUMBER,
+ PERIODFROM, PERIODTO, TOTALSUM,
+ STATE, CREATOR, HCPPERSONNAME, HCPCODE, UNIQUEID
+ FROM ED_BOOKOFSUBMISSIONS
+ ORDER BY ID DESC
+''')
+ed_cols_raw = [d[0] for d in cur.description]
+ed_rows = cur.fetchall()
+
+# Lidštější záhlaví
+ed_headers = [
+ 'ID', 'Vytvořeno', 'Odesláno', 'ZP', 'Typ podání',
+ 'Podací č.', 'Faktura',
+ 'Období od', 'Období do', 'Částka (Kč)',
+ 'Stav', 'Autor', 'Lékař', 'IČZ', 'UUID',
+]
+ws5.append(ed_headers)
+
+# Mapa CISFAK → řádek na listu FAK (pro hyperlink)
+cisfak_to_fak_row = {}
+for i, row in enumerate(fak_rows, start=2):
+ cisfak = row[1] # CISFAK je druhý sloupec z DB (index 1)
+ if cisfak and cisfak not in cisfak_to_fak_row:
+ cisfak_to_fak_row[cisfak] = i
+
+for i, row in enumerate(ed_rows, start=2):
+ (eid, created, sentdate, hiccode, reqtype,
+ submid, invoicenum,
+ perfrom, perto, totalsum,
+ state, creator, hcpperson, hcpcode, uniqueid) = row
+
+ out = [
+ eid,
+ fmt(created),
+ fmt(sentdate),
+ f"{hiccode} {HICCODE_MAP.get(str(hiccode), '')}" if hiccode else '',
+ REQUESTTYPE_MAP.get(reqtype, str(reqtype)) if reqtype is not None else '',
+ fmt(submid),
+ fmt(invoicenum),
+ fmt(perfrom),
+ fmt(perto),
+ float(totalsum) if totalsum is not None else '',
+ fmt(state),
+ fmt(creator),
+ fmt(hcpperson),
+ fmt(hcpcode),
+ fmt(uniqueid),
+ ]
+ ws5.append(out)
+ if i % 2 == 0:
+ for cell in ws5[i]:
+ cell.fill = ZEBRA_FILL
+
+style_header(ws5)
+ws5.freeze_panes = 'A2'
+autofit(ws5)
+
+# Hyperlink: Faktura (sloupec 7) → list FAK
+for i, row in enumerate(ed_rows, start=2):
+ invoicenum = row[6] # INVOICENUMBER
+ if invoicenum and invoicenum in cisfak_to_fak_row:
+ cell = ws5.cell(row=i, column=7)
+ # +1 kvůli vloženému sloupci FAKDET na listu FAK
+ cell.hyperlink = f'#FAK!B{cisfak_to_fak_row[invoicenum]}'
+ cell.font = LINK_FONT
+
+# =====================
+# List 6 – ED_PODANI_DATA (BLOBy z ED_BOOKOFSUBMISSIONS)
+# =====================
+
+ws6 = wb.create_sheet('ED_PODANI_DATA')
+
+cur.execute('''
+ SELECT ID, HICCODE, SENTDATE, SUBMISSIONID, INVOICENUMBER,
+ KDAVKACONTENT, REQUEST, SERVERRESPONSE, PROTOCOL
+ FROM ED_BOOKOFSUBMISSIONS
+ ORDER BY ID DESC
+''')
+ed_data_rows = cur.fetchall()
+
+ed_data_headers = ['ID', 'ZP', 'Odesláno', 'Podací č.', 'Faktura',
+ 'KDAVKA', 'REQUEST (XML)', 'SERVERRESPONSE', 'PROTOCOL']
+ws6.append(ed_data_headers)
+
+def decode_ed_blob(v, enc):
+ """Dekóduj BLOB z ED_BOOKOFSUBMISSIONS."""
+ if v is None:
+ return ''
+ if hasattr(v, 'read'):
+ raw = v.read()
+ else:
+ raw = v
+ if not raw:
+ return ''
+ if isinstance(raw, str):
+ # fdb vrátil jako win1250 string – zrekonstruuj bytes
+ raw = raw.encode('cp1250', errors='replace')
+ try:
+ text = raw.decode(enc, errors='replace')
+ except Exception:
+ return repr(raw[:200])
+ # Odstraň prázdné řádky a sjednoť odřádkování
+ lines = [l for l in text.splitlines() if l.strip()]
+ return '\n'.join(lines)
+
+for i, row in enumerate(ed_data_rows, start=2):
+ eid, hiccode, sentdate, submid, invoicenum, kdavka, request, serverresp, protocol = row
+
+ # KDAVKACONTENT – fdb vrací str přes win1250 spojení, použij přímo
+ if kdavka is None:
+ kdavka_txt = ''
+ else:
+ raw = kdavka.read() if hasattr(kdavka, 'read') else kdavka
+ if isinstance(raw, bytes):
+ raw = raw.encode('latin-1', errors='replace').decode('cp852', errors='replace')
+ lines = [l for l in raw.splitlines() if l.strip()]
+ kdavka_txt = '\n'.join(lines)
+
+ # REQUEST – XML; může být uložen jako UTF-16 binary nebo jako ASCII/UTF-8 string
+ request_txt = ''
+ if request is not None:
+ raw = request.read() if hasattr(request, 'read') else request
+ if isinstance(raw, str):
+ raw_b = raw.encode('latin-1', errors='replace')
+ else:
+ raw_b = raw
+ if raw_b:
+ # Detekce podle BOM – jedině tehdy jde o skutečné UTF-16 binární data
+ if raw_b[:2] in (b'\xff\xfe', b'\xfe\xff'):
+ request_txt = raw_b.decode('utf-16', errors='replace')
+ elif raw_b[:1] == b'<':
+ # ASCII/UTF-8 XML – fdb ho vrátil jako string, použij přímo
+ request_txt = raw_b.decode('utf-8', errors='replace')
+ else:
+ request_txt = raw_b.decode('cp1250', errors='replace')
+ lines = [l for l in request_txt.splitlines() if l.strip()]
+ request_txt = '\n'.join(lines)
+
+ # SERVERRESPONSE a PROTOCOL – latin-1 re-encoding, pak iso-8859-2
+ def decode_latin_blob(v, enc):
+ if v is None:
+ return ''
+ raw = v.read() if hasattr(v, 'read') else v
+ if not raw:
+ return ''
+ if isinstance(raw, str):
+ raw = raw.encode('latin-1', errors='replace')
+ text = raw.decode(enc, errors='replace')
+ lines = [l for l in text.splitlines() if l.strip()]
+ return '\n'.join(lines)
+
+ serverresp_txt = decode_latin_blob(serverresp, 'iso-8859-2')
+ protocol_txt = decode_latin_blob(protocol, 'iso-8859-2')
+
+ out = [
+ eid,
+ f"{hiccode} {HICCODE_MAP.get(str(hiccode), '')}" if hiccode else '',
+ fmt(sentdate),
+ fmt(submid),
+ fmt(invoicenum),
+ kdavka_txt,
+ request_txt,
+ serverresp_txt,
+ protocol_txt,
+ ]
+ ws6.append(out)
+ if i % 2 == 0:
+ for cell in ws6[i]:
+ cell.fill = ZEBRA_FILL
+ for cell in ws6[i]:
+ cell.alignment = WRAP
+ ws6.row_dimensions[i].height = 80
+
+style_header(ws6)
+ws6.freeze_panes = 'A2'
+
+# Šířky sloupců ED_PODANI_DATA
+for col, width in zip(['A','B','C','D','E','F','G','H','I'],
+ [6, 10, 12, 16, 14, 80, 60, 60, 40]):
+ ws6.column_dimensions[col].width = width
+
+# Hyperlinky ED_PODANI ↔ ED_PODANI_DATA (přes ID)
+ed_id_to_ws5_row = {row[0]: i for i, row in enumerate(ed_rows, start=2)}
+ed_id_to_ws6_row = {row[0]: i for i, row in enumerate(ed_data_rows, start=2)}
+
+# ED_PODANI sloupec 1 (ID) → ED_PODANI_DATA
+for i, row in enumerate(ed_rows, start=2):
+ eid = row[0]
+ if eid in ed_id_to_ws6_row:
+ cell = ws5.cell(row=i, column=1)
+ cell.hyperlink = f'#ED_PODANI_DATA!A{ed_id_to_ws6_row[eid]}'
+ cell.font = LINK_FONT
+
+# ED_PODANI_DATA sloupec 1 (ID) → ED_PODANI
+for i, row in enumerate(ed_data_rows, start=2):
+ eid = row[0]
+ if eid in ed_id_to_ws5_row:
+ cell = ws6.cell(row=i, column=1)
+ cell.hyperlink = f'#ED_PODANI!A{ed_id_to_ws5_row[eid]}'
+ cell.font = LINK_FONT
+ cell.alignment = WRAP
+
# =====================
# Uložení
# =====================
@@ -269,4 +487,4 @@ for i, row in enumerate(fak_rows, start=2):
conn.close()
wb.save(output_path)
sys.stdout.buffer.write(f'Ulozeno: {output_path}\n'.encode('utf-8'))
-sys.stdout.buffer.write(f'FAK: {len(fak_rows)} radku, FAKDET: {len(det_rows)} radku, PORTAL: {len(portal_rows)} radku\n'.encode('utf-8'))
+sys.stdout.buffer.write(f'FAK: {len(fak_rows)} radku, FAKDET: {len(det_rows)} radku, PORTAL: {len(portal_rows)} radku, ED_PODANI: {len(ed_rows)} radku\n'.encode('utf-8'))
diff --git a/MedicusWithClaudeFaktury/find_edavky2.py b/MedicusWithClaudeFaktury/find_edavky2.py
new file mode 100644
index 0000000..ff6e3b3
--- /dev/null
+++ b/MedicusWithClaudeFaktury/find_edavky2.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+import sys, io
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+# Ze screenshotu vime:
+# - sloupce: Vytvoreno, Odeslano, ZP, Stav, Zpravy, Podaci c., Faktura
+# - ZP: 111, 201, 205, 207, 209, 211
+# - Podaci c.: 58933293, 174804160, 26082877, D01F260218...
+# - datum: 01.03.2026, 05.03.2026, 12.03.2026, 23.03.2026, 24.03.2026
+# - typ: "Reg. listy" (bez faktury), vykonove davky (s fakturou)
+
+print("=" * 60)
+print("1. EOCK_DAVKA - sloupce a ukazka")
+print("=" * 60)
+cur.execute("""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = 'EOCK_DAVKA'
+ ORDER BY rf.RDB$FIELD_POSITION
+""")
+eock_cols = [r[0].strip() for r in cur.fetchall()]
+print(f" Sloupce: {eock_cols}")
+cur.execute("SELECT COUNT(*) FROM EOCK_DAVKA")
+print(f" Pocet: {cur.fetchone()[0]}")
+safe = [c for c in eock_cols if c not in ('DATA', 'DAVKA', 'ODPOVED')]
+if safe:
+ cur.execute(f"SELECT FIRST 3 {', '.join(safe)} FROM EOCK_DAVKA ORDER BY ID DESC")
+ for r in cur.fetchall():
+ print(f" {dict(zip(safe, r))}")
+
+print()
+print("=" * 60)
+print("2. FAK - zaznamy z brezna 2026")
+print("=" * 60)
+cur.execute("""
+ SELECT ID, CISFAK, POJ, DATVYS, DATODE, OBDOB, CENA, DRUH, STAV
+ FROM FAK
+ WHERE DATVYS >= '2026-03-01' OR DATODE >= '2026-03-01'
+ ORDER BY ID DESC
+""")
+rows = cur.fetchall()
+print(f" Celkem: {len(rows)}")
+for r in rows:
+ print(f" ID={r[0]} CISFAK={r[1]} POJ={r[2]} DATVYS={r[3]} DATODE={r[4]} OBDOB={r[5]} CENA={r[6]} DRUH={r[7]} STAV={r[8]}")
+
+print()
+print("=" * 60)
+print("3. Hledej tabulky ktere maji sloupec VYTVORENO nebo PODACI")
+print("=" * 60)
+cur.execute("""
+ SELECT DISTINCT rf.RDB$RELATION_NAME, rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
+ WHERE r.RDB$SYSTEM_FLAG = 0
+ AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%VYTVOR%'
+ OR TRIM(rf.RDB$FIELD_NAME) LIKE '%PODACI%')
+ ORDER BY rf.RDB$RELATION_NAME
+""")
+for r in cur.fetchall():
+ print(f" {r[0].strip():<40} {r[1].strip()}")
+
+print()
+print("=" * 60)
+print("4. Hledej tabulky ktere maji POJ sloupec a zaznamy z brezna 2026")
+print("=" * 60)
+# Vsechny tabulky s POJ sloupcem
+cur.execute("""
+ SELECT DISTINCT rf.RDB$RELATION_NAME
+ FROM RDB$RELATION_FIELDS rf
+ JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
+ WHERE r.RDB$SYSTEM_FLAG = 0
+ AND TRIM(rf.RDB$FIELD_NAME) = 'POJ'
+ ORDER BY rf.RDB$RELATION_NAME
+""")
+poj_tables = [r[0].strip() for r in cur.fetchall()]
+
+for table in poj_tables:
+ # Najdi vsechny datumove sloupce
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ AND TRIM(rf.RDB$FIELD_NAME) LIKE '%DAT%'
+ """)
+ date_cols = [r[0].strip() for r in cur.fetchall()]
+ for dc in date_cols:
+ try:
+ cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-03-01'")
+ cnt = cur.fetchone()[0]
+ if cnt > 0:
+ print(f" {table:<40} {dc}: {cnt} od 2026-03-01")
+ except Exception:
+ pass
+
+print()
+print("=" * 60)
+print("5. Prohledej vsechny tabulky - ktere maji ~15 zaznamu od 2026-03-01")
+print(" (v eDavky screenshotu je ~15 radku z brezna)")
+print("=" * 60)
+cur.execute("""
+ SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
+ WHERE RDB$SYSTEM_FLAG = 0
+ ORDER BY RDB$RELATION_NAME
+""")
+all_tables = [r[0].strip() for r in cur.fetchall()]
+
+# Hledej tabulky s datumovym sloupcem a 10-50 zaznamy od 2026-03
+hits = []
+for table in all_tables:
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ AND (TRIM(rf.RDB$FIELD_NAME) IN ('ODESLANO', 'VYTVORENO', 'DATODE', 'DATVYS', 'DATUM_ODESLANI'))
+ """)
+ date_cols = [r[0].strip() for r in cur.fetchall()]
+ for dc in date_cols:
+ try:
+ cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-03-01'")
+ cnt = cur.fetchone()[0]
+ if 5 <= cnt <= 100:
+ hits.append((table, dc, cnt))
+ except Exception:
+ pass
+
+hits.sort(key=lambda x: x[2])
+for table, dc, cnt in hits:
+ # Vypis sloupce teto tabulky
+ cur.execute(f"""
+ SELECT rf.RDB$FIELD_NAME
+ FROM RDB$RELATION_FIELDS rf
+ WHERE rf.RDB$RELATION_NAME = '{table}'
+ ORDER BY rf.RDB$FIELD_POSITION
+ """)
+ cols = [r[0].strip() for r in cur.fetchall()]
+ print(f" {table:<40} {dc}: {cnt} | sloupce: {cols[:10]}")
+
+conn.close()
+print("\nHotovo.")
diff --git a/MedicusWithClaudeFaktury/find_edavky_table.py b/MedicusWithClaudeFaktury/find_edavky_table.py
new file mode 100644
index 0000000..7adcfc1
--- /dev/null
+++ b/MedicusWithClaudeFaktury/find_edavky_table.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+"""
+Hledani tabulky pro eDavky / Kniha podani v Medicus DB.
+"""
+import sys
+import io
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import fdb
+
+conn = fdb.connect(
+ dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
+ user='SYSDBA', password='masterkey', charset='win1250'
+)
+cur = conn.cursor()
+
+print("=" * 60)
+print("1. Tabulky s relevantními názvy")
+print("=" * 60)
+cur.execute("""
+ SELECT RDB$RELATION_NAME
+ FROM RDB$RELATIONS
+ WHERE RDB$SYSTEM_FLAG = 0
+ AND (
+ TRIM(RDB$RELATION_NAME) LIKE '%DAV%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%PORTAL%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%PODANI%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%EDAVK%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%KNIHA%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%PODAC%'
+ OR TRIM(RDB$RELATION_NAME) LIKE '%ELEK%'
+ )
+ ORDER BY RDB$RELATION_NAME
+""")
+tables = [row[0].strip() for row in cur.fetchall()]
+for t in tables:
+ print(f" {t}")
+
+print()
+print("=" * 60)
+print("2. Počty záznamů a max datum v relevantních tabulkách")
+print("=" * 60)
+
+for table in tables:
+ try:
+ # Zkus najít datumový sloupec
+ cur.execute(f"""
+ SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS
+ WHERE RDB$RELATION_NAME = '{table}'
+ ORDER BY RDB$FIELD_POSITION
+ """)
+ cols = [r[0].strip() for r in cur.fetchall()]
+
+ cur.execute(f"SELECT COUNT(*) FROM {table}")
+ count = cur.fetchone()[0]
+
+ # Najdi datum sloupec
+ date_col = None
+ for c in cols:
+ if any(x in c for x in ['DAT', 'ODE', 'VYT', 'CAS', 'TIME']):
+ date_col = c
+ break
+
+ if date_col:
+ try:
+ cur.execute(f"SELECT MAX({date_col}) FROM {table}")
+ max_date = cur.fetchone()[0]
+ print(f" {table:<30} {count:>6} záznamů max {date_col}={max_date}")
+ except Exception:
+ print(f" {table:<30} {count:>6} záznamů cols: {cols[:5]}")
+ else:
+ print(f" {table:<30} {count:>6} záznamů cols: {cols[:5]}")
+ except Exception as e:
+ print(f" {table:<30} chyba: {e}")
+
+print()
+print("=" * 60)
+print("3. Aktuální stav PORTAL")
+print("=" * 60)
+try:
+ cur.execute("SELECT COUNT(*), MAX(ODESLANO) FROM PORTAL")
+ cnt, mx = cur.fetchone()
+ print(f" PORTAL: {cnt} záznamů, max ODESLANO={mx}")
+ cur.execute("SELECT FIRST 5 ID, IDFAK, ODESLANO, STAV, ID_PODANI, DAVKA_ROK FROM PORTAL ORDER BY ID DESC")
+ for row in cur.fetchall():
+ print(f" {row}")
+except Exception as e:
+ print(f" PORTAL chyba: {e}")
+
+print()
+print("=" * 60)
+print("4. Hledání tabulek s datem >= 2026-02-01 (čerstvá data)")
+print("=" * 60)
+# Projdi všechny tabulky a hledej ty které mají záznamy z 2026
+suspicious = []
+cur.execute("""
+ SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
+ WHERE RDB$SYSTEM_FLAG = 0
+ ORDER BY RDB$RELATION_NAME
+""")
+all_tables = [r[0].strip() for r in cur.fetchall()]
+
+for table in all_tables:
+ try:
+ # Najdi datum sloupce
+ cur.execute(f"""
+ SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS
+ WHERE RDB$RELATION_NAME = '{table}'
+ AND (
+ TRIM(RDB$FIELD_NAME) LIKE '%ODESLANO%'
+ OR TRIM(RDB$FIELD_NAME) LIKE '%VYTVORENO%'
+ OR TRIM(RDB$FIELD_NAME) LIKE '%DATUM_ODE%'
+ )
+ """)
+ date_cols = [r[0].strip() for r in cur.fetchall()]
+ for dc in date_cols:
+ cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-02-01'")
+ cnt = cur.fetchone()[0]
+ if cnt > 0:
+ suspicious.append((table, dc, cnt))
+ except Exception:
+ pass
+
+for table, dc, cnt in suspicious:
+ print(f" {table:<35} {dc}: {cnt} záznamů od 2026-02-01")
+
+conn.close()
+print()
+print("Hotovo.")
diff --git a/MedicusWithClaudeFaktury/parse_trace_edavky.py b/MedicusWithClaudeFaktury/parse_trace_edavky.py
new file mode 100644
index 0000000..dde9b50
--- /dev/null
+++ b/MedicusWithClaudeFaktury/parse_trace_edavky.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+import sys, io, re
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+
+TRACE_FILE = r'c:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
+TIME_FROM = '2026-03-29T13:20:00'
+TIME_TO = '2026-03-29T13:20:15'
+
+# Hledame dotazy ktere pravdepodobne patri k eDavky oknu:
+# - zminka o tabulkach s davkami/podanim na pojistovnu
+# - sloupce jako ZP, PODACI, ODESLANO, STAV v kontextu davek
+KEYWORDS = [
+ 'EDAVKY', 'EDAVKA', 'DAVKY_POJ', 'REGISTR_POJ',
+ 'PODACI_CISLO', 'PODACI',
+ 'FAKDAV', 'BB_DAVKA', 'BB_FAKTURA',
+ 'STAV_PODANI', 'ID_PODANI',
+ 'DAVKA_ROK', 'DAVKA_DISK', 'DAVKA_CASTKA',
+]
+
+# Tabulky ktere NECHCEME (HPN = neschopenky, RECEPT = recepty, atd.)
+EXCLUDE_TABLES = ['FROM HPN', 'FROM NES', 'FROM RECEPT', 'FROM CLICKDOC',
+ 'FROM UZIVATEL', 'FROM ZARIZENI', 'FROM ODDEL', 'FROM PRACOVISTE']
+
+print(f"Čtu trace: {TRACE_FILE}")
+print(f"Časové okno: {TIME_FROM} .. {TIME_TO}")
+print(f"Hledám klíčová slova: {KEYWORDS}")
+print("=" * 70)
+
+ts_re = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')
+
+in_window = False
+current_ts = ''
+current_block = []
+hits = []
+all_selects_tables = {} # tabulka -> pocet vyskytu
+
+def get_from_tables(text):
+ """Extrahuj tabulky z FROM klauzule."""
+ tables = re.findall(r'\bFROM\s+([A-Z_][A-Z0-9_]*)', text, re.IGNORECASE)
+ tables += re.findall(r'\bJOIN\s+([A-Z_][A-Z0-9_]*)', text, re.IGNORECASE)
+ return [t.upper() for t in tables]
+
+def process_block(ts, lines):
+ text = '\n'.join(lines)
+ if not re.search(r'\bSELECT\b', text, re.IGNORECASE):
+ return
+
+ text_up = text.upper()
+
+ # Spocitej tabulky
+ tables = get_from_tables(text_up)
+ for t in tables:
+ all_selects_tables[t] = all_selects_tables.get(t, 0) + 1
+
+ # Filtr - hledej klicova slova
+ for kw in KEYWORDS:
+ if kw in text_up:
+ hits.append((ts, kw, text))
+ return
+
+ # Alternativne: dotazy na PORTAL nebo FAK s datumem
+ if ('PORTAL' in text_up or 'FAKDAV' in text_up) and 'SELECT' in text_up:
+ # Vynech jednoduche dotazy
+ if len(text) > 100:
+ hits.append((ts, 'PORTAL/FAKDAV', text))
+
+try:
+ with open(TRACE_FILE, 'r', encoding='utf-8', errors='replace') as f:
+ for line in f:
+ line = line.rstrip('\n')
+ m = ts_re.match(line)
+ if m:
+ if current_block and in_window:
+ process_block(current_ts, current_block)
+ current_ts = line[:19]
+ current_block = [line]
+ in_window = (TIME_FROM <= current_ts <= TIME_TO)
+ else:
+ if in_window:
+ current_block.append(line)
+ if current_block and in_window:
+ process_block(current_ts, current_block)
+except FileNotFoundError:
+ print(f"CHYBA: soubor nenalezen: {TRACE_FILE}")
+ sys.exit(1)
+
+print(f"\nNejčastější tabulky v SELECT dotazech v okně (top 40):")
+sorted_tables = sorted(all_selects_tables.items(), key=lambda x: -x[1])
+for t, cnt in sorted_tables[:40]:
+ print(f" {t:<40} {cnt}x")
+
+print()
+print(f"{'='*70}")
+print(f"Nalezeno relevantních bloků (klíčová slova): {len(hits)}")
+print()
+
+for i, (ts, kw, text) in enumerate(hits):
+ print(f"{'='*70}")
+ print(f"[{i+1}] {ts} klíčové slovo: {kw}")
+ print(text[:4000])
+ print()
+
+if not hits:
+ print("Žádné přesné shody. Zkusíme PREPARE_STATEMENT bloky s FROM tabulkami:")
+ print("(viz seznam tabulek nahoře - neobvyklé názvy mohou být klíč)")
diff --git a/MedicusWithClaudeSelects/FakturaceADavky.md b/MedicusWithClaudeSelects/FakturaceADavky.md
index 204a49b..8c8c998 100644
--- a/MedicusWithClaudeSelects/FakturaceADavky.md
+++ b/MedicusWithClaudeSelects/FakturaceADavky.md
@@ -235,6 +235,246 @@ P ... – preventivní prohlídka
- Kapitace se v FAK.KAPITACE neukazuje (je 0), ale v FAKDET.CENAKAP ano – nutno ověřit
- PORTAL = registrační dávky, nesouvisí s fakturací, IDFAK bývá NULL
+---
+
+## eDávky – elektronické odesílání dávek pojišťovnám (zjištěno 2026-03-29)
+
+### Přehled tabulek
+
+Modul eDávky v Medicusu používá tabulky s prefixem `ED_`:
+
+| Tabulka | Záznamy | Popis |
+|---|---|---|
+| `ED_BOOKOFSUBMISSIONS` | 998 | Hlavní tabulka – Kniha podání (od 2016) |
+| `ED_BOOKOFSUBMISSIONATTACH` | 0 | Přílohy k podáním (zatím nevyužito) |
+| `ED_MAILBOXMESSAGE` | 0 | Schránka zpráv od pojišťoven (zatím prázdná) |
+| `ED_STORAGE` | 5 | Konfigurace – certifikáty a přihlašovací údaje |
+
+### Jak jsme tabulku našli
+Přes Firebird trace log – při otevření okna eDávky v Medicusu se nejčastěji dotazuje
+na `ED_BOOKOFSUBMISSIONS` (45x) a `ED_MAILBOXMESSAGE` (4990x).
+
+### PORTAL vs ED_BOOKOFSUBMISSIONS
+- **PORTAL** (180 záznamů, max 2026-01-27) = starý systém podávání dávek
+- **ED_BOOKOFSUBMISSIONS** (998 záznamů, od 2016) = nový systém (přechod ~únor 2026)
+- Nepřekrývají se – žádný záznam není v obou (různá ID_PODANI/SUBMISSIONID)
+- PORTAL má data od 2014, ED_BOOKOFSUBMISSIONS od 2016 (oba systémy běžely paralelně)
+
+---
+
+### ED_BOOKOFSUBMISSIONS – hlavní tabulka Knihy podání
+
+**Sloupce:**
+
+| Sloupec | Popis |
+|---|---|
+| `ID` | primární klíč |
+| `CREATED` | datum vytvoření |
+| `SENTDATE` | datum odeslání pojišťovně |
+| `CREATOR` | jméno autora (např. "Buzalková Michaela MUDr.") |
+| `HCPCODE` | IČZ ordinace (09305000) |
+| `HCPPERSONNAME` | jméno lékaře |
+| `HICCODE` | kód pojišťovny (111, 201, 205, 207, 209, 211...) |
+| `INVOICENUMBER` | číslo faktury (např. 0000260020) – NULL pro reg. dávky |
+| `PERIODFROM` / `PERIODTO` | období dávky |
+| `REQUESTTYPE` | typ: **0** = registrační (Reg. listy), **1** = výkonová s fakturou |
+| `STATE` | stav podání (0 = odesláno OK) |
+| `SUBMISSIONID` | podací číslo přidělené pojišťovnou (např. 59135047) |
+| `TOTALSUM` | celková částka (0 pro reg. dávky) |
+| `UNIQUEID` | UUID záznamu |
+| `USERDESCRIPTION` | popis (zobrazuje se ve sloupci "Zprávy") |
+| `REQUEST` | XML žádosti odeslané pojišťovně (BLOB) |
+| `SERVERRESPONSE` | odpověď pojišťovny (BLOB, bytes, kódování iso-8859-2) |
+| `FDAVKACONTENT` | obsah FDAVKA (BLOB) |
+| `KDAVKACONTENT` | obsah KDAVKA (BLOB) |
+| `PROTOCOL` | protokol (BLOB) |
+
+**Pojišťovny v datech:** 111 (VZP), 201 (VoZP), 205 (ČPZP), 207 (OZP), 209 (ZPŠ), 211 (ZPMV)
+
+### Kódování pojišťoven (správné!)
+- **111** = VZP (Všeobecná zdravotní pojišťovna)
+- **201** = VoZP (Vojenská zdravotní pojišťovna)
+- **205** = ČPZP (Česká průmyslová zdravotní pojišťovna)
+- **207** = OZP (Oborová zdravotní pojišťovna)
+- **209** = ZPŠ (Zdravotní pojišťovna Škoda)
+- **211** = ZPMV (Zdravotní pojišťovna ministerstva vnitra)
+
+---
+
+### ED_STORAGE – konfigurace certifikátů a přihlašovacích údajů
+
+Sloupce: `ID`, `NAME`, `VALUEB` (BLOB XML), `IDUZI`
+
+**Záznamy:**
+- `ServerSettingsXml` (per uživatel, IDUZI=None/2/4/6) – XML s certifikáty pro každou pojišťovnu
+- `LastMessagesDownloadTime` (IDUZI=None) – datum posledního stažení zpráv
+
+**Struktura ServerSettingsXml:**
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**Certifikát Buzalky Vladimíra (IDUZI=6):**
+- Vydavatel: I.CA EU Qualified CA2/RSA 06/2022 (První certifikační autorita, a.s.)
+- SerialNumber: `0247068517B0049E2E`
+- Platí pro pojišťovny: 201, 205, 207, 209, 213, 217, 228
+
+---
+
+### Formát REQUEST XML – registrační dávka (REQUESTTYPE=0)
+
+```xml
+
+
+ RegistrationCards
+ None
+ 2026-03-01T00:00:00.000
+ 2026-03-31T00:00:00.000
+ 0
+ 09305000
+ 09305000
+ 68366370
+ MUDr. Buzalka Vladimír
+ 111
+ Všeobecná zdravotní pojišťovna ČR
+ 0900
+ 0900
+ 1
+
+
+ 8
+ DP80
+ 0.00
+ 2026
+ 3
+ 0
+ 2
+
+
+
+```
+
+### Formát REQUEST XML – výkonová dávka s fakturou (REQUESTTYPE=1)
+
+```xml
+
+
+ HealthCareBilling
+ 0000260026
+ HealthCareInvoice
+ 2026-03-01T00:00:00.000
+ 2026-03-01T00:00:00.000
+ 2026-03-31T00:00:00.000
+ 2025-12-01T00:00:00.000
+ 2025-12-31T00:00:00.000
+ 85
+ 09305000
+ 68366370
+ 2800046620
+ 000000
+ 2010
+ Praktický lékař pro dospělé
+ Lovosická 440/40
+ Praha 9-Prosek
+ 19000
+ MUDr. Buzalková Michaela
+ 207
+ Ročkotova 1225/1
+ Praha 4
+ 140 21
+ 0900
+ 51
+
+
+ 34
+ DP05
+ 2025
+ 12
+ 85
+ 1
+
+
+
+```
+
+**Poznámka k diakritice v REQUEST:** Medicus ukládá XML v UTF-16, ale česká diakritika v polích jako HcpPersonName, HicName apod. bývá uložena bez háčků/čárek (bug Medicusu při tvorbě XML). Toto není chyba dekódování.
+
+### Formát SERVERRESPONSE (odpověď pojišťovny)
+- Kódování: **iso-8859-2** (bytes)
+- Struktura: XML `textPKCS7`
+- `PZP_IdPodani` = přidělené podací číslo
+- `PZP_Chyba="0"` = bez chyby
+- Odpověď je podepsána pojišťovnou (PKCS7)
+
+---
+
+### Plán: skript pro automatické odeslání žádosti o seznam registrovaných
+
+### Portály pojišťoven – 3 skupiny
+
+#### Skupina 1 – Společný portál (201, 205, 207, 209, 213, 217, 228)
+- Jeden společný portál pro všechny tyto pojišťovny
+- Autentizace a podepisování: **kvalifikovaný certifikát I.CA EU Qualified CA2/RSA 06/2022**
+- SerialNumber (Buzalka): `0247068517B0049E2E`
+- Vydavatel: První certifikační autorita, a.s.
+
+#### Skupina 2 – VZP (111) – vlastní portál
+- VZP má **samostatný portál** (`UseAlternativePortal="true"` v konfiguraci)
+- Podepisování: **komerční certifikát I.CA Public CA/RSA 06/2022** (nižší stupeň než kvalifikovaný)
+ - SerialNumber: `01DE0F46B713505F1F`
+- Autentizace vůči portálu: **certifikát Komerční banky (DCS CA KB)**
+ - SerialNumber: `46E67A`
+
+#### Skupina 3 – ZPMV (211) – samostatní exoti
+- Žádný certifikát, přihlašování **heslem**
+- Konfigurace: PIN, Password, Email (uloženo v plaintextu v ED_STORAGE)
+- Email: `ordinace@buzalkova.cz`
+
+### Formáty odpovědí v PORTAL.DATA (historické)
+Různé pojišťovny vracely různé formáty:
+- **ČPZP/ZPMV** (D01 portál): XML `100D01F...`
+- **OZP a ostatní** (starý portál): XML `HTML protokol`
+ - HTML protokol je v iso-8859-2, obsahuje tabulku s detaily dávky
+
+### Kódování BLOBů v ED_BOOKOFSUBMISSIONS
+
+| Sloupec | Kódování | Poznámka |
+|---|---|---|
+| `KDAVKACONTENT` | CP1250 | fdb vrací str přes win1250 spojení – použít přímo |
+| `REQUEST` | ASCII/UTF-8 nebo UTF-16 | Detekovat podle BOM: `FF FE`/`FE FF` → utf-16, začíná `<` → utf-8 |
+| `SERVERRESPONSE` | iso-8859-2 | latin-1 re-encoding → decode iso-8859-2 |
+| `PROTOCOL` | iso-8859-2 | latin-1 re-encoding → decode iso-8859-2 |
+
+**Důležité:** Pro re-encoding str→bytes vždy používat `latin-1` (ne `cp1250`), protože latin-1 zachová všechny bajty 0–255 beze změny včetně null bajtů. Pro utf-16 nikdy nezkoušet decode bez ověření BOM – bez BOM Python tiše vrátí čínské znaky místo chyby.
+
+**Co potřebujeme ještě zjistit:**
+- URL endpointů jednotlivých portálů (zachytit přes síťový trace)
+- Jak přesně se REQUEST podepisuje certifikátem (PKCS12 / Windows certificate store)
+
+**Co už máme:**
+- Formát REQUEST XML (viz výše)
+- Certifikáty a přihlašovací údaje z ED_STORAGE
+- Strukturu odpovědi pojišťovny
+- Rozdělení pojišťoven do 3 skupin podle způsobu přístupu
+
## Kódování KDAVKA/FDAVKA – důležité!
Dávkové soubory (KDAVKA, FDAVKA) jsou uloženy v **CP852** (DOS Latin-2, prahistorické kódování).