From ab0e5eee293a5cdafa9b410e19e6388981c1a7bb Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Mon, 27 Oct 2025 07:02:49 +0100 Subject: [PATCH] notebook --- 90 PSA.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 90 PSA.py diff --git a/90 PSA.py b/90 PSA.py new file mode 100644 index 0000000..03cd117 --- /dev/null +++ b/90 PSA.py @@ -0,0 +1,199 @@ +import firebirdsql as fb +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", + port=3050, + database=r"z:\Medicus 3\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; +""" + +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: + # " 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 + +# ---- 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" + +# ---- 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}")