import glob import os import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Border, Side, Alignment from openpyxl.utils import get_column_letter from datetime import date, datetime src_dir = os.path.dirname(os.path.abspath(__file__)) + "/" out_dir = "U:/Dropbox/!!!Days/Downloads Z230/" csv_files = glob.glob(src_dir + "sponsor-study-36940-kit-inventory-on-hand-expiration.csv") assert csv_files, "Kit inventory CSV not found!" csv_file = csv_files[0] print(f"Source: {csv_file}") timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S") out_filename = f"{timestamp} 77242113UCO3001 Kit Inventory CZE.xlsx" out_path = out_dir + out_filename df = pd.read_csv(csv_file, encoding="utf-8") cze = df[df["Country"] == "CZE"].copy() def parse_date(val): if pd.isna(val): return None try: return datetime.strptime(str(val).strip(), "%b %d, %Y") except: return None cze["Shipped Date"] = cze["Shipped Date"].apply(parse_date) cze["Expiration Date"] = cze["Expiration Date"].apply(parse_date) cze = cze.sort_values(["Site", "Kit Type", "Expiration Date"]).reset_index(drop=True) today_dt = datetime.combine(date.today(), datetime.min.time()) def bucket(exp_date): if exp_date is None: return None delta = (exp_date - today_dt).days if delta <= 30: return "soon" return "ok" cze["_bucket"] = cze["Expiration Date"].apply(bucket) kit_order = sorted(cze["Kit Type"].unique(), key=lambda x: (str(x).lstrip("T-").zfill(5), str(x))) kit_desc = cze.drop_duplicates("Kit Type").set_index("Kit Type")["Description"].to_dict() sites = sorted(cze["Site"].unique()) # ── Styly ──────────────────────────────────────────────────────────────────── thin = Side(style="thin") border = Border(left=thin, right=thin, top=thin, bottom=thin) header_font = Font(name="Calibri", bold=True, size=11, color="FFFFFF") data_font = Font(name="Calibri", size=11) sum_header_font = Font(name="Calibri", bold=True, size=11, color="000000") sum_total_font = Font(name="Calibri", bold=True, size=11) zero_font = Font(name="Calibri", size=11, color="BFBFBF") zero_red_font = Font(name="Calibri", size=11, color="C00000") blue_fill = PatternFill("solid", fgColor="4472C4") dark_blue_fill = PatternFill("solid", fgColor="203764") orange_fill = PatternFill("solid", fgColor="FFF2CC") green_fill = PatternFill("solid", fgColor="E2EFDA") total_fill = PatternFill("solid", fgColor="D9E1F2") exp_fill = PatternFill("solid", fgColor="FFE7E7") ok_fill = PatternFill("solid", fgColor="E2EFDA") # ── List 1: Kit Inventory CZE (surový listing) ──────────────────────────────── wb = Workbook() ws = wb.active ws.title = "Kit Inventory CZE" listing_columns = [ ("Project No.", 14), ("Region", 10), ("Country", 10), ("Site", 38), ("Kit Type", 12), ("Description", 22), ("Accession", 18), ("Shipped Date", 16), ("Expiration Date", 16), ("Days to Expiration", 20), ] for col_idx, (header, width) in enumerate(listing_columns, 1): cell = ws.cell(row=1, column=col_idx, value=header) cell.font = header_font cell.fill = blue_fill cell.border = border cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) ws.column_dimensions[get_column_letter(col_idx)].width = width ws.row_dimensions[1].height = 30 ws.freeze_panes = "A2" for row_idx, row in enumerate(cze.itertuples(index=False), 2): days = row[9] for col_idx, (col_name, _) in enumerate(listing_columns, 1): value = row[col_idx - 1] cell = ws.cell(row=row_idx, column=col_idx, value=value) cell.font = data_font cell.border = border cell.alignment = Alignment(horizontal="center", vertical="center") if col_name in ("Shipped Date", "Expiration Date") and value is not None: cell.number_format = "DD-MMM-YYYY" if col_name == "Days to Expiration": cell.fill = exp_fill if (pd.notna(days) and days <= 60) else ok_fill ws.auto_filter.ref = f"A1:{get_column_letter(len(listing_columns))}1" # ── Pomocná funkce pro vykreslení souhrnné tabulky ──────────────────────────── def write_summary_table(ws, current_row, title, rows_data, col_a_header): """ rows_data: list of (col_a_val, col_b_val, n_soon, n_ok) col_a_header: záhlaví prvního sloupce """ # Title for c in range(1, 5): cell = ws.cell(row=current_row, column=c) cell.fill = dark_blue_fill cell.border = border ws.cell(row=current_row, column=1, value=title).font = Font(name="Calibri", bold=True, size=12, color="FFFFFF") ws.cell(row=current_row, column=1).alignment = Alignment(horizontal="left", vertical="center") ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=4) ws.row_dimensions[current_row].height = 22 current_row += 1 # Header for col_idx, (h, f) in enumerate(zip( [col_a_header, "Description", "Expiruje do 30 dní", "Expiruje později"], [blue_fill, blue_fill, orange_fill, green_fill] ), 1): cell = ws.cell(row=current_row, column=col_idx, value=h) cell.font = sum_header_font cell.fill = f cell.border = border cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) ws.row_dimensions[current_row].height = 28 current_row += 1 totals = [0, 0] for col_a, col_b, n_soon, n_ok in rows_data: totals[0] += n_soon totals[1] += n_ok row_vals = [col_a, col_b, n_soon, n_ok] row_fills = [None, None, orange_fill if n_soon > 0 else None, green_fill if n_ok > 0 else None] all_zero = (n_soon == 0 and n_ok == 0) for col_idx, (val, rfill) in enumerate(zip(row_vals, row_fills), 1): cell = ws.cell(row=current_row, column=col_idx, value=val) if col_idx >= 3 and val == 0: cell.font = zero_red_font if all_zero else zero_font else: cell.font = data_font cell.border = border cell.alignment = Alignment(horizontal="center" if col_idx >= 2 else "left", vertical="center") if rfill: cell.fill = rfill current_row += 1 # Total for col_idx, val in enumerate(["CELKEM", "", totals[0], totals[1]], 1): cell = ws.cell(row=current_row, column=col_idx, value=val) cell.font = sum_total_font cell.fill = total_fill cell.border = border cell.alignment = Alignment(horizontal="center" if col_idx >= 2 else "left", vertical="center") current_row += 2 return current_row # ── List 2: Přehled po centrech ─────────────────────────────────────────────── ctr_ws = wb.create_sheet("Přehled po centrech") ctr_ws.column_dimensions["A"].width = 22 ctr_ws.column_dimensions["B"].width = 24 ctr_ws.column_dimensions["C"].width = 22 ctr_ws.column_dimensions["D"].width = 20 current_row = 1 for site in sites: site_df = cze[cze["Site"] == site] rows_data = [] for kit in kit_order: desc = kit_desc.get(kit, "") kit_site_df = site_df[site_df["Kit Type"] == kit] n_soon = (kit_site_df["_bucket"] == "soon").sum() n_ok = (kit_site_df["_bucket"] == "ok").sum() rows_data.append((f"{kit} — {desc}", desc, n_soon, n_ok)) current_row = write_summary_table(ctr_ws, current_row, site, rows_data, "Kit Type") # ── List 3: Přehled po typech kitů ─────────────────────────────────────────── sum_ws = wb.create_sheet("Přehled po typech") sum_ws.column_dimensions["A"].width = 38 sum_ws.column_dimensions["B"].width = 22 sum_ws.column_dimensions["C"].width = 22 sum_ws.column_dimensions["D"].width = 20 current_row = 1 for kit in kit_order: desc = kit_desc.get(kit, "") kit_df = cze[cze["Kit Type"] == kit] rows_data = [] for site in sorted(kit_df["Site"].unique()): site_df = kit_df[kit_df["Site"] == site] n_soon = (site_df["_bucket"] == "soon").sum() n_ok = (site_df["_bucket"] == "ok").sum() rows_data.append((site, desc, n_soon, n_ok)) current_row = write_summary_table(sum_ws, current_row, f"Kit Type {kit} — {desc}", rows_data, "Centrum") wb.save(out_path) print(f"Saved: {out_path}") print(f"CZE rows: {len(cze)}, Kit types: {len(kit_order)}, Sites: {len(sites)}")