From 25e033f0b4d53f13f4ee2b37bea6f27486f6040b Mon Sep 17 00:00:00 2001 From: "michaela.buzalkova" Date: Wed, 3 Dec 2025 22:45:00 +0100 Subject: [PATCH] lenovo --- PSA/01 PSA.py | 30 ++- PSA/Reporter PSA.py | 212 ++++++++++++++++++ .../12 Vakcina na samostatnych listech.py | 18 +- Vakcíny/Reporter Očkování report.py | 168 ++++++++++++++ 4 files changed, 414 insertions(+), 14 deletions(-) create mode 100644 PSA/Reporter PSA.py rename 12 Vakcina na samostatnych listech.py => Vakcíny/12 Vakcina na samostatnych listech.py (92%) create mode 100644 Vakcíny/Reporter Očkování report.py diff --git a/PSA/01 PSA.py b/PSA/01 PSA.py index 9c284c6..3fe5f33 100644 --- a/PSA/01 PSA.py +++ b/PSA/01 PSA.py @@ -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) diff --git a/PSA/Reporter PSA.py b/PSA/Reporter PSA.py new file mode 100644 index 0000000..eaf5313 --- /dev/null +++ b/PSA/Reporter PSA.py @@ -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: + # " 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}") diff --git a/12 Vakcina na samostatnych listech.py b/Vakcíny/12 Vakcina na samostatnych listech.py similarity index 92% rename from 12 Vakcina na samostatnych listech.py rename to Vakcíny/12 Vakcina na samostatnych listech.py index 87172e9..200b177 100644 --- a/12 Vakcina na samostatnych listech.py +++ b/Vakcíny/12 Vakcina na samostatnych listech.py @@ -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 we’re 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 diff --git a/Vakcíny/Reporter Očkování report.py b/Vakcíny/Reporter Očkování report.py new file mode 100644 index 0000000..200b177 --- /dev/null +++ b/Vakcíny/Reporter Očkování report.py @@ -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 we’re 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()}")