notebookvb

This commit is contained in:
Vladimir Buzalka
2026-04-29 06:51:47 +02:00
parent a1b9c93506
commit a9c143ba24
141 changed files with 30711 additions and 0 deletions
@@ -0,0 +1,252 @@
# Fakturace a dávky poznámky pro Clauda
## Přehled tabulek
### FAK faktury pojišťovnám
- **844 záznamů** (k 2026-03-28)
- Primární klíč: `ID`
| Sloupec | Popis |
|---|---|
| `ID` | primární klíč |
| `CISFAK` | číslo faktury (10 znaků) |
| `POJ` | pojišťovna (3 znaky, např. 111=VZP, 205=ČPZP, 207=OZP) |
| `ICZ`, `ICZ1` | IČZ ordinace |
| `IDICZ` | FK → ICZ.IDICZ (jen jedna ordinace, neřešit) |
| `PORCISLO` | pořadové číslo |
| `DATUMOD`, `DATUMDO` | období faktury (oddo) |
| `DATVYS` | datum vystavení |
| `DATODE` | datum odeslání |
| `OBDOB` | období (5 znaků, např. "20260") |
| `VYKONY` | částka za výkony (Kč) |
| `KAPITACE` | kapitační platba (Kč) |
| `ZALOHA` | záloha |
| `CENA` | celková cena faktury (Kč) |
| `DRUH` | druh faktury (např. "konečná") |
| `TYP` | typ (1 znak) |
| `ROK` | rok |
| `SPLAT` | datum splatnosti |
| `PROPLACENO` | datum proplacení (NULL = nezaplaceno) |
| `ZAPLACENO` | zaplacená částka |
| `ZUM` | ? |
| `HOSPAUSAL` | hospitalizační/paušální? |
| `FDAVKA` | BLOB odkaz na dávku |
| `KAPDETAIL`, `AGRDETAIL` | BLOBy detaily kapitace/agregace |
| `NAZFAK`, `POZFAK`, `OBDFAK` | název, poznámka, období faktury (texty) |
| `ICO`, `BANKA`, `UCET` | IČO, banka, účet |
| `ODJMENO/ULICE/MISTO/PSC` | adresa odesílatele |
| `PLNAZEV/ULICE/MISTO/PSC` | adresa plátce (pojišťovny) |
| `DRUHPOJ` | druh pojištění |
---
### FAKDET finanční detail faktury
- **1021 záznamů**
- Vazba: `IDFAK` → FAK.ID
- Typicky 12 řádky na fakturu (breakdown podle lékaře/IDUZI)
| Sloupec | Popis |
|---|---|
| `ID` | primární klíč |
| `IDFAK` | FK → FAK.ID |
| `ICP` | IČP ordinace (např. 09305001) |
| `ODB` | odbornost (001 = praktický lékař) |
| `IDUZI` | FK → UZIVATEL.IDUZI který lékař |
| `CENAVYK` | výkony (Kč) |
| `CENALEC` | léky (Kč) |
| `CENAKAP` | kapitace (Kč) |
**Poznámka:** IDUZI=0 = systémový záznam (kapitace bez konkrétního lékaře).
---
### FAKDAV dávky zahrnuté ve faktuře
- **1296 záznamů**
- Vazba: `IDFAK` → FAK.ID
| Sloupec | Popis |
|---|---|
| `ID` | primární klíč |
| `IDFAK` | FK → FAK.ID |
| `CISDAV` | číslo dávky |
| `DRUH` | druh dávky |
| `CENA` | celková cena dávky |
| `CENAVYK` | výkony |
| `CENALEC` | léky |
| `CENAPAU` | paušál |
| `ODB` | odbornost |
| `ICP` | IČP |
---
### PORTAL elektronické podání pojišťovně
- **180 záznamů**
- Vazba: `IDFAK` → FAK.ID
| Sloupec | Popis |
|---|---|
| `ID` | primární klíč |
| `IDFAK` | FK → FAK.ID |
| `ODESLANO` | timestamp odeslání |
| `DATA`, `KDAVKA`, `FDAVKA` | BLOBy pravděpodobně XML dávky |
| `CHYBA` | příznak chyby (1 znak) |
| `STAV` | stav podání (smallint) |
| `ID_PODANI` | ID podání u pojišťovny (32 znaků, UUID) |
| `IDPODANI` | interní ID podání |
| `IDCERT` | certifikát použitý pro podání |
| `DAVKA_ROK`, `DAVKA_DISK`, `DAVKA_IDICZ` | metadata dávky |
| `BB_DAVKA`, `BB_FAKTURA` | ? |
| `DAVKA_DATUMOD`, `DAVKA_DATUMDO` | období dávky |
| `DAVKA_CASTKA` | částka dávky |
---
## Schéma vazeb
```
FAK (faktura)
├── FAKDET (IDFAK → FAK.ID) finanční breakdown podle lékaře
├── FAKDAV (IDFAK → FAK.ID) seznam dávek ve faktuře
├── PORTAL (IDFAK → FAK.ID) viz níže, IDFAK bývá NULL
└── ICZ (IDICZ → ICZ.IDICZ) ordinace (jen jedna, neřešit)
```
---
## UZIVATEL lékaři a uživatelé
| IDUZI | Příjmení | Jméno | Zkratka | Aktivní |
|---|---|---|---|---|
| 2 | Jourová | Jana | JOU | F (neaktivní) |
| 3 | Sestra | — | SES | F (neaktivní) |
| 4 | Buzalková | Michaela | MBU | T **lékařka, manželka** |
| 5 | Ševčíková | Ivana | ISE | T |
| 6 | Buzalka | Vladimír | VBU | T **uživatel (já)** |
---
## Ukázkové záznamy
### FAK ID=856 (poslední)
- Faktura č. 260026, pojišťovna **205 (ČPZP)**, prosinec 2025
- Vystavena: 2026-03-12, druh: konečná
- VYKONY: 2 528,99 Kč, KAPITACE: 0, CENA: **2 528,99 Kč**
- ZAPLACENO: NULL, PROPLACENO: NULL
FAKDET k FAK ID=856:
| ID | IDUZI | CENAVYK | CENALEC | CENAKAP |
|---|---|---|---|---|
| 1034 | 0 (systém) | 0 | 0 | 1 902,54 |
| 1035 | 4 (Buzalková) | 2 528,99 | 0 | 0 |
### FAK ID=855 (předposlední)
- Faktura č. 260026, pojišťovna **207 (OZP)**, prosinec 2025
- Vystavena: 2026-03-01, druh: konečná
- VYKONY: 85 Kč, KAPITACE: 0, CENA: **85 Kč**
- ZAPLACENO: NULL, PROPLACENO: NULL
FAKDET k FAK ID=855:
| ID | IDUZI | CENAVYK | CENALEC | CENAKAP |
|---|---|---|---|---|
| 1033 | 6 (Buzalka Vladimír) | 85 | 0 | 0 |
---
## PORTAL upřesnění (zjištěno 2026-03-28)
PORTAL **nesouvisí s fakturací**. Je to samostatná agenda evidence **registračních dávek** odeslaných pojišťovně.
### Co PORTAL skutečně je
- Medicus přes PORTAL odesílá pojišťovně dávky s přihlášením/odhlášením pacientů k lékaři (MUDr. Buzalková jako praktický lékař)
- Bez peněz čistě administrativní agenda registrací
- **IDFAK = NULL** u všech registračních podání (není vazba na fakturu)
### Klíčové sloupce
| Sloupec | Popis |
|---|---|
| `KDAVKA` | odeslaná dávka pevný formát (hlavička + řádky I = pacienti, RC, datum) |
| `DATA` | odpověď pojišťovny jako XML (`<statuscode>100</statuscode>` = OK) |
| `ID_PODANI` | ID přidělené pojišťovnou (např. `D01F260118593316.D01`) |
| `DAVKA_DISK` | číslo dávky v roce (sekvenční) |
| `DAVKA_ROK` | rok dávky |
| `DAVKA_DATUMOD/DO` | období registrací v dávce |
| `CHYBA` | F = bez chyby |
### Ukázka KDAVKA (hlavička dávky)
```
DP80093050000900202506 15 1 0 0.00180:6.2.46
H21109305001 179202506001
I 1Příjmení Jméno RC datum_registrace
I 2...
```
### Ukázka DATA (odpověď pojišťovny)
```xml
<ekomunikace timestamp="20260127 063105">
<session id="ff59b436-...">
<statusline>Přijetí registrací proběhlo úspěšně</statusline>
<statuscode>100</statuscode>
<idpodani>D01F260118593316.D01</idpodani>
</session>
</ekomunikace>
```
---
## PORTAL dva typy dávek
### Typ 1 Registrační dávka (DP80)
- KDAVKA začíná `DP80`
- **IDFAK = NULL** bez vazby na fakturu
- Bez peněz (`DAVKA_CASTKA` = NULL)
- Přihlašuje/odhlašuje pacienty k lékaři (MUDr. Buzalková)
- Řádky `I` = pacient (RC, datum registrace)
### Typ 2 Výkonová dávka (DP98)
- KDAVKA začíná `DP98`
- **IDFAK = ID faktury** napojeno na FAK
- Má peníze (`DAVKA_CASTKA`, např. 15 452,91 Kč)
- `STAV = 10` (registrační mají NULL)
- Obsahuje výkony vykázané pojišťovně za dané období
**Struktura výkonové dávky (KDAVKA):**
```
DP98... hlavička dávky (IČP, rok, měsíc, disk, počet případů, částka)
A ... ambulantní případ (RC pacienta, diagnóza)
V ... výkon (datum, kód výkonu, body)
Z ... ZULP (zvlášť účtovaný léčivý přípravek)
L ... lékový řádek (kód, množství, cena)
DP05... druhá část dávky (prevence / jiný typ)
P ... preventivní prohlídka
```
**Odpověď pojišťovny pro výkonovou dávku:**
```xml
<statusline>Přijetí faktury proběhlo úspěšně</statusline>
<statuscode>100</statuscode>
```
---
## Poznámky
- Faktury jsou vystavovány zvlášť pro každou pojišťovnu
- FAKDET má řádky zvlášť pro každého lékaře (IDUZI)
- IDUZI=0 v FAKDET = systémový záznam pro kapitaci
- 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
## 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í).
fdb je čte přes connection charset win1250 a vrací je jako Python `str` (špatně dekódované).
**Správný postup dekódování:**
```python
# fdb vrátil string dekódovaný jako win1250 musíme to zvrátit
raw_bytes = s.encode('cp1250', errors='replace')
spravny_text = raw_bytes.decode('cp852', errors='replace')
```
- **KDAVKA, FDAVKA** → cp852
- **DATA** (XML odpověď pojišťovny) → cp1250 (win1250), fdb dekóduje správně
+316
View File
@@ -0,0 +1,316 @@
# 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.
---
## Panel pacienta hlavní UNION dotaz
Zachycen přes Firebird AuditTrace (`default_trace.log`) při otevření karty pacienta
(IDPAC=9733, datum 28.03.2026). Dotaz vrací **28 UNION částí** každá část má pevnou
strukturu sloupců: `ID, VAR1, VAR2, DATE1, DATE2, TIME1, INT1, TEXT1, NUM1, NUM2`.
Parametry při volání:
- `:IDPAC` ID pacienta (např. 9733)
- `:RODCIS` rodné číslo pacienta (např. '0308020152')
- `:DATUM` dnešní datum ve formátu YYYY-MM-DD (např. '2026-03-28')
- `:DATUM_CZ` dnešní datum ve formátu DD.MM.YYYY (např. '28.03.2026')
- `:ROK` aktuální rok (např. '2026')
### Mapování UNION části → položka panelu Medicusu
| ID (UNION část) | Tabulka | Položka v panelu Medicusu |
|------------------|--------------------|--------------------------------------------------|
| `BalickyPac` | BALICKYPAC | Balíčky pacienta (aktivní/budoucí) |
| `Dluh` | PLA / PLADET | Dluh (nezaplacené faktury po splatnosti) |
| `SouhlasPac` | HISTDOC / SOUHLASPACSABL | Souhlasy pacienta |
| `sCenaVykZUM` | DOKLADD / LECD | Cena výkonů v aktuálním roce (ZUM) |
| `Registrl` | KAR | Kdo registroval pacienta |
| `OseLekPrak` | KARUZIV_SEL | Ošetřující lékař (odb. 001/002) |
| `SledLek` | SLEDLEK | Sledující lékař (specialista) |
| `HistDoc` | HISTDOC | Historie dokumentů (posledních 10) |
| `LastSms` | SMS | Datum poslední odeslané SMS |
| `PozadLekar` | DOKLADH | Požadující lékař (EICZ) z posledního dokladu |
| `Prilohy` | FILES | Přílohy pacienta (posledních 10) |
| `Objednavky` | OBJOBJ | Objednávky od dnešního dne |
| `OseLek` | KARUZIV_SEL | Všichni lékaři přiřazení ke kartě |
| `PeProhlidky` | PREH / PREINIH | Preventivní a examinační prohlídky |
| `Medikace` | MEDIKACE | Aktuální medikace (platná k dnešku) |
| `NextDispenz` | DISPAC / DISSKU | Příští dispenzarizace (s termínem) |
| `Dispenz` | DISPAC / DISSKU | Dispenzarizace všechny záznamy |
| `Prohlidky` | PREPRI / PREINIH | Preventivní prohlídky záznamy |
| `NextOck` | OCKPRI / KLK | Příští očkování (plánované) |
| `LastVykon` | DOKLADD / DOKLADH | Poslední výkon (kód + datum) |
| `LastDekurs` | DEKURS | Poslední dekurz (datum) |
| `Karta` | KAR | Informace a poznámka z karty |
| `Saldo` | PACIENT_SALDO(SP) | Saldo pacienta (funkce) |
| `Anamneza` | ANAMNEZA | Anamnéza + krevní skupina |
| `Ockovani` | OCKZAZ | Očkovací záznamy (max 20, seskupeno po látce) |
| `NeschopenOd` | NES | Aktuální neschopenka (od do) |
| `Alergie` | ANAMNEZA | Alergie (z posledního záznamu anamnézy) |
| `Pojistovna` | ICP / ICZ | IDICP naší ordinace pro pojišťovnu pacienta |
### SQL dotaz (parametrizovaný)
```sql
SELECT
cast('BalickyPac' as varchar(11)) as ID, substring(cast(BPAC.KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(BPAC.CENPASMO as VARCHAR(70)) from 1 for 30) as VAR2, cast(BPAC.DATUMOD as DATE) as DATE1, cast(BPAC.DATUMDO as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from BALICKYPAC BPAC left join SP_BALICKYPAC_PRIZNAK(BPAC.ID, ':DATUM_CZ') PRI on 1 = 1 where BPAC.IDPAC = :IDPAC and PRI.PRIZNAK in ('A', 'B')
UNION SELECT
cast('Dluh' as varchar(11)) as ID, substring(cast(P.MENA as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast((SUM(P.CENA - P.SLEVAC) - (SUM(P.PLATBA) + SUM((COALESCE((select SUM(case ZD.TYP when 'R' then ZD.CELKEM else -ZD.CELKEM end) from PLADET ZD where ZD.IDPLA = P.IDPLA and (ZD.TYP <> P.DOKLADTYP) and (ZD.TYP <> '|') and ((ZD.CENA < 0) or (ZD.TYP = 'R'))), 0))))) as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM PLA P WHERE (P.IDPAC = :IDPAC) AND (P.DOKLADTYP = 'F') AND (P.STORNO IS NULL) AND (P.NENISALDO = 'F') AND ((P.SPLATNOST IS NULL) OR (P.SPLATNOST < ':DATUM')) AND (P.VALID = 'F') GROUP BY P.MENA
UNION SELECT
cast('SouhlasPac' as varchar(11)) as ID, substring(cast(case when S.NAZEV is null then case when H.TYP = 'ZSOUPOS' then 'Souhlas/Nesouhlas s poskytnutím zdravotních služeb nezletilému' when H.TYP = 'ZSOUPOZ' then 'Souhlas zákonného zástupce nezletilého pacienta staršího 15ti let' when H.TYP = 'ZSOUPO2' then 'Nesouhlas s poskytnutím zdravotních služeb - povinné očkování' when H.TYP = 'ZPOSIN2' then 'Určení osoby oprávněné dle zákona o zdravotních službách' when H.TYP = 'OdmPece' then 'Prohlášení o odmítnutí zdravotní péče pacientem - Negativní revers' end else S.NAZEV end as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(H.DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC H left join SOUHLASPACSABL S on H.IDSOUHLASPACSABL = S.ID where H.TYP in ('IndSou', 'ZSOUPOS', 'ZSOUPOZ', 'ZSOUPO2', 'ZPOSIN2', 'OdmPece') and H.IDPACI = :IDPAC
UNION SELECT
cast('sCenaVykZUM' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(coalesce(sum(cenabod),0) + coalesce(sum(cenamat),0) as NUMERIC(15,2)) as NUM1, cast((select coalesce(sum(cena),0) from LECD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N')) and exists (select h.IDLEC from LECH h where h.IDLEC = d.IDLEC and h.POJ = '207' and h.ICZ in ('09305001'))) as NUMERIC(15,2)) as NUM2 from DOKLADD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N' and d.KAT <> 'K' and d.KAT <> 'A')) and exists (select h.IDHLAV from DOKLADH h where h.IDHLAV = d.IDHLAV and h.POJ = '207' and h.ICZ in ('09305001'))
UNION SELECT
cast('Registrl' as varchar(11)) as ID, substring(cast(REGISTROVAL as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
UNION SELECT
cast('OseLekPrak' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(CAST(ODBORN as INTEGER) as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(:IDPAC, 'T') WHERE ODBORN in ('001', '002')
UNION SELECT
cast('SledLek' as varchar(11)) as ID, substring(cast(KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(LEK as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SLEDLEK where IDPAC = :IDPAC and DATUM <= ':DATUM'
UNION SELECT
first 10 cast('HistDoc' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC where IDPACI = :IDPAC and STAV is NULL and IDZARPR = 2 and IDODDPR = 2 and IDPRACPR = 2
UNION SELECT
first 1 cast('LastSms' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(SENDTIME) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SMS where PACID = :IDPAC and SENDTIME is not NULL and not(STATUS in (100,1000))
UNION SELECT
first 1 cast('PozadLekar' as varchar(11)) as ID, substring(cast(H.EICZ as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(H.EODZ as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADH H where H.IDHLAV = (select first 1 I.IDHLAV from DOKLADH I where I.RODCIS = ':RODCIS' and I.EICZ is not NULL order by I.IDHLAV desc)
UNION SELECT
first 10 cast('Prilohy' as varchar(11)) as ID, substring(cast(FILENAME as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from FILES where IDPAC = :IDPAC
UNION SELECT
first 10 cast('Objednavky' as varchar(11)) as ID, substring(cast(F_CONCAT(U.PRIJMENI, F_CONCAT(U.JMENO, U.TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(PRAC as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(CAS as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OBJOBJ O join PRACOVISTE P on (P.ID = O.IDPRAC) join UZIVATEL U on (U.IDUZI = O.IDUZI) where IDPAC = :IDPAC and DATUM >= ':DATUM_CZ'
UNION SELECT
cast('OseLek' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(:IDPAC, 'T')
UNION SELECT
cast('PeProhlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(TERMIN as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREH join PREINIH on (PREH.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC
UNION SELECT
cast('Medikace' as varchar(11)) as ID, substring(cast(NAZ as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(PLATI_OD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from MEDIKACE where IDPAC = :IDPAC and PLATI_OD <= ':DATUM_CZ' and (PLATI_DO >= ':DATUM_CZ' or PLATI_DO is NULL)
UNION SELECT
cast('NextDispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(PRISTI as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC and PRISTI is not NULL
UNION SELECT
cast('Dispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATZAR as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC
UNION SELECT
cast('Prohlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREPRI join PREINIH on (PREPRI.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC and datum is not null
UNION SELECT
cast('NextOck' as varchar(11)) as ID, substring(cast(coalesce(NAZ,ZKRATKA) as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUMD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OCKPRI o left join KLK k on o.ZKRATKA = k.KOD where IDPAC = :IDPAC
UNION SELECT
first 1 cast('LastVykon' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(D.KOD as VARCHAR(70)) from 1 for 30) as VAR2, cast(D.DATOSE as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADD D where D.ID = (select first 1 dd.id from dokladd dd join dokladh dh on (dh.idhlav = dd.idhlav) where dd.rodcis = ':RODCIS' and (dh.hodb = '001' or dh.hodb is null) order by dd.datose desc)
UNION SELECT
first 1 cast('LastDekurs' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DEKURS where IDPAC = :IDPAC and (IDPRAC = 2 or IDPRAC = -1)
UNION SELECT
first 1 cast('Karta' as varchar(11)) as ID, substring(cast(INFORMACE as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(CIZINEC as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(INFORMACE_COL as INTEGER) as INT1, POZNAMKA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
UNION SELECT
first 1 cast('Saldo' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(SALDO as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PACIENT_SALDO(:IDPAC, 1, 0, 0)
UNION SELECT
first 1 cast('Anamneza' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(KREVSKUP as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ANAMNEZA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC order by DATUM DESC, ID desc)
UNION SELECT
first 20 cast('Ockovani' as varchar(11)) as ID, substring(cast(ockzaz.LATKA as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(ockzaz.ZKRATKA as VARCHAR(70)) from 1 for 30) as VAR2, cast(max(ockzaz.DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ockzaz where ockzaz.idpac = :IDPAC group by ockzaz.ZKRATKA, ockzaz.LATKA
UNION SELECT
first 1 cast('NeschopenOd' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(ZACNES) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from NES where (IDPAC = :IDPAC) and (ZACNES <= ':DATUM_CZ') and ((KONNES is NULL) or (KONNES > ':DATUM_CZ')) and (STORNO = 'F')
UNION SELECT
first 1 cast('Alergie' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ALERGIE as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where IDPAC = :IDPAC and ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC and DATUM <= ':DATUM_CZ' order by DATUM desc, ID desc)
UNION SELECT
first 1 cast('Pojistovna' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(P.IDICP as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ICP P join ICZ Z on (Z.IDICZ = P.IDICZ) where Z.POJ = '207' and P.ODB = '001'
```
---
## Ošetřující lékař pacienta
### Kde je uložen
Ošetřující lékař není přímo v tabulce `KAR`. Je uložen v tabulce **`KARUZIV`** a čte se
přes stored procedure **`KARUZIV_SEL`**.
### Tabulka KARUZIV
Vazba pacient → lékař. Jeden pacient může mít více záznamů (více lékařů/pracovišť).
| Sloupec | Popis |
|------------|-------|
| `IDPAC` | FK na KAR pacient |
| `IDLEKAR` | FK na LEKARI externí lékař (specialista, cizí ordinace) |
| `IDUZI` | FK na UZIVATEL interní uživatel Medicusu (vlastní lékař) |
| `IDPRAC` | FK na PRACOVISTE pracoviště |
| `IDODD` | FK na ODDEL oddělení |
| `AUTOMAT` | `'F'` = ručně přiřazen, `'T'` = automaticky |
Pokud je vyplněn `IDLEKAR` → jde se do tabulky `LEKARI` (cizí lékaři).
Pokud je vyplněn `IDUZI` → jde se do tabulky `UZIVATEL` (lékaři v Medicusu).
### Tabulka REGISTR
Druhý zdroj registrace pacienta u lékaře/pojišťovny.
| Sloupec | Popis |
|------------------|-------|
| `IDPAC` | FK na KAR |
| `IDICP` | FK na ICP identifikace pracoviště/pojišťovny |
| `IDUZI` | FK na UZIVATEL lékař (nepovinný, dohledává se přes ICP) |
| `DATUM` | Datum začátku registrace |
| `DATUM_ZRUSENI` | Datum zrušení (NULL = stále platná) |
| `PRIZNAK` | `'V'`/`'D'`/`'A'` = aktivní; `'Z'`/`'N'` = zrušená/neaktivní |
### Stored procedure KARUZIV_SEL(IIDPAC, INCL_AUTOMAT)
Parametry:
- `IIDPAC` IDPAC pacienta
- `INCL_AUTOMAT` `'T'` = vrátit i automaticky přiřazené, `'F'` = jen ruční
Vrací sloupce: `ID, IDPAC, IDODD, ODD, IDUZI, IDPRAC, IDLEKAR, AUTOMAT, TITUL, PRIJMENI, JMENO, TITUL2, ODBORN`
**Logika (3 průchody):**
1. `KARUZIV` kde `AUTOMAT = 'F'` ručně přiřazení lékaři
2. `KARUZIV` kde `AUTOMAT = 'T'` automaticky přiřazení (jen pokud `INCL_AUTOMAT = 'T'`)
3. `REGISTR` aktivní registrace (datum platný, `PRIZNAK``'Z'`/`'N'`, nezrušená)
- přes `IDICP``ICP``PRACOVISTE``PRACUZIV``UZIVATEL`
### Použití v panelu pacienta (UNION dotaz)
```sql
-- Ošetřující lékař praktický (odbornost 001 nebo 002)
SELECT ... FROM KARUZIV_SEL(:IDPAC, 'T') WHERE ODBORN in ('001', '002')
-- → UNION část ID = 'OseLekPrak'
-- Všichni lékaři přiřazení ke kartě
SELECT ... FROM KARUZIV_SEL(:IDPAC, 'T')
-- → UNION část ID = 'OseLek'
```
### Zapojené tabulky (přehled)
```
KAR
└── KARUZIV ──► LEKARI (externí lékaři, specialisté)
└► UZIVATEL (interní lékaři v Medicusu)
└► PRACOVISTE (pracoviště / odbornost)
└► ODDEL (oddělení)
└── REGISTR ──► ICP (identifikace pracoviště)
└► PRACOVISTE ──► PRACUZIV ──► UZIVATEL
```
### Barevné rozlišení v GUI Medicusu
- **Černá** = záznam pochází z `KARUZIV` (explicitně přiřazený ošetřující lékař, `IDUZI` vyplněno)
- **Červená** = záznam pochází z `REGISTR` (registrující lékař SP vrací `ID = 0 - REGISTR.ID`)
- **Červená (ext.)** = záznam z `KARUZIV` kde je vyplněno `IDLEKAR` (externí lékař z tabulky `LEKARI`)
### Duplikát ošetřujícího lékaře known issue
`KARUZIV_SEL` prochází **vždy oba zdroje** (KARUZIV i REGISTR) bez ohledu na to, zda už byl lékař nalezen. Pokud má pacient záznam v KARUZIV (černá) i v REGISTR (červená) se stejným lékařem, zobrazí se **dvakrát**.
Příčina: SP neobsahuje podmínku „přeskoč REGISTR, pokud KARUZIV již vrátil výsledky".
**Stav ordinace Buzalková (duben 2026):**
- Všech 1620 registrovaných pacientů má v `KARUZIV` záznam IDUZI=4 (Michaela, černá)
- 1537 pacientů má v `REGISTR` IDUZI=4 (Michaela, červená) → duplikát
- Chování je konzistentní, ale GUI zobrazuje oba řádky čeká se na vyjádření supportu Medicusu
**Možná řešení (zatím neaplikováno):**
- A) Smazat KARUZIV záznamy → zůstane jen červená z REGISTR (jeden řádek)
- B) Nastavit REGISTR.IDUZI zpět na NULL → REGISTR path hledá přes PRACOVISTE (najde Michalu jako první NOSVYK='A') → duplikát stále, ale přes jiný lookup
- C) Řešení přes support Medicusu
@@ -0,0 +1,83 @@
SELECT
cast('BalickyPac' as varchar(11)) as ID, substring(cast(BPAC.KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(BPAC.CENPASMO as VARCHAR(70)) from 1 for 30) as VAR2, cast(BPAC.DATUMOD as DATE) as DATE1, cast(BPAC.DATUMDO as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from BALICKYPAC BPAC left join SP_BALICKYPAC_PRIZNAK(BPAC.ID, ':DATUM_CZ') PRI on 1 = 1 where BPAC.IDPAC = :IDPAC and PRI.PRIZNAK in ('A', 'B')
UNION SELECT
cast('Dluh' as varchar(11)) as ID, substring(cast(P.MENA as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast((SUM(P.CENA - P.SLEVAC) - (SUM(P.PLATBA) + SUM((COALESCE((select SUM(case ZD.TYP when 'R' then ZD.CELKEM else -ZD.CELKEM end) from PLADET ZD where ZD.IDPLA = P.IDPLA and (ZD.TYP <> P.DOKLADTYP) and (ZD.TYP <> '|') and ((ZD.CENA < 0) or (ZD.TYP = 'R'))), 0))))) as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM PLA P WHERE (P.IDPAC = :IDPAC) AND (P.DOKLADTYP = 'F') AND (P.STORNO IS NULL) AND (P.NENISALDO = 'F') AND ((P.SPLATNOST IS NULL) OR (P.SPLATNOST < ':DATUM')) AND (P.VALID = 'F') GROUP BY P.MENA
UNION SELECT
cast('SouhlasPac' as varchar(11)) as ID, substring(cast(case when S.NAZEV is null then case when H.TYP = 'ZSOUPOS' then 'Souhlas/Nesouhlas s poskytnutím zdravotních služeb nezletilému' when H.TYP = 'ZSOUPOZ' then 'Souhlas zákonného zástupce nezletilého pacienta staršího 15ti let' when H.TYP = 'ZSOUPO2' then 'Nesouhlas s poskytnutím zdravotních služeb - povinné oèkování' when H.TYP = 'ZPOSIN2' then 'Urèení osoby oprávnìné dle zákona o zdravotních službách' when H.TYP = 'OdmPece' then 'Prohlášení o odmítnutí zdravotní péèe pacientem - Negativní revers' end else S.NAZEV end as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(H.DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC H left join SOUHLASPACSABL S on H.IDSOUHLASPACSABL = S.ID where H.TYP in ('IndSou', 'ZSOUPOS', 'ZSOUPOZ', 'ZSOUPO2', 'ZPOSIN2', 'OdmPece') and H.IDPACI = :IDPAC
UNION SELECT
cast('sCenaVykZUM' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(coalesce(sum(cenabod),0) + coalesce(sum(cenamat),0) as NUMERIC(15,2)) as NUM1, cast((select coalesce(sum(cena),0) from LECD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N')) and exists (select h.IDLEC from LECH h where h.IDLEC = d.IDLEC and h.POJ = '207' and h.ICZ in ('09305001'))) as NUMERIC(15,2)) as NUM2 from DOKLADD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N' and d.KAT <> 'K' and d.KAT <> 'A')) and exists (select h.IDHLAV from DOKLADH h where h.IDHLAV = d.IDHLAV and h.POJ = '207' and h.ICZ in ('09305001'))
UNION SELECT
cast('Registrl' as varchar(11)) as ID, substring(cast(REGISTROVAL as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
UNION SELECT
cast('OseLekPrak' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(CAST(ODBORN as INTEGER) as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(9733, 'T') WHERE ODBORN in ('001', '002')
UNION SELECT
cast('SledLek' as varchar(11)) as ID, substring(cast(KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(LEK as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SLEDLEK where IDPAC = :IDPAC and DATUM <= ':DATUM'
UNION SELECT
first 10 cast('HistDoc' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC where IDPACI = :IDPAC and STAV is NULL and IDZARPR = 2 and IDODDPR = 2 and IDPRACPR = 2
UNION SELECT
first 1 cast('LastSms' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(SENDTIME) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SMS where PACID = :IDPAC and SENDTIME is not NULL and not(STATUS in (100,1000))
UNION SELECT
first 1 cast('PozadLekar' as varchar(11)) as ID, substring(cast(H.EICZ as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(H.EODZ as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADH H where H.IDHLAV = (select first 1 I.IDHLAV from DOKLADH I where I.RODCIS = ':RODCIS' and I.EICZ is not NULL order by I.IDHLAV desc)
UNION SELECT
first 10 cast('Prilohy' as varchar(11)) as ID, substring(cast(FILENAME as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from FILES where IDPAC = :IDPAC
UNION SELECT
first 10 cast('Objednavky' as varchar(11)) as ID, substring(cast(F_CONCAT(U.PRIJMENI, F_CONCAT(U.JMENO, U.TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(PRAC as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(CAS as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OBJOBJ O join PRACOVISTE P on (P.ID = O.IDPRAC) join UZIVATEL U on (U.IDUZI = O.IDUZI) where IDPAC = :IDPAC and DATUM >= ':DATUM_CZ'
UNION SELECT
cast('OseLek' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(9733, 'T')
UNION SELECT
cast('PeProhlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(TERMIN as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREH join PREINIH on (PREH.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC
UNION SELECT
cast('Medikace' as varchar(11)) as ID, substring(cast(NAZ as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(PLATI_OD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from MEDIKACE where IDPAC = :IDPAC and PLATI_OD <= ':DATUM_CZ' and (PLATI_DO >= ':DATUM_CZ' or PLATI_DO is NULL)
UNION SELECT
cast('NextDispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(PRISTI as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC and PRISTI is not NULL
UNION SELECT
cast('Dispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATZAR as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC
UNION SELECT
cast('Prohlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREPRI join PREINIH on (PREPRI.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC and datum is not null
UNION SELECT
cast('NextOck' as varchar(11)) as ID, substring(cast(coalesce(NAZ,ZKRATKA) as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUMD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OCKPRI o left join KLK k on o.ZKRATKA = k.KOD where IDPAC = :IDPAC
UNION SELECT
first 1 cast('LastVykon' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(D.KOD as VARCHAR(70)) from 1 for 30) as VAR2, cast(D.DATOSE as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADD D where D.ID = (select first 1 dd.id from dokladd dd join dokladh dh on (dh.idhlav = dd.idhlav) where dd.rodcis = ':RODCIS' and (dh.hodb = '001' or dh.hodb is null) order by dd.datose desc)
UNION SELECT
first 1 cast('LastDekurs' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DEKURS where IDPAC = :IDPAC and (IDPRAC = 2 or IDPRAC = -1)
UNION SELECT
first 1 cast('Karta' as varchar(11)) as ID, substring(cast(INFORMACE as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(CIZINEC as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(INFORMACE_COL as INTEGER) as INT1, POZNAMKA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
UNION SELECT
first 1 cast('Saldo' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(SALDO as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PACIENT_SALDO(9733, 1, 0, 0)
UNION SELECT
first 1 cast('Anamneza' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(KREVSKUP as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ANAMNEZA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where ID = (select first 1 ID from ANAMNEZA where IDPAC=9733 order by DATUM DESC, ID desc)
UNION SELECT
first 20 cast('Ockovani' as varchar(11)) as ID, substring(cast(ockzaz.LATKA as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(ockzaz.ZKRATKA as VARCHAR(70)) from 1 for 30) as VAR2, cast(max(ockzaz.DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ockzaz where ockzaz.idpac = :IDPAC group by ockzaz.ZKRATKA, ockzaz.LATKA
UNION SELECT
first 1 cast('NeschopenOd' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(ZACNES) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from NES where (IDPAC = :IDPAC) and (ZACNES <= ':DATUM_CZ') and ((KONNES is NULL) or (KONNES > ':DATUM_CZ')) and (STORNO = 'F')
UNION SELECT
first 1 cast('Alergie' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ALERGIE as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where IDPAC = :IDPAC and ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC and DATUM <= ':DATUM_CZ' order by DATUM desc, ID desc)
UNION SELECT
first 1 cast('Pojistovna' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(P.IDICP as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ICP P join ICZ Z on (Z.IDICZ = P.IDICZ) where Z.POJ = '207' and P.ODB = '001'
@@ -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()
@@ -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]}')
@@ -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)
@@ -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
@@ -0,0 +1,106 @@
"""
precti_trace.py přehledné SQL dotazy z Firebird audit trace logu
Čte od konce (nejnovější nahoře).
Použití:
python precti_trace.py # posledních 50 dotazů
python precti_trace.py 100 # posledních 100
python precti_trace.py 50 SELECT # filtr jen SELECT dotazy
"""
import re, sys, io
LOG_PATH = r'C:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
SEPARATOR = '-' * 80
TS_RE = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+)\s+\([\w:]+\)\s+(\w+)')
ATT_RE = re.compile(r'\(ATT_\d+,\s*([\w]+):')
SQL_S_RE = re.compile(r'^-{70,}$')
SQL_E_RE = re.compile(r'^\^{70,}$')
# ── Parametry ──────────────────────────────────────────────────────────────
limit = int(sys.argv[1]) if len(sys.argv) > 1 else 50
filtr = sys.argv[2].upper() if len(sys.argv) > 2 else ''
# ── Čtení logu ─────────────────────────────────────────────────────────────
with open(LOG_PATH, 'r', encoding='cp1252', errors='replace') as f:
lines = f.readlines()
# ── Rozděl log na bloky podle timestamp řádků ──────────────────────────────
# Každý blok = od jednoho timestamp řádku do dalšího
blocks = [] # (start_line_idx, ts, event, block_lines[])
i = 0
n = len(lines)
while i < n:
m = TS_RE.match(lines[i].rstrip())
if m:
ts = m.group(1)
event = m.group(2)
start = i
i += 1
block = []
while i < n and not TS_RE.match(lines[i].rstrip()):
block.append(lines[i].rstrip())
i += 1
blocks.append((ts, event, block))
else:
i += 1
# ── Extrahuj SQL z PREPARE_STATEMENT bloků ─────────────────────────────────
results = []
for ts, event, block in blocks:
if event != 'PREPARE_STATEMENT':
continue
# Uživatel
user = '?'
for line in block:
att = ATT_RE.search(line)
if att:
user = att.group(1)
break
# SQL mezi --- a ^^^
sql_lines = []
plan_lines = []
in_sql = False
after_sql = False
for line in block:
if SQL_S_RE.match(line):
in_sql = True
after_sql = False
continue
if SQL_E_RE.match(line):
in_sql = False
after_sql = True
continue
if in_sql:
sql_lines.append(line)
elif after_sql and line.startswith('PLAN'):
plan_lines.append(line)
sql = '\n'.join(sql_lines).strip()
plan = ' '.join(plan_lines).strip()
if sql:
results.append((ts, user, sql, plan))
# ── Výstup nejnovější nahoře ─────────────────────────────────────────────
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
filtered = [r for r in results if filtr in r[2].upper()] if filtr else results
filtered = list(reversed(filtered))[:limit]
print(f'Firebird trace posledních {len(filtered)} dotazu'
+ (f' [filtr: {filtr}]' if filtr else ''))
print(SEPARATOR)
for ts, user, sql, plan in filtered:
print(f'[{ts}] {user}')
print(sql)
if plan:
print(f' --> {plan}')
print(SEPARATOR)
@@ -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"
}
@@ -0,0 +1,144 @@
# trace_report.py Excel report z Firebird audit trace logu
# Ulozi do u:\Dropbox\!!!Days\Downloads Z230\, smaze predchozi verzi.
import re, sys, io, os, openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter
from datetime import datetime
LOG_PATH = r'C:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
OUTPUT_DIR = r'u:\Dropbox\!!!Days\Downloads Z230'
TS_RE = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+)\s+\([\w:]+\)\s+(\w+)')
ATT_RE = re.compile(r'\(ATT_\d+,\s*([\w]+):')
SQL_S_RE = re.compile(r'^-{70,}$')
SQL_E_RE = re.compile(r'^\^{70,}$')
HEADER_FILL = PatternFill('solid', fgColor='2F5496')
HEADER_FONT = Font(bold=True, color='FFFFFF')
ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1')
WRAP_TOP = Alignment(wrap_text=True, vertical='top')
# ── Smazání předchozích verzí ───────────────────────────────────────────────
for f in os.listdir(OUTPUT_DIR):
if f.endswith('_trace.xlsx'):
os.remove(os.path.join(OUTPUT_DIR, f))
# ── Výstupní soubor ─────────────────────────────────────────────────────────
now = datetime.now()
filename = now.strftime('%Y-%m-%d_%H-%M-%S') + '_trace.xlsx'
out_path = os.path.join(OUTPUT_DIR, filename)
# ── Čtení logu ─────────────────────────────────────────────────────────────
with open(LOG_PATH, 'r', encoding='cp1252', errors='replace') as f:
lines = f.readlines()
# ── Rozděl na bloky ─────────────────────────────────────────────────────────
blocks = []
i, n = 0, len(lines)
while i < n:
m = TS_RE.match(lines[i].rstrip())
if m:
ts, event = m.group(1), m.group(2)
i += 1
block = []
while i < n and not TS_RE.match(lines[i].rstrip()):
block.append(lines[i].rstrip())
i += 1
blocks.append((ts, event, block))
else:
i += 1
# ── Extrahuj SQL z PREPARE_STATEMENT bloků ─────────────────────────────────
results = []
for ts, event, block in blocks:
if event != 'PREPARE_STATEMENT':
continue
user = '?'
for line in block:
att = ATT_RE.search(line)
if att:
user = att.group(1)
break
sql_lines, plan_lines = [], []
in_sql, after_sql = False, False
for line in block:
if SQL_S_RE.match(line):
in_sql, after_sql = True, False
continue
if SQL_E_RE.match(line):
in_sql, after_sql = False, True
continue
if in_sql:
sql_lines.append(line)
elif after_sql and line.startswith('PLAN'):
plan_lines.append(line)
sql = '\n'.join(sql_lines).strip()
plan = ' '.join(plan_lines).strip()
if sql:
# Detekuj typ dotazu
first = sql.lstrip().upper()
if first.startswith('SELECT'):
typ = 'SELECT'
elif first.startswith('INSERT'):
typ = 'INSERT'
elif first.startswith('UPDATE'):
typ = 'UPDATE'
elif first.startswith('DELETE'):
typ = 'DELETE'
elif first.startswith('EXECUTE'):
typ = 'EXECUTE'
else:
typ = 'OTHER'
results.append((ts, user, typ, sql, plan))
# Nejnovější nahoře
results = list(reversed(results))
# ── Excel ───────────────────────────────────────────────────────────────────
wb = openpyxl.Workbook()
ws = wb.active
ws.title = 'TRACE'
cols = ['CAS', 'UZIVATEL', 'TYP', 'SQL', 'PLAN']
ws.append(cols)
for i, (ts, user, typ, sql, plan) in enumerate(results, start=2):
ws.append([ts, user, typ, sql, plan])
if i % 2 == 0:
for cell in ws[i]:
cell.fill = ZEBRA_FILL
for cell in ws[i]:
cell.alignment = WRAP_TOP
# Záhlaví
for cell in ws[1]:
cell.fill = HEADER_FILL
cell.font = HEADER_FONT
cell.alignment = Alignment(horizontal='center')
# Šířky sloupců
for col, width in zip(['A','B','C','D','E'], [22, 12, 10, 80, 60]):
ws.column_dimensions[col].width = width
# Výška řádků SQL může být víceřádkový
for row in ws.iter_rows(min_row=2):
lines_count = max(row[3].value.count('\n') + 1 if row[3].value else 1, 1)
ws.row_dimensions[row[0].row].height = min(lines_count * 15, 120)
ws.freeze_panes = 'A2'
# Autofiltr
ws.auto_filter.ref = f'A1:E{len(results)+1}'
wb.save(out_path)
sys.stdout.buffer.write(f'Ulozeno: {out_path}\n'.encode('utf-8'))
sys.stdout.buffer.write(f'Dotazu: {len(results)}\n'.encode('utf-8'))