notebook vb

This commit is contained in:
2026-03-20 18:03:44 +01:00
parent f2dc33a05e
commit 1d9c6cca48
10 changed files with 3312 additions and 2923 deletions

View File

@@ -6,6 +6,7 @@
- Zatím: úspěšně čteme i zapisujeme do DB, rozumíme RTF formátu
- **2026-03-17**: Obnovena session Claude si přečetl poznámky, připraven pokračovat
- **2026-03-18**: Obnovena session Claude si přečetl poznámky, připraven pokračovat. Průběžně zapisuje do tohoto souboru.
- **2026-03-20**: Obnovena session merge logika z `test_import_single.py` integrována do `s03soubory.py`. Import pipeline kompletní.
## Bezpečnost
- Pracujeme na **místní kopii** poškození DB nevadí, obnova = 5 minut
@@ -333,16 +334,30 @@ Správný RTF formát klikacího odkazu:
### Stav testování (2026-03-18)
- `test_import_3files.py` ✅ ověřeno klikací odkazy fungují, "Vložené přílohy:" správně
- `test_import_merge.py` připraveno, nespuštěno
- `test_import_single.py` připraveno, nespuštěno
- `test_import_merge.py` připraveno merge 3 souborů do dnešního dekurzu (bez sekce)
- `test_import_single.py` připraveno přidání 1 souboru DO existující sekce přílohy
### TODO integrace do s03soubory.py
- Přidat `najdi_posledni_dekurs_dnes()` do s03soubory.py
- Přidat `pridat_do_sekce_prilohy()` a `merge_rtf_prepend()` do s03soubory.py
- Nahradit přímý INSERT DEKURS rozhodovací logikou (3 případy)
- Pozn: s03soubory.py má starý `prvnibookmark=True` blok odstranit (relikt)
### Stav s03soubory.py (2026-03-20) KOMPLETNÍ ✅
- Merge logika plně integrována z `test_import_single.py`
- Přidány konstanty `PRILOHY_HEADER`, `PRILOHY_CLOSING`
- Přidány funkce: `najdi_posledni_dekurs_dnes()`, `ma_sekci_prilohy()`, `pridat_do_sekce_prilohy()`, `merge_rtf_prepend()`
- RTF šablona přesunuta do konstanty `RTF_TEMPLATE`
- Odstraněn dead code: `prvnibookmark`, staré šablony, nepoužívané `convert_to1250`
- `pridat_do_sekce_prilohy()` podporuje **více souborů najednou** (oproti test_import_single.py, kde byl jen 1)
- `idpac` se bere z prvního záznamu skupiny (čistěji než z `row` po skončení loopu)
### Rozhodovací logika s03soubory.py 3 případy
1. Dnešní dekurs **má** sekci `Vložené přílohy` → soubory přidány **dovnitř** sekce (`pridat_do_sekce_prilohy`)
2. Dnešní dekurs **nemá** sekci příloh → nová sekce vložena **na začátek** (`merge_rtf_prepend`)
3. Žádný dnešní dekurs → **nový** záznam (INSERT)
### Stav testovacích skriptů
- `test_import_3files.py` ✅ ověřeno klikací odkazy fungují
- `test_import_merge.py` ✅ otestováno merge 3 souborů (jen 2 případy, bez detekce sekce)
- `test_import_single.py` ✅ otestováno všechny 3 případy, základ pro s03soubory.py
## Další postup (nápady)
- Otestovat `s03soubory.py` na Windows se skutečnými soubory (všechny 3 případy)
- Napsat `rtf_to_text()` pro extrakci čistého textu z dekurzů
- Prozkoumat tabulky: LECH/LECD (léky?), POU (poukazy?), AMBULEKY (výkony?)
- První report domluvit s uživatelem co chce vidět

File diff suppressed because one or more lines are too long

View File

@@ -1,233 +1,323 @@
import os,shutil,fdb,time
import re,datetime,funkce,funkce_ext
import os, shutil, fdb, time
import re, datetime, funkce, funkce_ext
# Connect to the Firebird database
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb', # Database path
user='SYSDBA', # Username
password="masterkey", # Password,
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA',
password="masterkey",
charset="win1250")
# cesta=r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
cesta=r"u:\NextcloudOrdinace\Dokumentace_ke_zpracová"
# cestazpracovana=r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentacezpracovaná"
cestazpracovana=r"u:\NextcloudOrdinace\Dokumentace_zpracovaná"
cesta = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
cestazpracovana = r"u:\NextcloudOrdinace\Dokumentace_zpracovaná"
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
# ─── Helper funkce ────────────────────────────────────────────────────────────
def restore_files_for_import(retezec):
drop=r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
next=r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
# Check if the directory exists
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
if not os.path.exists(drop):
print(f"The directory '{drop}' does not exist.")
return
# Iterate over all files and subdirectories in the directory
for item in os.listdir(drop):
item_path = os.path.join(drop, item)
# If it's a file or a symbolic link, delete it
if os.path.isfile(item_path) or os.path.islink(item_path):
os.unlink(item_path)
print(f"Deleted file: {item_path}")
# If it's a directory, delete it recursively
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
print(f"Deleted directory: {item_path}")
for item in os.listdir(next):
item_path = os.path.join(next, item)
# If it's a file finished with PDF, copy it
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
shutil.copy(item_path,os.path.join(drop,item))
shutil.copy(item_path, os.path.join(drop, item))
print(f"Copied file: {item_path}")
def kontrola_rc(rc,connection):
def kontrola_rc(rc, connection):
cur = connection.cursor()
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac",(rc,))
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
row = cur.fetchone()
if row:
return row[1]
else:
return False
def kontrola_struktury(souborname,connection):
def kontrola_struktury(souborname, connection):
if souborname.endswith('.pdf'):
#kontrola struktury
pattern=re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
match=pattern.search(souborname)
# print(souborname)
vpohode=True
if match and len(match.groups())==5:
datum=match.group(2)
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
match = pattern.search(souborname)
vpohode = True
if match and len(match.groups()) == 5:
datum = match.group(2)
try:
datum_object = datetime.datetime.strptime(datum,"%Y-%m-%d").date()
# print(datum_object)
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
except:
vpohode=False
vpohode = False
return vpohode
cur = connection.cursor()
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
row = cur.fetchone()[0]
if row!=1:
if row != 1:
vpohode = False
return vpohode
else:
vpohode=False
vpohode = False
return vpohode
else:
vpohode=False
vpohode = False
return vpohode
return vpohode
def vrat_info_o_souboru(souborname, connection):
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
match = pattern.search(souborname)
rc = match.group(1)
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
jmeno = match.group(3)
rc = match.group(1)
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
jmeno = match.group(3)
prvnizavorka = match.group(4)
druhazavorka = match.group(5)
cur=connection.cursor()
cur.execute("select idpac from kar where rodcis=?",(rc,))
cur = connection.cursor()
cur.execute("select idpac from kar where rodcis=?", (rc,))
idpac = cur.fetchone()[0]
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta,souborname)))
return (rc,idpac,datum,jmeno,prvnizavorka,druhazavorka,souborname,datumsouboru)
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
def prejmenuj_chybny_soubor(souborname,cesta):
if souborname[0]!="":
def prejmenuj_chybny_soubor(souborname, cesta):
if souborname[0] != "":
soubornovy = "" + souborname
os.rename(os.path.join(cesta,souborname),os.path.join(cesta,soubornovy))
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
"""Vrátí (id, rtf) posledního dekurzu pacienta pokud je z dnešního dne, jinak None."""
cur = conn.cursor()
cur.execute("""
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
WHERE IDPAC = ?
ORDER BY ID DESC
""", (idpac,))
row = cur.fetchone()
if row is None:
return None
dekurs_id, dekurs_datum, dekurs_rtf = row
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
if dekurs_datum == datum_vlozeni:
print(f" → dnešní den ({datum_vlozeni}) ✓")
return (dekurs_id, dekurs_rtf)
else:
print(f" → jiný den ({dekurs_datum}{datum_vlozeni}), vytvoříme nový")
return None
# print(kontrola_struktury(ss))
# info=vrat_info_o_souboru(ss)
# print(kontrola_rc(info[0],conn))
# restore_files_for_import("")
# restore_files_for_import("346204097")
def ma_sekci_prilohy(rtf):
return PRILOHY_HEADER in rtf
info=[]
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
Postup:
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
"""
# 1. Počet existujících Files: odkazů
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
if bkm_match:
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
else:
n_files = 0
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
prilohy_pos = rtf.find(PRILOHY_HEADER)
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
if closing_pos == -1:
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
new_pards = ''
for i, fname in enumerate(filenameforbookmark_list):
idx = n_files + i
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
def append_bookmarks(m):
entries = [e for e in m.group(1).split(';') if e.strip()]
entries.extend(bookmark_list)
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
return rtf
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
rtf = existing_rtf
rtf = re.sub(r'\\bkmkstart (\d+)',
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
rtf = re.sub(r'\\bkmkend (\d+)',
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
new_bkm_str = ';'.join(new_bkm_list)
def merge_bkm(m):
existing = m.group(1).strip()
combined = new_bkm_str + (';' + existing if existing else '')
return '{\\info{\\bookmarks ' + combined + '}}'
if re.search(r'\{\\info\{\\bookmarks', rtf):
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
else:
rtf = re.sub(r'(\\deflang\d+)',
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
match = re.search(r'\\uc1\\pard', rtf)
if match:
pos = match.start()
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
return rtf
# Šablona RTF pro nový dekurs
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
BOOKMARKSTEXT
\pard\s10\plain\cs15\f0\fs20 \par
}"""
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
info = []
for soubor in os.listdir(cesta):
if os.path.isfile(os.path.join(cesta,soubor)):
if os.path.isfile(os.path.join(cesta, soubor)):
print(soubor)
if kontrola_struktury(soubor,conn):
info.append(vrat_info_o_souboru(soubor,conn))
# os.remove(os.path.join(cesta,soubor))
if kontrola_struktury(soubor, conn):
info.append(vrat_info_o_souboru(soubor, conn))
else:
prejmenuj_chybny_soubor(soubor,cesta)
prejmenuj_chybny_soubor(soubor, cesta)
info = sorted(info, key=lambda x: (x[0], x[1]))
print(info)
skupiny={}
skupiny = {}
for row in info:
skupiny[row[0]]=[]
skupiny[row[0]] = []
for row in info:
skupiny[row[0]].append(row)
# print(skupiny)
# rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES }}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
# {\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
# {\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
# \uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
# \pard\s10{\*\bkmkstart 0}\plain\cs22\f0\ul\fs20\cf1 BOOKMARKNAMESTEXT{\*\bkmkend 0}\par
# \pard\s10\plain\cs15\f0\fs20 \par
# }"""
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES }}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
\uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
BOOKMARKSTEXT
\pard\s10\plain\cs15\f0\fs20 \par
}"""
for key in skupiny.keys():
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par
BOOKMARKSTEXT
\pard\s10\plain\cs15\f0\fs20 \par
}"""
print(f"\n{'='*60}")
print(f"RC: {key}, souborů: {len(skupiny[key])}")
cislo = 9
poradi = 0
bookmark_list = []
filenameforbookmark_list = []
bookmarks_body = ''
idpac = skupiny[key][0][1]
# if key=="8257300425": #346204097
if True:
prvnibookmark=True
print(key,len(skupiny[key]))
cislo=9
poradi=0
bookmark=""
bookmarks=""
for row in skupiny[key]:
# print(row)
pacid=row[1]
filename=row[6]
fileid= funkce_ext.zapis_file_ext(vstupconnection=conn, idpac=row[1],
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
for row in skupiny[key]:
fileid = funkce_ext.zapis_file_ext(
vstupconnection=conn, idpac=row[1],
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
print(f" → FILES.ID = {fileid} ({row[6]})")
for attempt in range(3):
try:
# Replace this with the command that might raise an error
if not os.path.exists(os.path.join(cestazpracovana,row[6])):
shutil.move(os.path.join(cesta,row[6]), os.path.join(cestazpracovana,row[6]))
print("Command succeeded!")
break # Exit the loop if the command succeeds
else:
now = datetime.datetime.now()
datetime_string = now.strftime("%Y-%m-%d %H-%M-%S")
print(os.path.join(cestazpracovana,row[6][:-4]+" "+datetime_string+".pdf"))
shutil.move(os.path.join(cesta,row[6]),os.path.join(cestazpracovana,row[6][:-4]+" "+datetime_string+".pdf"))
print("Command succeeded!")
break # Exit the loop if the command succeeds
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < 3 - 1:
print(f"Retrying in {5} seconds...")
time.sleep(5)
else:
print("Max retries reached. Command failed.")
# Přesun souboru do zpracovaných
for attempt in range(3):
try:
dest = os.path.join(cestazpracovana, row[6])
if not os.path.exists(dest):
shutil.move(os.path.join(cesta, row[6]), dest)
else:
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
shutil.move(os.path.join(cesta, row[6]),
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
print(" Přesun OK!")
break
except Exception as e:
print(f" Attempt {attempt + 1} failed: {e}")
if attempt < 2:
print(" Retrying in 5 seconds...")
time.sleep(5)
else:
print(" Max retries reached. Command failed.")
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
filenameforbookmark_list.append(filenameforbookmark)
cislo += 7
filename= funkce.convert_to1250(filename)
print("Encodedfilename", filename)
filenameforbookmark=row[2].strftime('%Y-%m-%d')+" "+row[4]+": "+row[5]
bookmark=bookmark+'"'+filenameforbookmark+'","Files:'+str(fileid)+'",'+str(cislo)+";"
cislo+=7
# print(bookmark)
bookmarks += r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark + r'{\*\bkmkend ' + str(poradi) + r'}\par'
poradi += 1
bookmark=bookmark[:-1]
# bookmarks=bookmarks[:-2]
print(bookmark)
print(bookmarks)
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
poradi += 1
rtf = rtf.replace("BOOKMARKNAMES", bookmark)
rtf=rtf.replace("BOOKMARKSTEXT",bookmarks)
print(rtf)
dekursid = funkce.get_dekurs_id(conn)
datumzapisu = datetime.datetime.now().date()
caszapisu = datetime.datetime.now().time()
cur=conn.cursor()
cur.execute("insert into dekurs (id,iduzi,idprac,idodd,idpac,datum,cas,dekurs)"
" values(?,?,?,?,?,?,?,?)",
(dekursid,6,2,2, row[1],datumzapisu,caszapisu, rtf))
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
+ bookmarks_body + '\n'
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
datumzapisu = datetime.datetime.now().date()
caszapisu = datetime.datetime.now().time()
cur = conn.cursor()
print(f"\n>>> Hledám poslední dekurs pro IDPAC={idpac}...")
existujici = najdi_posledni_dekurs_dnes(conn, idpac, datumzapisu)
if existujici:
dekurs_id, existing_rtf = existujici
if ma_sekci_prilohy(existing_rtf):
# Případ 1: dnešní dekurs má sekci příloh → přidáme soubory dovnitř
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
print(">>> Přidávám soubory DO existující sekce...")
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
else:
# Případ 2: dnešní dekurs existuje, ale sekci příloh nemá → prepend
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
print("\n=== Výsledný RTF ===")
print(merged_rtf)
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
conn.commit()
# rtf = rtf.replace("FILEID", str(idfile))
#Zde zapisujeme soubor
# fileid=funkce.zapis_file(conn,row[1],cesta,row[6],row[4],row[2],row[7],row[5])
# zapis_dekurs(vstupconnection, idpac, idodd, iduzi, idprac, idfile, filename, text, datumzpravy,datumsouboru)
# return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} hotovo!")
# Zde zapisujeme dekurs
# text=row[2].strftime("%Y-%m-%d")+" "+row[4].strip()+": "+row[5].strip()
# funkce.zapis_dekurs(conn, row[1], 2, 6, 2, fileid, text, text, row[7], row[2])
# os.remove(os.path.join(cesta, soubor))
else:
# Případ 3: žádný dnešní dekurs → vytvoříme nový
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
bookmark_str = ';'.join(bookmark_list)
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
print("\n=== Výsledný RTF ===")
print(rtf)
dekursid = funkce.get_dekurs_id(conn)
cur.execute(
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
)
conn.commit()
print(f"\n>>> Nový DEKURS ID={dekursid}")
print("\n=== HOTOVO ===")
conn.close()

View File

@@ -0,0 +1,81 @@
# MedicusWithClaudeSelects SQL dotazy
## Registrovaní pacienti
Přesný select který Medicus používá pro záložku **Registrovaní** (zachycen přes FBScanner, dotaz č. 143).
### Počet registrovaných pacientů
```sql
SELECT COUNT(*) FROM KAR
WHERE (vyrazen = 'N')
AND EXISTS (
SELECT id FROM registr r
JOIN icp i ON r.idicp = i.idicp
WHERE r.idpac = kar.idpac
AND (r.datum <= '2026-03-20')
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= '2026-03-20')
AND (r.priznak IN ('V','D','A'))
AND (i.icp = '09305001')
AND (i.odb = '001')
)
```
Vrátí: **1618 pacientů** (ověřit na Windows).
### Podmínky registrace vysvětlení
- `vyrazen = 'N'` pacient není vyřazen z kartotéky
- `r.datum <= dnes` registrace již začala
- `r.datum_zruseni IS NULL OR r.datum_zruseni >= dnes` registrace dosud platí
- `r.priznak IN ('V','D','A')` aktivní příznak (ne 'Z' = zrušen, ne 'N')
- `i.icp = '09305001'` IČP naší ordinace
- `i.odb = '001'` odbornost praktický lékař
### Skript pro Python
Viz `count_registrovani.py` v této složce spustit na Windows.
### Plný select Medicusu (seznam pacientů s metadaty)
```sql
SELECT
KAR.DATNAR,
KAR.IDPAC,
KAR.INFORMACE,
KAR.INFORMACE_COL,
KAR.JMENO,
KAR.POHLAVI,
GPP.POJ,
KAR.POZNAMKA,
KAR.PRIJMENI,
KAR.PRIJMENI_UP,
(SELECT DATUM_REGISTRACE FROM SP_GETREGDAT(kar.IDPAC)) AS REGDATUM,
KAR.REGISTROVAL,
(SELECT PRIZNAK FROM SP_GETREGDAT(kar.IDPAC)) AS REGPRIZNAK,
KAR.RODCIS,
KAR.ROZENA,
KAR.TITUL,
KAR.TITULZA,
KAR.TRVOBEC,
KAR.TRVPSC,
KAR.TRVULICE,
KAR.VYRAZEN
FROM KAR
LEFT JOIN GETPACPOJ(KAR.IDPAC, '2026-03-20') GPP ON GPP.IDPAC = KAR.IDPAC
WHERE (vyrazen = 'N')
AND EXISTS (
SELECT id FROM registr r
JOIN icp i ON r.idicp = i.idicp
WHERE r.idpac = kar.idpac
AND (r.datum <= '2026-03-20')
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= '2026-03-20')
AND (r.priznak IN ('V','D','A'))
AND (i.icp = '09305001')
AND (i.odb = '001')
)
ORDER BY KAR.PRIJMENI_UP ASC, KAR.RODCIS ASC
```
Poznámka: `GETPACPOJ` a `SP_GETREGDAT` jsou uložené procedury Medicusu
fungují v kontextu Firebird připojení přes SYSDBA/masterkey.

View File

@@ -0,0 +1,27 @@
import fdb, datetime
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA', password='masterkey', charset='win1250')
cur = conn.cursor()
dnes = datetime.date.today().isoformat()
cur.execute("""
SELECT COUNT(*) FROM KAR
WHERE (vyrazen = 'N')
AND EXISTS (
SELECT id FROM registr r
JOIN icp i ON r.idicp = i.idicp
WHERE r.idpac = kar.idpac
AND (r.datum <= ?)
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
AND (r.priznak IN ('V','D','A'))
AND (i.icp = '09305001')
AND (i.odb = '001')
)
""", (dnes, dnes))
pocet = cur.fetchone()[0]
print(f'Registrovaných pacientů: {pocet}')
conn.close()

View File

@@ -0,0 +1,78 @@
"""db_bridge_vm.py VM strana souborového mostu k Medicusu.
Použití z Linuxu:
from db_bridge_vm import query
rows, columns = query("SELECT COUNT(*) FROM KAR")
print(columns, rows)
"""
import json, time, os, uuid
BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
REQUEST = os.path.join(BRIDGE_DIR, 'query_request.json')
RESPONSE = os.path.join(BRIDGE_DIR, 'query_response.json')
TIMEOUT_SEC = 30
POLL_SEC = 0.3
def query(sql, params=None, timeout=TIMEOUT_SEC):
"""Pošle SQL dotaz přes souborový most a vrátí (rows, columns).
Raises:
TimeoutError watchdog neodpověděl do timeout sekund
RuntimeError Firebird vrátil chybu
"""
# Smaž případnou starou response
if os.path.exists(RESPONSE):
os.remove(RESPONSE)
req_id = uuid.uuid4().hex
req = {'id': req_id, 'sql': sql, 'params': params or []}
with open(REQUEST, 'w', encoding='utf-8') as f:
json.dump(req, f, ensure_ascii=False)
# Čekej na odpověď
waited = 0.0
while waited < timeout:
time.sleep(POLL_SEC)
waited += POLL_SEC
if os.path.exists(RESPONSE):
with open(RESPONSE, 'r', encoding='utf-8') as f:
resp = json.load(f)
os.remove(RESPONSE)
if resp.get('status') == 'error':
raise RuntimeError(f"DB chyba: {resp.get('error')}")
return resp.get('rows', []), resp.get('columns', [])
# Timeout smaž request aby watchdog nezpracoval zastaralý dotaz
if os.path.exists(REQUEST):
os.remove(REQUEST)
raise TimeoutError(f'Watchdog neodpověděl do {timeout}s běží db_bridge_windows.py?')
def query_print(sql, params=None):
"""Spustí dotaz a vypíše výsledek přehledně."""
rows, cols = query(sql, params)
if not cols:
print('(žádné sloupce)')
return rows, cols
col_w = [max(len(str(c)), max((len(str(r[i])) for r in rows), default=0))
for i, c in enumerate(cols)]
sep = '+' + '+'.join('-' * (w + 2) for w in col_w) + '+'
fmt = '|' + '|'.join(f' {{:<{w}}} ' for w in col_w) + '|'
print(sep)
print(fmt.format(*cols))
print(sep)
for row in rows:
print(fmt.format(*[str(v) if v is not None else 'NULL' for v in row]))
print(sep)
print(f'{len(rows)} řádků')
return rows, cols
if __name__ == '__main__':
# Rychlý test
print('Testuji spojení...')
rows, cols = query('SELECT COUNT(*) AS POCET FROM KAR')
print(f'OK pacientů v KAR: {rows[0][0]}')

View File

@@ -0,0 +1,90 @@
"""db_bridge_windows.py Windows watchdog pro dotazy z Linux VM.
Spusť jednou na Windows:
python db_bridge_windows.py
Skript sleduje soubor query_request.json ve stejné složce.
Jakmile ho najde, spustí SQL dotaz proti Medicusu a zapíše výsledek
do query_response.json. Pak čeká na další dotaz.
Ukonči: Ctrl+C
"""
import fdb, json, time, os, traceback, datetime
# ── Konfigurace ───────────────────────────────────────────────────────────────
DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
USER = 'SYSDBA'
PASSWORD = 'masterkey'
CHARSET = 'win1250'
BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
REQUEST = os.path.join(BRIDGE_DIR, 'query_request.json')
RESPONSE = os.path.join(BRIDGE_DIR, 'query_response.json')
POLL_SEC = 0.5
# ── Pomocné funkce ────────────────────────────────────────────────────────────
def serialize(val):
"""Převede Python hodnoty na JSON-serializovatelné typy."""
if isinstance(val, (datetime.date, datetime.datetime)):
return val.isoformat()
if isinstance(val, datetime.time):
return val.isoformat()
if isinstance(val, bytes):
return f'<bytes len={len(val)}>'
return val
def run_query(sql, params=None):
conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET)
try:
cur = conn.cursor()
cur.execute(sql, params or [])
columns = [d[0] for d in cur.description] if cur.description else []
rows = [[serialize(v) for v in row] for row in cur.fetchall()]
return {'status': 'ok', 'columns': columns, 'rows': rows, 'error': None}
except Exception as e:
return {'status': 'error', 'columns': [], 'rows': [], 'error': str(e)}
finally:
conn.close()
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
print(f'DB Bridge spuštěn. Sleduju: {REQUEST}')
print('Ukončení: Ctrl+C\n')
while True:
try:
if os.path.exists(REQUEST):
print(f'[{datetime.datetime.now().strftime("%H:%M:%S")}] Přijat dotaz...')
with open(REQUEST, 'r', encoding='utf-8') as f:
req = json.load(f)
os.remove(REQUEST)
sql = req.get('sql', '')
params = req.get('params', [])
req_id = req.get('id', '')
result = run_query(sql, params)
result['id'] = req_id
result['sql'] = sql
with open(RESPONSE, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
if result['status'] == 'ok':
print(f' → OK, {len(result["rows"])} řádků')
else:
print(f' → CHYBA: {result["error"]}')
time.sleep(POLL_SEC)
except KeyboardInterrupt:
print('\nDB Bridge ukončen.')
break
except Exception as e:
print(f'Neočekávaná chyba: {e}')
traceback.print_exc()
time.sleep(2)

View File

@@ -0,0 +1,30 @@
"""get_kar_sortby_idlist.py přečte definici stored procedure KAR_SORTBY_IDLIST z Firebirdu.
Spustit na Windows.
"""
import fdb
conn = fdb.connect(
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
user='SYSDBA', password='masterkey', charset='win1250')
cur = conn.cursor()
cur.execute("""
SELECT RDB$PROCEDURE_SOURCE FROM RDB$PROCEDURES
WHERE RDB$PROCEDURE_NAME = 'KAR_SORTBY_IDLIST'
""")
row = cur.fetchone()
if row and row[0]:
print(row[0])
else:
# Možná je to funkce (FUNCTION), ne procedure
cur.execute("""
SELECT RDB$FUNCTION_SOURCE FROM RDB$FUNCTIONS
WHERE RDB$FUNCTION_NAME = 'KAR_SORTBY_IDLIST'
""")
row = cur.fetchone()
if row and row[0]:
print(row[0])
else:
print("Nenalezeno ani jako PROCEDURE ani jako FUNCTION.")
conn.close()

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,74 @@
{
"status": "ok",
"columns": [
"ID",
"DATUM",
"CAS",
"UZIVATEL",
"IDPAC",
"TABULKA",
"IDREC",
"AKCE",
"DETAIL"
],
"rows": [
[
2882053,
"2026-03-20",
"17:43:50.523000",
"VBU ",
3234,
10000,
3234,
"V",
null
],
[
2882052,
"2026-03-20",
"17:43:46.435000",
"VBU ",
3234,
10000,
3234,
"V",
null
],
[
2882051,
"2026-03-20",
"17:24:19.777000",
"VBU ",
4757,
10000,
4757,
"V",
null
],
[
2882050,
"2026-03-20",
"17:24:15.866000",
"VBU ",
3234,
10000,
3234,
"V",
null
],
[
2882049,
"2026-03-20",
"07:11:41.434000",
"VBU ",
4757,
10000,
4757,
"V",
null
]
],
"error": null,
"id": "11859d529a784bedb653030ba60131de",
"sql": "SELECT FIRST 5 * FROM LOG ORDER BY ID DESC"
}