z230
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
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)}")
|
||||
Reference in New Issue
Block a user