Merge remote-tracking branch 'origin/main'

This commit is contained in:
2025-12-04 06:18:38 +01:00
4 changed files with 414 additions and 14 deletions

View File

@@ -1,11 +1,11 @@
import firebirdsql as fb
import firebirdsql as fb,os
import pandas as pd
# TCP to the Firebird 2.5 server. Use the DB path as seen by the *server* (Windows path).
conn = fb.connect(
host="192.168.1.4",
host="192.168.1.10",
port=3050,
database=r"z:\Medicus 3\data\MEDICUS.FDB", # raw string for backslashes
database=r"m:\Medicus\data\MEDICUS.FDB", # raw string for backslashes
user="SYSDBA",
password="masterkey",
charset="WIN1250", # adjust if needed
@@ -49,7 +49,7 @@ SELECT
FROM dokladd dd
WHERE dd.rodcis = kar.rodcis
AND (dd.kod = '01130' or dd.kod = '01131' OR dd.kod = '01132' OR dd.kod = '01133' OR dd.kod = '01134')
AND dd.datose BETWEEN vh.datum - 7 AND vh.datum + 7
AND dd.datose BETWEEN vh.datum - 365 AND vh.datum + 365
) AS vykodovano,
lm.kodtext,
lm.nazev,
@@ -119,11 +119,23 @@ from openpyxl.formatting.rule import ColorScaleRule
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import FormulaRule
# ---- 1) Build timestamped output path ----
base_path = Path("u:\Dropbox\!!!Days\Downloads Z230")
base_path.mkdir(parents=True, exist_ok=True) # ensure folder exists
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = base_path / f"lab_results_2025_{timestamp}.xlsx"
base_path = Path(r"z:\Dropbox\Ordinace\Reporty")
base_path.mkdir(parents=True, exist_ok=True)
# ================= DELETE OLD PSA REPORTS ==================
for fname in os.listdir(base_path):
if fname.endswith("PSA report.xlsx"):
try:
os.remove(base_path / fname)
print(f"🗑️ Deleted old PSA report: {fname}")
except Exception as e:
print(f"⚠️ Could not delete {fname}: {e}")
# ================= CREATE NEW FILENAME ==================
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
output_file = base_path / f"{timestamp} PSA report.xlsx"
print(f"📄 New PSA report will be saved as: {output_file}")
# ---- 2) Export DataFrame to Excel ----
# Assumes df_direct already exists (your joined query result)

212
PSA/Reporter PSA.py Normal file
View File

@@ -0,0 +1,212 @@
import firebirdsql as fb,os
import pandas as pd
# TCP to the Firebird 2.5 server. Use the DB path as seen by the *server* (Windows path).
conn = fb.connect(
host="192.168.1.10",
port=3050,
database=r"m:\Medicus\data\MEDICUS.FDB", # raw string for backslashes
user="SYSDBA",
password="masterkey",
charset="WIN1250", # adjust if needed
)
# Tiny helper to fetch directly into DataFrame (avoids the pandas/SQLAlchemy warning)
def query_df(sql, params=None):
cur = conn.cursor()
cur.execute(sql, params or ())
rows = cur.fetchall()
cols = [d[0].strip() for d in cur.description] # Firebird pads column names
return pd.DataFrame(rows, columns=cols)
# Smoke test
print(query_df("SELECT 1 AS ONE FROM RDB$DATABASE"))
# Your table
df = query_df("SELECT FIRST 100 * FROM kar")
print(df)
from datetime import datetime
start = datetime(2025, 1, 1)
end = datetime(2026, 1, 1)
sql = """
SELECT
/*vh.idvh,*/
vh.idpacient,
kar.prijmeni,
kar.jmeno,
kar.rodcis,
vh.datum,
/*vh.idhodn,*/
/*vd.poradi,*/
/*vd.idmetod,*/
/* NEW: list of matching dokladd entries within ±7 days, one cell */
(
SELECT LIST(CAST(dd.datose AS VARCHAR(10)) || ' ' || dd.kod, ', ')
FROM dokladd dd
WHERE dd.rodcis = kar.rodcis
AND (dd.kod = '01130' or dd.kod = '01131' OR dd.kod = '01132' OR dd.kod = '01133' OR dd.kod = '01134')
AND dd.datose BETWEEN vh.datum - 7 AND vh.datum + 7
) AS vykodovano,
lm.kodtext,
lm.nazev,
vd.vysl,
lj.jedn,
ls.normdol,
ls.normhor
FROM labvh vh
JOIN labvd vd ON vd.idvh = vh.idvh
JOIN kar ON kar.idpac = vh.idpacient
JOIN labmetod lm ON lm.idmetod = vd.idmetod
JOIN labjedn lj ON lj.idjedn = vd.idjedn
JOIN labskaly ls ON ls.idskaly = vd.idskaly
WHERE vh.datum >= ?
AND vh.datum < ?
AND lm.nazev CONTAINING 'PSA'
/*ORDER BY kar.idpac, vh.datum, vd.poradi;*/
ORDER BY vh.datum desc;
"""
df_direct = query_df(sql, (start, end))
import re
import numpy as np
# --- 0) Helper: parse numeric value from string like "5,6", "<0.1", "3.2 mmol/L" ---
num_re = re.compile(r'[-+]?\d+(?:[.,]\d+)?(?:[eE][-+]?\d+)?')
def to_num(x):
if x is None:
return np.nan
s = str(x).strip()
if not s:
return np.nan
m = num_re.search(s.replace('\u00A0', ' ')) # remove NBSP if any
if not m:
return np.nan
val_str = m.group(0).replace(',', '.')
try:
val = float(val_str)
except ValueError:
return np.nan
# Heuristic for qualifiers:
# "<x" -> take half of x (below detection limit), ">x" -> take x (at least)
if s.lstrip().startswith('<'):
return val * 0.5
if s.lstrip().startswith('>'):
return val
return val
# --- 1) Prepare numeric columns + ratio in pandas before export ---
# Assumes df_direct exists with columns 'VYSL' and 'NORMHOR' (case per your SELECT)
df_direct["VYSL_NUM"] = df_direct["VYSL"].apply(to_num)
df_direct["NORMHOR_NUM"] = df_direct["NORMHOR"].apply(to_num)
# Avoid division by zero/NaN
den = df_direct["NORMHOR_NUM"].replace(0, np.nan)
df_direct["RATIO"] = (df_direct["VYSL_NUM"] / den).clip(lower=0) # can exceed 1 if over ULN
from datetime import datetime
from pathlib import Path
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Alignment, Border, Side
from openpyxl.formatting.rule import ColorScaleRule
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import FormulaRule
base_path = Path(r"z:\Dropbox\Ordinace\Reporty")
base_path.mkdir(parents=True, exist_ok=True)
# ================= DELETE OLD PSA REPORTS ==================
for fname in os.listdir(base_path):
if fname.endswith("PSA report.xlsx"):
try:
os.remove(base_path / fname)
print(f"🗑️ Deleted old PSA report: {fname}")
except Exception as e:
print(f"⚠️ Could not delete {fname}: {e}")
# ================= CREATE NEW FILENAME ==================
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
output_file = base_path / f"{timestamp} PSA report.xlsx"
print(f"📄 New PSA report will be saved as: {output_file}")
# ---- 2) Export DataFrame to Excel ----
# Assumes df_direct already exists (your joined query result)
df_direct.to_excel(output_file, index=False, sheet_name="PSA")
# ---- 3) Open with openpyxl for formatting ----
wb = load_workbook(output_file)
ws = wb["PSA"]
# Auto width for columns
for col in ws.columns:
max_len = 0
col_letter = get_column_letter(col[0].column)
for cell in col:
try:
if cell.value is not None:
max_len = max(max_len, len(str(cell.value)))
except Exception:
pass
ws.column_dimensions[col_letter].width = min(max_len + 2, 50) # cap width
# Thin border style
thin_border = Border(
left=Side(style="thin"),
right=Side(style="thin"),
top=Side(style="thin"),
bottom=Side(style="thin"),
)
# Apply borders to all cells and center A, B, E
for row in ws.iter_rows(min_row=1, max_row=ws.max_row, min_col=1, max_col=ws.max_column):
for cell in row:
cell.border = thin_border
if cell.column_letter in ["A", "B", "E"]:
cell.alignment = Alignment(horizontal="center")
# Enable filter on header row and freeze it
ws.auto_filter.ref = ws.dimensions
ws.freeze_panes = "A2"
# map headers
hdr = {c.value: i+1 for i, c in enumerate(ws[1])}
vysl_idx = hdr.get("VYSL")
ratio_idx = hdr.get("RATIO")
if not (vysl_idx and ratio_idx):
raise RuntimeError("Missing required columns: VYSL and/or RATIO")
vysl_col = get_column_letter(vysl_idx)
ratio_col = get_column_letter(ratio_idx)
max_row = ws.max_row
rng_vysl = f"{vysl_col}2:{vysl_col}{max_row}"
green = PatternFill(start_color="63BE7B", end_color="63BE7B", fill_type="solid")
yellow = PatternFill(start_color="FFEB84", end_color="FFEB84", fill_type="solid")
red = PatternFill(start_color="F8696B", end_color="F8696B", fill_type="solid")
# Non-overlapping rules; stop when one matches
ws.conditional_formatting.add(
rng_vysl,
FormulaRule(formula=[f"${ratio_col}2<=0.80"], fill=green, stopIfTrue=True)
)
ws.conditional_formatting.add(
rng_vysl,
FormulaRule(formula=[f"AND(${ratio_col}2>0.80, ${ratio_col}2<1)"], fill=yellow, stopIfTrue=True)
)
ws.conditional_formatting.add(
rng_vysl,
FormulaRule(formula=[f"${ratio_col}2>=1"], fill=red, stopIfTrue=True)
)
wb.save(output_file)
print(f"Saved: {output_file}")

View File

@@ -3,23 +3,23 @@
from pathlib import Path
import time
import fdb
import firebirdsql as fb
import pandas as pd
import re
from openpyxl import load_workbook
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter
from Functions import get_medicus_connection
# from Functions import get_medicus_connection
# ================== Výstupní cesta ==================
BASE_DIR = Path(r"z:\Dropbox\Ordinace\Reporty") # uprav dle potřeby
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
xlsx_name = f"Pacienti očkování {timestamp}.xlsx"
xlsx_name = f"{timestamp} Očkování report.xlsx"
xlsx_path = BASE_DIR / xlsx_name
# ================== Smazání starých souborů ==================
for old_file in BASE_DIR.glob("Pacienti očkování *.xlsx"):
for old_file in BASE_DIR.glob("*očkování report.xlsx"):
try:
if old_file != xlsx_path: # skip the file were about to create
old_file.unlink()
@@ -39,7 +39,15 @@ SHEETS = {
}
# ================== Připojení k DB ==================
con = get_medicus_connection()
# con = get_medicus_connection()
con = fb.connect(
host="192.168.1.10",
port=3050,
database=r"m:\Medicus\data\MEDICUS.FDB", # raw string for backslashes
user="SYSDBA",
password="masterkey",
charset="WIN1250", # adjust if needed
)
# ================== SQL dotaz ==================
sql = """
SELECT

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pathlib import Path
import time
import firebirdsql as fb
import pandas as pd
import re
from openpyxl import load_workbook
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter
# from Functions import get_medicus_connection
# ================== Výstupní cesta ==================
BASE_DIR = Path(r"z:\Dropbox\Ordinace\Reporty") # uprav dle potřeby
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
xlsx_name = f"{timestamp} Očkování report.xlsx"
xlsx_path = BASE_DIR / xlsx_name
# ================== Smazání starých souborů ==================
for old_file in BASE_DIR.glob("*očkování report.xlsx"):
try:
if old_file != xlsx_path: # skip the file were about to create
old_file.unlink()
print(f"Smazán starý soubor: {old_file.name}")
except Exception as e:
print(f"⚠️ Nelze smazat {old_file.name}: {e}")
# ================== Definice skupin vakcín ==================
SHEETS = {
"COVID-19": ["commirnaty", "spikevax", "nuvaxovid"],
"Chřipka": ["vaxigrip", "influvac", "fluarix", "afluria"],
"Klíšťová encefalitida": ["fsme", "encepur"],
"Tetanus": ["tetavax", "boostrix", "adacel"],
"HepA": ["avaxim", "havrix","vaqta"],
"HepB": ["engerix"],
"HepA+B": ["twinrix"],
}
# ================== Připojení k DB ==================
# con = get_medicus_connection()
con = fb.connect(
host="192.168.1.10",
port=3050,
database=r"m:\Medicus\data\MEDICUS.FDB", # raw string for backslashes
user="SYSDBA",
password="masterkey",
charset="WIN1250", # adjust if needed
)
# ================== SQL dotaz ==================
sql = """
SELECT
kar.rodcis AS "Rodné číslo",
kar.prijmeni AS "Příjmení",
kar.jmeno AS "Jméno",
ockzaz.datum AS "Datum očkování",
ockzaz.kodmz AS "Kód MZ",
ockzaz.poznamka AS "Šarže",
ockzaz.latka AS "Látka",
ockzaz.nazev AS "Název",
ockzaz.expire AS "Expirace",
(
SELECT LIST(l.kod, ', ')
FROM lecd l
WHERE l.rodcis = kar.rodcis
AND l.datose = CAST(ockzaz.datum AS DATE)
) AS "LECD kódy (ten den)",
(
SELECT LIST(d.kod, ', ')
FROM dokladd d
WHERE d.rodcis = kar.rodcis
AND d.datose = CAST(ockzaz.datum AS DATE)
) AS "Výkony (ten den)"
FROM registr
JOIN kar ON registr.idpac = kar.idpac
JOIN ockzaz ON registr.idpac = ockzaz.idpac
WHERE
registr.datum_zruseni IS NULL
AND kar.vyrazen <> 'A'
AND kar.rodcis IS NOT NULL
AND idicp <> 0
AND EXTRACT(YEAR FROM ockzaz.datum) = 2025
ORDER BY ockzaz.datum DESC
"""
# ================== Načtení do DataFrame ==================
df = pd.read_sql(sql, con)
con.close()
# ================== Datové typy ==================
for col in ["Kód MZ", "Šarže", "Rodné číslo", "Látka", "Název", "Příjmení", "Jméno", "LECD kódy", "Výkony"]:
if col in df.columns:
df[col] = df[col].astype("string")
for dcol in ["Datum očkování", "Expirace"]:
if dcol in df.columns:
df[dcol] = pd.to_datetime(df[dcol], errors="coerce")
# ================== Uložení do Excelu více listů ==================
with pd.ExcelWriter(xlsx_path, engine="openpyxl") as writer:
for sheet_name, vakciny in SHEETS.items():
pattern = "|".join(re.escape(v) for v in vakciny if v)
mask = df["Látka"].astype(str).str.contains(pattern, case=False, na=False)
df_filtered = df[mask]
if not df_filtered.empty:
df_filtered.to_excel(writer, index=False, sheet_name=sheet_name)
# navíc celkový přehled všech očkování
df.to_excel(writer, index=False, sheet_name="Vše")
# ================== Formátování ==================
wb = load_workbook(xlsx_path)
def autosize_columns(ws):
for col_idx in range(1, ws.max_column + 1):
col_letter = get_column_letter(col_idx)
max_len = 0
for cell in ws[col_letter]:
val = "" if cell.value is None else str(cell.value)
if len(val) > max_len:
max_len = len(val)
ws.column_dimensions[col_letter].width = min(max(12, max_len + 2), 60)
def safe_table_name(sheet_name):
"""Return an Excel-safe, unique table name."""
name = re.sub(r"[^0-9A-Za-z_]", "_", sheet_name)
return f"tbl_{name[:25]}"
def style_table(ws):
max_row = ws.max_row
max_col = ws.max_column
if max_col == 0:
return
header_fill = PatternFill("solid", fgColor="D9E1F2")
for cell in ws[1]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(vertical="center")
ws.freeze_panes = "A2"
if max_row < 2:
autosize_columns(ws)
return
ref = f"A1:{get_column_letter(max_col)}{max_row}"
tbl = Table(displayName=safe_table_name(ws.title), ref=ref)
tbl.tableStyleInfo = TableStyleInfo(
name="TableStyleMedium9", showRowStripes=True, showColumnStripes=False
)
ws.add_table(tbl)
autosize_columns(ws)
def format_dates(ws, columns_names):
header = [c.value for c in ws[1]]
date_cols = [header.index(name) + 1 for name in columns_names if name in header]
for col_idx in date_cols:
for row in ws.iter_rows(min_row=2, min_col=col_idx, max_col=col_idx, max_row=ws.max_row):
row[0].number_format = "DD.MM.YYYY"
for ws in wb.worksheets:
style_table(ws)
format_dates(ws, ["Datum očkování", "Expirace"])
wb.save(xlsx_path)
print(f"✅ Hotovo. Uloženo do: {xlsx_path.resolve()}")