diff --git a/Covance/CreatedReports/2026-05-05 142546 42847922MDD3003 Covance.xlsx b/Covance/CreatedReports/2026-05-05 142546 42847922MDD3003 Covance.xlsx new file mode 100644 index 0000000..e1f4f0e Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 142546 42847922MDD3003 Covance.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 142600 42847922MDD3003 Covance.xlsx b/Covance/CreatedReports/2026-05-05 142600 42847922MDD3003 Covance.xlsx new file mode 100644 index 0000000..d1375a5 Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 142600 42847922MDD3003 Covance.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1353.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1353.xlsx deleted file mode 100644 index 0ae097a..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1353.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1400.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1400.xlsx deleted file mode 100644 index 964d0fa..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1400.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1402.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1402.xlsx deleted file mode 100644 index 741a9b0..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1402.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1403.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1403.xlsx deleted file mode 100644 index 537fe09..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1403.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1406.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1406.xlsx deleted file mode 100644 index 5036892..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1406.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1407.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1407.xlsx deleted file mode 100644 index 51ad809..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1407.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx deleted file mode 100644 index 1b93bdb..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx deleted file mode 100644 index 76e900f..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx deleted file mode 100644 index 00fe7fa..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx deleted file mode 100644 index 8836fdf..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx deleted file mode 100644 index dfafbe2..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx and /dev/null differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples.xlsx deleted file mode 100644 index 338d2cf..0000000 Binary files a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples.xlsx and /dev/null differ diff --git a/Covance/SourceData/Exploratory/MDD3003SampletrackingCZ_bbe4dce7.xlsx b/Covance/SourceData/Exploratory/MDD3003SampletrackingCZ_bbe4dce7.xlsx new file mode 100644 index 0000000..392f568 Binary files /dev/null and b/Covance/SourceData/Exploratory/MDD3003SampletrackingCZ_bbe4dce7.xlsx differ diff --git a/Covance/SourceData/Exploratory/MDD3003Specimeninventoryreport_b4d52855.xlsx b/Covance/SourceData/Exploratory/MDD3003Specimeninventoryreport_b4d52855.xlsx new file mode 100644 index 0000000..d3cdee7 Binary files /dev/null and b/Covance/SourceData/Exploratory/MDD3003Specimeninventoryreport_b4d52855.xlsx differ diff --git a/Covance/create_report.py b/Covance/create_report.py index d1097d5..5aca60d 100644 --- a/Covance/create_report.py +++ b/Covance/create_report.py @@ -39,7 +39,9 @@ EVEN_FILL = PatternFill("solid", fgColor="EBF3FB") ODD_FILL = PatternFill("solid", fgColor="FFFFFF") NOTRCV_FILL = PatternFill("solid", fgColor="FCE4D6") CANCELLED_FILL = PatternFill("solid", fgColor="F2F2F2") -OPEN_FILL = PatternFill("solid", fgColor="FFC7CE") +OPEN_FILL = PatternFill("solid", fgColor="FFC7CE") +OPEN_QUERY_FILL = PatternFill("solid", fgColor="FFD966") +HYPERLINK_FONT = Font(name="Arial", size=10, color="0563C1", underline="single") CENTER = Alignment(horizontal="center", vertical="center") LEFT = Alignment(horizontal="left", vertical="center") @@ -84,6 +86,33 @@ def load_data(): return pd.DataFrame(rows, columns=cols) +def load_equery_data(): + conn = mysql.connector.connect( + host=db_config.DB_HOST, port=db_config.DB_PORT, + user=db_config.DB_USER, password=db_config.DB_PASSWORD, + database=db_config.DB_NAME, + ) + sql = """ + SELECT site_code, investigator_name, subject, visit, + accession, visit_collection_date, equery_id, + create_date, response_datetime, issue_type, status, + time_before_response, user_name, study_role + FROM covance_equeries + WHERE import_id = ( + SELECT MAX(import_id) FROM iwrs_import + WHERE study = %s AND report_type = 'covance_equeries' + ) + ORDER BY site_code ASC, create_date DESC + """ + cursor = conn.cursor() + cursor.execute(sql, (STUDY,)) + cols = [d[0] for d in cursor.description] + rows = cursor.fetchall() + cursor.close() + conn.close() + return pd.DataFrame(rows, columns=cols) + + def load_kit_data(): conn = mysql.connector.connect( host=db_config.DB_HOST, port=db_config.DB_PORT, @@ -137,7 +166,9 @@ def write_title(ws, text, ncols): # ── sheet 1: Přehled ───────────────────────────────────────────────────────── -def write_prehled(wb, df): +def write_prehled(wb, df, accession_eq_row=None): + if accession_eq_row is None: + accession_eq_row = {} ws = wb.create_sheet("Přehled") ws.sheet_view.showGridLines = False @@ -163,16 +194,24 @@ def write_prehled(wb, df): ) for r_idx, row in agg.iterrows(): - excel_row = r_idx + 3 + excel_row = r_idx + 3 has_missing = row["not_received"] > 0 - fill = NOTRCV_FILL if has_missing else (EVEN_FILL if r_idx % 2 == 0 else ODD_FILL) + accession = row["accession"] + eq_row = accession_eq_row.get(accession) # None pokud nemá Open query + + if eq_row: + fill = OPEN_QUERY_FILL + elif has_missing: + fill = NOTRCV_FILL + else: + fill = EVEN_FILL if r_idx % 2 == 0 else ODD_FILL col_date = row["collection_date"] date_str = col_date.strftime("%d-%b-%Y") if hasattr(col_date, "strftime") else str(col_date) values = [ row["investigator_no"], row["investigator_name"], row["patient_no"], - row["protocol_visit_code"], row["accession"], date_str, + row["protocol_visit_code"], accession, date_str, int(row["celkem"]), int(row["received"]), int(row["not_received"]), ] for c_idx, val in enumerate(values, 1): @@ -180,7 +219,11 @@ def write_prehled(wb, df): cell.fill = fill cell.border = BORDER cell.alignment = CENTER if c_idx in (1, 4, 5, 6, 7, 8, 9) else LEFT - if c_idx == 9 and has_missing: + + if c_idx == 5 and eq_row: + cell.hyperlink = f"#'eQueries'!A{eq_row}" + cell.font = HYPERLINK_FONT + elif c_idx == 9 and has_missing: cell.font = RED_FONT else: cell.font = NORMAL_FONT @@ -359,9 +402,66 @@ def write_kity(wb, df_kits): ws.freeze_panes = "A3" -# ── sheet 4: ZDROJ (samples) ───────────────────────────────────────────────── +# ── sheet 4: eQueries ──────────────────────────────────────────────────────── -# ── sheet 5: ZDROJ Kity ────────────────────────────────────────────────────── +def write_equeries(wb, df_eq): + ws = wb.create_sheet("eQueries") + ws.sheet_view.showGridLines = False + + today = datetime.date.today().strftime("%d-%b-%Y") + write_title(ws, f"eQueries — {STUDY} ({today})", 14) + + headers = ["Site", "Investigátor", "Pacient", "Visit", "Accession", + "Visit Datum", "eQuery ID", "Vytvořeno", "Odpovězeno", + "Issue Type", "Status", "Čas odpovědi", "Uživatel", "Role"] + widths = [9, 22, 14, 26, 13, 13, 10, 16, 16, 20, 9, 13, 22, 13] + write_headers(ws, headers, widths) + + def fmt_dt(val, fmt="%d-%b-%Y %H:%M"): + if val is None or (isinstance(val, float) and val != val): + return None + try: + if pd.isna(val): + return None + except (TypeError, ValueError): + pass + if hasattr(val, "strftime"): + return val.strftime(fmt) + return str(val) + + for r_idx, row in df_eq.iterrows(): + excel_row = r_idx + 3 + is_open = str(row.get("status", "")).strip().lower() == "open" + fill = OPEN_FILL if is_open else (EVEN_FILL if r_idx % 2 == 0 else ODD_FILL) + font = Font(name="Arial", bold=True, size=10, color="9C0006") if is_open else NORMAL_FONT + + values = [ + row["site_code"], row["investigator_name"], row["subject"], + row["visit"], row["accession"], + fmt_dt(row["visit_collection_date"], "%d-%b-%Y"), + row["equery_id"], + fmt_dt(row["create_date"]), + fmt_dt(row["response_datetime"]), + row["issue_type"], row["status"], + row["time_before_response"], row["user_name"], row["study_role"], + ] + for c_idx, val in enumerate(values, 1): + if isinstance(val, float) and val != val: + val = None + cell = ws.cell(row=excel_row, column=c_idx, value=val) + cell.fill = fill + cell.border = BORDER + cell.font = font + cell.alignment = CENTER if c_idx in (1, 6, 7, 8, 9, 11, 12) else LEFT + ws.row_dimensions[excel_row].height = 16 + + ws.freeze_panes = "A3" + ws.auto_filter.ref = f"A2:N{len(df_eq) + 2}" + + +# ── sheet 5: ZDROJ Vzorky ──────────────────────────────────────────────────── + +# ── sheet 6: ZDROJ Kity ────────────────────────────────────────────────────── def write_zdroj_kity(wb, df_kits): ws = wb.create_sheet("ZDROJ Kity") @@ -427,6 +527,41 @@ def write_zdroj(wb, df): ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1" +def write_zdroj_equeries(wb, df_eq): + ws = wb.create_sheet("ZDROJ eQuery") + ws.sheet_view.showGridLines = True + + headers = list(df_eq.columns) + for c, h in enumerate(headers, 1): + cell = ws.cell(row=1, column=c, value=h) + cell.font = Font(name="Arial", bold=True, size=9, color="FFFFFF") + cell.fill = PatternFill("solid", fgColor="404040") + cell.alignment = LEFT + cell.border = BORDER + ws.column_dimensions[get_column_letter(c)].width = 20 + + for r_idx, (_, row) in enumerate(df_eq.iterrows(), 2): + fill = EVEN_FILL if r_idx % 2 == 0 else ODD_FILL + for c_idx, col in enumerate(headers, 1): + val = row[col] + try: + is_na = pd.isna(val) + except (TypeError, ValueError): + is_na = False + if is_na or val is None: + val = "" + elif hasattr(val, "strftime"): + val = val.strftime("%Y-%m-%d %H:%M") + cell = ws.cell(row=r_idx, column=c_idx, value=val) + cell.font = Font(name="Arial", size=9) + cell.fill = fill + cell.border = BORDER + cell.alignment = LEFT + + ws.freeze_panes = "A2" + ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1" + + # ── main ───────────────────────────────────────────────────────────────────── def main(): @@ -435,20 +570,33 @@ def main(): print("Načítám data z MySQL...") df = load_data() df_kits = load_kit_data() - print(f" Vzorky: {len(df)} řádků, {df['patient_no'].nunique()} pacientů") - print(f" Kity: {len(df_kits)} kitů, {df_kits['site_code'].nunique()} center") + df_eq = load_equery_data() + print(f" Vzorky: {len(df)} řádků, {df['patient_no'].nunique()} pacientů") + print(f" Kity: {len(df_kits)} kitů, {df_kits['site_code'].nunique()} center") + print(f" eQueries: {len(df_eq)} záznamů ({(df_eq['status']=='Open').sum()} Open)") + + # mapping accession → řádek v listu eQueries (jen Open queries, první výskyt) + open_accs = set(df_eq[df_eq["status"] == "Open"]["accession"].dropna()) + accession_eq_row = {} + for r_idx, row in df_eq.iterrows(): + acc = row.get("accession") + if acc and acc in open_accs and acc not in accession_eq_row: + accession_eq_row[acc] = r_idx + 3 # řádek 1=title, 2=header, data od 3 wb = Workbook() wb.remove(wb.active) - write_prehled(wb, df) + write_prehled(wb, df, accession_eq_row) write_chybejici(wb, df) write_kity(wb, df_kits) + write_equeries(wb, df_eq) write_zdroj(wb, df) write_zdroj_kity(wb, df_kits) + write_zdroj_equeries(wb, df_eq) - today = datetime.date.today().strftime("%Y-%m-%d") - out_path = unique_path(f"{today} {STUDY} Covance Samples") + now = datetime.datetime.now() + stamp = now.strftime("%Y-%m-%d %H%M%S") + out_path = unique_path(f"{stamp} {STUDY} Covance") wb.save(out_path) print(f"Uloženo: {out_path}")