diff --git a/Feasibility/77242113UCO2001/classify_krok_v1.0.py b/Feasibility/77242113UCO2001/classify_krok_v1.0.py index 4ab3d8e..795ec44 100644 --- a/Feasibility/77242113UCO2001/classify_krok_v1.0.py +++ b/Feasibility/77242113UCO2001/classify_krok_v1.0.py @@ -51,7 +51,7 @@ OVERRIDES = { } # Ocekavane pocty (dle odsouhlasene klasifikace 09JUN2026) pro kontrolu -EXPECTED = {K0: 4, K0N: 36, K1: 25, K2: 25, K31: 3, K32: 12, K4: 11, K5: 0, K6: 14, K7: 0} +EXPECTED = {K0: 4, K0N: 36, K1: 17, K2: 30, K31: 3, K32: 14, K4: 9, K5: 3, K6: 13, K7: 1} def classify(status: str) -> str: diff --git a/Feasibility/77242113UCO2001/store_cda_batch_v1.0.py b/Feasibility/77242113UCO2001/store_cda_batch_v1.0.py index 9251a75..99bad63 100644 --- a/Feasibility/77242113UCO2001/store_cda_batch_v1.0.py +++ b/Feasibility/77242113UCO2001/store_cda_batch_v1.0.py @@ -33,26 +33,28 @@ TOWER_USER = "root" TOWER_PASS = "7309208104" REMOTE_DIR = "/mnt/user/JNJEMAILS" TMPDIR = r"u:\Dropbox\!!!Days\Downloads Z230\_cda_tmp" -STORED_AT = "2026-06-09" +STORED_AT = "2026-06-10" # investigator_id -> (msg_filename, attachment_filename, label) -# DAVKA 2 (09JUN2026): institucionalni CDA, soubor jmenovan primo v STATUS lekare +# DAVKA 3 (10JUN2026): nove CDA z 10.6. (krok 4 -> 5) MAPPING = [ - ("6a19832b5fc2213518257950", "FC130007D8A1F0E30000.msg", - "CZ_CDA Template Master institution_Axon Clinical, s.r.o._fully signed 08Jun2026.pdf", - "Matous Jan (AXON Clinical, master)"), - ("6a19832b5fc2213518257958", "FC130007D8A1F0E60000.msg", - "SK_CDA institution_Gastro LM_fully signed_08Jun2026.pdf", - "Mihalkanin Lubomir (Gastro LM)"), - ("6a198b661218c31ab0f5ba4e", "FC130007C1643CA10000.msg", - "06_CDA-Janssen a FN v Motole_fully executed.pdf", - "Krizova Viera (FN Motol master)"), + ("6a19832b5fc221351825796c", "FC130007DE92C2040000.msg", + "CZ_CDA institution_MUDr. GREGAR s.r.o_Jan Gregar_fully signed_09Jun2026.pdf", + "Gregar Jan (MUDr. GREGAR s.r.o.)"), + ("6a19832b5fc2213518257969", "FC130007DE92C2030000.msg", + "SK_CDA PI_Durina_FN Nove Zamky_fully signed 09Jun2026.pdf", + "Durina Juraj (FN Nove Zamky)"), + ("6a19832b5fc2213518257973", "FC130007DE92C1FE0000.msg", + "SK_CDA_Institution_Accout Center s.r.o_09Jun2026.pdf", + "Horvath Frantisek (Accout Center)"), ] -# DAVKA 1 (09JUN2026) - jiz ulozeno, ponechano pro historii: +# DAVKA 1+2 (09JUN2026) - jiz ulozeno, ponechano pro historii: # Hlavaty/Cliniq FC1300053049739C, Fedurco/ENDOMED FC1300053049739B, # Tichy FC13000530495B95, Falc FC130007D8A1F0E6, Pesta FC130007D8A1F0E1, -# Jungwirthova FC130007D8A1F0E2, Lukac FC130007C9E971FF (store_cda_to_mongo_v1.0) +# Jungwirthova FC130007D8A1F0E2, Lukac FC130007C9E971FF (store_cda_to_mongo_v1.0), +# Matous/Axon FC130007D8A1F0E3, Mihalkanin/GastroLM FC130007D8A1F0E6, +# Krizova/Motol FC130007C1643CA1 def norm(s): diff --git a/IWRS/Drugs/Trash/import_to_mongo.py b/IWRS/Drugs/Trash/import_to_mongo.py deleted file mode 100644 index 18123b2..0000000 --- a/IWRS/Drugs/Trash/import_to_mongo.py +++ /dev/null @@ -1,253 +0,0 @@ -""" -Import Drugs dat (shipments, shipment_items, inventory, destruction) z XLSX do MongoDB. - -Volá se z IWRS/Drugs/run_all.py po stažení reportů. -""" - -import os -import sys -import re -import glob - -import pandas as pd - -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from common.mongo_writer import ( - to_str, to_int, to_date, - ensure_indexes, log_import, - bulk_upsert_with_snapshot, bulk_upsert_only, -) - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -# ── XLSX parsery (převzaté z run_all.py + úprava na Mongo dokumenty) ───────── - -def parse_shipments_report(study): - path = os.path.join(BASE_DIR, f"xls_shipments_{study}", f"shipments_report_{study}.xlsx") - if not os.path.exists(path): - print(f" CHYBI: {path}") - return [] - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Shipment ID" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - return [] - df = pd.read_excel(path, header=header_row).dropna(how="all") - df = df[df["Location"].astype(str).str.contains("Czech", na=False, case=False)] - col = df.columns.tolist() - rows = [] - for _, r in df.iterrows(): - sid = to_str(r["Shipment ID"]) - if not sid: - continue - rows.append({ - "_id": sid, - "shipment_id": sid, - "study": study, - "status": to_str(r["IRT Shipment Status"]), - "type": to_str(r["Type"]), - "ship_from": to_str(r["Shipment From"]), - "ship_to_site": to_str(r["Ship To:"]), - "location": to_str(r["Location"]), - "request_date": to_date(r["Request Date"]), - "shipped_date": to_date(r["Shipped Date"]), - "received_date": to_date(r["Received Date"]) if "Received Date" in col else None, - "received_by": to_str(r["Received by"]) if "Received by" in col else None, - "delivered_date_utc": to_date(r["Delivered Date [UTC]"]) if "Delivered Date [UTC]" in col else None, - "delivery_recipient": to_str(r["Delivery Recipient"]) if "Delivery Recipient" in col else None, - "delivery_details": to_str(r["Delivery Details"]) if "Delivery Details" in col else None, - "cancelled_date": to_date(r["Cancelled Date"]) if "Cancelled Date" in col else None, - "total_medication_ids": to_int(r["Total Medication IDs"]) if "Total Medication IDs" in col else None, - "tracking_no": to_str(r["Tracking #"]) if "Tracking #" in col else None, - "shipping_category": to_str(r["Shipping Category"]) if "Shipping Category" in col else None, - "expected_arrival": to_date(r["Expected Arrival"]) if "Expected Arrival" in col else None, - }) - return rows - - -def parse_shipment_details(study): - detail_dir = os.path.join(BASE_DIR, f"xls_shipment_details_{study}") - files = sorted(glob.glob(os.path.join(detail_dir, "shipment_details_*.xlsx"))) - rows = [] - for path in files: - m = re.search(r"shipment_details_(.+)\.xlsx", os.path.basename(path)) - shipment_id = m.group(1) if m else "UNKNOWN" - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Medication ID" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - continue - df = pd.read_excel(path, header=header_row).dropna(how="all") - for _, r in df.iterrows(): - med_desc = (to_str(r.get("Medication Description")) - or to_str(r.get("Medication ID Description"))) - med_type = (to_str(r.get("Medication type")) - or to_str(r.get("Medication ID type"))) - med_id = to_str(r.get("Medication ID")) - if not med_id: - continue - rows.append({ - "_id": f"{shipment_id}:{med_id}", - "study": study, - "shipment_id": shipment_id, - "destination_location": to_str(r.get("Destination Location")), - "shipment_status": to_str(r.get("IRT Shipment Status")), - "shipment_type": to_str(r.get("Type")), - "destination_site": to_str(r.get("Destination Site")), - "investigator": to_str(r.get("Investigator")), - "medication_description": med_desc, - "medication_type": med_type, - "medication_id": med_id, - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "container_id": to_str(r.get("Container ID")), - "quantity": to_int(r.get("Quantity of Medication IDs")), - "expiration_date": to_date(r.get("Expiration Date")), - "item_status": to_str(r.get("Status")), - }) - # dedupe (poslední vyhrává) - by_id = {r["_id"]: r for r in rows} - return list(by_id.values()) - - -def parse_inventory(study): - inv_dir = os.path.join(BASE_DIR, f"xls_reports_{study}") - files = sorted(glob.glob(os.path.join(inv_dir, "onsite_inventory_detail_*.xlsx"))) - rows = [] - for path in files: - raw = pd.read_excel(path, header=None) - site = investigator = location = None - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - if first.startswith("Site:"): - site = first.replace("Site:", "").strip() - elif first.startswith("Investigator:"): - investigator = first.replace("Investigator:", "").strip() - elif first.startswith("Location:"): - location = first.replace("Location:", "").strip() - if first in ("Medication", "Medication ID") and header_row is None: - header_row = i - if header_row is None: - continue - df = pd.read_excel(path, header=header_row).dropna(how="all") - df = df.rename(columns={df.columns[0]: "medication_id"}) - for _, r in df.iterrows(): - med_id = to_str(r["medication_id"]) - if not med_id or not site: - continue - rows.append({ - "_id": f"{site}:{med_id}", - "study": study, - "site": site, - "investigator": investigator, - "location": location, - "medication_id": med_id, - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "original_expiration_date": to_date(r.get("Original Expiration Date when Packaged Lot was Added")), - "expiration_date": to_date(r.get("Expiration date")), - "received_date": to_date(r.get("Received Date")), - "receipt_user": to_str(r.get("Shipment Receipt User")), - "subject_identifier": to_str(r.get("Subject Identifier")), - "quantity_assigned": to_int(r.get("Quantity Assigned")), - "irt_transaction": to_str(r.get("IRT Transaction")), - "date_assigned": to_date(r.get("Date Assigned")), - "assignment_user": to_str(r.get("Assignment User")), - "dispensation_status": to_str(r.get("Dispensation Status")), - "dispensing_date": to_date(r.get("Dispensing date") or r.get("Dispensing Date")), - "quantity_dispensed": to_int(r.get("Quantity Dispensed")), - "dispensing_user": to_str(r.get("Dispensing User")), - "quantity_returned": to_int(r.get("Quantity Returned")), - "date_returned": to_date(r.get("Date Returned")), - "return_user": to_str(r.get("Return User")), - }) - by_id = {r["_id"]: r for r in rows} - return list(by_id.values()) - - -def parse_destruction_files(study): - dest_dir = os.path.join(BASE_DIR, f"xls_ip_destruction_{study}") - files = sorted(glob.glob(os.path.join(dest_dir, "ip_destruction_basket_*.xlsx"))) - rows = [] - for path in files: - raw = pd.read_excel(path, header=None) - meta = {} - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - for key, attr in [ - ("Investigator Name:", "investigator"), - ("Site ID:", "site_id"), - ("Location:", "location"), - ("Basket ID:", "basket_id"), - ("Drug Destruction Created Date:", "destruction_date"), - ]: - if first.startswith(key): - meta[attr] = first.replace(key, "").strip() - if first == "Medication ID Description" and header_row is None: - header_row = i - if header_row is None: - continue - df = pd.read_excel(path, header=header_row).dropna(how="all") - basket_id = meta.get("basket_id") - for _, r in df.iterrows(): - med_id = to_str(r.get("Medication ID")) - if not med_id or not basket_id: - continue - rows.append({ - "_id": f"{basket_id}:{med_id}", - "study": study, - "site_id": meta.get("site_id"), - "investigator": meta.get("investigator"), - "location": meta.get("location"), - "basket_id": basket_id, - "destruction_date": to_date(meta.get("destruction_date")), - "medication_description": to_str(r.get("Medication ID Description")), - "medication_id": med_id, - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "comments": to_str(r.get("Comments")), - }) - by_id = {r["_id"]: r for r in rows} - return list(by_id.values()) - - -# ── hlavní import ──────────────────────────────────────────────────────────── - -def import_study(study): - print(f"\n [{study}] parsovani XLSX...") - shipments = parse_shipments_report(study) - items = parse_shipment_details(study) - inventory = parse_inventory(study) - destruct = parse_destruction_files(study) - print(f" Zasilky: {len(shipments)} | Polozky: {len(items)} | Sklad: {len(inventory)} | Destrukce: {len(destruct)}") - - import_id = log_import(study, f"drugs_{study}", "drugs", { - "shipments": len(shipments), - "shipment_items": len(items), - "inventory": len(inventory), - "destruction": len(destruct), - }) - print(f" import_id = {import_id}") - - bulk_upsert_with_snapshot("iwrs_shipments", "iwrs_shipments_snapshots", shipments, import_id) - bulk_upsert_with_snapshot("iwrs_shipment_items", "iwrs_shipment_items_snapshots", items, import_id) - bulk_upsert_with_snapshot("iwrs_inventory", "iwrs_inventory_snapshots", inventory, import_id) - bulk_upsert_only("iwrs_destruction", destruct, import_id) - - -def run(studies): - ensure_indexes() - for s in studies: - import_study(s) - - -if __name__ == "__main__": - studies = sys.argv[1:] if len(sys.argv) > 1 else ["77242113UCO3001", "42847922MDD3003"] - run(studies) diff --git a/IWRS/Drugs/Trash/run_all.py b/IWRS/Drugs/Trash/run_all.py deleted file mode 100644 index 136d2f3..0000000 --- a/IWRS/Drugs/Trash/run_all.py +++ /dev/null @@ -1,245 +0,0 @@ -""" -Kompletní pipeline pro Drugs: - 1. Onsite inventory detail (per site, vždy přepisuje) - 2. IP destruction (per košík, přeskočí již existující soubory) - 3. Shipments report (jeden soubor na studii, přepisuje) - 4. Shipment details (per zásilka CZ, vždy přepisuje) - 5. Import do MongoDB (studie.iwrs_shipments / iwrs_shipment_items / iwrs_inventory / iwrs_destruction) - -Spusť tento skript — zpracuje obě studie automaticky. -""" - -import os -import glob -import re -import datetime - -import sys -import pandas as pd -from playwright.sync_api import sync_playwright - -import import_to_mongo as drugs_mongo - -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -SITES = { - "77242113UCO3001": [ - "DD5-CZ10001", "DD5-CZ10003", "DD5-CZ10006", "DD5-CZ10009", - "DD5-CZ10010", "DD5-CZ10012", "DD5-CZ10013", "DD5-CZ10015", - "DD5-CZ10016", "DD5-CZ10020", "DD5-CZ10021", "DD5-CZ10022", - ], - "42847922MDD3003": [ - "S10-CZ10002", "S10-CZ10004", "S10-CZ10005", - "S10-CZ10008", "S10-CZ10011", "S10-CZ10012", - ], -} - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) - - - -# ── login ──────────────────────────────────────────────────────────────────── - -def login(page, study): - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - -# ── download funkce ────────────────────────────────────────────────────────── - -def download_inventory(page, study): - out_dir = os.path.join(BASE_DIR, f"xls_reports_{study}") - os.makedirs(out_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/onsite_inventory_detail") - page.wait_for_load_state("networkidle", timeout=120000) - - for site_id in SITES[study]: - print(f" [{site_id}] inventory...") - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.get_by_role("option", name=site_id).click() - page.wait_for_load_state("networkidle", timeout=120000) - - filename = os.path.join(out_dir, f"onsite_inventory_detail_{site_id}.xlsx") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - print(f" Inventory OK ({len(SITES[study])} center)") - - -def download_destruction(page, study): - out_dir = os.path.join(BASE_DIR, f"xls_ip_destruction_{study}") - os.makedirs(out_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/ip_destruction_form") - page.wait_for_load_state("networkidle", timeout=120000) - - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - baskets = [b.strip() for b in page.locator("mat-option").all_inner_texts() - if b.strip() and b.strip() != "No results found"] - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not baskets: - print(" Žádné destruction košíky") - return - - new_count = 0 - for basket in baskets: - filename = os.path.join(out_dir, f"ip_destruction_basket_{basket}.xlsx") - if os.path.exists(filename): - continue # destrukce se nemění — přeskočit - print(f" [košík {basket}] stahování...") - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(basket) - page.wait_for_timeout(500) - page.locator("mat-option").first.dispatch_event("click") - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - new_count += 1 - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(f" Destruction OK ({new_count} nových, {len(baskets) - new_count} přeskočeno)") - - -def download_shipments_report(page, study): - out_dir = os.path.join(BASE_DIR, f"xls_shipments_{study}") - os.makedirs(out_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/shipments_report") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = os.path.join(out_dir, f"shipments_report_{study}.xlsx") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" Shipments report OK") - - -def download_shipment_details(page, study): - out_dir = os.path.join(BASE_DIR, f"xls_shipment_details_{study}") - os.makedirs(out_dir, exist_ok=True) - - # načti CZ shipment IDs z právě staženého shipments reportu - report_path = os.path.join(BASE_DIR, f"xls_shipments_{study}", f"shipments_report_{study}.xlsx") - raw = pd.read_excel(report_path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Shipment ID" in [str(v).strip() for v in row]: - header_row = i - break - df = pd.read_excel(report_path, header=header_row) - df = df.dropna(how="all") - df = df[df["Location"].astype(str).str.contains("Czech", na=False, case=False)] - cz_shipments = list(zip( - df["Shipment ID"].astype(str).str.strip(), - df["IRT Shipment Status"].astype(str).str.strip() if "IRT Shipment Status" in df.columns else [""] * len(df), - )) - print(f" CZ zásilek ke stažení: {len(cz_shipments)}") - - page.goto(f"{BASE_URL}/report/shipment_details_report") - page.wait_for_load_state("networkidle", timeout=120000) - - skipped = 0 - for shipment, status in cz_shipments: - filename = os.path.join(out_dir, f"shipment_details_{shipment}.xlsx") - if os.path.exists(filename) and status.upper() == "RECEIVED": - skipped += 1 - continue # finální stav, soubor se nemění - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(shipment) - page.wait_for_timeout(500) - page.locator("mat-option").first.dispatch_event("click") - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{shipment}] ({status}) OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(f" Přeskočeno (RECEIVED): {skipped}") - - -# ── main ───────────────────────────────────────────────────────────────────── - -def main(): - os.chdir(BASE_DIR) - - # ── Stahování ──────────────────────────────────────────────────────────── - with sync_playwright() as p: - for study in STUDIES: - print(f"\n{'='*60}") - print(f"[{study}] STAHOVÁNÍ") - print(f"{'='*60}") - - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - try: - print(" Přihlášení...") - login(page, study) - - print("\n [1/4] Onsite inventory...") - download_inventory(page, study) - - print("\n [2/4] IP destruction...") - download_destruction(page, study) - - print("\n [3/4] Shipments report...") - download_shipments_report(page, study) - - print("\n [4/4] Shipment details (CZ)...") - download_shipment_details(page, study) - - except Exception as e: - import traceback - print(f" CHYBA při stahování: {e}") - traceback.print_exc() - finally: - browser.close() - - # ── Import do MongoDB ───────────────────────────────────────────────────── - print(f"\n{'='*60}") - print("IMPORT DO MongoDB") - print(f"{'='*60}") - - try: - drugs_mongo.run(STUDIES) - except Exception as e: - import traceback - print(f" CHYBA při importu: {e}") - traceback.print_exc() - - print(f"\n{'='*60}") - print("Vše hotovo.") - print(f"{'='*60}") - - -main() diff --git a/IWRS/Drugs/Working/_create_tables.py b/IWRS/Drugs/Working/_create_tables.py deleted file mode 100644 index c364929..0000000 --- a/IWRS/Drugs/Working/_create_tables.py +++ /dev/null @@ -1,139 +0,0 @@ -import mysql.connector -import db_config - -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 -) -c = conn.cursor() - -# Přidat report_type do iwrs_import (pokud ještě neexistuje) -try: - c.execute("""ALTER TABLE iwrs_import - ADD COLUMN report_type VARCHAR(20) NOT NULL DEFAULT 'patients' - AFTER source_file""") - print("ALTER TABLE iwrs_import OK — report_type přidán") -except mysql.connector.errors.DatabaseError as e: - if "Duplicate column" in str(e): - print("report_type již existuje — přeskočeno") - else: - raise - -stmts = [ - ( - "iwrs_shipments", - """CREATE TABLE IF NOT EXISTS iwrs_shipments ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - study VARCHAR(20) NOT NULL, - shipment_id VARCHAR(20) NOT NULL, - status VARCHAR(50), - type VARCHAR(30), - ship_from VARCHAR(50), - ship_to_site VARCHAR(50), - location VARCHAR(50), - request_date DATE, - shipped_date DATE, - received_date DATE, - received_by VARCHAR(100), - delivered_date_utc DATE, - delivery_recipient VARCHAR(100), - delivery_details VARCHAR(200), - cancelled_date DATE, - total_medication_ids SMALLINT, - tracking_no VARCHAR(100), - shipping_category VARCHAR(50), - expected_arrival DATE, - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_study_shipment (study, shipment_id) -)""" - ), - ( - "iwrs_shipment_items", - """CREATE TABLE IF NOT EXISTS iwrs_shipment_items ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - study VARCHAR(20) NOT NULL, - shipment_id VARCHAR(20) NOT NULL, - destination_location VARCHAR(50), - shipment_status VARCHAR(50), - shipment_type VARCHAR(30), - destination_site VARCHAR(50), - investigator VARCHAR(100), - medication_description VARCHAR(200), - medication_type VARCHAR(50), - medication_id VARCHAR(20), - packaged_lot_no VARCHAR(50), - packaged_lot_description VARCHAR(100), - container_id VARCHAR(50), - quantity SMALLINT, - expiration_date DATE, - item_status VARCHAR(50), - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_med_id (medication_id) -)""" - ), - ( - "iwrs_inventory", - """CREATE TABLE IF NOT EXISTS iwrs_inventory ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - study VARCHAR(20) NOT NULL, - site VARCHAR(50), - investigator VARCHAR(100), - location VARCHAR(50), - medication_id VARCHAR(20), - packaged_lot_no VARCHAR(50), - original_expiration_date DATE, - expiration_date DATE, - received_date DATE, - receipt_user VARCHAR(100), - subject_identifier VARCHAR(20), - quantity_assigned SMALLINT, - irt_transaction VARCHAR(100), - date_assigned DATE, - assignment_user VARCHAR(100), - dispensation_status VARCHAR(50), - dispensing_date DATE, - quantity_dispensed SMALLINT, - dispensing_user VARCHAR(100), - quantity_returned SMALLINT, - date_returned DATE, - return_user VARCHAR(100), - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_site (study, site) -)""" - ), - ( - "iwrs_destruction", - """CREATE TABLE IF NOT EXISTS iwrs_destruction ( - id INT AUTO_INCREMENT PRIMARY KEY, - study VARCHAR(20) NOT NULL, - site_id VARCHAR(50), - investigator VARCHAR(100), - location VARCHAR(50), - basket_id VARCHAR(20) NOT NULL, - destruction_date DATE, - medication_description VARCHAR(200), - medication_id VARCHAR(20), - packaged_lot_description VARCHAR(100), - comments VARCHAR(500), - imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE KEY uq_destruction (study, basket_id, medication_id), - INDEX idx_study_basket (study, basket_id) -)""" - ), -] - -for name, sql in stmts: - c.execute(sql) - print(f"OK: {name}") - -conn.commit() -c.close() -conn.close() -print("\nVšechny tabulky připraveny.") diff --git a/IWRS/Drugs/Working/create_accountability_report.py b/IWRS/Drugs/Working/create_accountability_report.py deleted file mode 100644 index 5bb5196..0000000 --- a/IWRS/Drugs/Working/create_accountability_report.py +++ /dev/null @@ -1,364 +0,0 @@ -import sys -import os -import mysql.connector -import pandas as pd -from datetime import date -from pathlib import Path -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -import db_config - -STUDY = "42847922MDD3003" -# STUDY = "77242113UCO3001" - -BASE_DIR = Path(os.path.dirname(os.path.abspath(__file__))) -OUTPUT_DIR = BASE_DIR / "output" -OUTPUT_FILE = OUTPUT_DIR / f"{date.today().strftime('%Y-%m-%d')} {STUDY} CZ IWRS overview.xlsx" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", "Max Visit Date", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, - "Max Visit Date": 16, -} - -# shipments sheet: kolík kde začínají detail sloupce (1-based, pro format_shipment_sheet) -N_SHIP_COLS = 9 - - -# ── DB ──────────────────────────────────────────────────────────────────────── - -def get_conn(): - return 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, - ) - - -def get_latest_import_id(cursor, study): - cursor.execute( - "SELECT MAX(import_id) AS mid FROM iwrs_import WHERE study=%s AND report_type='drugs'", - (study,), - ) - row = cursor.fetchone() - mid = row["mid"] - if mid is None: - raise RuntimeError(f"Žádná data v MySQL pro studii {study}") - return mid - - -# ── Načítání dat z MySQL ────────────────────────────────────────────────────── - -def load_inventory(cursor, study, import_id): - """ - Vrátí DataFrame s inventory + destruction join. - Sloupce jsou rovnou přejmenované pro downstream funkce. - """ - sql = """ - SELECT - i.site AS Site, - i.medication_id AS `Med ID`, - i.packaged_lot_no AS `Lot No.`, - i.original_expiration_date AS `Orig Exp Date`, - i.expiration_date AS `Exp Date`, - i.received_date AS `Rcv Date`, - i.receipt_user AS `Rcpt User`, - i.subject_identifier AS `Subject ID`, - i.quantity_assigned AS `Qty Asgn`, - i.irt_transaction AS `IRT Tx`, - i.date_assigned AS `Date Asgn`, - i.assignment_user AS `Asgn User`, - i.dispensation_status AS `Disp Status`, - i.dispensing_date AS `Disp Date`, - i.quantity_dispensed AS `Qty Disp`, - i.dispensing_user AS `Disp User`, - i.quantity_returned AS `Qty Ret`, - i.date_returned AS `Date Ret`, - i.return_user AS `Ret User`, - d.destruction_date AS Destroyed, - d.basket_id AS `Basket No.` - FROM iwrs_inventory i - LEFT JOIN ( - SELECT medication_id, - ANY_VALUE(basket_id) AS basket_id, - ANY_VALUE(destruction_date) AS destruction_date - FROM iwrs_destruction - WHERE study = %s - GROUP BY medication_id - ) d ON d.medication_id = i.medication_id - WHERE i.import_id = %s - AND i.study = %s - ORDER BY i.site, i.received_date, i.medication_id - """ - cursor.execute(sql, (study, import_id, study)) - rows = cursor.fetchall() - df = pd.DataFrame(rows) - for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - print(f" Inventory: {len(df)} kitu") - return df - - -def load_shipments(cursor, study, import_id): - """ - Vrátí DataFrame se spojenými shipments + items. - """ - sql = """ - SELECT - s.shipment_id AS `Shipment ID`, - s.status AS `IRT Shipment Status`, - s.type AS Type, - s.ship_from AS `Shipment From`, - s.ship_to_site AS `Ship To:`, - s.request_date AS `Request Date`, - s.received_date AS `Received Date`, - s.received_by AS `Received by`, - s.expected_arrival AS `Expected Arrival`, - i.investigator AS Investigator, - i.medication_description AS `Medication Description`, - i.medication_id AS `Medication ID`, - i.packaged_lot_no AS `Packaged Lot number`, - i.expiration_date AS `Expiration Date`, - i.item_status AS Status - FROM iwrs_shipments s - JOIN iwrs_shipment_items i - ON i.study = s.study - AND i.shipment_id = s.shipment_id - AND i.import_id = %s - WHERE s.import_id = %s - AND s.study = %s - ORDER BY s.ship_to_site, s.shipment_id, i.medication_id - """ - cursor.execute(sql, (import_id, import_id, study)) - rows = cursor.fetchall() - df = pd.DataFrame(rows) - for col in ("Request Date", "Received Date", "Expiration Date", "Expected Arrival"): - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - print(f" Shipments: {df['Shipment ID'].nunique() if len(df) else 0} zásilek, {len(df)} kitu") - return df - - -# ── Odvozené sheety ─────────────────────────────────────────────────────────── - -def build_site_summary(shipments_df): - STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] - pivot = shipments_df.groupby("Ship To:")["Status"].value_counts().unstack(fill_value=0) - for s in STATUS_COLS: - if s not in pivot.columns: - pivot[s] = 0 - pivot = ( - pivot[STATUS_COLS] - .reset_index() - .rename(columns={"Ship To:": "Site", "Returned by Subject": "Returned"}) - .sort_values("Site") - .reset_index(drop=True) - ) - pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) - print(f" Site Summary: {len(pivot)} center") - return pivot - - -def build_expired(df): - today = date.today() - mask = ( - df["Basket No."].isna() & - df["Subject ID"].isna() & - (df["Exp Date"] < pd.Timestamp(today)) - ) - filtered = df[mask].copy().reset_index(drop=True) - sheet_name = f"Expired as of {today.strftime('%d-%b-%Y')}" - print(f" Expired: {len(filtered)}") - return filtered, sheet_name - - -def build_assigned_not_dispensed(df): - mask = df["Subject ID"].notna() & df["Disp Date"].isna() - filtered = df[mask].copy().reset_index(drop=True) - print(f" Assigned not dispensed: {len(filtered)}") - return filtered - - -def build_not_returned(df): - no_ret = df[ - df["Date Ret"].isna() & - df["Subject ID"].notna() & - (df["Disp Status"].fillna("").str.upper() != "NOT DISPENSED") - ].copy() - max_asgn = df.groupby("Subject ID")["Date Asgn"].max().rename("Max Visit Date") - no_ret = no_ret.join(max_asgn, on="Subject ID") - filtered = no_ret[no_ret["Date Asgn"] < no_ret["Max Visit Date"]].copy() - filtered = filtered.drop(columns=["Qty Ret", "Date Ret", "Ret User", "Destroyed", "Basket No."]) - filtered = filtered.reset_index(drop=True) - print(f" Not returned: {len(filtered)}") - return filtered - - -def build_kits_for_destruction(df): - mask = ( - df["Basket No."].isna() & - (df["Date Ret"].notna() | (df["Disp Status"].fillna("").str.upper() == "NOT DISPENSED")) - ) - filtered = ( - df[mask] - .copy() - .sort_values(["Site", "Date Ret"], ascending=[True, True]) - .drop(columns=["Destroyed", "Basket No."]) - .reset_index(drop=True) - ) - print(f" Kits for destruction: {len(filtered)}") - return filtered - - -# ── Formátování ─────────────────────────────────────────────────────────────── - -def format_sheet(ws, header_color, highlight_col=None, highlight_color=None): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - header_fill = PatternFill("solid", start_color=header_color) - header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) - row_font = Font(name="Arial", size=10) - hi_fill = PatternFill("solid", start_color=highlight_color) if highlight_color else None - - headers = [cell.value for cell in ws[1]] - - for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if hi_fill and col_name == highlight_col: - cell.fill = hi_fill - - for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -def format_shipment_sheet(ws, header_color_ship, header_color_detail, n_ship_cols): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - hfont = Font(bold=True, color="FFFFFF", name="Arial", size=10) - dfont = Font(name="Arial", size=10) - fill_ship = PatternFill("solid", start_color=header_color_ship) - fill_detail = PatternFill("solid", start_color=header_color_detail) - - for cell in ws[1]: - cell.fill = fill_ship if cell.column <= n_ship_cols else fill_detail - cell.font = hfont - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) - cell.border = border - ws.column_dimensions[get_column_letter(cell.column)].width = min( - len(str(cell.value or "")) + 4, 35 - ) - ws.row_dimensions[1].height = 30 - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - cell.font = dfont - cell.border = border - cell.alignment = Alignment(horizontal="center", vertical="center") - if cell.value.__class__.__name__ in ("datetime", "date", "Timestamp"): - cell.number_format = "DD-MMM-YYYY" - - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -# ── Main ────────────────────────────────────────────────────────────────────── - -def main(): - OUTPUT_DIR.mkdir(exist_ok=True) - - print(f"\nNačítám data z MySQL pro {STUDY}...") - conn = get_conn() - cursor = conn.cursor(dictionary=True) - import_id = get_latest_import_id(cursor, STUDY) - print(f" import_id = {import_id}") - - df = load_inventory(cursor, STUDY, import_id) - shipments_df = load_shipments(cursor, STUDY, import_id) - - cursor.close() - conn.close() - - expired_df, expired_sheet = build_expired(df) - assigned_df = build_assigned_not_dispensed(df) - not_returned_df = build_not_returned(df) - destruction_df = build_kits_for_destruction(df) - site_summary_df = build_site_summary(shipments_df) - - with pd.ExcelWriter(OUTPUT_FILE, engine="openpyxl") as writer: - df.to_excel( writer, index=False, sheet_name="CountryMedicationOverview") - expired_df.to_excel( writer, index=False, sheet_name=expired_sheet) - assigned_df.to_excel( writer, index=False, sheet_name="Assigned not dispensed") - not_returned_df.to_excel( writer, index=False, sheet_name="Not returned") - destruction_df.to_excel( writer, index=False, sheet_name="Kits for destruction") - shipments_df.to_excel( writer, index=False, sheet_name="Shipments") - site_summary_df.to_excel( writer, index=False, sheet_name="Site Summary") - - wb = load_workbook(OUTPUT_FILE) - - ws_main = wb["CountryMedicationOverview"] - format_sheet(ws_main, header_color="1F4E79") - new_col_fill = PatternFill("solid", start_color="E2EFDA") - headers_main = [c.value for c in ws_main[1]] - for row in ws_main.iter_rows(min_row=2, max_row=ws_main.max_row): - for cell in row: - col_name = headers_main[cell.column - 1] if cell.column <= len(headers_main) else None - if col_name in ("Destroyed", "Basket No."): - cell.fill = new_col_fill - - format_sheet(wb[expired_sheet], header_color="C00000", highlight_col="Exp Date", highlight_color="FFE0E0") - format_sheet(wb["Assigned not dispensed"], header_color="833C00", highlight_col="Subject ID", highlight_color="FFF2CC") - format_sheet(wb["Not returned"], header_color="375623", highlight_col="Max Visit Date", highlight_color="E2EFDA") - format_sheet(wb["Kits for destruction"], header_color="595959") - format_shipment_sheet(wb["Shipments"], "1F4E79", "375623", N_SHIP_COLS) - format_sheet(wb["Site Summary"], header_color="1F4E79") - - wb.save(OUTPUT_FILE) - print(f"\nUloženo: {OUTPUT_FILE} ({len(df)} řádků, sheety: {wb.sheetnames})") - - -if __name__ == "__main__": - main() diff --git a/IWRS/Drugs/Working/create_shipment_report.py b/IWRS/Drugs/Working/create_shipment_report.py deleted file mode 100644 index 3ac66c8..0000000 --- a/IWRS/Drugs/Working/create_shipment_report.py +++ /dev/null @@ -1,205 +0,0 @@ -import sys -import os -import mysql.connector -import openpyxl -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter -from datetime import date -import pandas as pd - -# db_config.py je v nadřazeném adresáři (Drugs/) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -import db_config - -STUDY = "77242113UCO3001" -OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output") - -os.makedirs(OUTPUT_DIR, exist_ok=True) - - -def get_conn(): - return 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, - ) - - -def load_data(study): - conn = get_conn() - cursor = conn.cursor(dictionary=True) - - # nejnovější import_id pro danou studii - cursor.execute( - "SELECT MAX(import_id) AS mid FROM iwrs_import WHERE study=%s AND report_type='drugs'", - (study,), - ) - row = cursor.fetchone() - import_id = row["mid"] - if import_id is None: - raise RuntimeError(f"Žádná data v MySQL pro studii {study}") - print(f" import_id = {import_id}") - - sql = """ - SELECT - s.shipment_id, - s.status AS irt_shipment_status, - s.type, - s.ship_from AS shipment_from, - s.ship_to_site AS ship_to, - s.request_date, - s.received_date, - s.received_by, - s.expected_arrival, - i.investigator, - i.medication_description, - i.medication_id, - i.packaged_lot_no, - i.expiration_date, - i.item_status AS status - FROM iwrs_shipments s - JOIN iwrs_shipment_items i - ON i.study = s.study - AND i.shipment_id = s.shipment_id - AND i.import_id = %s - WHERE s.import_id = %s - AND s.study = %s - ORDER BY s.ship_to_site, s.shipment_id, i.medication_id - """ - cursor.execute(sql, (import_id, import_id, study)) - rows = cursor.fetchall() - cursor.close() - conn.close() - print(f" Načteno řádků: {len(rows)}") - return rows - - -# shipment sloupce (modrý header) / detail sloupce (zelený header) -SHIP_COLS = [ - ("shipment_id", "Shipment ID"), - ("irt_shipment_status","IRT Shipment Status"), - ("type", "Type"), - ("shipment_from", "Shipment From"), - ("ship_to", "Ship To:"), - ("request_date", "Request Date"), - ("received_date", "Received Date"), - ("received_by", "Received by"), - ("expected_arrival", "Expected Arrival"), -] - -DETAIL_COLS = [ - ("investigator", "Investigator"), - ("medication_description", "Medication Description"), - ("medication_id", "Medication ID"), - ("packaged_lot_no", "Packaged Lot number"), - ("expiration_date", "Expiration Date"), - ("status", "Status"), -] - -ALL_COLS = SHIP_COLS + DETAIL_COLS -N_SHIP_COLS = len(SHIP_COLS) - -HEADER_FILL_SHIP = PatternFill("solid", fgColor="1F4E79") -HEADER_FILL_DETAIL = PatternFill("solid", fgColor="375623") -HEADER_FONT = Font(name="Arial", bold=True, color="FFFFFF", size=10) -DATA_FONT = Font(name="Arial", size=10) -THIN_BORDER = Border( - left=Side(style="thin", color="BFBFBF"), - right=Side(style="thin", color="BFBFBF"), - bottom=Side(style="thin", color="BFBFBF"), -) - - -def write_shipments_sheet(wb, rows): - ws = wb.active - ws.title = "Shipments" - - # záhlaví - for ci, (_, label) in enumerate(ALL_COLS, 1): - cell = ws.cell(row=1, column=ci, value=label) - cell.font = HEADER_FONT - cell.fill = HEADER_FILL_SHIP if ci <= N_SHIP_COLS else HEADER_FILL_DETAIL - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) - cell.border = THIN_BORDER - ws.row_dimensions[1].height = 30 - - # data - for ri, row in enumerate(rows, 2): - for ci, (key, _) in enumerate(ALL_COLS, 1): - val = row[key] - cell = ws.cell(row=ri, column=ci, value=val) - cell.font = DATA_FONT - cell.border = THIN_BORDER - cell.alignment = Alignment(horizontal="center", vertical="center") - if isinstance(val, date): - cell.number_format = "DD-MMM-YYYY" - - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - # šířky sloupců - for ci, (key, label) in enumerate(ALL_COLS, 1): - vals = [label] + [str(r[key]) for r in rows if r[key] is not None] - ws.column_dimensions[get_column_letter(ci)].width = min( - max((len(v) for v in vals), default=10) + 2, 35 - ) - - -def write_summary_sheet(wb, rows): - STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] - - df = pd.DataFrame(rows) - pivot = df.groupby("ship_to")["status"].value_counts().unstack(fill_value=0) - for s in STATUS_COLS: - if s not in pivot.columns: - pivot[s] = 0 - pivot = ( - pivot[STATUS_COLS] - .reset_index() - .rename(columns={"ship_to": "Site", "Returned by Subject": "Returned"}) - .sort_values("Site") - .reset_index(drop=True) - ) - pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) - - ws = wb.create_sheet("Site Summary") - s_cols = ["Site", "Available", "Assigned", "Dispensed", "Returned", "Total"] - - for ci, col in enumerate(s_cols, 1): - cell = ws.cell(row=1, column=ci, value=col) - cell.font = HEADER_FONT - cell.fill = PatternFill("solid", fgColor="1F4E79") - cell.alignment = Alignment(horizontal="center", vertical="center") - cell.border = THIN_BORDER - ws.row_dimensions[1].height = 25 - - for ri, (_, row) in enumerate(pivot.iterrows(), 2): - for ci, col in enumerate(s_cols, 1): - cell = ws.cell(row=ri, column=ci, value=row[col]) - cell.font = DATA_FONT - cell.border = THIN_BORDER - cell.alignment = Alignment(horizontal="center", vertical="center") - - for ci, col in enumerate(s_cols, 1): - vals = [col] + [str(pivot.iloc[r][col]) for r in range(len(pivot))] - ws.column_dimensions[get_column_letter(ci)].width = min( - max(len(v) for v in vals) + 4, 35 - ) - - ws.freeze_panes = "A2" - - -def build_report(): - print(f"\nNačítám data z MySQL pro {STUDY}...") - rows = load_data(STUDY) - - wb = openpyxl.Workbook() - write_shipments_sheet(wb, rows) - write_summary_sheet(wb, rows) - - outfile = os.path.join(OUTPUT_DIR, f"{date.today()} {STUDY} CZ Shipments.xlsx") - wb.save(outfile) - print(f"\nUloženo -> {outfile}") - - -build_report() diff --git a/IWRS/Drugs/Working/create_studie_report.py b/IWRS/Drugs/Working/create_studie_report.py deleted file mode 100644 index dfbba66..0000000 --- a/IWRS/Drugs/Working/create_studie_report.py +++ /dev/null @@ -1,393 +0,0 @@ -import sys -import os -import mysql.connector -import pandas as pd -from datetime import date -from pathlib import Path -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -import db_config - -STUDIES = [ - ("77242113UCO3001", "UCO"), - ("42847922MDD3003", "MDD"), -] - -BASE_DIR = Path(os.path.dirname(os.path.abspath(__file__))) -OUTPUT_DIR = BASE_DIR / "output" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", "Max Visit Date", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, - "Max Visit Date": 16, -} - -N_SHIP_COLS = 9 # počet shipment sloupců (modrý header v Shipments sheetu) - - -# ── DB ──────────────────────────────────────────────────────────────────────── - -def get_conn(): - return 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, - ) - - -def get_latest_import_id(cursor, study): - cursor.execute( - "SELECT MAX(import_id) AS mid FROM iwrs_import WHERE study=%s AND report_type='drugs'", - (study,), - ) - row = cursor.fetchone() - mid = row["mid"] - if mid is None: - raise RuntimeError(f"Žádná data v MySQL pro studii {study}") - return mid - - -# ── Načítání dat ────────────────────────────────────────────────────────────── - -def load_inventory(cursor, study, import_id): - sql = """ - SELECT - i.site AS Site, - i.medication_id AS `Med ID`, - i.packaged_lot_no AS `Lot No.`, - i.original_expiration_date AS `Orig Exp Date`, - i.expiration_date AS `Exp Date`, - i.received_date AS `Rcv Date`, - i.receipt_user AS `Rcpt User`, - i.subject_identifier AS `Subject ID`, - i.quantity_assigned AS `Qty Asgn`, - i.irt_transaction AS `IRT Tx`, - i.date_assigned AS `Date Asgn`, - i.assignment_user AS `Asgn User`, - i.dispensation_status AS `Disp Status`, - i.dispensing_date AS `Disp Date`, - i.quantity_dispensed AS `Qty Disp`, - i.dispensing_user AS `Disp User`, - i.quantity_returned AS `Qty Ret`, - i.date_returned AS `Date Ret`, - i.return_user AS `Ret User`, - d.destruction_date AS Destroyed, - d.basket_id AS `Basket No.` - FROM iwrs_inventory i - LEFT JOIN ( - SELECT medication_id, - ANY_VALUE(basket_id) AS basket_id, - ANY_VALUE(destruction_date) AS destruction_date - FROM iwrs_destruction - WHERE study = %s - GROUP BY medication_id - ) d ON d.medication_id = i.medication_id - WHERE i.import_id = %s - AND i.study = %s - ORDER BY i.site, i.received_date, i.medication_id - """ - cursor.execute(sql, (study, import_id, study)) - rows = cursor.fetchall() - df = pd.DataFrame(rows) - for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - print(f" Inventory: {len(df)} kitu") - return df - - -def load_shipments(cursor, study, import_id): - sql = """ - SELECT - s.shipment_id AS `Shipment ID`, - s.status AS `IRT Shipment Status`, - s.type AS Type, - s.ship_from AS `Shipment From`, - s.ship_to_site AS `Ship To:`, - s.request_date AS `Request Date`, - s.received_date AS `Received Date`, - s.received_by AS `Received by`, - s.expected_arrival AS `Expected Arrival`, - i.investigator AS Investigator, - i.medication_description AS `Medication Description`, - i.medication_id AS `Medication ID`, - i.packaged_lot_no AS `Packaged Lot number`, - i.expiration_date AS `Expiration Date`, - i.item_status AS Status - FROM iwrs_shipments s - JOIN iwrs_shipment_items i - ON i.study = s.study - AND i.shipment_id = s.shipment_id - AND i.import_id = %s - WHERE s.import_id = %s - AND s.study = %s - ORDER BY s.ship_to_site, s.shipment_id, i.medication_id - """ - cursor.execute(sql, (import_id, import_id, study)) - rows = cursor.fetchall() - df = pd.DataFrame(rows) - for col in ("Request Date", "Received Date", "Expiration Date", "Expected Arrival"): - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - n_ship = df["Shipment ID"].nunique() if len(df) else 0 - print(f" Shipments: {n_ship} zásilek, {len(df)} kitu") - return df - - -# ── Odvozené sheety ─────────────────────────────────────────────────────────── - -def build_site_summary(shipments_df): - STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] - pivot = shipments_df.groupby("Ship To:")["Status"].value_counts().unstack(fill_value=0) - for s in STATUS_COLS: - if s not in pivot.columns: - pivot[s] = 0 - pivot = ( - pivot[STATUS_COLS] - .reset_index() - .rename(columns={"Ship To:": "Site", "Returned by Subject": "Returned"}) - .sort_values("Site") - .reset_index(drop=True) - ) - pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) - print(f" Site Summary: {len(pivot)} center") - return pivot - - -def build_expired(df): - today = date.today() - mask = ( - df["Basket No."].isna() & - df["Subject ID"].isna() & - (df["Exp Date"] < pd.Timestamp(today)) - ) - filtered = df[mask].copy().reset_index(drop=True) - print(f" Expired: {len(filtered)}") - return filtered - - -def build_assigned_not_dispensed(df): - mask = df["Subject ID"].notna() & df["Disp Date"].isna() - filtered = df[mask].copy().reset_index(drop=True) - print(f" Assigned not dispensed: {len(filtered)}") - return filtered - - -def build_not_returned(df): - no_ret = df[ - df["Date Ret"].isna() & - df["Subject ID"].notna() & - (df["Disp Status"].fillna("").str.upper() != "NOT DISPENSED") - ].copy() - max_asgn = df.groupby("Subject ID")["Date Asgn"].max().rename("Max Visit Date") - no_ret = no_ret.join(max_asgn, on="Subject ID") - filtered = no_ret[no_ret["Date Asgn"] < no_ret["Max Visit Date"]].copy() - filtered = filtered.drop(columns=["Qty Ret", "Date Ret", "Ret User", "Destroyed", "Basket No."]) - filtered = filtered.reset_index(drop=True) - print(f" Not returned: {len(filtered)}") - return filtered - - -def build_kits_for_destruction(df): - mask = ( - df["Basket No."].isna() & - (df["Date Ret"].notna() | (df["Disp Status"].fillna("").str.upper() == "NOT DISPENSED")) - ) - filtered = ( - df[mask] - .copy() - .sort_values(["Site", "Date Ret"], ascending=[True, True]) - .drop(columns=["Destroyed", "Basket No."]) - .reset_index(drop=True) - ) - print(f" Kits for destruction: {len(filtered)}") - return filtered - - -# ── Formátování ─────────────────────────────────────────────────────────────── - -def format_sheet(ws, header_color, highlight_col=None, highlight_color=None): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - header_fill = PatternFill("solid", start_color=header_color) - header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) - row_font = Font(name="Arial", size=10) - hi_fill = PatternFill("solid", start_color=highlight_color) if highlight_color else None - - headers = [cell.value for cell in ws[1]] - - for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if hi_fill and col_name == highlight_col: - cell.fill = hi_fill - - for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -def format_overview_sheet(ws): - format_sheet(ws, header_color="1F4E79") - new_col_fill = PatternFill("solid", start_color="E2EFDA") - headers = [c.value for c in ws[1]] - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - if col_name in ("Destroyed", "Basket No."): - cell.fill = new_col_fill - - -def format_shipment_sheet(ws): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - hfont = Font(bold=True, color="FFFFFF", name="Arial", size=10) - dfont = Font(name="Arial", size=10) - fill_ship = PatternFill("solid", start_color="1F4E79") - fill_detail = PatternFill("solid", start_color="375623") - - for cell in ws[1]: - cell.fill = fill_ship if cell.column <= N_SHIP_COLS else fill_detail - cell.font = hfont - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) - cell.border = border - ws.column_dimensions[get_column_letter(cell.column)].width = min( - len(str(cell.value or "")) + 4, 35 - ) - ws.row_dimensions[1].height = 30 - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - cell.font = dfont - cell.border = border - cell.alignment = Alignment(horizontal="center", vertical="center") - if cell.value.__class__.__name__ in ("datetime", "date", "Timestamp"): - cell.number_format = "DD-MMM-YYYY" - - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -# ── Main ────────────────────────────────────────────────────────────────────── - -SHEETS_DEF = [ - ("CountryMedicationOverview", "overview"), - ("Expired", "expired"), - ("Assigned not dispensed", "assigned"), - ("Not returned", "not_returned"), - ("Kits for destruction", "destruction"), - ("Shipments", "shipments"), - ("Site Summary", "site_summary"), -] - -FORMAT_MAP = { - "overview": lambda ws: format_overview_sheet(ws), - "expired": lambda ws: format_sheet(ws, "C00000", "Exp Date", "FFE0E0"), - "assigned": lambda ws: format_sheet(ws, "833C00", "Subject ID", "FFF2CC"), - "not_returned": lambda ws: format_sheet(ws, "375623", "Max Visit Date", "E2EFDA"), - "destruction": lambda ws: format_sheet(ws, "595959"), - "shipments": lambda ws: format_shipment_sheet(ws), - "site_summary": lambda ws: format_sheet(ws, "1F4E79"), -} - - -def process_study(cursor, study): - today = date.today().strftime("%d-%b-%Y") - import_id = get_latest_import_id(cursor, study) - print(f" import_id = {import_id}") - - df = load_inventory(cursor, study, import_id) - shipments_df = load_shipments(cursor, study, import_id) - - expired_df = build_expired(df) - assigned_df = build_assigned_not_dispensed(df) - not_returned_df = build_not_returned(df) - destruction_df = build_kits_for_destruction(df) - site_summ_df = build_site_summary(shipments_df) - - return [ - df, expired_df, assigned_df, not_returned_df, - destruction_df, shipments_df, site_summ_df, - ] - - -def save_study_report(study, data_frames): - output_file = OUTPUT_DIR / f"{date.today().strftime('%Y-%m-%d')} {study} report.xlsx" - - with pd.ExcelWriter(output_file, engine="openpyxl") as writer: - for (sheet_name, _), df_sheet in zip(SHEETS_DEF, data_frames): - df_sheet.to_excel(writer, index=False, sheet_name=sheet_name) - - wb = load_workbook(output_file) - for (sheet_name, fmt_key) in SHEETS_DEF: - FORMAT_MAP[fmt_key](wb[sheet_name]) - wb.save(output_file) - print(f" Uloženo: {output_file}") - - -def main(): - OUTPUT_DIR.mkdir(exist_ok=True) - - conn = get_conn() - cursor = conn.cursor(dictionary=True) - - for study, _ in STUDIES: - print(f"\n{'='*55}") - print(f"[{study}]") - print(f"{'='*55}") - try: - data_frames = process_study(cursor, study) - save_study_report(study, data_frames) - except Exception as e: - import traceback - print(f" CHYBA: {e}") - traceback.print_exc() - - cursor.close() - conn.close() - print(f"\nHotovo.") - - -if __name__ == "__main__": - main() diff --git a/IWRS/Drugs/Working/download_ip_destruction.py b/IWRS/Drugs/Working/download_ip_destruction.py deleted file mode 100644 index d139bd2..0000000 --- a/IWRS/Drugs/Working/download_ip_destruction.py +++ /dev/null @@ -1,76 +0,0 @@ -from playwright.sync_api import sync_playwright -import os - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" - -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -# STUDY = "42847922MDD3003" -STUDY = "77242113UCO3001" - -OUTPUT_DIR = f"xls_ip_destruction_{STUDY}" -# ──────────────────────────────────────────────────────────────────────────── - -def run(page, study): - output_dir = f"xls_ip_destruction_{study}" - os.makedirs(output_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/ip_destruction_form") - page.wait_for_load_state("networkidle", timeout=120000) - - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - baskets = [b.strip() for b in page.locator('mat-option').all_inner_texts() - if b.strip() and b.strip() != "No results found"] - print(f" Nalezeno {len(baskets)} kosiku: {baskets}") - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not baskets: - print(" Zadne destruction kosite — preskakuji.") - return - - for basket in baskets: - filename = os.path.join(output_dir, f"ip_destruction_basket_{basket}.xlsx") - if os.path.exists(filename): - print(f" [{basket}] Preskakuji — existuje.") - continue - print(f" [{basket}] Stahuji...") - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(basket) - page.wait_for_timeout(500) - page.locator('mat-option').first.dispatch_event('click') - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{basket}] OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(" Destruction hotovo.") - - -if __name__ == "__main__": - from playwright.sync_api import sync_playwright - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - run(page, STUDY) - browser.close() diff --git a/IWRS/Drugs/Working/download_reports.py b/IWRS/Drugs/Working/download_reports.py deleted file mode 100644 index e67955a..0000000 --- a/IWRS/Drugs/Working/download_reports.py +++ /dev/null @@ -1,83 +0,0 @@ -from playwright.sync_api import sync_playwright -import os - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" - -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -# STUDY = "42847922MDD3003" -STUDY = "77242113UCO3001" - -SITES = { - "42847922MDD3003": [ - "S10-CZ10002", - "S10-CZ10004", - "S10-CZ10005", - "S10-CZ10008", - "S10-CZ10011", - "S10-CZ10012", - ], - "77242113UCO3001": [ - "DD5-CZ10001", - "DD5-CZ10003", - "DD5-CZ10006", - "DD5-CZ10009", - "DD5-CZ10010", - "DD5-CZ10012", - "DD5-CZ10013", - "DD5-CZ10015", - "DD5-CZ10016", - "DD5-CZ10020", - "DD5-CZ10021", - "DD5-CZ10022", - ], -} - -OUTPUT_DIR = f"xls_reports_{STUDY}" -# ──────────────────────────────────────────────────────────────────────────── - -def run(page, study): - output_dir = f"xls_reports_{study}" - os.makedirs(output_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/onsite_inventory_detail") - page.wait_for_load_state("networkidle", timeout=120000) - - for site_id in SITES[study]: - print(f" [{site_id}] Stahuji...") - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.get_by_role("option", name=site_id).click() - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - - dl.value.save_as(os.path.join(output_dir, f"onsite_inventory_detail_{site_id}.xlsx")) - print(f" [{site_id}] OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(" Inventory hotovo.") - - -if __name__ == "__main__": - from playwright.sync_api import sync_playwright - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - run(page, STUDY) - browser.close() diff --git a/IWRS/Drugs/Working/download_shipment_details.py b/IWRS/Drugs/Working/download_shipment_details.py deleted file mode 100644 index d024bb5..0000000 --- a/IWRS/Drugs/Working/download_shipment_details.py +++ /dev/null @@ -1,95 +0,0 @@ -from playwright.sync_api import sync_playwright -import os -import pandas as pd - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" - -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDY = "42847922MDD3003" -#STUDY = "77242113UCO3001" - -OUTPUT_DIR = f"xls_shipment_details_{STUDY}" -# ──────────────────────────────────────────────────────────────────────────── - -def get_cz_shipment_ids(study): - path = f"xls_shipments_{study}/shipments_report_{study}.xlsx" - if not os.path.exists(path): - return None - df = pd.read_excel(path, header=5) - df.columns = df.columns.str.strip() - df = df.dropna(how="all") - df["Shipment ID"] = df["Shipment ID"].astype(str).str.strip() - cz = df[df["Location"].str.contains("Czech", na=False, case=False)] - return cz["Shipment ID"].tolist() - - -def run(page, study): - output_dir = f"xls_shipment_details_{study}" - os.makedirs(output_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/shipment_details_report") - page.wait_for_load_state("networkidle", timeout=120000) - - cz_ids = get_cz_shipment_ids(study) - if cz_ids is not None: - shipments = cz_ids - print(f" Filtrovano ze shipments reportu: {len(shipments)} CZ shipmentu") - else: - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - shipments = [s.strip() for s in page.locator('mat-option').all_inner_texts() - if s.strip() and s.strip() != "No results found"] - print(f" Nalezeno {len(shipments)} shipmentu z dropdownu") - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not shipments: - print(" Zadne shipments — preskakuji.") - return - - for shipment in shipments: - filename = os.path.join(output_dir, f"shipment_details_{shipment}.xlsx") - if os.path.exists(filename): - print(f" [{shipment}] Preskakuji — existuje.") - continue - print(f" [{shipment}] Stahuji...") - - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(shipment) - page.wait_for_timeout(500) - page.locator('mat-option').first.dispatch_event('click') - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{shipment}] OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(" Shipment details hotovo.") - - -if __name__ == "__main__": - from playwright.sync_api import sync_playwright - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - run(page, STUDY) - browser.close() diff --git a/IWRS/Drugs/Working/download_shipments_report.py b/IWRS/Drugs/Working/download_shipments_report.py deleted file mode 100644 index 5644e97..0000000 --- a/IWRS/Drugs/Working/download_shipments_report.py +++ /dev/null @@ -1,47 +0,0 @@ -from playwright.sync_api import sync_playwright -import os - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" - -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -# STUDY = "42847922MDD3003" -STUDY = "77242113UCO3001" - -OUTPUT_DIR = f"xls_shipments_{STUDY}" -# ──────────────────────────────────────────────────────────────────────────── - -def run(page, study): - output_dir = f"xls_shipments_{study}" - os.makedirs(output_dir, exist_ok=True) - - page.goto(f"{BASE_URL}/report/shipments_report") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = os.path.join(output_dir, f"shipments_report_{study}.xlsx") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" Shipments report OK -> {filename}") - - -if __name__ == "__main__": - from playwright.sync_api import sync_playwright - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - run(page, STUDY) - browser.close() diff --git a/IWRS/Drugs/Working/import_drugs_to_mysql.py b/IWRS/Drugs/Working/import_drugs_to_mysql.py deleted file mode 100644 index 3ae3ccd..0000000 --- a/IWRS/Drugs/Working/import_drugs_to_mysql.py +++ /dev/null @@ -1,441 +0,0 @@ -""" -Importuje drugs data z IWRS Excel reportů do MySQL. - -Tabulky: - iwrs_shipments — zásilky (jen CZ, verzováno import_id) - iwrs_shipment_items — obsah zásilek (verzováno import_id) - iwrs_inventory — lékový sklad na centrech (verzováno import_id) - iwrs_destruction — destrukce (bez verzování, přeskočí již importované košíky) - -Spustit po stažení souborů (nebo přes run_all.py). -""" - -import os -import glob -import re -import datetime - -import numpy as np -import pandas as pd -import mysql.connector - -import db_config - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -SITES = { - "77242113UCO3001": [ - "DD5-CZ10001", "DD5-CZ10003", "DD5-CZ10006", "DD5-CZ10009", - "DD5-CZ10010", "DD5-CZ10012", "DD5-CZ10013", "DD5-CZ10015", - "DD5-CZ10016", "DD5-CZ10020", "DD5-CZ10021", "DD5-CZ10022", - ], - "42847922MDD3003": [ - "S10-CZ10002", "S10-CZ10004", "S10-CZ10005", - "S10-CZ10008", "S10-CZ10011", "S10-CZ10012", - ], -} - - -# ── type converters ────────────────────────────────────────────────────────── - -def _py(val): - if isinstance(val, np.generic): - return val.item() - return val - -def to_date(val): - val = _py(val) - if val is None: - return None - if isinstance(val, float) and (val != val): - return None - try: - if pd.isna(val): - return None - except (TypeError, ValueError): - pass - if isinstance(val, pd.Timestamp): - return None if pd.isna(val) else val.date() - if isinstance(val, datetime.datetime): - return val.date() - if isinstance(val, datetime.date): - return val - s = str(val).strip() - if not s or s.lower() in ("nat", "nan", "none", ""): - return None - for fmt in ("%Y-%m-%d", "%d-%b-%Y", "%d-%m-%Y", "%Y-%m-%d %H:%M:%S"): - try: - return datetime.datetime.strptime(s, fmt).date() - except ValueError: - pass - return None - -def to_int(val): - val = _py(val) - try: - v = float(val) - return None if (v != v) else int(v) - except (TypeError, ValueError): - return None - -def to_str(val): - val = _py(val) - if val is None: - return None - if isinstance(val, float) and (val != val): - return None - s = str(val).strip() - return None if s.lower() in ("nan", "nat", "none", "") else s - - -# ── DB helpers ─────────────────────────────────────────────────────────────── - -def get_conn(): - return 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, - ) - -def insert_import(cursor, study, source_label): - cursor.execute( - "INSERT INTO iwrs_import (study, imported_at, source_file, report_type) VALUES (%s, %s, %s, %s)", - (study, datetime.datetime.now(), source_label, "drugs"), - ) - return cursor.lastrowid - -def basket_already_imported(cursor, study, basket_id): - cursor.execute( - "SELECT 1 FROM iwrs_destruction WHERE study=%s AND basket_id=%s LIMIT 1", - (study, str(basket_id)), - ) - return cursor.fetchone() is not None - - -# ── parsers ────────────────────────────────────────────────────────────────── - -def parse_shipments_report(study): - path = os.path.join(BASE_DIR, f"xls_shipments_{study}", f"shipments_report_{study}.xlsx") - if not os.path.exists(path): - print(f" CHYBÍ: {path}") - return [] - - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Shipment ID" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - return [] - - df = pd.read_excel(path, header=header_row) - df = df.dropna(how="all") - # pouze CZ zásilky - df = df[df["Location"].astype(str).str.contains("Czech", na=False, case=False)] - col = df.columns.tolist() - - rows = [] - for _, r in df.iterrows(): - rows.append({ - "shipment_id": to_str(r["Shipment ID"]), - "status": to_str(r["IRT Shipment Status"]), - "type": to_str(r["Type"]), - "ship_from": to_str(r["Shipment From"]), - "ship_to_site": to_str(r["Ship To:"]), - "location": to_str(r["Location"]), - "request_date": to_date(r["Request Date"]), - "shipped_date": to_date(r["Shipped Date"]), - "received_date": to_date(r["Received Date"]) if "Received Date" in col else None, - "received_by": to_str(r["Received by"]) if "Received by" in col else None, - "delivered_date_utc": to_date(r["Delivered Date [UTC]"]) if "Delivered Date [UTC]" in col else None, - "delivery_recipient": to_str(r["Delivery Recipient"]) if "Delivery Recipient" in col else None, - "delivery_details": to_str(r["Delivery Details"]) if "Delivery Details" in col else None, - "cancelled_date": to_date(r["Cancelled Date"]) if "Cancelled Date" in col else None, - "total_medication_ids": to_int(r["Total Medication IDs"]) if "Total Medication IDs" in col else None, - "tracking_no": to_str(r["Tracking #"]) if "Tracking #" in col else None, - "shipping_category": to_str(r["Shipping Category"]) if "Shipping Category" in col else None, - "expected_arrival": to_date(r["Expected Arrival"]) if "Expected Arrival" in col else None, - }) - return rows - - -def parse_shipment_details(study): - detail_dir = os.path.join(BASE_DIR, f"xls_shipment_details_{study}") - files = sorted(glob.glob(os.path.join(detail_dir, "shipment_details_*.xlsx"))) - rows = [] - for path in files: - # shipment ID z názvu souboru - m = re.search(r"shipment_details_(.+)\.xlsx", os.path.basename(path)) - shipment_id = m.group(1) if m else "UNKNOWN" - - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Medication ID" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - continue - - df = pd.read_excel(path, header=header_row) - df = df.dropna(how="all") - col = df.columns.tolist() - - for _, r in df.iterrows(): - # normalizace názvů sloupců lišících se mezi studiemi - med_desc = (to_str(r.get("Medication Description")) - or to_str(r.get("Medication ID Description"))) - med_type = (to_str(r.get("Medication type")) - or to_str(r.get("Medication ID type"))) - rows.append({ - "shipment_id": shipment_id, - "destination_location": to_str(r.get("Destination Location")), - "shipment_status": to_str(r.get("IRT Shipment Status")), - "shipment_type": to_str(r.get("Type")), - "destination_site": to_str(r.get("Destination Site")), - "investigator": to_str(r.get("Investigator")), - "medication_description": med_desc, - "medication_type": med_type, - "medication_id": to_str(r.get("Medication ID")), - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "container_id": to_str(r.get("Container ID")), - "quantity": to_int(r.get("Quantity of Medication IDs")), - "expiration_date": to_date(r.get("Expiration Date")), - "item_status": to_str(r.get("Status")), - }) - return rows - - -def parse_inventory(study): - inv_dir = os.path.join(BASE_DIR, f"xls_reports_{study}") - files = sorted(glob.glob(os.path.join(inv_dir, "onsite_inventory_detail_*.xlsx"))) - rows = [] - for path in files: - raw = pd.read_excel(path, header=None) - - # extrahuj metadata ze záhlaví - site = investigator = location = None - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - if first.startswith("Site:"): - site = first.replace("Site:", "").strip() - elif first.startswith("Investigator:"): - investigator = first.replace("Investigator:", "").strip() - elif first.startswith("Location:"): - location = first.replace("Location:", "").strip() - # hlavička dat — první sloupec je "Medication" nebo "Medication ID" - if first in ("Medication", "Medication ID") and header_row is None: - header_row = i - if header_row is None: - continue - - df = pd.read_excel(path, header=header_row) - df = df.dropna(how="all") - # normalizuj první sloupec na "medication_id" - df = df.rename(columns={df.columns[0]: "medication_id"}) - col = df.columns.tolist() - - for _, r in df.iterrows(): - rows.append({ - "site": site, - "investigator": investigator, - "location": location, - "medication_id": to_str(r["medication_id"]), - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "original_expiration_date": to_date(r.get("Original Expiration Date when Packaged Lot was Added")), - "expiration_date": to_date(r.get("Expiration date")), - "received_date": to_date(r.get("Received Date")), - "receipt_user": to_str(r.get("Shipment Receipt User")), - "subject_identifier": to_str(r.get("Subject Identifier")), - "quantity_assigned": to_int(r.get("Quantity Assigned")), - "irt_transaction": to_str(r.get("IRT Transaction")), - "date_assigned": to_date(r.get("Date Assigned")), - "assignment_user": to_str(r.get("Assignment User")), - "dispensation_status": to_str(r.get("Dispensation Status")), - "dispensing_date": to_date(r.get("Dispensing date") or r.get("Dispensing Date")), - "quantity_dispensed": to_int(r.get("Quantity Dispensed")), - "dispensing_user": to_str(r.get("Dispensing User")), - "quantity_returned": to_int(r.get("Quantity Returned")), - "date_returned": to_date(r.get("Date Returned")), - "return_user": to_str(r.get("Return User")), - }) - return rows - - -def parse_destruction_files(study): - dest_dir = os.path.join(BASE_DIR, f"xls_ip_destruction_{study}") - files = sorted(glob.glob(os.path.join(dest_dir, "ip_destruction_basket_*.xlsx"))) - baskets = [] - for path in files: - raw = pd.read_excel(path, header=None) - - # metadata z záhlaví - meta = {} - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - for key, attr in [ - ("Investigator Name:", "investigator"), - ("Site ID:", "site_id"), - ("Location:", "location"), - ("Basket ID:", "basket_id"), - ("Drug Destruction Created Date:", "destruction_date"), - ]: - if first.startswith(key): - meta[attr] = first.replace(key, "").strip() - if first == "Medication ID Description" and header_row is None: - header_row = i - - if header_row is None: - continue - - df = pd.read_excel(path, header=header_row) - df = df.dropna(how="all") - - items = [] - for _, r in df.iterrows(): - items.append({ - "medication_description": to_str(r.get("Medication ID Description")), - "medication_id": to_str(r.get("Medication ID")), - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "comments": to_str(r.get("Comments")), - }) - - baskets.append({ - "site_id": meta.get("site_id"), - "investigator": meta.get("investigator"), - "location": meta.get("location"), - "basket_id": meta.get("basket_id"), - "destruction_date": to_date(meta.get("destruction_date")), - "items": items, - }) - return baskets - - -# ── inserters ──────────────────────────────────────────────────────────────── - -def insert_shipments(cursor, import_id, study, rows): - sql = """INSERT INTO iwrs_shipments - (import_id, study, shipment_id, status, type, ship_from, ship_to_site, - location, request_date, shipped_date, received_date, received_by, - delivered_date_utc, delivery_recipient, delivery_details, cancelled_date, - total_medication_ids, tracking_no, shipping_category, expected_arrival) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""" - for r in rows: - cursor.execute(sql, ( - import_id, study, r["shipment_id"], r["status"], r["type"], - r["ship_from"], r["ship_to_site"], r["location"], - r["request_date"], r["shipped_date"], r["received_date"], - r["received_by"], r["delivered_date_utc"], r["delivery_recipient"], - r["delivery_details"], r["cancelled_date"], r["total_medication_ids"], - r["tracking_no"], r["shipping_category"], r["expected_arrival"], - )) - - -def insert_shipment_items(cursor, import_id, study, rows): - sql = """INSERT INTO iwrs_shipment_items - (import_id, study, shipment_id, destination_location, shipment_status, - shipment_type, destination_site, investigator, medication_description, - medication_type, medication_id, packaged_lot_no, packaged_lot_description, - container_id, quantity, expiration_date, item_status) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""" - for r in rows: - cursor.execute(sql, ( - import_id, study, r["shipment_id"], r["destination_location"], - r["shipment_status"], r["shipment_type"], r["destination_site"], - r["investigator"], r["medication_description"], r["medication_type"], - r["medication_id"], r["packaged_lot_no"], r["packaged_lot_description"], - r["container_id"], r["quantity"], r["expiration_date"], r["item_status"], - )) - - -def insert_inventory(cursor, import_id, study, rows): - sql = """INSERT INTO iwrs_inventory - (import_id, study, site, investigator, location, medication_id, - packaged_lot_no, original_expiration_date, expiration_date, received_date, - receipt_user, subject_identifier, quantity_assigned, irt_transaction, - date_assigned, assignment_user, dispensation_status, dispensing_date, - quantity_dispensed, dispensing_user, quantity_returned, date_returned, return_user) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""" - for r in rows: - cursor.execute(sql, ( - import_id, study, r["site"], r["investigator"], r["location"], - r["medication_id"], r["packaged_lot_no"], r["original_expiration_date"], - r["expiration_date"], r["received_date"], r["receipt_user"], - r["subject_identifier"], r["quantity_assigned"], r["irt_transaction"], - r["date_assigned"], r["assignment_user"], r["dispensation_status"], - r["dispensing_date"], r["quantity_dispensed"], r["dispensing_user"], - r["quantity_returned"], r["date_returned"], r["return_user"], - )) - - -def insert_destruction(cursor, study, baskets): - sql = """INSERT IGNORE INTO iwrs_destruction - (study, site_id, investigator, location, basket_id, destruction_date, - medication_description, medication_id, packaged_lot_description, comments) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""" - skipped = 0 - imported = 0 - for b in baskets: - if basket_already_imported(cursor, study, b["basket_id"]): - skipped += 1 - continue - for item in b["items"]: - cursor.execute(sql, ( - study, b["site_id"], b["investigator"], b["location"], - b["basket_id"], b["destruction_date"], - item["medication_description"], item["medication_id"], - item["packaged_lot_description"], item["comments"], - )) - imported += 1 - return imported, skipped - - -# ── main ───────────────────────────────────────────────────────────────────── - -def import_study(study): - print(f"\n Parsování dat pro {study}...") - shipments = parse_shipments_report(study) - items = parse_shipment_details(study) - inventory = parse_inventory(study) - baskets = parse_destruction_files(study) - - print(f" Zásilky: {len(shipments)} | Položky zásilek: {len(items)} | Sklad: {len(inventory)} | Destrukční košíky: {len(baskets)}") - - conn = get_conn() - cursor = conn.cursor() - - import_id = insert_import(cursor, study, f"drugs_{study}") - print(f" import_id = {import_id}") - - insert_shipments(cursor, import_id, study, shipments) - insert_shipment_items(cursor, import_id, study, items) - insert_inventory(cursor, import_id, study, inventory) - dest_imported, dest_skipped = insert_destruction(cursor, study, baskets) - - conn.commit() - cursor.close() - conn.close() - print(f" Destrukce: {dest_imported} nových | {dest_skipped} košíků přeskočeno (již importováno)") - - -def main(): - for study in STUDIES: - print(f"\n{'='*60}") - print(f"[{study}]") - print(f"{'='*60}") - try: - import_study(study) - print(f" OK") - except Exception as e: - import traceback - print(f" CHYBA: {e}") - traceback.print_exc() - print("\nHotovo.") - - -main() diff --git a/IWRS/Drugs/Working/output/2026-05-05 42847922MDD3003 report.xlsx b/IWRS/Drugs/Working/output/2026-05-05 42847922MDD3003 report.xlsx deleted file mode 100644 index 1b185f9..0000000 Binary files a/IWRS/Drugs/Working/output/2026-05-05 42847922MDD3003 report.xlsx and /dev/null differ diff --git a/IWRS/Drugs/Working/output/2026-05-05 77242113UCO3001 report.xlsx b/IWRS/Drugs/Working/output/2026-05-05 77242113UCO3001 report.xlsx deleted file mode 100644 index e2d221b..0000000 Binary files a/IWRS/Drugs/Working/output/2026-05-05 77242113UCO3001 report.xlsx and /dev/null differ diff --git a/IWRS/Drugs/Working/run.py b/IWRS/Drugs/Working/run.py deleted file mode 100644 index 945444c..0000000 --- a/IWRS/Drugs/Working/run.py +++ /dev/null @@ -1,85 +0,0 @@ -import sys -import os -from playwright.sync_api import sync_playwright - -import download_reports -import download_ip_destruction -import download_shipments_report -import download_shipment_details -import create_accountability_report - -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = { - "1": "77242113UCO3001", - "2": "42847922MDD3003", -} - - -def pick_study(): - print("Vyber studii:") - for k, v in STUDIES.items(): - print(f" {k}) {v}") - while True: - choice = input("Volba (1/2): ").strip() - if choice in STUDIES: - return STUDIES[choice] - print(" Neplatna volba, zkus znovu.") - - -def login_and_select_study(page, study): - print(f"\n[1/5] Prihlaseni a vyber studie {study}...") - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - print(" OK") - - -def main(): - os.chdir(os.path.dirname(os.path.abspath(__file__))) - - study = pick_study() - - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - login_and_select_study(page, study) - - print(f"\n[2/5] Stahuji inventory reporty...") - download_reports.run(page, study) - - print(f"\n[3/5] Stahuji IP destruction reporty...") - download_ip_destruction.run(page, study) - - print(f"\n[4/5] Stahuji shipments report...") - download_shipments_report.run(page, study) - - print(f"\n[5/5] Stahuji shipment details...") - download_shipment_details.run(page, study) - - browser.close() - - print(f"\n[6/6] Generuji accountability report...") - create_accountability_report.STUDY = study - create_accountability_report.INVENTORY_DIR = __import__("pathlib").Path(f"xls_reports_{study}") - create_accountability_report.DESTRUCTION_DIR= __import__("pathlib").Path(f"xls_ip_destruction_{study}") - create_accountability_report.SHIPMENTS_FILE = __import__("pathlib").Path(f"xls_shipments_{study}/shipments_report_{study}.xlsx") - create_accountability_report.DETAILS_DIR = __import__("pathlib").Path(f"xls_shipment_details_{study}") - create_accountability_report.OUTPUT_FILE = create_accountability_report.OUTPUT_DIR / f"{__import__('datetime').date.today().strftime('%Y-%m-%d')} {study} CZ IWRS overview.xlsx" - create_accountability_report.main() - - print("\nVse hotovo!") - - -main() diff --git a/IWRS/Drugs/create_report.py b/IWRS/Drugs/create_report.py deleted file mode 100644 index 09eb78b..0000000 --- a/IWRS/Drugs/create_report.py +++ /dev/null @@ -1,649 +0,0 @@ -import os -import sys -import pandas as pd -from datetime import date -from pathlib import Path -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from common.mongo_writer import get_db - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -BASE_DIR = Path(os.path.dirname(os.path.abspath(__file__))) -OUTPUT_DIR = BASE_DIR / "output" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", "Max Visit Date", - "Visit Date", "Scheduled Date", -} - -N_SHIP_COLS = 9 # počet shipment sloupců před detail sloupci - - -# ── Načítání dat z MongoDB ──────────────────────────────────────────────────── - -INVENTORY_COLS = [ - ("site", "Site"), - ("medication_id", "Med ID"), - ("packaged_lot_no", "Lot No."), - ("original_expiration_date", "Orig Exp Date"), - ("expiration_date", "Exp Date"), - ("received_date", "Rcv Date"), - ("receipt_user", "Rcpt User"), - ("subject_identifier", "Subject ID"), - ("quantity_assigned", "Qty Asgn"), - ("irt_transaction", "IRT Tx"), - ("date_assigned", "Date Asgn"), - ("assignment_user", "Asgn User"), - ("dispensation_status", "Disp Status"), - ("dispensing_date", "Disp Date"), - ("quantity_dispensed", "Qty Disp"), - ("dispensing_user", "Disp User"), - ("quantity_returned", "Qty Ret"), - ("date_returned", "Date Ret"), - ("return_user", "Ret User"), -] - - -def load_inventory(study): - db = get_db() - inv = list(db.iwrs_inventory.find({"study": study})) - destr = list(db.iwrs_destruction.find({"study": study})) - # map medication_id -> first basket+date - destr_map = {} - for d in destr: - mid = d.get("medication_id") - if mid and mid not in destr_map: - destr_map[mid] = (d.get("basket_id"), d.get("destruction_date")) - - records = [] - for doc in inv: - row = {label: doc.get(key) for key, label in INVENTORY_COLS} - b, dt = destr_map.get(doc.get("medication_id"), (None, None)) - row["Destroyed"] = dt - row["Basket No."] = b - records.append(row) - - df = pd.DataFrame(records) - if df.empty: - print(" Inventory: 0 kitu") - return df - - df = df.sort_values(["Site", "Rcv Date", "Med ID"], na_position="last").reset_index(drop=True) - for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - print(f" Inventory: {len(df)} kitu") - return df - - -SHIP_COLS = [ - ("shipment_id", "Shipment ID"), - ("status", "IRT Shipment Status"), - ("type", "Type"), - ("ship_from", "Shipment From"), - ("ship_to_site", "Ship To:"), - ("request_date", "Request Date"), - ("received_date", "Received Date"), - ("received_by", "Received by"), - ("expected_arrival", "Expected Arrival"), -] - -ITEM_COLS = [ - ("investigator", "Investigator"), - ("medication_description", "Medication Description"), - ("medication_id", "Medication ID"), - ("packaged_lot_no", "Packaged Lot number"), - ("expiration_date", "Expiration Date"), - ("item_status", "Status"), -] - - -def load_shipments(study): - db = get_db() - ships = list(db.iwrs_shipments.find({"study": study})) - items = list(db.iwrs_shipment_items.find({"study": study})) - - # index items by shipment_id - items_by_ship = {} - for it in items: - items_by_ship.setdefault(it.get("shipment_id"), []).append(it) - - records = [] - for s in ships: - base = {label: s.get(key) for key, label in SHIP_COLS} - for it in items_by_ship.get(s.get("shipment_id"), []): - row = dict(base) - for key, label in ITEM_COLS: - row[label] = it.get(key) - records.append(row) - - df = pd.DataFrame(records) - if df.empty: - print(" Shipments: 0 zásilek, 0 kitu") - return df - - df = df.sort_values(["Ship To:", "Shipment ID", "Medication ID"], na_position="last").reset_index(drop=True) - for col in ("Request Date", "Received Date", "Expiration Date", "Expected Arrival"): - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - n_ship = df["Shipment ID"].nunique() - print(f" Shipments: {n_ship} zásilek, {len(df)} kitu") - return df - - -def load_visits(study): - db = get_db() - cur = db.iwrs_visits.find({ - "study": study, - "visit_type": "Past", - "irt_transaction_no": {"$ne": None}, - }) - rows = [] - for v in cur: - rows.append({ - "Subject": v.get("subject"), - "Visit Date": v.get("actual_date") or v.get("scheduled_date"), - "Scheduled Date": v.get("scheduled_date"), - "IRT Tx No": v.get("irt_transaction_no"), - "Visit": v.get("irt_transaction_description"), - "Medication": v.get("medication_assignment"), - "medication_id": v.get("medication_id"), - "quantity_assigned": v.get("quantity_assigned"), - }) - df = pd.DataFrame(rows) - if df.empty: - print(" Visits: 0 radku") - return df - - # GROUP BY subject/actual/scheduled/irt_no/desc/medication - grouped = ( - df.groupby(["Subject", "Visit Date", "Scheduled Date", "IRT Tx No", "Visit", "Medication"], - dropna=False, as_index=False) - .agg(**{ - "Med IDs": ("medication_id", lambda s: ", ".join(sorted([str(x) for x in s if pd.notna(x)]))), - "Qty": ("quantity_assigned", "sum"), - }) - ) - grouped = grouped.sort_values(["Subject", "Visit Date"]).reset_index(drop=True) - for col in ("Visit Date", "Scheduled Date"): - if col in grouped.columns: - grouped[col] = pd.to_datetime(grouped[col], errors="coerce") - if study == "77242113UCO3001": - grouped["Visit"] = grouped["Visit"].replace("Subject Number Creation", "Screening") - print(f" Visits: {len(grouped)} řádků") - return grouped - - -# ── Odvozené sheety ─────────────────────────────────────────────────────────── - -def build_site_summary(shipments_df): - STATUS_COLS = ["Available", "Assigned", "Dispensed", "Returned by Subject"] - pivot = shipments_df.groupby("Ship To:")["Status"].value_counts().unstack(fill_value=0) - for s in STATUS_COLS: - if s not in pivot.columns: - pivot[s] = 0 - pivot = ( - pivot[STATUS_COLS] - .reset_index() - .rename(columns={"Ship To:": "Site", "Returned by Subject": "Returned"}) - .sort_values("Site") - .reset_index(drop=True) - ) - pivot["Total"] = pivot[["Available", "Assigned", "Dispensed", "Returned"]].sum(axis=1) - print(f" Site Summary: {len(pivot)} center") - return pivot - - -def build_expired(df): - today = date.today() - mask = ( - df["Basket No."].isna() & - df["Subject ID"].isna() & - (df["Exp Date"] < pd.Timestamp(today)) - ) - filtered = df[mask].copy().reset_index(drop=True) - sheet_name = f"Expired as of {today.strftime('%d-%b-%Y')}" - print(f" Expired: {len(filtered)}") - return filtered, sheet_name - - -def build_assigned_not_dispensed(df): - mask = df["Subject ID"].notna() & df["Disp Date"].isna() - filtered = df[mask].copy().reset_index(drop=True) - print(f" Assigned not dispensed: {len(filtered)}") - return filtered - - -def build_not_returned(df): - no_ret = df[ - df["Date Ret"].isna() & - df["Subject ID"].notna() & - (df["Disp Status"].fillna("").str.upper() != "NOT DISPENSED") - ].copy() - max_asgn = df.groupby("Subject ID")["Date Asgn"].max().rename("Max Visit Date") - no_ret = no_ret.join(max_asgn, on="Subject ID") - filtered = no_ret[no_ret["Date Asgn"] < no_ret["Max Visit Date"]].copy() - filtered = filtered.drop(columns=["Qty Ret", "Date Ret", "Ret User", "Destroyed", "Basket No."]) - filtered = filtered.reset_index(drop=True) - print(f" Not returned: {len(filtered)}") - return filtered - - -def build_kits_for_destruction(df): - mask = ( - df["Basket No."].isna() & - (df["Date Ret"].notna() | (df["Disp Status"].fillna("").str.upper() == "NOT DISPENSED")) - ) - filtered = ( - df[mask] - .copy() - .sort_values(["Site", "Date Ret"], ascending=[True, True]) - .drop(columns=["Destroyed", "Basket No."]) - .reset_index(drop=True) - ) - print(f" Kits for destruction: {len(filtered)}") - return filtered - - -# ── Formátování ─────────────────────────────────────────────────────────────── - -STRIPE_GRAY = PatternFill("solid", start_color="F2F2F2") -STRIPE_WHITE = PatternFill("solid", start_color="FFFFFF") - -# pacienti — styly zachovány z create_subject_report.py -_PAT_HEADER_FILL = PatternFill("solid", start_color="1F4E79") -_PAT_HEADER_FONT = Font(name="Arial", bold=True, color="FFFFFF", size=10) -_PAT_NORMAL_FONT = Font(name="Arial", size=10) -_PAT_BOLD_FONT = Font(name="Arial", bold=True, size=10) -_PAT_STRIKE_FONT = Font(name="Arial", size=10, strike=True, color="999999") -_PAT_ADOLESC_FONT = Font(name="Arial", bold=True, size=10) -_PAT_THIN = Side(style="thin", color="CCCCCC") -_PAT_BORDER = Border(left=_PAT_THIN, right=_PAT_THIN, top=_PAT_THIN, bottom=_PAT_THIN) -_PAT_EVEN_FILL = PatternFill("solid", start_color="EBF3FB") -_PAT_ODD_FILL = PatternFill("solid", start_color="FFFFFF") -_PAT_CENTER = Alignment(horizontal="center", vertical="center") -_PAT_LEFT = Alignment(horizontal="left", vertical="center") - - -def _autofit(ws): - for col_cells in ws.columns: - max_len = 0 - col_letter = get_column_letter(col_cells[0].column) - for cell in col_cells: - if cell.value is None: - continue - # datum se zobrazí jako DD-MMM-YYYY = 11 znaků - if hasattr(cell.value, "strftime") or cell.number_format == "DD-MMM-YYYY": - length = 11 - else: - length = len(str(cell.value)) - if length > max_len: - max_len = length - ws.column_dimensions[col_letter].width = min(max_len + 3, 50) - - -def format_sheet(ws, header_color, highlight_col=None, highlight_color=None): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - header_fill = PatternFill("solid", start_color=header_color) - header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) - row_font = Font(name="Arial", size=10) - hi_fill = PatternFill("solid", start_color=highlight_color) if highlight_color else None - - headers = [cell.value for cell in ws[1]] - - for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - stripe = STRIPE_GRAY if row[0].row % 2 == 0 else STRIPE_WHITE - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if hi_fill and col_name == highlight_col: - cell.fill = hi_fill - else: - cell.fill = stripe - - _autofit(ws) - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -def format_shipment_sheet(ws, header_color_ship, header_color_detail, n_ship_cols): - thin = Side(style="thin", color="000000") - border = Border(left=thin, right=thin, top=thin, bottom=thin) - hfont = Font(bold=True, color="FFFFFF", name="Arial", size=10) - dfont = Font(name="Arial", size=10) - fill_ship = PatternFill("solid", start_color=header_color_ship) - fill_detail = PatternFill("solid", start_color=header_color_detail) - - for cell in ws[1]: - cell.fill = fill_ship if cell.column <= n_ship_cols else fill_detail - cell.font = hfont - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) - cell.border = border - ws.row_dimensions[1].height = 30 - - for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - stripe = STRIPE_GRAY if row[0].row % 2 == 0 else STRIPE_WHITE - for cell in row: - cell.font = dfont - cell.border = border - cell.alignment = Alignment(horizontal="center", vertical="center") - cell.fill = stripe - if cell.value.__class__.__name__ in ("datetime", "date", "Timestamp"): - cell.number_format = "DD-MMM-YYYY" - - _autofit(ws) - ws.auto_filter.ref = ws.dimensions - ws.freeze_panes = "A2" - - -# ── Pacienti ───────────────────────────────────────────────────────────────── - -def load_patients(study): - db = get_db() - docs = list(db.iwrs_subject_summary.find({"study": study})) - if not docs: - raise RuntimeError(f"Žádná data v Mongo pro pacienty {study}") - - base_cols = [ - ("subject", "Subject"), - ("investigator", "Investigator"), - ("age", "Subject's age collection"), - ("cohort_per_irt", "Cohort per IRT"), - ("irt_subject_status", "IRT Subject Status"), - ("last_irt_transaction", "Last Recorded IRT Transaction"), - ("next_irt_transaction", "Next Expected IRT Transaction"), - ("next_irt_transaction_date_local", "Next Expected IRT Transaction Date [Local]"), - ] - uco_extra = [ - ("rescreened_subject", "Rescreened Subject"), - ("adt_ir", "ADT-IR"), - ("three_or_more_advanced_therapies", "3+ Adv. Therapies"), - ("only_oral_5asa_compounds", "Only 5-ASA"), - ("ustekinumab", "Ustekinumab"), - ("isolated_proctitis", "Isolated Proctitis"), - ] - cols = list(base_cols) - if study == "77242113UCO3001": - cols += uco_extra - - rows = [{label: d.get(key) for key, label in cols} for d in docs] - df = pd.DataFrame(rows).sort_values("Subject").reset_index(drop=True) - - if "Next Expected IRT Transaction Date [Local]" in df.columns: - df["Next Expected IRT Transaction Date [Local]"] = pd.to_datetime( - df["Next Expected IRT Transaction Date [Local]"], errors="coerce" - ) - print(f" Pacienti: {len(df)} subjektů") - return df - - -def _simplify_cohort(val): - if pd.isna(val): - return "" - val = str(val) - if "dolescent" in val: - return "Adolescent" - if val.startswith("Adult"): - return "Adult" - return val - - -def _fmt_date(val): - if pd.isna(val): - return "" - if hasattr(val, "strftime"): - return val.strftime("%Y-%m-%d") - return str(val)[:10] - - -def _write_prehled(wb, df_raw, study): - ws = wb.create_sheet("Přehled", 0) - ws.sheet_view.showGridLines = False - - is_uco = (study == "77242113UCO3001") - - if is_uco: - display_headers = ["Subject", "Investigator", "Věk", "Cohort", - "Rescreened", "ADT-IR", "≥3 Adv.Th.", "5-ASA only", - "Uste.", "Isol.Proct.", - "Status", "Last IRT", "Next Visit", "Next Date"] - col_widths = [14, 22, 6, 12, 11, 8, 11, 10, 8, 12, 14, 12, 12, 13] - status_col = 11 - flag_cols = set(range(5, 11)) # 1-indexed sloupce s Yes/No hodnotami - else: - display_headers = ["Subject", "Investigator", "Věk", "Cohort", "Status", "Last IRT", "Next Visit", "Next Date"] - col_widths = [14, 22, 6, 12, 14, 12, 12, 13] - status_col = 5 - flag_cols = set() - - last_col = get_column_letter(len(display_headers)) - ws.merge_cells(f"A1:{last_col}1") - title = ws["A1"] - title.value = f"Subject Summary — {study} ({date.today().strftime('%d-%b-%Y')})" - title.font = Font(name="Arial", bold=True, size=12, color="1F4E79") - title.alignment = Alignment(horizontal="left", vertical="center") - ws.row_dimensions[1].height = 22 - - for c, (h, w) in enumerate(zip(display_headers, col_widths), 1): - cell = ws.cell(row=2, column=c, value=h) - cell.font = _PAT_HEADER_FONT - cell.fill = _PAT_HEADER_FILL - cell.alignment = _PAT_CENTER - cell.border = _PAT_BORDER - ws.column_dimensions[get_column_letter(c)].width = w - ws.row_dimensions[2].height = 18 - - base = { - "Subject": df_raw["Subject"].fillna(""), - "Investigator": df_raw["Investigator"].fillna(""), - "Věk": df_raw["Subject's age collection"].apply(lambda v: "" if pd.isna(v) else int(v)), - "Cohort": df_raw["Cohort per IRT"].apply(_simplify_cohort), - } - if is_uco: - base.update({ - "Rescreened": df_raw["Rescreened Subject"].fillna(""), - "ADT-IR": df_raw["ADT-IR"].fillna(""), - "≥3 Adv.Th.": df_raw["3+ Adv. Therapies"].fillna(""), - "5-ASA only": df_raw["Only 5-ASA"].fillna(""), - "Uste.": df_raw["Ustekinumab"].fillna(""), - "Isol.Proct.": df_raw["Isolated Proctitis"].fillna(""), - }) - base.update({ - "Status": df_raw["IRT Subject Status"].fillna(""), - "Last IRT": df_raw["Last Recorded IRT Transaction"].fillna("—"), - "Next Visit": df_raw["Next Expected IRT Transaction"].fillna("—"), - "Next Date": df_raw["Next Expected IRT Transaction Date [Local]"].apply(_fmt_date), - }) - display = pd.DataFrame(base).sort_values("Subject").reset_index(drop=True) - - for r_idx, row in display.iterrows(): - excel_row = r_idx + 3 - status = str(row["Status"]) - is_failed = "Screen Failed" in status or "Discontinued" in status - is_randomized = "Randomized" in status - is_adolescent = row["Cohort"] == "Adolescent" - fill = _PAT_EVEN_FILL if r_idx % 2 == 0 else _PAT_ODD_FILL - - for c_idx, val in enumerate(row, 1): - cell = ws.cell(row=excel_row, column=c_idx, value=val if val != "" else None) - cell.fill = fill - cell.border = _PAT_BORDER - cell.alignment = _PAT_CENTER if (c_idx == 3 or c_idx in flag_cols) else _PAT_LEFT - if is_failed: - cell.font = _PAT_STRIKE_FONT - elif c_idx == status_col and is_randomized: - cell.font = _PAT_BOLD_FONT - elif c_idx == 4 and is_adolescent: - cell.font = _PAT_ADOLESC_FONT - else: - cell.font = _PAT_NORMAL_FONT - ws.row_dimensions[excel_row].height = 16 - - ws.freeze_panes = "A3" - ws.auto_filter.ref = f"A2:{last_col}{len(display) + 2}" - - -def _write_next_visits(wb, df_raw, study, visits_df=None): - ws = wb.create_sheet("Next Visits", 1) - ws.sheet_view.showGridLines = False - - ws.merge_cells("A1:D1") - title = ws["A1"] - title.value = f"Next Expected Visits — {study} ({date.today().strftime('%d-%b-%Y')})" - title.font = Font(name="Arial", bold=True, size=12, color="1F4E79") - title.alignment = Alignment(horizontal="left", vertical="center") - ws.row_dimensions[1].height = 22 - - nv_headers = ["Subject", "Investigator", "Next Visit", "Datum"] - nv_widths = [14, 22, 26, 13] - for c, (h, w) in enumerate(zip(nv_headers, nv_widths), 1): - cell = ws.cell(row=2, column=c, value=h) - cell.font = _PAT_HEADER_FONT - cell.fill = _PAT_HEADER_FILL - cell.alignment = _PAT_CENTER - cell.border = _PAT_BORDER - ws.column_dimensions[get_column_letter(c)].width = w - ws.row_dimensions[2].height = 18 - - df = pd.DataFrame({ - "Subject": df_raw["Subject"].fillna(""), - "Investigator": df_raw["Investigator"].fillna(""), - "Next Visit": df_raw["Next Expected IRT Transaction"].fillna(""), - "Datum": df_raw["Next Expected IRT Transaction Date [Local]"], - "Status": df_raw["IRT Subject Status"].fillna(""), - }) - - # I-0: datum = screening date + 42 dní - if visits_df is not None and not visits_df.empty: - screen = ( - visits_df[visits_df["Visit"].str.contains("Screen", case=False, na=False)] - .groupby("Subject")["Visit Date"].min() - .rename("Screening Date") - ) - df = df.join(screen, on="Subject") - mask_i0 = df["Next Visit"].str.contains("I-0", na=False) - df.loc[mask_i0, "Datum"] = df.loc[mask_i0, "Screening Date"] + pd.Timedelta(days=42) - df = df.drop(columns=["Screening Date"]) - - df = df[df["Datum"].notna()] - df = df[~df["Status"].str.contains("Screen Failed|Discontinued", na=False)] - df = df.sort_values("Datum").reset_index(drop=True) - - for r_idx, row in df.iterrows(): - excel_row = r_idx + 3 - fill = _PAT_EVEN_FILL if r_idx % 2 == 0 else _PAT_ODD_FILL - datum_val = row["Datum"] - datum_str = datum_val.strftime("%Y-%m-%d") if hasattr(datum_val, "strftime") else str(datum_val)[:10] - for c_idx, val in enumerate([row["Subject"], row["Investigator"], row["Next Visit"], datum_str], 1): - cell = ws.cell(row=excel_row, column=c_idx, value=val if val != "" else None) - cell.fill = fill - cell.border = _PAT_BORDER - cell.font = _PAT_NORMAL_FONT - cell.alignment = _PAT_LEFT - ws.row_dimensions[excel_row].height = 16 - - ws.freeze_panes = "A3" - ws.auto_filter.ref = f"A2:D{len(df) + 2}" - - -# ── Jeden report pro jednu studii ───────────────────────────────────────────── - -def create_study_report(study): - today = date.today() - - # číslování: najdi nejvyšší existující verzi pro dnešní datum - existing = sorted(OUTPUT_DIR.glob(f"{today} {study} CZ IWRS overview v*.xlsx")) - if existing: - last = existing[-1].stem # např. "2026-05-12 42847922MDD3003 CZ IWRS overview v3" - last_ver = int(last.rsplit("v", 1)[-1]) - version = last_ver + 1 - else: - version = 1 - - output_file = OUTPUT_DIR / f"{today} {study} CZ IWRS overview v{version}.xlsx" - - print(f"\n[{study}] Nacitam z MongoDB...") - df = load_inventory(study) - shipments_df = load_shipments(study) - df_patients = load_patients(study) - visits_df = load_visits(study) - - expired_df, expired_sheet = build_expired(df) - assigned_df = build_assigned_not_dispensed(df) - not_returned_df = build_not_returned(df) - destruction_df = build_kits_for_destruction(df) - site_summary_df = build_site_summary(shipments_df) - - with pd.ExcelWriter(output_file, engine="openpyxl") as writer: - df.to_excel( writer, index=False, sheet_name="CountryMedicationOverview") - expired_df.to_excel( writer, index=False, sheet_name=expired_sheet) - assigned_df.to_excel( writer, index=False, sheet_name="Assigned not dispensed") - not_returned_df.to_excel( writer, index=False, sheet_name="Not returned") - destruction_df.to_excel( writer, index=False, sheet_name="Kits for destruction") - shipments_df.to_excel( writer, index=False, sheet_name="Shipments") - site_summary_df.to_excel( writer, index=False, sheet_name="Site Summary") - visits_df.to_excel( writer, index=False, sheet_name="Patient Visits") - - wb = load_workbook(output_file) - - ws_main = wb["CountryMedicationOverview"] - format_sheet(ws_main, header_color="1F4E79") - green_fill = PatternFill("solid", start_color="E2EFDA") - headers_main = [c.value for c in ws_main[1]] - for row in ws_main.iter_rows(min_row=2, max_row=ws_main.max_row): - for cell in row: - col_name = headers_main[cell.column - 1] if cell.column <= len(headers_main) else None - if col_name in ("Destroyed", "Basket No."): - cell.fill = green_fill - - format_sheet(wb[expired_sheet], header_color="C00000", highlight_col="Exp Date", highlight_color="FFE0E0") - format_sheet(wb["Assigned not dispensed"], header_color="833C00", highlight_col="Subject ID", highlight_color="FFF2CC") - format_sheet(wb["Not returned"], header_color="375623", highlight_col="Max Visit Date", highlight_color="E2EFDA") - format_sheet(wb["Kits for destruction"], header_color="595959") - format_shipment_sheet(wb["Shipments"], "1F4E79", "375623", N_SHIP_COLS) - format_sheet(wb["Site Summary"], header_color="1F4E79") - format_sheet(wb["Patient Visits"], header_color="1F4E79") - - # ── pacienti (Přehled + Next Visits) na začátek ────────────────────────── - _write_prehled(wb, df_patients, study) - _write_next_visits(wb, df_patients, study, visits_df) - - # ── pořadí listů: Patient Visits jako první ────────────────────────────── - names = wb.sheetnames - wb._sheets = [wb["Patient Visits"]] + [wb[s] for s in names if s != "Patient Visits"] - - wb.save(output_file) - print(f" Uloženo: {output_file.name} ({len(df)} řádků)") - - -# ── Main ────────────────────────────────────────────────────────────────────── - -def main(): - OUTPUT_DIR.mkdir(exist_ok=True) - for study in STUDIES: - try: - create_study_report(study) - except Exception as e: - import traceback - print(f"\n[{study}] CHYBA: {e}") - traceback.print_exc() - print("\nHotovo.") - - -main() diff --git a/IWRS/Drugs/db_config.py b/IWRS/Drugs/db_config.py deleted file mode 100644 index bfa5959..0000000 --- a/IWRS/Drugs/db_config.py +++ /dev/null @@ -1,5 +0,0 @@ -DB_HOST = "192.168.1.76" -DB_PORT = 3306 -DB_USER = "root" -DB_PASSWORD = "Vlado9674+" -DB_NAME = "studie" diff --git a/IWRS/Drugs/download_drugs.py b/IWRS/Drugs/download_drugs.py deleted file mode 100644 index c43cbf0..0000000 --- a/IWRS/Drugs/download_drugs.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -download_drugs.py — stažení Drugs reportů pro jednu studii do IWRS/Incoming/. -Verze: 1.0 | Datum: 2026-06-10 - -Volá se z IWRS/run_all_v1.0.py s již přihlášenou Playwright page (login + -výběr studie zajišťuje common.iwrs_portal.login). - - 1. Onsite inventory detail (per site, stahuje se vždy) - 2. IP destruction (per košík; přeskočí košíky už importované - v Mongo iwrs_destruction — destrukce se nemění) - 3. Shipments report (jeden soubor na studii, stahuje se vždy) - 4. Shipment details (per CZ zásilka; přeskočí zásilky, jejichž - položky jsou v Mongo iwrs_shipment_items se - statusem RECEIVED — finální stav) - -Názvy souborů (datumované, aby zapadly do Incoming/ flow): - YYYY-MM-DD {study} Onsite Inventory {site}.xlsx - YYYY-MM-DD {study} IP Destruction {basket}.xlsx - YYYY-MM-DD {study} Shipments Report.xlsx - YYYY-MM-DD {study} Shipment Details {shipment_id}.xlsx -""" - -import os -import sys -import datetime - -import pandas as pd - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -IWRS_DIR = os.path.dirname(BASE_DIR) -for _p in (IWRS_DIR, BASE_DIR): - if _p not in sys.path: - sys.path.insert(0, _p) - -from common.iwrs_portal import BASE_URL -from common.paths import INCOMING_DIR, unique_path -from common.mongo_writer import get_db - -SITES = { - "77242113UCO3001": [ - "DD5-CZ10001", "DD5-CZ10003", "DD5-CZ10006", "DD5-CZ10009", - "DD5-CZ10010", "DD5-CZ10012", "DD5-CZ10013", "DD5-CZ10015", - "DD5-CZ10016", "DD5-CZ10020", "DD5-CZ10021", "DD5-CZ10022", - ], - "42847922MDD3003": [ - "S10-CZ10002", "S10-CZ10004", "S10-CZ10005", - "S10-CZ10008", "S10-CZ10011", "S10-CZ10012", - ], -} - - -def _today(): - return datetime.date.today().strftime("%Y-%m-%d") - - -# ── skip-logika přes Mongo (náhrada za dřívější "soubor existuje") ─────────── - -def get_existing_baskets(study): - """Košíky už importované v iwrs_destruction — destrukce je immutable.""" - try: - db = get_db() - return set(db.iwrs_destruction.distinct("basket_id", {"study": study})) - except Exception as e: - print(f" UPOZORNĚNÍ: nelze načíst košíky z Mongo ({e}), stahuji vše") - return set() - - -def get_received_shipments(study): - """Zásilky, jejichž položky už jsou v Mongo se statusem RECEIVED (finální stav).""" - try: - db = get_db() - return set(db.iwrs_shipment_items.distinct( - "shipment_id", - {"study": study, "shipment_status": {"$regex": "^received$", "$options": "i"}}, - )) - except Exception as e: - print(f" UPOZORNĚNÍ: nelze načíst zásilky z Mongo ({e}), stahuji vše") - return set() - - -# ── download funkce ────────────────────────────────────────────────────────── - -def download_inventory(page, study): - today = _today() - page.goto(f"{BASE_URL}/report/onsite_inventory_detail") - page.wait_for_load_state("networkidle", timeout=120000) - - for site_id in SITES[study]: - print(f" [{site_id}] inventory...") - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.get_by_role("option", name=site_id).click() - page.wait_for_load_state("networkidle", timeout=120000) - - filename = unique_path(INCOMING_DIR, f"{today} {study} Onsite Inventory {site_id}") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - print(f" Inventory OK ({len(SITES[study])} center)") - - -def download_destruction(page, study): - today = _today() - page.goto(f"{BASE_URL}/report/ip_destruction_form") - page.wait_for_load_state("networkidle", timeout=120000) - - page.locator('input[placeholder="search"], input[type="text"]').first.click() - page.wait_for_timeout(1000) - baskets = [b.strip() for b in page.locator("mat-option").all_inner_texts() - if b.strip() and b.strip() != "No results found"] - page.keyboard.press("Escape") - page.wait_for_timeout(500) - - if not baskets: - print(" Žádné destruction košíky") - return - - existing = get_existing_baskets(study) - new_count = 0 - for basket in baskets: - if basket in existing: - continue # destrukce se nemění — přeskočit - print(f" [košík {basket}] stahování...") - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(basket) - page.wait_for_timeout(500) - page.locator("mat-option").first.dispatch_event("click") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = unique_path(INCOMING_DIR, f"{today} {study} IP Destruction {basket}") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - new_count += 1 - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(f" Destruction OK ({new_count} nových, {len(baskets) - new_count} přeskočeno)") - - -def download_shipments_report(page, study): - today = _today() - page.goto(f"{BASE_URL}/report/shipments_report") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = unique_path(INCOMING_DIR, f"{today} {study} Shipments Report") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" Shipments report OK -> {os.path.basename(filename)}") - return filename - - -def download_shipment_details(page, study, shipments_report_path): - today = _today() - - # načti CZ shipment IDs z právě staženého shipments reportu - raw = pd.read_excel(shipments_report_path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Shipment ID" in [str(v).strip() for v in row]: - header_row = i - break - df = pd.read_excel(shipments_report_path, header=header_row) - df = df.dropna(how="all") - df = df[df["Location"].astype(str).str.contains("Czech", na=False, case=False)] - cz_shipments = list(zip( - df["Shipment ID"].astype(str).str.strip(), - df["IRT Shipment Status"].astype(str).str.strip() if "IRT Shipment Status" in df.columns else [""] * len(df), - )) - print(f" CZ zásilek celkem: {len(cz_shipments)}") - - received = get_received_shipments(study) - - page.goto(f"{BASE_URL}/report/shipment_details_report") - page.wait_for_load_state("networkidle", timeout=120000) - - skipped = 0 - for shipment, status in cz_shipments: - if shipment in received: - skipped += 1 - continue # položky v Mongo už mají finální stav RECEIVED - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(shipment) - page.wait_for_timeout(500) - page.locator("mat-option").first.dispatch_event("click") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = unique_path(INCOMING_DIR, f"{today} {study} Shipment Details {shipment}") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{shipment}] ({status}) OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - print(f" Přeskočeno (RECEIVED v Mongo): {skipped}") - - -def run(page, study): - """Stáhne všechny 4 typy Drugs reportů pro studii do IWRS/Incoming/.""" - os.makedirs(INCOMING_DIR, exist_ok=True) - - print("\n [1/4] Onsite inventory...") - download_inventory(page, study) - - print("\n [2/4] IP destruction...") - download_destruction(page, study) - - print("\n [3/4] Shipments report...") - report_path = download_shipments_report(page, study) - - print("\n [4/4] Shipment details (CZ)...") - download_shipment_details(page, study, report_path) diff --git a/IWRS/Drugs/import_drugs.py b/IWRS/Drugs/import_drugs.py deleted file mode 100644 index 53474a7..0000000 --- a/IWRS/Drugs/import_drugs.py +++ /dev/null @@ -1,306 +0,0 @@ -""" -import_drugs.py — import Drugs reportů z IWRS/Incoming/ do MongoDB. -Verze: 1.0 | Datum: 2026-06-10 - -Nahrazuje Drugs/import_to_mongo.py (ten parsoval pevné adresáře xls_*; -nyní se parsují datumované soubory z IWRS/Incoming/). - -Per studie a běh: jeden import_id. Soubory se zpracují nejstarší napřed, -při více souborech stejného záznamu vyhrává poslední (poslední stav). -Po úspěšném zápisu do Monga se zparsované soubory přesunou do -IWRS/Incoming/Processed/; soubor s chybou parsování zůstává v Incoming/. - -Cílové kolekce (db `studie`): - iwrs_shipments / iwrs_shipment_items / iwrs_inventory (upsert + snapshot) - iwrs_destruction (upsert only, immutable) - -Volá se z IWRS/run_all_v1.0.py (ensure_indexes volá orchestrátor); -lze spustit i samostatně: python import_drugs.py -""" - -import os -import re -import sys -import glob - -import pandas as pd - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -IWRS_DIR = os.path.dirname(BASE_DIR) -for _p in (IWRS_DIR, BASE_DIR): - if _p not in sys.path: - sys.path.insert(0, _p) - -from common.paths import INCOMING_DIR, STUDIES, move_done, sorted_by_mtime -from common.mongo_writer import ( - to_str, to_int, to_date, - ensure_indexes, log_import, - bulk_upsert_with_snapshot, bulk_upsert_only, -) - - -def _pending(pattern): - return sorted_by_mtime(glob.glob(os.path.join(INCOMING_DIR, pattern))) - - -def _find_header_row(raw, marker): - for i, row in raw.iterrows(): - if marker in [str(v).strip() for v in row]: - return i - return None - - -# ── XLSX parsery (per soubor) ──────────────────────────────────────────────── - -def parse_shipments_file(path, study): - raw = pd.read_excel(path, header=None) - header_row = _find_header_row(raw, "Shipment ID") - if header_row is None: - raise ValueError("hlavičkový řádek 'Shipment ID' nenalezen") - df = pd.read_excel(path, header=header_row).dropna(how="all") - df = df[df["Location"].astype(str).str.contains("Czech", na=False, case=False)] - col = df.columns.tolist() - rows = [] - for _, r in df.iterrows(): - sid = to_str(r["Shipment ID"]) - if not sid: - continue - rows.append({ - "_id": sid, - "shipment_id": sid, - "study": study, - "status": to_str(r["IRT Shipment Status"]), - "type": to_str(r["Type"]), - "ship_from": to_str(r["Shipment From"]), - "ship_to_site": to_str(r["Ship To:"]), - "location": to_str(r["Location"]), - "request_date": to_date(r["Request Date"]), - "shipped_date": to_date(r["Shipped Date"]), - "received_date": to_date(r["Received Date"]) if "Received Date" in col else None, - "received_by": to_str(r["Received by"]) if "Received by" in col else None, - "delivered_date_utc": to_date(r["Delivered Date [UTC]"]) if "Delivered Date [UTC]" in col else None, - "delivery_recipient": to_str(r["Delivery Recipient"]) if "Delivery Recipient" in col else None, - "delivery_details": to_str(r["Delivery Details"]) if "Delivery Details" in col else None, - "cancelled_date": to_date(r["Cancelled Date"]) if "Cancelled Date" in col else None, - "total_medication_ids": to_int(r["Total Medication IDs"]) if "Total Medication IDs" in col else None, - "tracking_no": to_str(r["Tracking #"]) if "Tracking #" in col else None, - "shipping_category": to_str(r["Shipping Category"]) if "Shipping Category" in col else None, - "expected_arrival": to_date(r["Expected Arrival"]) if "Expected Arrival" in col else None, - }) - return rows - - -def parse_shipment_details_file(path, study): - # shipment_id z názvu: "... Shipment Details {id}[ HHMM].xlsx" - m = re.search(r"Shipment Details (\S+?)(?: \d{4})?\.xlsx$", os.path.basename(path)) - shipment_id = m.group(1) if m else "UNKNOWN" - raw = pd.read_excel(path, header=None) - header_row = _find_header_row(raw, "Medication ID") - if header_row is None: - raise ValueError("hlavičkový řádek 'Medication ID' nenalezen") - df = pd.read_excel(path, header=header_row).dropna(how="all") - rows = [] - for _, r in df.iterrows(): - med_desc = (to_str(r.get("Medication Description")) - or to_str(r.get("Medication ID Description"))) - med_type = (to_str(r.get("Medication type")) - or to_str(r.get("Medication ID type"))) - med_id = to_str(r.get("Medication ID")) - if not med_id: - continue - rows.append({ - "_id": f"{shipment_id}:{med_id}", - "study": study, - "shipment_id": shipment_id, - "destination_location": to_str(r.get("Destination Location")), - "shipment_status": to_str(r.get("IRT Shipment Status")), - "shipment_type": to_str(r.get("Type")), - "destination_site": to_str(r.get("Destination Site")), - "investigator": to_str(r.get("Investigator")), - "medication_description": med_desc, - "medication_type": med_type, - "medication_id": med_id, - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "container_id": to_str(r.get("Container ID")), - "quantity": to_int(r.get("Quantity of Medication IDs")), - "expiration_date": to_date(r.get("Expiration Date")), - "item_status": to_str(r.get("Status")), - }) - return rows - - -def parse_inventory_file(path, study): - raw = pd.read_excel(path, header=None) - site = investigator = location = None - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - if first.startswith("Site:"): - site = first.replace("Site:", "").strip() - elif first.startswith("Investigator:"): - investigator = first.replace("Investigator:", "").strip() - elif first.startswith("Location:"): - location = first.replace("Location:", "").strip() - if first in ("Medication", "Medication ID") and header_row is None: - header_row = i - if header_row is None: - raise ValueError("hlavičkový řádek 'Medication' nenalezen") - df = pd.read_excel(path, header=header_row).dropna(how="all") - df = df.rename(columns={df.columns[0]: "medication_id"}) - rows = [] - for _, r in df.iterrows(): - med_id = to_str(r["medication_id"]) - if not med_id or not site: - continue - rows.append({ - "_id": f"{site}:{med_id}", - "study": study, - "site": site, - "investigator": investigator, - "location": location, - "medication_id": med_id, - "packaged_lot_no": to_str(r.get("Packaged Lot number")), - "original_expiration_date": to_date(r.get("Original Expiration Date when Packaged Lot was Added")), - "expiration_date": to_date(r.get("Expiration date")), - "received_date": to_date(r.get("Received Date")), - "receipt_user": to_str(r.get("Shipment Receipt User")), - "subject_identifier": to_str(r.get("Subject Identifier")), - "quantity_assigned": to_int(r.get("Quantity Assigned")), - "irt_transaction": to_str(r.get("IRT Transaction")), - "date_assigned": to_date(r.get("Date Assigned")), - "assignment_user": to_str(r.get("Assignment User")), - "dispensation_status": to_str(r.get("Dispensation Status")), - "dispensing_date": to_date(r.get("Dispensing date") or r.get("Dispensing Date")), - "quantity_dispensed": to_int(r.get("Quantity Dispensed")), - "dispensing_user": to_str(r.get("Dispensing User")), - "quantity_returned": to_int(r.get("Quantity Returned")), - "date_returned": to_date(r.get("Date Returned")), - "return_user": to_str(r.get("Return User")), - }) - return rows - - -def parse_destruction_file(path, study): - raw = pd.read_excel(path, header=None) - meta = {} - header_row = None - for i, row in raw.iterrows(): - first = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else "" - for key, attr in [ - ("Investigator Name:", "investigator"), - ("Site ID:", "site_id"), - ("Location:", "location"), - ("Basket ID:", "basket_id"), - ("Drug Destruction Created Date:", "destruction_date"), - ]: - if first.startswith(key): - meta[attr] = first.replace(key, "").strip() - if first == "Medication ID Description" and header_row is None: - header_row = i - if header_row is None: - raise ValueError("hlavičkový řádek 'Medication ID Description' nenalezen") - df = pd.read_excel(path, header=header_row).dropna(how="all") - basket_id = meta.get("basket_id") - rows = [] - for _, r in df.iterrows(): - med_id = to_str(r.get("Medication ID")) - if not med_id or not basket_id: - continue - rows.append({ - "_id": f"{basket_id}:{med_id}", - "study": study, - "site_id": meta.get("site_id"), - "investigator": meta.get("investigator"), - "location": meta.get("location"), - "basket_id": basket_id, - "destruction_date": to_date(meta.get("destruction_date")), - "medication_description": to_str(r.get("Medication ID Description")), - "medication_id": med_id, - "packaged_lot_description": to_str(r.get("Packaged Lot description")), - "comments": to_str(r.get("Comments")), - }) - return rows - - -# ── zpracování souborů ─────────────────────────────────────────────────────── - -def _parse_files(files, parser, study, label): - """Zparsuje soubory (nejstarší napřed, poslední vyhrává per _id). - - Vrací (docs, ok_paths, failed_paths). - """ - docs, ok, failed = {}, [], [] - for path in files: - try: - for d in parser(path, study): - docs[d["_id"]] = d - ok.append(path) - except Exception as e: - failed.append(path) - print(f" [{study}] CHYBA parsování {label} {os.path.basename(path)}: {e}") - return list(docs.values()), ok, failed - - -def import_study(study): - ship_files = _pending(f"* {study} Shipments Report*.xlsx") - item_files = _pending(f"* {study} Shipment Details *.xlsx") - inv_files = _pending(f"* {study} Onsite Inventory *.xlsx") - dest_files = _pending(f"* {study} IP Destruction *.xlsx") - - if not (ship_files or item_files or inv_files or dest_files): - print(f" [{study}] drugs: nic ke zpracování") - return - - shipments, ok_ship, _ = _parse_files(ship_files, parse_shipments_file, study, "shipments") - items, ok_item, _ = _parse_files(item_files, parse_shipment_details_file, study, "details") - inventory, ok_inv, _ = _parse_files(inv_files, parse_inventory_file, study, "inventory") - destruct, ok_dest, _ = _parse_files(dest_files, parse_destruction_file, study, "destruction") - - ok_files = ok_ship + ok_item + ok_inv + ok_dest - if not ok_files: - print(f" [{study}] drugs: žádný soubor se nepodařilo zparsovat") - return - - print(f" [{study}] Zásilky: {len(shipments)} | Položky: {len(items)} | " - f"Sklad: {len(inventory)} | Destrukce: {len(destruct)}") - - import_id = log_import(study, f"drugs_{study}", "drugs", { - "shipments": len(shipments), - "shipment_items": len(items), - "inventory": len(inventory), - "destruction": len(destruct), - }) - print(f" [{study}] import_id = {import_id}") - - bulk_upsert_with_snapshot("iwrs_shipments", "iwrs_shipments_snapshots", shipments, import_id) - bulk_upsert_with_snapshot("iwrs_shipment_items", "iwrs_shipment_items_snapshots", items, import_id) - bulk_upsert_with_snapshot("iwrs_inventory", "iwrs_inventory_snapshots", inventory, import_id) - bulk_upsert_only("iwrs_destruction", destruct, import_id) - - # zápis do Monga prošel → archivovat zdrojové soubory - for path in ok_files: - move_done(path) - print(f" [{study}] drugs: {len(ok_files)} soubor(ů) přesunuto do Processed") - - -def run(studies=None): - studies = studies or STUDIES - if not os.path.isdir(INCOMING_DIR): - print(f"Adresář neexistuje: {INCOMING_DIR}") - return - print("=" * 60) - print("Import Drugs (shipments / items / inventory / destruction)") - print("=" * 60) - for study in studies: - try: - import_study(study) - except Exception as e: - import traceback - print(f" [{study}] CHYBA importu drugs: {e}") - traceback.print_exc() - - -if __name__ == "__main__": - ensure_indexes() - run(sys.argv[1:] or None) diff --git a/IWRS/Drugs/output/2026-04-21 42847922MDD3003 CZ IWRS overview.xlsx b/IWRS/Drugs/output/2026-04-21 42847922MDD3003 CZ IWRS overview.xlsx deleted file mode 100644 index a9515da..0000000 Binary files a/IWRS/Drugs/output/2026-04-21 42847922MDD3003 CZ IWRS overview.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments.xlsx b/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments.xlsx deleted file mode 100644 index cc31dc5..0000000 Binary files a/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments_100177.xlsx b/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments_100177.xlsx deleted file mode 100644 index cfd1874..0000000 Binary files a/IWRS/Drugs/output/2026-04-21 77242113UCO3001 CZ Shipments_100177.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-04-27 77242113UCO3001 CZ IWRS overview.xlsx b/IWRS/Drugs/output/2026-04-27 77242113UCO3001 CZ IWRS overview.xlsx deleted file mode 100644 index 91f0461..0000000 Binary files a/IWRS/Drugs/output/2026-04-27 77242113UCO3001 CZ IWRS overview.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 993c7da..0000000 Binary files a/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v2.xlsx deleted file mode 100644 index 92607f3..0000000 Binary files a/IWRS/Drugs/output/2026-05-12 42847922MDD3003 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index ed4ecad..0000000 Binary files a/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v2.xlsx deleted file mode 100644 index a8a616b..0000000 Binary files a/IWRS/Drugs/output/2026-05-12 77242113UCO3001 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-13 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-13 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 30a7849..0000000 Binary files a/IWRS/Drugs/output/2026-05-13 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-13 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-13 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index a8dd09b..0000000 Binary files a/IWRS/Drugs/output/2026-05-13 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index e5dedb7..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v2.xlsx deleted file mode 100644 index 8d65305..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v3.xlsx b/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v3.xlsx deleted file mode 100644 index bf8628a..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v3.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v4.xlsx b/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v4.xlsx deleted file mode 100644 index 16d0130..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v4.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v5.xlsx b/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v5.xlsx deleted file mode 100644 index 5c1a8c4..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 42847922MDD3003 CZ IWRS overview v5.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 87cebc1..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v2.xlsx deleted file mode 100644 index 8ac7c7d..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v3.xlsx b/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v3.xlsx deleted file mode 100644 index 2d2afa4..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v3.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v4.xlsx b/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v4.xlsx deleted file mode 100644 index 30f62cd..0000000 Binary files a/IWRS/Drugs/output/2026-05-15 77242113UCO3001 CZ IWRS overview v4.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-19 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-19 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 548968d..0000000 Binary files a/IWRS/Drugs/output/2026-05-19 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-19 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-19 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index ec01f02..0000000 Binary files a/IWRS/Drugs/output/2026-05-19 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-20 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-20 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index b2ceeaf..0000000 Binary files a/IWRS/Drugs/output/2026-05-20 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-20 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-20 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index c257c0b..0000000 Binary files a/IWRS/Drugs/output/2026-05-20 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-21 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-21 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 3d2ed0d..0000000 Binary files a/IWRS/Drugs/output/2026-05-21 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-21 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-21 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index f620180..0000000 Binary files a/IWRS/Drugs/output/2026-05-21 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 668d48b..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v2.xlsx deleted file mode 100644 index 1ef233d..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v3.xlsx b/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v3.xlsx deleted file mode 100644 index 0910f2c..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 42847922MDD3003 CZ IWRS overview v3.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 4833832..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v2.xlsx b/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v2.xlsx deleted file mode 100644 index 1430f62..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v2.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v3.xlsx b/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v3.xlsx deleted file mode 100644 index 9b86d08..0000000 Binary files a/IWRS/Drugs/output/2026-05-25 77242113UCO3001 CZ IWRS overview v3.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-26 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-26 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index c145e10..0000000 Binary files a/IWRS/Drugs/output/2026-05-26 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-26 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-26 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 82ba4d0..0000000 Binary files a/IWRS/Drugs/output/2026-05-26 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-27 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-27 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index ad1b0bc..0000000 Binary files a/IWRS/Drugs/output/2026-05-27 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-27 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-27 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 148526a..0000000 Binary files a/IWRS/Drugs/output/2026-05-27 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-31 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-31 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 751fd35..0000000 Binary files a/IWRS/Drugs/output/2026-05-31 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-05-31 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-05-31 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 3f27a64..0000000 Binary files a/IWRS/Drugs/output/2026-05-31 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-01 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-01 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 29c9241..0000000 Binary files a/IWRS/Drugs/output/2026-06-01 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-01 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-01 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 50fb7e8..0000000 Binary files a/IWRS/Drugs/output/2026-06-01 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-03 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-03 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 992ce3f..0000000 Binary files a/IWRS/Drugs/output/2026-06-03 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-03 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-03 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index b9aed45..0000000 Binary files a/IWRS/Drugs/output/2026-06-03 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-05 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-05 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 831a7c4..0000000 Binary files a/IWRS/Drugs/output/2026-06-05 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-05 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-05 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index b5d15d8..0000000 Binary files a/IWRS/Drugs/output/2026-06-05 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-09 42847922MDD3003 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-09 42847922MDD3003 CZ IWRS overview v1.xlsx deleted file mode 100644 index 450f348..0000000 Binary files a/IWRS/Drugs/output/2026-06-09 42847922MDD3003 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/output/2026-06-09 77242113UCO3001 CZ IWRS overview v1.xlsx b/IWRS/Drugs/output/2026-06-09 77242113UCO3001 CZ IWRS overview v1.xlsx deleted file mode 100644 index 673f5db..0000000 Binary files a/IWRS/Drugs/output/2026-06-09 77242113UCO3001 CZ IWRS overview v1.xlsx and /dev/null differ diff --git a/IWRS/Drugs/preview_visits.py b/IWRS/Drugs/preview_visits.py deleted file mode 100644 index 979f425..0000000 --- a/IWRS/Drugs/preview_visits.py +++ /dev/null @@ -1,52 +0,0 @@ -import mysql.connector -import pandas as pd -import db_config - -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, -) -cursor = conn.cursor(dictionary=True) - -# Vezmi nejnovější import_id pro každou studii -for study in ["77242113UCO3001", "42847922MDD3003"]: - cursor.execute( - "SELECT MAX(import_id) AS mid FROM iwrs_import WHERE study=%s AND report_type='patients'", - (study,), - ) - row = cursor.fetchone() - mid = row["mid"] - print(f"\n=== {study} (import_id={mid}) ===") - - cursor.execute(""" - SELECT - v.subject, - v.actual_date, - v.scheduled_date, - v.irt_transaction_no, - v.irt_transaction_description, - v.medication_assignment, - GROUP_CONCAT(v.medication_id ORDER BY v.medication_id SEPARATOR ', ') AS medication_ids, - SUM(v.quantity_assigned) AS quantity_assigned - FROM iwrs_subject_visits v - WHERE v.import_id = %s AND v.study = %s AND v.visit_type = 'Past' - AND v.irt_transaction_no IS NOT NULL - GROUP BY v.subject, v.actual_date, v.scheduled_date, v.irt_transaction_no, - v.irt_transaction_description, v.medication_assignment - ORDER BY v.subject, v.actual_date - LIMIT 20 - """, (mid, study)) - - rows = cursor.fetchall() - df = pd.DataFrame(rows) - if df.empty: - print(" Žádná data.") - else: - pd.set_option("display.max_columns", None) - pd.set_option("display.width", 200) - pd.set_option("display.max_colwidth", 30) - print(df.to_string(index=False)) - -cursor.close() -conn.close() diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_194.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_194.xlsx deleted file mode 100644 index f7c1e0a..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_194.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_202.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_202.xlsx deleted file mode 100644 index a890c9e..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_202.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_248.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_248.xlsx deleted file mode 100644 index 46fad8f..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_248.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_269.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_269.xlsx deleted file mode 100644 index 0e590b5..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_269.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_273.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_273.xlsx deleted file mode 100644 index 86ffc69..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_273.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_276.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_276.xlsx deleted file mode 100644 index d193ae7..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_276.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_286.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_286.xlsx deleted file mode 100644 index 6e29d3a..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_286.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_289.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_289.xlsx deleted file mode 100644 index ce22899..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_289.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_301.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_301.xlsx deleted file mode 100644 index eb84fc5..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_301.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_313.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_313.xlsx deleted file mode 100644 index a855cf2..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_313.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_326.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_326.xlsx deleted file mode 100644 index 3397dc6..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_326.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_343.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_343.xlsx deleted file mode 100644 index 400e438..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_343.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_358.xlsx b/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_358.xlsx deleted file mode 100644 index 8642e53..0000000 Binary files a/IWRS/Drugs/xls_ip_destruction_42847922MDD3003/ip_destruction_basket_358.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10002.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10002.xlsx deleted file mode 100644 index 970b463..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10002.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10004.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10004.xlsx deleted file mode 100644 index 126414f..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10004.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10005.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10005.xlsx deleted file mode 100644 index 5580549..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10005.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10008.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10008.xlsx deleted file mode 100644 index 72bd572..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10008.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10011.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10011.xlsx deleted file mode 100644 index 627f772..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10011.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10012.xlsx b/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10012.xlsx deleted file mode 100644 index 80279df..0000000 Binary files a/IWRS/Drugs/xls_reports_42847922MDD3003/onsite_inventory_detail_S10-CZ10012.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10001.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10001.xlsx deleted file mode 100644 index 3e9a340..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10001.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10003.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10003.xlsx deleted file mode 100644 index 5d5eee7..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10003.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10006.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10006.xlsx deleted file mode 100644 index 7533a8b..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10006.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10009.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10009.xlsx deleted file mode 100644 index 5365856..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10009.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10010.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10010.xlsx deleted file mode 100644 index a677935..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10010.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10012.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10012.xlsx deleted file mode 100644 index 866e5fc..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10012.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10013.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10013.xlsx deleted file mode 100644 index 41c70c8..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10013.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10015.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10015.xlsx deleted file mode 100644 index f645880..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10015.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10016.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10016.xlsx deleted file mode 100644 index a1b0376..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10016.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10020.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10020.xlsx deleted file mode 100644 index e07c3e2..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10020.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10021.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10021.xlsx deleted file mode 100644 index ee7ae1c..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10021.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10022.xlsx b/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10022.xlsx deleted file mode 100644 index b23e88d..0000000 Binary files a/IWRS/Drugs/xls_reports_77242113UCO3001/onsite_inventory_detail_DD5-CZ10022.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100873.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100873.xlsx deleted file mode 100644 index 2f7fa54..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100873.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100874.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100874.xlsx deleted file mode 100644 index 258c572..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100874.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100880.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100880.xlsx deleted file mode 100644 index 5b6ccc1..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100880.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100881.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100881.xlsx deleted file mode 100644 index f02f476..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100881.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100895.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100895.xlsx deleted file mode 100644 index 4200081..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100895.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100905.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100905.xlsx deleted file mode 100644 index ef9904d..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100905.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100946.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100946.xlsx deleted file mode 100644 index 72d2c9c..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100946.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100971.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100971.xlsx deleted file mode 100644 index 0fc1dfe..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100971.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100980.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100980.xlsx deleted file mode 100644 index f18a7c0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100980.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100986.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100986.xlsx deleted file mode 100644 index 079ad78..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100986.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100994.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100994.xlsx deleted file mode 100644 index 5dfdae7..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_100994.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101085.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101085.xlsx deleted file mode 100644 index 8c81ed6..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101085.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101092.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101092.xlsx deleted file mode 100644 index 4b516c3..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101092.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101102.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101102.xlsx deleted file mode 100644 index 2efc8ec..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101102.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101110.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101110.xlsx deleted file mode 100644 index a534d23..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101110.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101117.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101117.xlsx deleted file mode 100644 index 2fd361e..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101117.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101118.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101118.xlsx deleted file mode 100644 index 4e2e32a..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101118.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101119.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101119.xlsx deleted file mode 100644 index 08d8da7..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101119.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101139.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101139.xlsx deleted file mode 100644 index 33bd95c..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101139.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101246.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101246.xlsx deleted file mode 100644 index b6818ac..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101246.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101270.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101270.xlsx deleted file mode 100644 index fa69ca4..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101270.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101274.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101274.xlsx deleted file mode 100644 index 96769ef..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101274.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101293.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101293.xlsx deleted file mode 100644 index bd3d5c9..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101293.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101300.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101300.xlsx deleted file mode 100644 index b2b8248..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101300.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101322.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101322.xlsx deleted file mode 100644 index e8823a8..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101322.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101327.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101327.xlsx deleted file mode 100644 index 0925291..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101327.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101357.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101357.xlsx deleted file mode 100644 index 4884854..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101357.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101378.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101378.xlsx deleted file mode 100644 index 94534aa..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101378.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101385.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101385.xlsx deleted file mode 100644 index b16cc01..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101385.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101418.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101418.xlsx deleted file mode 100644 index f67b7f5..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101418.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101444.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101444.xlsx deleted file mode 100644 index 8be9ff9..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101444.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101487.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101487.xlsx deleted file mode 100644 index 6de6ab8..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101487.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101508.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101508.xlsx deleted file mode 100644 index 3218104..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101508.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101524.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101524.xlsx deleted file mode 100644 index 90184b7..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101524.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101530.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101530.xlsx deleted file mode 100644 index 12f82a0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101530.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101531.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101531.xlsx deleted file mode 100644 index 6bda309..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101531.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101555.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101555.xlsx deleted file mode 100644 index 35372ba..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101555.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101589.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101589.xlsx deleted file mode 100644 index 6949101..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101589.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101662.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101662.xlsx deleted file mode 100644 index b56e198..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101662.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101688.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101688.xlsx deleted file mode 100644 index bc8adc3..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101688.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101700.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101700.xlsx deleted file mode 100644 index c7755da..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101700.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101720.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101720.xlsx deleted file mode 100644 index 3162abb..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101720.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101732.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101732.xlsx deleted file mode 100644 index f659594..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101732.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101738.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101738.xlsx deleted file mode 100644 index a3a2165..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101738.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101750.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101750.xlsx deleted file mode 100644 index 40a86e9..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101750.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101751.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101751.xlsx deleted file mode 100644 index c968f07..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101751.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101784.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101784.xlsx deleted file mode 100644 index 0d0521a..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101784.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101785.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101785.xlsx deleted file mode 100644 index 9eb04d1..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101785.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101827.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101827.xlsx deleted file mode 100644 index a173fe5..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101827.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101858.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101858.xlsx deleted file mode 100644 index 61d9755..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101858.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101910.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101910.xlsx deleted file mode 100644 index b1fbaf4..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101910.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101919.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101919.xlsx deleted file mode 100644 index b09acd7..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101919.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101925.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101925.xlsx deleted file mode 100644 index 063ec16..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101925.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101962.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101962.xlsx deleted file mode 100644 index 75ea307..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101962.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101963.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101963.xlsx deleted file mode 100644 index eafb3a0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101963.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101964.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101964.xlsx deleted file mode 100644 index b9982d8..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101964.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101965.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101965.xlsx deleted file mode 100644 index 81ad3ae..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101965.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101966.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101966.xlsx deleted file mode 100644 index d59ba91..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101966.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101967.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101967.xlsx deleted file mode 100644 index ac045f4..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_101967.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102071.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102071.xlsx deleted file mode 100644 index 46a38d2..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102071.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102075.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102075.xlsx deleted file mode 100644 index 394516a..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102075.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102094.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102094.xlsx deleted file mode 100644 index c5cb372..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102094.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102108.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102108.xlsx deleted file mode 100644 index 6ad52c0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102108.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102136.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102136.xlsx deleted file mode 100644 index 41d1c5f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102136.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102137.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102137.xlsx deleted file mode 100644 index db40fe0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102137.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102160.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102160.xlsx deleted file mode 100644 index 992a07f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102160.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102193.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102193.xlsx deleted file mode 100644 index 93e239a..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102193.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102199.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102199.xlsx deleted file mode 100644 index d233738..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102199.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102247.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102247.xlsx deleted file mode 100644 index 3a8f04f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102247.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102256.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102256.xlsx deleted file mode 100644 index a1e7bd6..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102256.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102275.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102275.xlsx deleted file mode 100644 index ead0f33..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102275.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102295.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102295.xlsx deleted file mode 100644 index baa5a4e..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102295.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102322.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102322.xlsx deleted file mode 100644 index f2a1d6b..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102322.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102341.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102341.xlsx deleted file mode 100644 index 71a421f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102341.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102403.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102403.xlsx deleted file mode 100644 index 5c7bddf..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102403.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102418.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102418.xlsx deleted file mode 100644 index ac31e27..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102418.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102439.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102439.xlsx deleted file mode 100644 index dd6627e..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102439.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102455.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102455.xlsx deleted file mode 100644 index db4a44b..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102455.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102497.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102497.xlsx deleted file mode 100644 index 08b64b4..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102497.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102538.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102538.xlsx deleted file mode 100644 index 61eb222..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102538.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102550.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102550.xlsx deleted file mode 100644 index 219c81c..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102550.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102596.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102596.xlsx deleted file mode 100644 index 67de467..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102596.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102602.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102602.xlsx deleted file mode 100644 index 72dd112..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102602.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102640.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102640.xlsx deleted file mode 100644 index 81e9679..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102640.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102641.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102641.xlsx deleted file mode 100644 index 2206827..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102641.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102758.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102758.xlsx deleted file mode 100644 index df43764..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102758.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102784.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102784.xlsx deleted file mode 100644 index 265d30b..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102784.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102814.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102814.xlsx deleted file mode 100644 index d53eaac..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102814.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102839.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102839.xlsx deleted file mode 100644 index 914560d..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102839.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102840.xlsx b/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102840.xlsx deleted file mode 100644 index 77b74a7..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_42847922MDD3003/shipment_details_102840.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100177.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100177.xlsx deleted file mode 100644 index 8d40975..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100177.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100222.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100222.xlsx deleted file mode 100644 index e5d0347..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100222.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100354.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100354.xlsx deleted file mode 100644 index f943d52..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100354.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100382.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100382.xlsx deleted file mode 100644 index eaf5976..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100382.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100411.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100411.xlsx deleted file mode 100644 index bb5fa9e..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100411.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100421.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100421.xlsx deleted file mode 100644 index 6dc3f08..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100421.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100498.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100498.xlsx deleted file mode 100644 index e16b7ce..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100498.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100510.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100510.xlsx deleted file mode 100644 index 36e1676..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100510.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100587.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100587.xlsx deleted file mode 100644 index 8b4b60e..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100587.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100593.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100593.xlsx deleted file mode 100644 index b4d740c..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100593.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100616.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100616.xlsx deleted file mode 100644 index 557406f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100616.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100678.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100678.xlsx deleted file mode 100644 index d121f04..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100678.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100717.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100717.xlsx deleted file mode 100644 index 97f5958..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100717.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100728.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100728.xlsx deleted file mode 100644 index 1100be4..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100728.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100733.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100733.xlsx deleted file mode 100644 index 972ff9a..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100733.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100740.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100740.xlsx deleted file mode 100644 index bd3c538..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100740.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100776.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100776.xlsx deleted file mode 100644 index 1436168..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100776.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100843.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100843.xlsx deleted file mode 100644 index 3841e30..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100843.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100845.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100845.xlsx deleted file mode 100644 index 7dd92ad..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100845.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100956.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100956.xlsx deleted file mode 100644 index 2a4700c..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100956.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100974.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100974.xlsx deleted file mode 100644 index 1d141e0..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100974.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100991.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100991.xlsx deleted file mode 100644 index 68a67b5..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_100991.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101039.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101039.xlsx deleted file mode 100644 index 0dadc82..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101039.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101060.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101060.xlsx deleted file mode 100644 index 32ec8c9..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101060.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101061.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101061.xlsx deleted file mode 100644 index 7fb9f95..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101061.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101087.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101087.xlsx deleted file mode 100644 index 422a853..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101087.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101104.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101104.xlsx deleted file mode 100644 index 3486dfa..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101104.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101132.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101132.xlsx deleted file mode 100644 index 30cfcfc..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101132.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101135.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101135.xlsx deleted file mode 100644 index d6847eb..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101135.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101185.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101185.xlsx deleted file mode 100644 index 6443df2..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101185.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101204.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101204.xlsx deleted file mode 100644 index 9548536..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101204.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101210.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101210.xlsx deleted file mode 100644 index e64a94f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101210.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101245.xlsx b/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101245.xlsx deleted file mode 100644 index 632aa3f..0000000 Binary files a/IWRS/Drugs/xls_shipment_details_77242113UCO3001/shipment_details_101245.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipments_42847922MDD3003/shipments_report_42847922MDD3003.xlsx b/IWRS/Drugs/xls_shipments_42847922MDD3003/shipments_report_42847922MDD3003.xlsx deleted file mode 100644 index a2193d2..0000000 Binary files a/IWRS/Drugs/xls_shipments_42847922MDD3003/shipments_report_42847922MDD3003.xlsx and /dev/null differ diff --git a/IWRS/Drugs/xls_shipments_77242113UCO3001/shipments_report_77242113UCO3001.xlsx b/IWRS/Drugs/xls_shipments_77242113UCO3001/shipments_report_77242113UCO3001.xlsx deleted file mode 100644 index 5a34722..0000000 Binary files a/IWRS/Drugs/xls_shipments_77242113UCO3001/shipments_report_77242113UCO3001.xlsx and /dev/null differ diff --git a/IWRS/Patients/CONTEXT.md b/IWRS/Patients/CONTEXT.md deleted file mode 100644 index 8e0a7d5..0000000 --- a/IWRS/Patients/CONTEXT.md +++ /dev/null @@ -1,56 +0,0 @@ -# Kontext práce — IWRS Notifications Pipeline -## Datum: 2026-06-01 - -## Co bylo uděláno - -### Nové soubory -- `download_subject_notifications.py` — standalone skript pro stažení notifikací (referenční, nepoužívaný v pipeline) -- `test_notifications.py` — testovací skript pro jednoho pacienta (CZ100222003 / UCO3001) -- `create_iwrs_tables.py` — jednorázový skript pro vytvoření MySQL tabulek - -### Upravené soubory -- `download_subject_details.py` — přidáno stahování notifikací (PDF + JSON) pro každý subjekt přímo v loopě -- `import_to_mysql.py` — přidána funkce `import_notifications()` která importuje JSON+PDF do DB a přesouvá do `Zpracováno/` -- `create_iwrs_tables.sql` — přidána tabulka `iwrs_notifications` -- `run_all.py` — krok 2 nyní volá `dsd.run()` z `download_subject_details.py` - -## Jak to funguje - -### Stahování notifikací (v `download_subject_details.py`) -1. Při výběru subjektu se zachytí `table_1` API response (obsahuje notifikace s `pk`, `et_title`, `label`, `body`, `actual_date_raw`) -2. Porovná `pk` s DB (`iwrs_notifications`) — stahuje jen nové -3. Stáhne PDF přes `page.request.get()` s Bearer tokenem (JWT se načítá čerstvě před každým requestem) -4. Uloží PDF + JSON do `IncomingSourceReportsDetails/{study}/` -5. Název souboru: `{actual_date_raw}_{label_s_podtržítky}.pdf` (při kolizi přidá `_pk{pk}`) - -### API endpointy -- **Notifikace data**: `POST /_/p/{instance_id}/api/v1/reports_api/report_data?path=patient_detail_report&id={subject}&key=table_1&unblinded=false` -- **PDF download**: `GET /_/p/{instance_id}/api/v1/meta_api/pdfnotification?pk={pk}&title={et_title}&html=true` -- **app_instances** (pro zjištění instance_id): `GET /_/api/dispatch/app_instances/` -- Headers: `Authorization: Bearer {JWT}`, `lang: en`, `prancer_study: {study_code}` - -### Instance ID mapping -- `77242113UCO3001` → `/_/p/106` -- `42847922MDD3003` → `/_/p/70` -- `77242113CRD3001` → `/_/p/103` - -### Import (`import_to_mysql.py`) -- Čte všechny `.json` soubory z `IncomingSourceReportsDetails/{study}/` -- Načte příslušné `.pdf` jako binární data -- Uloží do tabulky `iwrs_notifications` (UNIQUE KEY na `pk` — bez duplikátů) -- Přesune soubory do `IncomingSourceReportsDetails/{study}/Zpracováno/` - -## MySQL tabulka `iwrs_notifications` -```sql -id, study, subject, pk (UNIQUE), title, label, event, actual_date, text (TEXT), pdf (MEDIUMBLOB), source_file, imported_at -``` - -## Aktuální stav -- UCO3001: ~76 notifikací importováno -- MDD3003: ~119 notifikací importováno (část 403 chyb — JWT expiroval, opraveno načítáním JWT čerstvě) -- MDD3003 notifikace s 403 čekají na příští `run_all.py` (soubory nejsou v `Zpracováno`, takže se znovu stáhnou) - -## Co zbývá / možná vylepšení -- Ověřit že MDD3003 403 chyby jsou opraveny (JWT refresh) -- `CZ100132003` UCO3001 — timeout při stahování XLS (subjekt přeskočen, zkusit znovu) -- Případně přidat retry logiku pro timeout diff --git a/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary 1306.xlsx b/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary 1306.xlsx deleted file mode 100644 index 8e81c20..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary 1306.xlsx and /dev/null differ diff --git a/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary.xlsx b/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary.xlsx deleted file mode 100644 index 6971da4..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-04 42847922MDD3003 Subject Summary.xlsx and /dev/null differ diff --git a/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary 1306.xlsx b/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary 1306.xlsx deleted file mode 100644 index 31d24d0..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary 1306.xlsx and /dev/null differ diff --git a/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary.xlsx b/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary.xlsx deleted file mode 100644 index b315716..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-04 77242113UCO3001 Subject Summary.xlsx and /dev/null differ diff --git a/IWRS/Patients/CreatedReports/2026-05-12 42847922MDD3003 Subject Summary.xlsx b/IWRS/Patients/CreatedReports/2026-05-12 42847922MDD3003 Subject Summary.xlsx deleted file mode 100644 index d0c6c06..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-12 42847922MDD3003 Subject Summary.xlsx and /dev/null differ diff --git a/IWRS/Patients/CreatedReports/2026-05-12 77242113UCO3001 Subject Summary.xlsx b/IWRS/Patients/CreatedReports/2026-05-12 77242113UCO3001 Subject Summary.xlsx deleted file mode 100644 index 430c0e3..0000000 Binary files a/IWRS/Patients/CreatedReports/2026-05-12 77242113UCO3001 Subject Summary.xlsx and /dev/null differ diff --git a/IWRS/Patients/Trash/create_iwrs_tables.py b/IWRS/Patients/Trash/create_iwrs_tables.py deleted file mode 100644 index 1f80b74..0000000 --- a/IWRS/Patients/Trash/create_iwrs_tables.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Jednorázový skript — vytvoří/aktualizuje tabulky v MySQL. -Spusť jednou: python create_iwrs_tables.py -""" -import os -import mysql.connector -import db_config - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -SQL_FILE = os.path.join(BASE_DIR, "create_iwrs_tables.sql") - -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, -) -cursor = conn.cursor() - -sql = open(SQL_FILE, encoding="utf-8").read() -# Odstraň komentáře a rozdělíme na příkazy -stmts = [s.strip() for s in sql.split(";")] -for stmt in stmts: - # Odstraň řádkové komentáře - lines = [l for l in stmt.splitlines() if not l.strip().startswith("--")] - stmt = "\n".join(lines).strip() - if not stmt or stmt.upper().startswith("USE"): - continue - try: - cursor.execute(stmt) - print(f"OK: {stmt[:80]}") - except Exception as e: - print(f"SKIP: {e}") - -conn.commit() -cursor.close() -conn.close() -print("\nHotovo.") diff --git a/IWRS/Patients/Trash/create_iwrs_tables.sql b/IWRS/Patients/Trash/create_iwrs_tables.sql deleted file mode 100644 index b022bc7..0000000 --- a/IWRS/Patients/Trash/create_iwrs_tables.sql +++ /dev/null @@ -1,128 +0,0 @@ --- IWRS tabulky pro databázi studie --- Spustit jednou: mysql -h 192.168.1.76 -u root -p studie < create_iwrs_tables.sql - -USE studie; - --- ── Import log ─────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS iwrs_import ( - import_id INT AUTO_INCREMENT PRIMARY KEY, - study VARCHAR(20) NOT NULL, - imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - source_file VARCHAR(500) NOT NULL, - INDEX idx_study (study) -); - --- ── UCO3001 subject summary ─────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS iwrs_uco3001_subject_summary ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - subject VARCHAR(20) NOT NULL, - prior_subject_identifier VARCHAR(20), - site VARCHAR(50), - investigator VARCHAR(100), - location VARCHAR(50), - cohort_per_irt VARCHAR(100), - informed_consent_date DATE, - adolescent_assent_date DATE, - age SMALLINT, - weight DECIMAL(5,1), - rescreened_subject VARCHAR(10), - adt_ir VARCHAR(10), - three_or_more_advanced_therapies VARCHAR(10), - only_oral_5asa_compounds VARCHAR(10), - ustekinumab VARCHAR(10), - isolated_proctitis VARCHAR(10), - clinical_responder_status_i12_m0 VARCHAR(100), - irt_subject_status VARCHAR(50), - i0_rand_date_local DATE, - last_irt_transaction VARCHAR(100), - last_irt_transaction_date_local DATE, - last_irt_transaction_date_utc DATE, - next_irt_transaction VARCHAR(100), - next_irt_transaction_date_local DATE, - most_recent_med_assignment_date DATE, - days_since_last_med_assignment SMALLINT, - patient_forecast_status VARCHAR(50), - patient_forecast_status_changed_date DATE, - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_subject (subject) -); - --- ── MDD3003 subject summary ─────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS iwrs_mdd3003_subject_summary ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - subject VARCHAR(20) NOT NULL, - prior_subject_identifier VARCHAR(20), - site VARCHAR(50), - investigator VARCHAR(100), - location VARCHAR(50), - cohort_per_irt VARCHAR(50), - madrs_criteria_integrated VARCHAR(50), - informed_consent_date DATE, - age SMALLINT, - madrs_criteria_v15 VARCHAR(10), - madrs_criteria_v16 VARCHAR(10), - madrs_criteria_v17 VARCHAR(10), - stratification_country VARCHAR(10), - age_group VARCHAR(20), - stable_remitters VARCHAR(50), - irt_subject_status VARCHAR(100), - last_irt_transaction VARCHAR(100), - last_irt_transaction_date_local DATE, - last_irt_transaction_date_utc DATE, - next_irt_transaction VARCHAR(100), - next_irt_transaction_date_local DATE, - date_screened DATE, - date_screen_failed DATE, - date_randomized_part1 DATE, - date_early_withdraw_randomized_part1 DATE, - date_open_label_induction DATE, - date_early_withdraw_open_label_induction DATE, - date_randomized_part2 DATE, - date_early_withdraw_randomized_part2 DATE, - date_completed DATE, - date_unblinded DATE, - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_subject (subject) -); - --- ── Notifications ──────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS iwrs_notifications ( - id INT AUTO_INCREMENT PRIMARY KEY, - study VARCHAR(20) NOT NULL, - subject VARCHAR(20) NOT NULL, - pk INT NOT NULL, - title VARCHAR(100), - label VARCHAR(500), - event VARCHAR(50), - actual_date DATE, - text TEXT, - pdf MEDIUMBLOB, - source_file VARCHAR(500), - imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE KEY uq_pk (pk), - INDEX idx_study_subject (study, subject) -); - --- ── Subject visits / transactions (obě studie) ─────────────────────────────── -CREATE TABLE IF NOT EXISTS iwrs_subject_visits ( - id INT AUTO_INCREMENT PRIMARY KEY, - import_id INT NOT NULL, - study VARCHAR(20) NOT NULL, - subject VARCHAR(20) NOT NULL, - visit_type ENUM('Past','Upcoming') NOT NULL, - scheduled_date DATE, - window_days VARCHAR(20), - actual_date DATE, - irt_transaction_no SMALLINT, - irt_transaction_description VARCHAR(200), - medication_assignment VARCHAR(200), - quantity_assigned SMALLINT, - medication_id VARCHAR(20), - FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), - INDEX idx_import (import_id), - INDEX idx_study_subject (study, subject) -); diff --git a/IWRS/Patients/Trash/download_all.py b/IWRS/Patients/Trash/download_all.py deleted file mode 100644 index 72376af..0000000 --- a/IWRS/Patients/Trash/download_all.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Stažení reportů z IWRS portálu — vše do jednoho adresáře `Incoming/`. - - 1. Subject Summary Report (per studie) - 2. Subject Detail Reports + notifikace (per subjekt) - -Import se spouští samostatně skriptem `import_all.py`. -""" - -import os -import datetime - -from playwright.sync_api import sync_playwright - -import download_subject_details as dsd - -# ── CONFIG ─────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "Incoming") - - -def unique_path(directory, stem, ext=".xlsx"): - path = os.path.join(directory, f"{stem}{ext}") - if not os.path.exists(path): - return path - time_tag = datetime.datetime.now().strftime("%H%M") - return os.path.join(directory, f"{stem} {time_tag}{ext}") - - -def login(page, study): - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - -def download_summary(page, study, today): - print(f" [{study}] Stahuji Subject Summary Report...") - page.goto(f"{BASE_URL}/report/patient_summary_report") - page.wait_for_load_state("networkidle", timeout=120000) - filename = unique_path(INCOMING_DIR, f"{today} {study} Subject Summary Report") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{study}] Summary OK -> {os.path.basename(filename)}") - return filename - - -def main(): - today = datetime.date.today().strftime("%Y-%m-%d") - os.makedirs(INCOMING_DIR, exist_ok=True) - - with sync_playwright() as p: - for study in STUDIES: - print("\n" + "=" * 60) - print(f"[{study}] Stažení reportů") - print("=" * 60) - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - try: - login(page, study) - download_summary(page, study, today) - # detail XLSX + notifikace přímo do Incoming/ - dsd.run(page, study, out_dir=INCOMING_DIR, subjects_source_dir=INCOMING_DIR) - except Exception as e: - print(f" [{study}] CHYBA: {e}") - finally: - browser.close() - - print("\n" + "=" * 60) - print(f"Stahování hotovo. Soubory v: {INCOMING_DIR}") - print("Pro import spusť: python import_all.py") - print("=" * 60) - - -if __name__ == "__main__": - main() diff --git a/IWRS/Patients/Trash/download_subject_notifications.py b/IWRS/Patients/Trash/download_subject_notifications.py deleted file mode 100644 index 029a3e1..0000000 --- a/IWRS/Patients/Trash/download_subject_notifications.py +++ /dev/null @@ -1,201 +0,0 @@ -from playwright.sync_api import sync_playwright -import os -import glob -import datetime -import requests - -import pandas as pd - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "IncomingSourceReports") -DETAILS_DIR = os.path.join(BASE_DIR, "IncomingSourceReportsDetails") -# ──────────────────────────────────────────────────────────────────────────── - - -def get_subjects(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} Subject Summary Report.xlsx") - files = sorted( - [f for f in glob.glob(pattern) if not os.path.basename(f).startswith("~$")], - key=os.path.getmtime, - reverse=True, - ) - if not files: - raise FileNotFoundError(f"Nenalezen Subject Summary Report pro {study}") - today = datetime.date.today().strftime("%Y-%m-%d") - if not os.path.basename(files[0]).startswith(today): - raise FileNotFoundError( - f"Dnešní Subject Summary Report pro {study} neexistuje — spusť nejdříve download_subject_summary.py" - ) - path = files[0] - print(f" Čtu subjekty z: {os.path.basename(path)}") - - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Subject" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - raise ValueError("Hlavičkový řádek nenalezen") - - df = pd.read_excel(path, header=header_row) - subjects = df["Subject"].dropna().astype(str).str.strip().tolist() - return subjects - - -def get_jwt_and_api_base(page, study): - """Získá JWT token a api_base_url pro danou studii.""" - jwt = page.evaluate("localStorage.getItem('JWT.access')") - if not jwt: - raise ValueError("JWT token nenalezen v localStorage") - - instances = page.evaluate("""async (jwt) => { - const res = await fetch('/_/api/dispatch/app_instances/', { - headers: { 'Authorization': `Bearer ${jwt}` } - }); - return res.json(); - }""", jwt) - - instance = next( - (i for i in instances if study in i.get("label", "")), - None - ) - if not instance: - raise ValueError(f"app_instance pro studii {study} nenalezena") - - return jwt, instance["api_base_url"] - - -def get_notifications(jwt, api_base, study, subject): - """Načte seznam notifikací pro daného subjekta přes report_data API.""" - url = f"{BASE_URL}{api_base}/api/v1/reports_api/report_data" - params = { - "path": "patient_detail_report", - "id": subject, - "key": "table_1", - "unblinded": "false", - } - payload = { - "path": "patient_detail_report", - "study": study, - "id": subject, - "key": "table_1", - "fields": {}, - "filters": [{"tableId": "table_1", "tableFilters": {}}], - "pagination_details": {"order": "type", "reverseOrder": False, "page": 1, "limit": 500}, - "cache_key": f"py_{subject}_{datetime.datetime.now().timestamp()}", - } - headers = { - "Authorization": f"Bearer {jwt}", - "Content-Type": "application/json", - "lang": "en", - } - resp = requests.post(url, params=params, json=payload, headers=headers) - resp.raise_for_status() - data = resp.json() - - notifications = [] - for row in data.get("data", []): - for notif in row.get("notification", []): - item = notif.get("item", {}) - pk = item.get("pk") - title = item.get("et_title") - if pk and title: - notifications.append({"pk": pk, "title": title, "event": row.get("event_event_id", "")}) - return notifications - - -def download_pdf(jwt, api_base, pk, title, out_path): - """Stáhne PDF notifikaci a uloží ji.""" - url = f"{BASE_URL}{api_base}/api/v1/meta_api/pdfnotification" - params = {"pk": pk, "title": title, "html": "true"} - headers = { - "Authorization": f"Bearer {jwt}", - "lang": "en", - "Accept": "*/*", - } - resp = requests.get(url, params=params, headers=headers) - resp.raise_for_status() - with open(out_path, "wb") as f: - f.write(resp.content) - - -def run(page, study): - out_dir = os.path.join(DETAILS_DIR, study) - os.makedirs(out_dir, exist_ok=True) - - subjects = get_subjects(study) - print(f" Nalezeno {len(subjects)} subjektů") - today = datetime.date.today().strftime("%Y-%m-%d") - - # Načteme stránku aby byl platný session kontext - page.goto(f"{BASE_URL}/report/patient_detail_report") - page.wait_for_load_state("networkidle", timeout=120000) - - jwt, api_base = get_jwt_and_api_base(page, study) - print(f" API base: {api_base}") - - for subject in subjects: - print(f" [{subject}] Stahuji notifikace...") - try: - notifications = get_notifications(jwt, api_base, study, subject) - if not notifications: - print(f" [{subject}] Žádné notifikace") - continue - - for notif in notifications: - pk = notif["pk"] - title = notif["title"] - filename = os.path.join(out_dir, f"{today} {study} {subject} Notification {title} pk{pk}.pdf") - if os.path.exists(filename): - print(f" [{subject}] {title} (pk={pk}) — již existuje, přeskakuji") - continue - download_pdf(jwt, api_base, pk, title, filename) - print(f" [{subject}] {title} (pk={pk}) OK") - - except Exception as e: - print(f" [{subject}] CHYBA při notifikacích: {e}") - - print(f" [{study}] Notifikace hotovo.") - - -def main(): - os.makedirs(DETAILS_DIR, exist_ok=True) - - with sync_playwright() as p: - for study in STUDIES: - print(f"\n[{study}] Přihlášení...") - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - try: - run(page, study) - except Exception as e: - print(f" [{study}] CHYBA: {e}") - - browser.close() - - print("\nVše hotovo.") - - -main() diff --git a/IWRS/Patients/Trash/download_subject_summary.py b/IWRS/Patients/Trash/download_subject_summary.py deleted file mode 100644 index 517f2bc..0000000 --- a/IWRS/Patients/Trash/download_subject_summary.py +++ /dev/null @@ -1,76 +0,0 @@ -from playwright.sync_api import sync_playwright -import os -import datetime - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "IncomingSourceReports") -CREATED_DIR = os.path.join(BASE_DIR, "CreatedReports") -# ──────────────────────────────────────────────────────────────────────────── - - -def unique_path(directory, stem): - path = os.path.join(directory, f"{stem}.xlsx") - if not os.path.exists(path): - return path - time_tag = datetime.datetime.now().strftime("%H%M") - return os.path.join(directory, f"{stem} {time_tag}.xlsx") - - -def download_study(page, study, today): - print(f"\n[{study}] Prihlaseni...") - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - - print(f"[{study}] Vyber studie...") - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - print(f"[{study}] Stahuji Subject Summary Report...") - page.goto(f"{BASE_URL}/report/patient_summary_report") - page.wait_for_load_state("networkidle", timeout=120000) - - filename = unique_path(INCOMING_DIR, f"{today} {study} Subject Summary Report") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f"[{study}] OK -> {filename}") - return filename - - -def main(): - today = datetime.date.today().strftime("%Y-%m-%d") - os.makedirs(INCOMING_DIR, exist_ok=True) - os.makedirs(CREATED_DIR, exist_ok=True) - - downloaded = [] - - with sync_playwright() as p: - for study in STUDIES: - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - filename = download_study(page, study, today) - downloaded.append((study, filename)) - - browser.close() - - print("\nVse stazeno:") - for study, path in downloaded: - print(f" {study}: {path}") - - -main() diff --git a/IWRS/Patients/Trash/import_all.py b/IWRS/Patients/Trash/import_all.py deleted file mode 100644 index fd9ed8f..0000000 --- a/IWRS/Patients/Trash/import_all.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Import všech čekajících reportů z `Incoming/` do MongoDB. - -Pořadí zpracování per typ + studie: nejstarší soubor podle mtime první -(důležité pro chronologickou správnost snapshotů). - -Po úspěšném importu se soubor přesune do `Incoming/Zpracováno/`. -Při chybě zůstane soubor v `Incoming/`. -""" - -import os -import sys -import glob -import shutil - -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from common.mongo_writer import ensure_indexes - -import import_to_mongo -import import_notifications_to_mongo - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "Incoming") -DONE_DIR = os.path.join(INCOMING_DIR, "Zpracováno") - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - - -def _move_done(path): - os.makedirs(DONE_DIR, exist_ok=True) - dst = os.path.join(DONE_DIR, os.path.basename(path)) - # kolize → přepiš (Mongo už má aktuální data, soubor je jen archiv) - if os.path.exists(dst): - os.remove(dst) - shutil.move(path, dst) - - -def _sorted_by_mtime(paths): - """Nejstarší první.""" - return sorted( - (p for p in paths if not os.path.basename(p).startswith("~$")), - key=os.path.getmtime, - ) - - -def import_summaries(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} Subject Summary Report*.xlsx") - files = _sorted_by_mtime(glob.glob(pattern)) - if not files: - print(f" [{study}] summary: nic ke zpracování") - return - print(f" [{study}] summary: {len(files)} soubor(ů) (oldest first)") - for path in files: - try: - import_to_mongo.import_subject_summary(study, path) - _move_done(path) - except Exception as e: - print(f" [{study}] CHYBA summary {os.path.basename(path)}: {e}") - - -def import_details(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} * Subject Detail.xlsx") - files = _sorted_by_mtime(glob.glob(pattern)) - if not files: - print(f" [{study}] detail: nic ke zpracování") - return - print(f" [{study}] detail: {len(files)} soubor(ů) (oldest first)") - for path in files: - parsed = import_to_mongo.parse_detail_filename(path) - if not parsed: - print(f" [{study}] PŘESKAKUJI (nelze parsovat název): {os.path.basename(path)}") - continue - _, parsed_study, subject = parsed - if parsed_study != study: - continue # patří jiné studii - try: - import_to_mongo.import_visits_single_file(study, subject, path) - _move_done(path) - except Exception as e: - print(f" [{study}] CHYBA detail {os.path.basename(path)}: {e}") - - -def main(): - if not os.path.isdir(INCOMING_DIR): - print(f"Adresář neexistuje: {INCOMING_DIR}") - return - ensure_indexes() - - print("=" * 60) - print("Import Subject Summary + Visits") - print("=" * 60) - for study in STUDIES: - import_summaries(study) - import_details(study) - - print("\n" + "=" * 60) - print("Import notifikací") - print("=" * 60) - import_notifications_to_mongo.import_from_dir(INCOMING_DIR, DONE_DIR, STUDIES) - - print("\n" + "=" * 60) - print(f"Hotovo. Zpracované soubory: {DONE_DIR}") - print("=" * 60) - - -if __name__ == "__main__": - main() diff --git a/IWRS/Patients/Trash/import_to_mysql.py b/IWRS/Patients/Trash/import_to_mysql.py deleted file mode 100644 index d8ffe6b..0000000 --- a/IWRS/Patients/Trash/import_to_mysql.py +++ /dev/null @@ -1,453 +0,0 @@ -""" -Importuje data z IWRS Excel reportů do MySQL (databáze studie). - -Pořadí spuštění: - 1. download_subject_summary.py - 2. download_subject_details.py - 3. tento skript - -Každé spuštění vytvoří nový import_id v iwrs_import. -Reportovací skripty pracují vždy s MAX(import_id) pro danou studii. -""" - -import os -import glob -import datetime -import re - -import numpy as np -import pandas as pd -import mysql.connector - -import db_config - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "IncomingSourceReports") -DETAILS_DIR = os.path.join(BASE_DIR, "IncomingSourceReportsDetails") - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - - -# ── helpers ────────────────────────────────────────────────────────────────── - -def get_conn(): - return 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, - ) - - -def _py(val): - """Převede numpy skalár na Python nativní typ.""" - if isinstance(val, np.generic): - return val.item() - return val - - -def to_date(val): - """Převede pandas Timestamp / string / NaT / NaN na date nebo None.""" - val = _py(val) - if val is None or (isinstance(val, float) and (val != val)): # NaN check - return None - try: - if pd.isna(val): - return None - except (TypeError, ValueError): - pass - if isinstance(val, pd.Timestamp): - return None if pd.isna(val) else val.date() - if isinstance(val, datetime.datetime): - return val.date() - if isinstance(val, datetime.date): - return val - s = str(val).strip() - if not s or s.lower() in ("nat", "nan", "none", ""): - return None - for fmt in ("%Y-%m-%d", "%d-%b-%Y", "%d-%m-%Y", "%Y-%m-%d %H:%M:%S"): - try: - return datetime.datetime.strptime(s, fmt).date() - except ValueError: - pass - return None - - -def to_int(val): - val = _py(val) - try: - v = float(val) - return None if (v != v) else int(v) # v != v je True jen pro NaN - except (TypeError, ValueError): - return None - - -def to_float(val): - val = _py(val) - try: - v = float(val) - return None if (v != v) else float(v) - except (TypeError, ValueError): - return None - - -def to_str(val): - val = _py(val) - if val is None: - return None - if isinstance(val, float) and (val != val): # NaN - return None - s = str(val).strip() - return None if s.lower() in ("nan", "nat", "none", "") else s - - -def find_summary_file(study): - today = datetime.date.today().strftime("%Y-%m-%d") - pattern = os.path.join(INCOMING_DIR, f"* {study} Subject Summary Report.xlsx") - files = sorted( - [f for f in glob.glob(pattern) if not os.path.basename(f).startswith("~$")], - key=os.path.getmtime, - reverse=True, - ) - if not files: - raise FileNotFoundError(f"Nenalezen Subject Summary Report pro {study}") - if not os.path.basename(files[0]).startswith(today): - print(f" UPOZORNĚNÍ: nejnovější Summary Report pro {study} není z dnešního dne ({os.path.basename(files[0])[:10]})") - return files[0] - - -def read_summary_df(path): - """Přečte Summary xlsx, vrátí DataFrame od řádku s hlavičkou.""" - raw = pd.read_excel(path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Subject" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - raise ValueError(f"Hlavičkový řádek nenalezen v {path}") - return pd.read_excel(path, header=header_row) - - -def find_detail_files(study): - out_dir = os.path.join(DETAILS_DIR, study) - # Vezme soubory ze stejného dne jako nejnovější Summary Report - summary_path = find_summary_file(study) - file_date = os.path.basename(summary_path)[:10] # "YYYY-MM-DD" - pattern = os.path.join(out_dir, f"{file_date} {study} * Subject Detail.xlsx") - files = [f for f in glob.glob(pattern) if not os.path.basename(f).startswith("~$")] - return sorted(files) - - -def parse_detail_visits(path): - """ - Vrátí list slovníků s daty visitů z Detail xlsx. - Každý řádek tabulky (od řádku s hlavičkou Visit Type) je jedna transakce. - """ - df = pd.read_excel(path, sheet_name="patient_detail_report", header=None) - - header_row = None - for i, row in df.iterrows(): - if "Visit Type" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - return [] - - visits_df = df.iloc[header_row + 1:].copy() - visits_df.columns = range(visits_df.shape[1]) - - rows = [] - for _, r in visits_df.iterrows(): - visit_type = to_str(r.get(0)) - if visit_type not in ("Past", "Upcoming"): - continue - rows.append({ - "visit_type": visit_type, - "scheduled_date": to_date(r.get(1)), - "window_days": to_str(r.get(2)), - "actual_date": to_date(r.get(3)), - "irt_transaction_no": to_int(r.get(4)), - "irt_transaction_description": to_str(r.get(5)), - "medication_assignment": to_str(r.get(6)), - "quantity_assigned": to_int(r.get(7)), - "medication_id": to_str(r.get(8)), - }) - return rows - - -# ── insert helpers ──────────────────────────────────────────────────────────── - -def insert_import(cursor, study, source_file): - cursor.execute( - "INSERT INTO iwrs_import (study, imported_at, source_file) VALUES (%s, %s, %s)", - (study, datetime.datetime.now(), os.path.basename(source_file)), - ) - return cursor.lastrowid - - -def insert_uco3001_summary(cursor, import_id, df): - sql = """ - INSERT INTO iwrs_uco3001_subject_summary ( - import_id, subject, prior_subject_identifier, site, investigator, location, - cohort_per_irt, informed_consent_date, adolescent_assent_date, age, weight, - rescreened_subject, adt_ir, three_or_more_advanced_therapies, - only_oral_5asa_compounds, ustekinumab, isolated_proctitis, - clinical_responder_status_i12_m0, irt_subject_status, - i0_rand_date_local, last_irt_transaction, - last_irt_transaction_date_local, last_irt_transaction_date_utc, - next_irt_transaction, next_irt_transaction_date_local, - most_recent_med_assignment_date, days_since_last_med_assignment, - patient_forecast_status, patient_forecast_status_changed_date - ) VALUES ( - %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s - ) - """ - col = df.columns.tolist() - - def c(name): - return col.index(name) if name in col else None - - for _, r in df.iterrows(): - cursor.execute(sql, ( - import_id, - to_str(r["Subject"]), - to_str(r["Prior Subject Identifier"]) if "Prior Subject Identifier" in col else None, - to_str(r["Site"]), - to_str(r["Investigator"]), - to_str(r["Location"]), - to_str(r["Cohort per IRT"]), - to_date(r["Informed Consent Date"]), - to_date(r["Adolescent Assent Date"]) if "Adolescent Assent Date" in col else None, - to_int(r["Subject's age collection"]), - to_float(r["Subject's weight collection"]) if "Subject's weight collection" in col else None, - to_str(r["Rescreened Subject"]) if "Rescreened Subject" in col else None, - to_str(r["ADT-IR"]) if "ADT-IR" in col else None, - to_str(r["3 or More Advanced Therapies"]) if "3 or More Advanced Therapies" in col else None, - to_str(r["Only Oral 5-ASA Compounds"]) if "Only Oral 5-ASA Compounds" in col else None, - to_str(r["Ustekinumab"]) if "Ustekinumab" in col else None, - to_str(r["Isolated Proctitis"]) if "Isolated Proctitis" in col else None, - to_str(r["Clinical Responder Status at I-12 / M-0"]) if "Clinical Responder Status at I-12 / M-0" in col else None, - to_str(r["IRT Subject Status"]), - to_date(r["I0_RAND_TIMESTAMP_LOCAL [Local]"]) if "I0_RAND_TIMESTAMP_LOCAL [Local]" in col else None, - to_str(r["Last Recorded IRT Transaction"]), - to_date(r["Last Recorded IRT Transaction Date [Local]"]), - to_date(r["Last Recorded IRT Transaction Date (UTC)"]), - to_str(r["Next Expected IRT Transaction"]), - to_date(r["Next Expected IRT Transaction Date [Local]"]), - to_date(r["Most Recent Medication Assignment Transaction [Local]"]) if "Most Recent Medication Assignment Transaction [Local]" in col else None, - to_int(r["Days Since Last Medication Assignment Transaction"]) if "Days Since Last Medication Assignment Transaction" in col else None, - to_str(r["Patient Forecast Status"]) if "Patient Forecast Status" in col else None, - to_date(r["Patient Forecast Status Changed Date (UTC)"]) if "Patient Forecast Status Changed Date (UTC)" in col else None, - )) - - -def insert_mdd3003_summary(cursor, import_id, df): - sql = """ - INSERT INTO iwrs_mdd3003_subject_summary ( - import_id, subject, prior_subject_identifier, site, investigator, location, - cohort_per_irt, madrs_criteria_integrated, informed_consent_date, age, - madrs_criteria_v15, madrs_criteria_v16, madrs_criteria_v17, - stratification_country, age_group, stable_remitters, irt_subject_status, - last_irt_transaction, last_irt_transaction_date_local, - last_irt_transaction_date_utc, next_irt_transaction, - next_irt_transaction_date_local, date_screened, date_screen_failed, - date_randomized_part1, date_early_withdraw_randomized_part1, - date_open_label_induction, date_early_withdraw_open_label_induction, - date_randomized_part2, date_early_withdraw_randomized_part2, - date_completed, date_unblinded - ) VALUES ( - %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s - ) - """ - col = df.columns.tolist() - - for _, r in df.iterrows(): - cursor.execute(sql, ( - import_id, - to_str(r["Subject"]), - to_str(r["Prior Subject Identifier"]) if "Prior Subject Identifier" in col else None, - to_str(r["Site"]), - to_str(r["Investigator"]), - to_str(r["Location"]), - to_str(r["Cohort per IRT"]), - to_str(r["MADRS response criteria integrated or manually entered"]) if "MADRS response criteria integrated or manually entered" in col else None, - to_date(r["Informed Consent Date"]), - to_int(r["Subject's age collection"]), - to_str(r["MADRS response criteria v1.5 from RAVE"]) if "MADRS response criteria v1.5 from RAVE" in col else None, - to_str(r["MADRS response criteria v1.6 from RAVE"]) if "MADRS response criteria v1.6 from RAVE" in col else None, - to_str(r["MADRS response criteria v1.7 from RAVE"]) if "MADRS response criteria v1.7 from RAVE" in col else None, - to_str(r["Stratification Country"]) if "Stratification Country" in col else None, - to_str(r["Age Group"]) if "Age Group" in col else None, - to_str(r["Stable Remitters vs. Non Stable Remitters"]) if "Stable Remitters vs. Non Stable Remitters" in col else None, - to_str(r["IRT Subject Status"]), - to_str(r["Last Recorded IRT Transaction"]), - to_date(r["Last Recorded IRT Transaction Date [Local]"]), - to_date(r["Last Recorded IRT Transaction Date (UTC)"]), - to_str(r["Next Expected IRT Transaction"]), - to_date(r["Next Expected IRT Transaction Date [Local]"]), - to_date(r["Date Screened [Local]"]) if "Date Screened [Local]" in col else None, - to_date(r["Date Screen Failed [Local]"]) if "Date Screen Failed [Local]" in col else None, - to_date(r["Date Randomized Part 1 [Local]"]) if "Date Randomized Part 1 [Local]" in col else None, - to_date(r["Date Early Withdraw Randomized Part 1 [Local]"]) if "Date Early Withdraw Randomized Part 1 [Local]" in col else None, - to_date(r["Date Open Label Induction [Local]"]) if "Date Open Label Induction [Local]" in col else None, - to_date(r["Date Early Withdraw Open Label Induction [Local]"]) if "Date Early Withdraw Open Label Induction [Local]" in col else None, - to_date(r["Date Randomized Part 2 [Local]"]) if "Date Randomized Part 2 [Local]" in col else None, - to_date(r["Date Early Withdraw Randomized Part 2 [Local]"]) if "Date Early Withdraw Randomized Part 2 [Local]" in col else None, - to_date(r["Date Completed [Local]"]) if "Date Completed [Local]" in col else None, - to_date(r["Date Unblinded [Local]"]) if "Date Unblinded [Local]" in col else None, - )) - - -def insert_visits(cursor, import_id, study, subject, visits): - if not visits: - return - sql = """ - INSERT INTO iwrs_subject_visits ( - import_id, study, subject, visit_type, scheduled_date, window_days, - actual_date, irt_transaction_no, irt_transaction_description, - medication_assignment, quantity_assigned, medication_id - ) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) - """ - for v in visits: - cursor.execute(sql, ( - import_id, study, subject, - v["visit_type"], v["scheduled_date"], v["window_days"], - v["actual_date"], v["irt_transaction_no"], - v["irt_transaction_description"], v["medication_assignment"], - v["quantity_assigned"], v["medication_id"], - )) - - -# ── notifications ───────────────────────────────────────────────────────────── - -def find_notification_json_files(study): - """Najde všechny .json soubory notifikací pro danou studii.""" - out_dir = os.path.join(DETAILS_DIR, study) - return sorted(glob.glob(os.path.join(out_dir, "*.json"))) - - -def import_notifications(conn, study): - import json as json_lib - json_files = find_notification_json_files(study) - if not json_files: - print(f" Žádné notifikace k importu pro {study}") - return 0 - - sql = """ - INSERT INTO iwrs_notifications - (study, subject, pk, title, label, event, actual_date, text, pdf, source_file) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - ON DUPLICATE KEY UPDATE - label = VALUES(label), - text = VALUES(text), - pdf = VALUES(pdf), - source_file = VALUES(source_file) - """ - - done_dir = os.path.join(os.path.join(DETAILS_DIR, study), "Zpracováno") - os.makedirs(done_dir, exist_ok=True) - - cursor = conn.cursor() - count = 0 - for json_path in json_files: - try: - with open(json_path, "r", encoding="utf-8") as f: - meta = json_lib.load(f) - - pdf_path = json_path.replace(".json", ".pdf") - pdf_data = None - if os.path.exists(pdf_path): - with open(pdf_path, "rb") as f: - pdf_data = f.read() - - cursor.execute(sql, ( - meta.get("study", study), - meta.get("subject"), - meta.get("pk"), - meta.get("title"), - meta.get("label"), - meta.get("event"), - to_date(meta.get("actual_date")), - meta.get("text"), - pdf_data, - os.path.basename(json_path), - )) - count += 1 - - # Přesun do Zpracováno - import shutil - shutil.move(json_path, os.path.join(done_dir, os.path.basename(json_path))) - if os.path.exists(pdf_path): - shutil.move(pdf_path, os.path.join(done_dir, os.path.basename(pdf_path))) - - except Exception as e: - print(f" CHYBA při importu {os.path.basename(json_path)}: {e}") - - conn.commit() - cursor.close() - print(f" Notifikací uloženo/přesunuto: {count}") - return count - - -# ── main ────────────────────────────────────────────────────────────────────── - -def import_study(conn, study): - summary_path = find_summary_file(study) - print(f" Summary: {os.path.basename(summary_path)}") - - df_summary = read_summary_df(summary_path) - df_summary = df_summary.dropna(how="all") - - detail_files = find_detail_files(study) - print(f" Detail souborů: {len(detail_files)}") - - cursor = conn.cursor() - import_id = insert_import(cursor, study, summary_path) - print(f" import_id = {import_id}") - - if study == "77242113UCO3001": - insert_uco3001_summary(cursor, import_id, df_summary) - else: - insert_mdd3003_summary(cursor, import_id, df_summary) - print(f" Summary řádků: {len(df_summary)}") - - visited = 0 - for path in detail_files: - fname = os.path.basename(path) - # název: "2026-05-04 77242113UCO3001 CZ100012001 Subject Detail.xlsx" - m = re.search(r"\d{4}-\d{2}-\d{2} \S+ (\S+) Subject Detail\.xlsx", fname) - subject = m.group(1) if m else "UNKNOWN" - visits = parse_detail_visits(path) - insert_visits(cursor, import_id, study, subject, visits) - visited += len(visits) - - conn.commit() - cursor.close() - print(f" Transakce uloženo: {visited}") - return import_id - - -def main(): - conn = get_conn() - print("Připojeno k MySQL.\n") - - for study in STUDIES: - print(f"[{study}]") - try: - import_id = import_study(conn, study) - print(f" OK — import_id {import_id}") - except Exception as e: - print(f" CHYBA: {e}") - try: - import_notifications(conn, study) - except Exception as e: - print(f" CHYBA notifikace: {e}") - print() - - conn.close() - print("Hotovo.") - - -main() diff --git a/IWRS/Patients/Trash/run_all.py b/IWRS/Patients/Trash/run_all.py deleted file mode 100644 index 8ea266a..0000000 --- a/IWRS/Patients/Trash/run_all.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Kompletní pipeline: - 1. Stažení Subject Summary Reportů (obě studie) - 2. Stažení Subject Detail Reportů + notifikací (obě studie) - 3. Import do MongoDB (subject_summary + visits + notifications) - -Spusť tento skript místo samostatných skriptů. -""" - -import os -import sys -import datetime -import glob - -from playwright.sync_api import sync_playwright - -import download_subject_details as dsd -import import_to_mongo -import import_notifications_to_mongo - -# ── CONFIG ─────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "IncomingSourceReports") -DETAILS_DIR = os.path.join(BASE_DIR, "IncomingSourceReportsDetails") - - -# ── helpers ─────────────────────────────────────────────────────────────────── - -def unique_path(directory, stem): - path = os.path.join(directory, f"{stem}.xlsx") - if not os.path.exists(path): - return path - time_tag = datetime.datetime.now().strftime("%H%M") - return os.path.join(directory, f"{stem} {time_tag}.xlsx") - - -def login(page, study): - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - page.get_by_label("Study *").click() - page.get_by_role("option", name=study).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - -# ── KROK 1: Subject Summary ─────────────────────────────────────────────────── - -def download_summary(page, study, today): - print(f" [{study}] Stahuji Subject Summary Report...") - page.goto(f"{BASE_URL}/report/patient_summary_report") - page.wait_for_load_state("networkidle", timeout=120000) - filename = unique_path(INCOMING_DIR, f"{today} {study} Subject Summary Report") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{study}] Summary OK -> {os.path.basename(filename)}") - return filename - - -# ── KROK 2: Subject Details ─────────────────────────────────────────────────── - -def get_subjects_from_summary(summary_path): - import pandas as pd - raw = pd.read_excel(summary_path, header=None) - header_row = None - for i, row in raw.iterrows(): - if "Subject" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - raise ValueError("Hlavičkový řádek nenalezen") - df = pd.read_excel(summary_path, header=header_row) - return df["Subject"].dropna().astype(str).str.strip().tolist() - - -def download_details(page, study, summary_path, today): - out_dir = os.path.join(DETAILS_DIR, study) - os.makedirs(out_dir, exist_ok=True) - - subjects = get_subjects_from_summary(summary_path) - print(f" [{study}] Subjektů k stažení: {len(subjects)}") - - page.goto(f"{BASE_URL}/report/patient_detail_report") - page.wait_for_load_state("networkidle", timeout=120000) - - for subject in subjects: - filename = os.path.join(out_dir, f"{today} {study} {subject} Subject Detail.xlsx") - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(subject) - page.wait_for_timeout(500) - page.locator("mat-option").first.dispatch_event("click") - page.wait_for_load_state("networkidle", timeout=120000) - - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{study}] Detail {subject} OK") - - page.get_by_role("button", name="Clear").click() - page.wait_for_load_state("networkidle", timeout=120000) - - -# ── KROK 3: Import do MongoDB ──────────────────────────────────────────────── - -def main(): - today = datetime.date.today().strftime("%Y-%m-%d") - os.makedirs(INCOMING_DIR, exist_ok=True) - os.makedirs(DETAILS_DIR, exist_ok=True) - - summary_paths = {} - - # Krok 1 + 2: stahování (Playwright, každá studie zvlášť kvůli session) - with sync_playwright() as p: - for study in STUDIES: - print("\n" + "=" * 60) - print(f"[{study}] KROK 1: Subject Summary Report") - print("=" * 60) - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - try: - login(page, study) - summary_path = download_summary(page, study, today) - summary_paths[study] = summary_path - - print(f"\n[{study}] KROK 2: Subject Detail Reports + notifikace") - dsd.run(page, study) - - except Exception as e: - print(f" [{study}] CHYBA při stahování: {e}") - summary_paths[study] = None - finally: - browser.close() - - # Krok 3: import do MongoDB - print("\n" + "=" * 60) - print("KROK 3: Import do MongoDB") - print("=" * 60) - - for study in STUDIES: - summary_path = summary_paths.get(study) - if not summary_path: - print(f" [{study}] PŘESKOČENO — stahování selhalo") - continue - - try: - import_to_mongo.run(study, summary_path, DETAILS_DIR, today) - except Exception as e: - print(f" [{study}] CHYBA při importu summary/visits: {e}") - - # Notifikace: PDF/JSON z disku rovnou do Mongo iwrs_notifications - print("\n [notifikace] import PDF/JSON do Mongo...") - try: - import_notifications_to_mongo.main(STUDIES) - except Exception as e: - print(f" CHYBA při importu notifikací: {e}") - - print("\n" + "=" * 60) - print("Vše hotovo.") - print("=" * 60) - - -main() diff --git a/IWRS/Patients/Trash/test_notifications.py b/IWRS/Patients/Trash/test_notifications.py deleted file mode 100644 index ae2c5d3..0000000 --- a/IWRS/Patients/Trash/test_notifications.py +++ /dev/null @@ -1,172 +0,0 @@ -from playwright.sync_api import sync_playwright -import re -import os -import datetime -import mysql.connector -import db_config - - -def get_existing_pks(study): - """Vrátí set pk notifikací které už jsou v DB pro danou studii.""" - try: - 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, - ) - cursor = conn.cursor() - cursor.execute("SELECT pk FROM iwrs_notifications WHERE study = %s", (study,)) - pks = {row[0] for row in cursor.fetchall()} - cursor.close() - conn.close() - return pks - except Exception as e: - print(f" UPOZORNĚNÍ: nelze načíst existující pk z DB ({e}), stahuji vše") - return set() - -BASE_URL = "https://janssen.4gclinical.com" -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-+" - -STUDY = "77242113UCO3001" -SUBJECT = "CZ100222003" - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -DETAILS_DIR = os.path.join(BASE_DIR, "IncomingSourceReportsDetails") - - -def strip_html(html): - text = re.sub(r"", "\n", html, flags=re.IGNORECASE) - text = re.sub(r"<[^>]+>", "", text) - text = re.sub(r"\n{3,}", "\n\n", text) - return text.strip() - - -def main(): - existing_pks = get_existing_pks(STUDY) - print(f"V DB již existuje {len(existing_pks)} notifikací pro {STUDY}") - - with sync_playwright() as p: - browser = p.chromium.launch(headless=False, args=["--start-maximized"]) - context = browser.new_context(no_viewport=True) - page = context.new_page() - - print("Přihlašuji se...") - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator("#login__submit").click() - page.wait_for_load_state("networkidle") - - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - page.goto(f"{BASE_URL}/report/patient_detail_report") - page.wait_for_load_state("networkidle", timeout=60000) - - # JWT + api_base - jwt = page.evaluate("localStorage.getItem('JWT.access')") - print(f"JWT: {jwt[:30]}...") - instances = page.evaluate("""async (jwt) => { - const res = await fetch('/_/api/dispatch/app_instances/', { - headers: { 'Authorization': `Bearer ${jwt}` } - }); - return res.json(); - }""", jwt) - instance = next((i for i in instances if STUDY in i.get("label", "")), None) - if not instance: - raise ValueError(f"Instance pro {STUDY} nenalezena") - api_base = instance["api_base_url"] - print(f"API base: {api_base}") - - # Vyber subjekt a zachyť table_1 response přímo - print(f"Vybírám subjekt {SUBJECT}...") - input_field = page.locator('input[placeholder="search"], input[type="text"]').first - input_field.click() - input_field.fill(SUBJECT) - page.wait_for_timeout(1000) - - captured = {} - with page.expect_response( - lambda r: "report_data" in r.url and "table_1" in r.url, - timeout=60000 - ) as resp_info: - page.locator("mat-option").first.dispatch_event("click") - - response = resp_info.value - data = response.json() - - out_dir = os.path.join(DETAILS_DIR, STUDY) - os.makedirs(out_dir, exist_ok=True) - today = datetime.date.today().strftime("%Y-%m-%d") - - print(f"\n{'='*60}") - print(f"Subjekt: {SUBJECT} | Studie: {STUDY}") - print(f"{'='*60}") - - count = 0 - for row in data.get("data", []): - for notif in (row.get("notification") or []): - item = notif.get("item", {}) - pk = item.get("pk") - title = item.get("et_title") - label = (notif.get("label") or title or "").strip() - # Celý label, mezery → podtržítka, nepovolené znaky pryč - safe_label = re.sub(r'[\\/*?:"<>|]', "", label).replace(" ", "_") - body = item.get("body", "") - text = strip_html(body) - count += 1 - print(f"\n--- Notifikace #{count}: {safe_label} (pk={pk}) | event: {row.get('event_event_id')} ---") - print(text) - - if pk in existing_pks: - print(f" → pk={pk} již v DB, přeskakuji") - continue - - actual_date = row.get("actual_date_raw", "0000-00-00") - pdf_filename = os.path.join(out_dir, f"{actual_date}_{safe_label}.pdf") - if os.path.exists(pdf_filename): - pdf_filename = os.path.join(out_dir, f"{actual_date}_{safe_label}_pk{pk}.pdf") - - pdf_url = f"{BASE_URL}{api_base}/api/v1/meta_api/pdfnotification?pk={pk}&title={title}&html=true" - pdf_resp = page.request.get(pdf_url, headers={ - "Authorization": f"Bearer {jwt}", - "lang": "en", - "prancer_study": STUDY, - "Accept": "application/json, text/plain, */*", - }) - if pdf_resp.ok: - with open(pdf_filename, "wb") as f: - f.write(pdf_resp.body()) - print(f" → PDF uloženo: {os.path.basename(pdf_filename)}") - json_filename = pdf_filename.replace(".pdf", ".json") - import json - with open(json_filename, "w", encoding="utf-8") as f: - json.dump({ - "pk": pk, - "title": title, - "label": label, - "event": row.get("event_event_id"), - "actual_date": actual_date, - "subject": SUBJECT, - "study": STUDY, - "text": text, - }, f, ensure_ascii=False, indent=2) - print(f" → JSON uloženo: {os.path.basename(json_filename)}") - else: - print(f" → PDF chyba: {pdf_resp.status}") - page.wait_for_timeout(300) - - if count == 0: - print("Žádné notifikace nalezeny.") - else: - print(f"\n{'='*60}") - print(f"Celkem notifikací: {count}") - - browser.close() - - -main() diff --git a/IWRS/Patients/create_subject_report.py b/IWRS/Patients/create_subject_report.py deleted file mode 100644 index 2b5af71..0000000 --- a/IWRS/Patients/create_subject_report.py +++ /dev/null @@ -1,310 +0,0 @@ -import os -import glob -import datetime -import pandas as pd -from openpyxl import Workbook -from openpyxl.styles import ( - Font, PatternFill, Alignment, Border, Side, GradientFill -) -from openpyxl.utils import get_column_letter - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -INCOMING_DIR = os.path.join(BASE_DIR, "IncomingSourceReports") -CREATED_DIR = os.path.join(BASE_DIR, "CreatedReports") - -STUDIES = ["77242113UCO3001", "42847922MDD3003"] - -SOURCE_COLS = [ - "Subject", - "Investigator", - "Subject's age collection", - "Cohort per IRT", - "IRT Subject Status", - "Last Recorded IRT Transaction", - "Next Expected IRT Transaction", - "Next Expected IRT Transaction Date [Local]", -] - -DISPLAY_HEADERS = [ - "Subject", - "Investigator", - "Věk", - "Cohort", - "Status", - "Last IRT", - "Next Visit", - "Next Date", -] - -COL_WIDTHS = [14, 22, 6, 12, 14, 12, 12, 13] - -# ── Styles ─────────────────────────────────────────────────────────────────── -HEADER_FILL = PatternFill("solid", fgColor="1F4E79") -HEADER_FONT = Font(name="Arial", bold=True, color="FFFFFF", size=10) -NORMAL_FONT = Font(name="Arial", size=10) -BOLD_FONT = Font(name="Arial", bold=True, size=10) -STRIKE_FONT = Font(name="Arial", size=10, strike=True, color="999999") -ADOLESC_FONT = Font(name="Arial", bold=True, size=10) - -THIN = Side(style="thin", color="CCCCCC") -BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN) - -EVEN_FILL = PatternFill("solid", fgColor="EBF3FB") -ODD_FILL = PatternFill("solid", fgColor="FFFFFF") - -CENTER = Alignment(horizontal="center", vertical="center", wrap_text=False) -LEFT = Alignment(horizontal="left", vertical="center", wrap_text=False) - - -def unique_path(directory, stem): - path = os.path.join(directory, f"{stem}.xlsx") - if not os.path.exists(path): - return path - time_tag = datetime.datetime.now().strftime("%H%M") - return os.path.join(directory, f"{stem} {time_tag}.xlsx") - - -def find_latest_source(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} Subject Summary Report.xlsx") - files = sorted( - [f for f in glob.glob(pattern) if not os.path.basename(f).startswith("~$")], - key=os.path.getmtime, - reverse=True, - ) - if not files: - raise FileNotFoundError(f"Nenalezen zdrojový soubor pro {study} v {INCOMING_DIR}") - return files[0] - - -def load_source(path): - raw = pd.read_excel(path, header=None) - # find header row (row with "Subject" in first cell) - header_row = None - for i, row in raw.iterrows(): - if "Subject" in [str(v).strip() for v in row]: - header_row = i - break - if header_row is None: - raise ValueError("Hlavičkový řádek nenalezen") - df = pd.read_excel(path, header=header_row) - return df - - -def simplify_cohort(val): - if pd.isna(val): - return "" - val = str(val) - if "dolescent" in val: - return "Adolescent" - if val.startswith("Adult"): - return "Adult" - # MDD3003: "Part 1", "Part 2" — keep as-is - return val - - -def format_date(val): - if pd.isna(val): - return "" - if hasattr(val, "strftime"): - return val.strftime("%Y-%m-%d") - return str(val)[:10] - - -def write_zdroj(wb, df_raw, source_path): - mtime = datetime.datetime.fromtimestamp(os.path.getmtime(source_path)) - sheet_name = f"ZDROJ ({mtime.strftime('%d%b%Y').upper()})" - ws = wb.create_sheet(sheet_name) - ws.sheet_view.showGridLines = True - - # write raw headers + data as plain table - headers = list(df_raw.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, (_, row) in enumerate(df_raw.iterrows(), 2): - fill = EVEN_FILL if r % 2 == 0 else ODD_FILL - for c, col in enumerate(headers, 1): - val = row[col] - if pd.isna(val): - val = "" - elif hasattr(val, "strftime"): - val = val.strftime("%Y-%m-%d") - cell = ws.cell(row=r, column=c, 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" - - -def write_prehled(wb, df_raw, study): - ws = wb.create_sheet("Přehled") - ws.sheet_view.showGridLines = False - ws.sheet_view.showRowColHeaders = True - - # ── title row ──────────────────────────────────────────────────────────── - ws.merge_cells("A1:H1") - title = ws["A1"] - title.value = f"Subject Summary — {study} ({datetime.date.today().strftime('%d-%b-%Y')})" - title.font = Font(name="Arial", bold=True, size=12, color="1F4E79") - title.alignment = Alignment(horizontal="left", vertical="center") - ws.row_dimensions[1].height = 22 - - # ── header row ─────────────────────────────────────────────────────────── - for c, (h, w) in enumerate(zip(DISPLAY_HEADERS, COL_WIDTHS), 1): - cell = ws.cell(row=2, column=c, value=h) - cell.font = HEADER_FONT - cell.fill = HEADER_FILL - cell.alignment = CENTER - cell.border = BORDER - ws.column_dimensions[get_column_letter(c)].width = w - ws.row_dimensions[2].height = 18 - - # ── build display dataframe ─────────────────────────────────────────────── - display = pd.DataFrame() - display["Subject"] = df_raw["Subject"].fillna("") - display["Investigator"]= df_raw["Investigator"].fillna("") - display["Věk"] = df_raw["Subject's age collection"].apply( - lambda v: "" if pd.isna(v) else int(v)) - display["Cohort"] = df_raw["Cohort per IRT"].apply(simplify_cohort) - display["Status"] = df_raw["IRT Subject Status"].fillna("") - display["Last IRT"] = df_raw["Last Recorded IRT Transaction"].fillna("—") - display["Next Visit"] = df_raw["Next Expected IRT Transaction"].fillna("—") - display["Next Date"] = df_raw["Next Expected IRT Transaction Date [Local]"].apply(format_date) - - display = display.sort_values("Subject").reset_index(drop=True) - - # ── data rows ──────────────────────────────────────────────────────────── - for r_idx, row in display.iterrows(): - excel_row = r_idx + 3 # row 1=title, row 2=header - status = str(row["Status"]) - is_failed = "Screen Failed" in status or "Discontinued" in status - is_randomized = "Randomized" in status - is_adolescent = row["Cohort"] == "Adolescent" - fill = EVEN_FILL if r_idx % 2 == 0 else ODD_FILL - - values = [ - row["Subject"], row["Investigator"], row["Věk"], - row["Cohort"], row["Status"], row["Last IRT"], - row["Next Visit"], row["Next Date"], - ] - - for c_idx, val in enumerate(values, 1): - cell = ws.cell(row=excel_row, column=c_idx, value=val if val != "" else None) - cell.fill = fill - cell.border = BORDER - - # alignment - cell.alignment = CENTER if c_idx in (3,) else LEFT - - # font logic - if is_failed: - cell.font = STRIKE_FONT - elif c_idx == 5 and is_randomized: - cell.font = BOLD_FONT - elif c_idx == 4 and is_adolescent: - cell.font = ADOLESC_FONT - else: - cell.font = NORMAL_FONT - - ws.row_dimensions[excel_row].height = 16 - - ws.freeze_panes = "A3" - last_data_row = len(display) + 2 - ws.auto_filter.ref = f"A2:H{last_data_row}" - - -def write_next_visits(wb, df_raw, study): - ws = wb.create_sheet("Next Visits") - ws.sheet_view.showGridLines = False - - # title - ws.merge_cells("A1:D1") - title = ws["A1"] - title.value = f"Next Expected Visits — {study} ({datetime.date.today().strftime('%d-%b-%Y')})" - title.font = Font(name="Arial", bold=True, size=12, color="1F4E79") - title.alignment = Alignment(horizontal="left", vertical="center") - ws.row_dimensions[1].height = 22 - - # headers - nv_headers = ["Subject", "Investigator", "Next Visit", "Datum"] - nv_widths = [14, 22, 26, 13] - for c, (h, w) in enumerate(zip(nv_headers, nv_widths), 1): - cell = ws.cell(row=2, column=c, value=h) - cell.font = HEADER_FONT - cell.fill = HEADER_FILL - cell.alignment = CENTER - cell.border = BORDER - ws.column_dimensions[get_column_letter(c)].width = w - ws.row_dimensions[2].height = 18 - - # data — only rows with a Next Date, exclude Screen Failed / Discontinued - df = pd.DataFrame() - df["Subject"] = df_raw["Subject"].fillna("") - df["Investigator"]= df_raw["Investigator"].fillna("") - df["Next Visit"] = df_raw["Next Expected IRT Transaction"].fillna("") - df["Datum"] = df_raw["Next Expected IRT Transaction Date [Local]"] - df["Status"] = df_raw["IRT Subject Status"].fillna("") - - df = df[df["Datum"].notna()] - df = df[~df["Status"].str.contains("Screen Failed|Discontinued", na=False)] - df = df.sort_values("Datum").reset_index(drop=True) - - for r_idx, row in df.iterrows(): - excel_row = r_idx + 3 - fill = EVEN_FILL if r_idx % 2 == 0 else ODD_FILL - datum_val = row["Datum"] - datum_str = datum_val.strftime("%Y-%m-%d") if hasattr(datum_val, "strftime") else str(datum_val)[:10] - - values = [row["Subject"], row["Investigator"], row["Next Visit"], datum_str] - for c_idx, val in enumerate(values, 1): - cell = ws.cell(row=excel_row, column=c_idx, value=val if val != "" else None) - cell.fill = fill - cell.border = BORDER - cell.font = NORMAL_FONT - cell.alignment = LEFT - ws.row_dimensions[excel_row].height = 16 - - ws.freeze_panes = "A3" - last_data_row = len(df) + 2 - ws.auto_filter.ref = f"A2:D{last_data_row}" - - -def create_report(study): - source_path = find_latest_source(study) - print(f"[{study}] Čtu: {os.path.basename(source_path)}") - - df_raw = load_source(source_path) - - wb = Workbook() - wb.remove(wb.active) # remove default sheet - - write_prehled(wb, df_raw, study) - write_next_visits(wb, df_raw, study) - write_zdroj(wb, df_raw, source_path) - - today = datetime.date.today().strftime("%Y-%m-%d") - out_path = unique_path(CREATED_DIR, f"{today} {study} Subject Summary") - wb.save(out_path) - print(f"[{study}] Uloženo: {out_path}") - return out_path - - -def main(): - os.makedirs(CREATED_DIR, exist_ok=True) - for study in STUDIES: - try: - create_report(study) - except FileNotFoundError as e: - print(f"[{study}] PŘESKOČENO: {e}") - print("\nHotovo.") - - -main() diff --git a/IWRS/Patients/db_config.py b/IWRS/Patients/db_config.py deleted file mode 100644 index bfa5959..0000000 --- a/IWRS/Patients/db_config.py +++ /dev/null @@ -1,5 +0,0 @@ -DB_HOST = "192.168.1.76" -DB_PORT = 3306 -DB_USER = "root" -DB_PASSWORD = "Vlado9674+" -DB_NAME = "studie" diff --git a/IWRS/Patients/download_patients.py b/IWRS/Patients/download_patients.py deleted file mode 100644 index 57e2b7a..0000000 --- a/IWRS/Patients/download_patients.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -download_patients.py — stažení pacientských reportů pro jednu studii. -Verze: 1.0 | Datum: 2026-06-10 - -Volá se z IWRS/run_all_v1.0.py s již přihlášenou Playwright page (login + -výběr studie zajišťuje common.iwrs_portal.login). - - 1. Subject Summary Report - 2. Subject Detail Reports + notifikace PDF+JSON (per subjekt, jen nové dle pk v Mongo) - -Vše se ukládá ploše do IWRS/Incoming/ s datumovanými názvy. -""" - -import os -import sys -import datetime - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -IWRS_DIR = os.path.dirname(BASE_DIR) -for _p in (IWRS_DIR, BASE_DIR): - if _p not in sys.path: - sys.path.insert(0, _p) - -from common.iwrs_portal import BASE_URL -from common.paths import INCOMING_DIR, unique_path - -import download_subject_details as dsd - - -def download_summary(page, study, today): - print(f" [{study}] Stahuji Subject Summary Report...") - page.goto(f"{BASE_URL}/report/patient_summary_report") - page.wait_for_load_state("networkidle", timeout=120000) - filename = unique_path(INCOMING_DIR, f"{today} {study} Subject Summary Report") - with page.expect_download(timeout=120000) as dl: - page.get_by_role("button", name="Download XLS").click() - dl.value.save_as(filename) - print(f" [{study}] Summary OK -> {os.path.basename(filename)}") - return filename - - -def run(page, study): - """Stáhne summary + detaily + notifikace pro studii do IWRS/Incoming/.""" - os.makedirs(INCOMING_DIR, exist_ok=True) - today = datetime.date.today().strftime("%Y-%m-%d") - download_summary(page, study, today) - # detail XLSX + notifikace přímo do Incoming/ (flat názvy se study+subject) - dsd.run(page, study, out_dir=INCOMING_DIR, subjects_source_dir=INCOMING_DIR) diff --git a/IWRS/Patients/import_patients.py b/IWRS/Patients/import_patients.py deleted file mode 100644 index cd814c5..0000000 --- a/IWRS/Patients/import_patients.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -import_patients.py — import pacientských reportů z IWRS/Incoming/ do MongoDB. -Verze: 1.0 | Datum: 2026-06-10 - -Pořadí zpracování per typ + studie: nejstarší soubor podle mtime první -(důležité pro chronologickou správnost snapshotů). - -Po úspěšném importu se soubor přesune do IWRS/Incoming/Processed/. -Při chybě zůstane soubor v Incoming/. - -Volá se z IWRS/run_all_v1.0.py (ensure_indexes volá orchestrátor); -lze spustit i samostatně: python import_patients.py -""" - -import os -import sys -import glob - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -IWRS_DIR = os.path.dirname(BASE_DIR) -for _p in (IWRS_DIR, BASE_DIR): - if _p not in sys.path: - sys.path.insert(0, _p) - -from common.paths import INCOMING_DIR, PROCESSED_DIR, STUDIES, move_done, sorted_by_mtime -from common.mongo_writer import ensure_indexes - -import import_to_mongo -import import_notifications_to_mongo - - -def import_summaries(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} Subject Summary Report*.xlsx") - files = sorted_by_mtime(glob.glob(pattern)) - if not files: - print(f" [{study}] summary: nic ke zpracování") - return - print(f" [{study}] summary: {len(files)} soubor(ů) (oldest first)") - for path in files: - try: - import_to_mongo.import_subject_summary(study, path) - move_done(path) - except Exception as e: - print(f" [{study}] CHYBA summary {os.path.basename(path)}: {e}") - - -def import_details(study): - pattern = os.path.join(INCOMING_DIR, f"* {study} * Subject Detail.xlsx") - files = sorted_by_mtime(glob.glob(pattern)) - if not files: - print(f" [{study}] detail: nic ke zpracování") - return - print(f" [{study}] detail: {len(files)} soubor(ů) (oldest first)") - for path in files: - parsed = import_to_mongo.parse_detail_filename(path) - if not parsed: - print(f" [{study}] PŘESKAKUJI (nelze parsovat název): {os.path.basename(path)}") - continue - _, parsed_study, subject = parsed - if parsed_study != study: - continue # patří jiné studii - try: - import_to_mongo.import_visits_single_file(study, subject, path) - move_done(path) - except Exception as e: - print(f" [{study}] CHYBA detail {os.path.basename(path)}: {e}") - - -def run(studies=None): - studies = studies or STUDIES - if not os.path.isdir(INCOMING_DIR): - print(f"Adresář neexistuje: {INCOMING_DIR}") - return - - print("=" * 60) - print("Import Subject Summary + Visits") - print("=" * 60) - for study in studies: - import_summaries(study) - import_details(study) - - print("\n" + "=" * 60) - print("Import notifikací") - print("=" * 60) - import_notifications_to_mongo.import_from_dir(INCOMING_DIR, PROCESSED_DIR, studies) - - -if __name__ == "__main__": - ensure_indexes() - run() diff --git a/IWRS/Patients/parse_notifications_to_mongo.py b/IWRS/Patients/parse_notifications_to_mongo.py deleted file mode 100644 index 0f2fc3b..0000000 --- a/IWRS/Patients/parse_notifications_to_mongo.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -Parsuje texty IWRS notifikací z MySQL (iwrs_notifications) a ukládá strukturovaná -data do MongoDB (databáze 'studie', kolekce 'iwrs'). - -Idempotentní: upsert podle pk (unikátní identifikátor notifikace v IWRS). -""" - -import re -import datetime -import sys - -import mysql.connector -from pymongo import MongoClient, ASCENDING - -import db_config - -MONGO_URI = "mongodb://192.168.1.76:27017" -MONGO_DB = "studie" -MONGO_COLL = "iwrs_notifications" - - -# ── parsery ────────────────────────────────────────────────────────────────── - -def parse_kv_lines(text): - """Vytáhne všechny řádky typu 'Klíč: Hodnota' do dictu. - Když je hodnota za dvojtečkou prázdná, vezme se první neprázdný následující řádek.""" - out = {} - lines = [l.strip() for l in text.splitlines()] - pending_key = None - for line in lines: - # čekáme na hodnotu pro klíč z předchozího řádku - if pending_key is not None: - if not line: - continue - if ":" not in line: - out.setdefault(pending_key, line) - pending_key = None - continue - # další řádek je sám "Klíč: Hodnota" → zahodíme pending a zpracujeme normálně - pending_key = None - - if not line or ":" not in line: - continue - if line.lower().startswith("http"): - continue - key, _, val = line.partition(":") - key = key.strip() - val = val.strip() - if not key or (" " in key and len(key.split()) > 8): - continue - if not val: - pending_key = key - continue - out.setdefault(key, val) - return out - - -DATE_RE = re.compile(r"^\d{2}-[A-Z][a-z]{2}-\d{4}$") -DATETIME_RE = re.compile(r"^(\d{2}-[A-Z][a-z]{2}-\d{4})\s+(\d{2}:\d{2}:\d{2})$") - - -def to_date(s): - if not s: - return None - s = s.strip() - if DATE_RE.match(s): - try: - return datetime.datetime.strptime(s, "%d-%b-%Y") - except ValueError: - return None - return None - - -def to_datetime(s): - if not s: - return None - s = re.sub(r"\s+", " ", s.strip()) - m = DATETIME_RE.match(s) - if m: - try: - return datetime.datetime.strptime(f"{m.group(1)} {m.group(2)}", "%d-%b-%Y %H:%M:%S") - except ValueError: - return None - return None - - -MED_ROW_RE = re.compile( - r"(?P\d{7})\s*[\s\n]*" - r"(?P[A-Za-z][A-Za-z0-9 /+\-]+?)\s*[\s\n]*" - r"(?P[A-Z0-9]{5,10})\s*[\s\n]*" - r"(?P\d{2}-[A-Z][a-z]{2}-\d{4})" -) - - -def parse_medication_table(text): - """Najde záznamy medikace (med_no, med_type, lot, expirace) v textu. - Pracuje s oběma formáty (UCO3001 multiline i MDD3003 concatenated).""" - rows = [] - # zkomprimuj whitespace pro snadnější regex - compact = re.sub(r"\s+", " ", text) - for m in MED_ROW_RE.finditer(compact): - med_type = m.group("type").strip() - # uřízni nadbytečné koncové fragmenty - med_type = re.sub(r"\s+(Packaged|Lot|Expiration|No|Date|Medication).*$", "", med_type).strip() - rows.append({ - "medication_no": m.group("no"), - "medication_type": med_type, - "lot_no": m.group("lot"), - "expiration_date": to_date(m.group("exp")), - }) - # dedupe - seen = set() - unique = [] - for r in rows: - key = (r["medication_no"], r["lot_no"]) - if key in seen: - continue - seen.add(key) - unique.append(r) - return unique - - -# fields, které chceme v dokumentu vyloučit z kv (ošklivé / nepotřebné) -KV_BLACKLIST = { - "If you have questions about this notification, please contact 4G Clinical Support at", -} - - -def build_document(row): - pk, study, subject, title, label, event, actual_date, text = row - - kv = parse_kv_lines(text) - meds = parse_medication_table(text) - - # převod známých datumových/datetime polí - dt_site = to_datetime(kv.get("Transaction Date/Time (site local)")) - dt_sys = to_datetime(kv.get("Transaction Date/Time (system local)")) - - date_fields = [ - "Informed Consent Date", - "Informed Consent Date at Screening", - "Informed Consent Date at Subject Creation", - "Date of Subject Creation in IRT", - "Date of Screening in IRT", - "Screenfail Date", - "Discontinuation date", - "Dispensation date", - "Returned Date", - ] - parsed_dates = {} - for f in date_fields: - if f in kv: - d = to_date(kv[f]) - if d: - parsed_dates[f] = d - - doc = { - "_id": pk, # použij IWRS pk jako _id (idempotence) - "pk": pk, - "study": study, - "subject": subject, - "title": title, - "label": label, - "event": event, - "actual_date": ( - datetime.datetime.combine(actual_date, datetime.time()) - if isinstance(actual_date, datetime.date) and not isinstance(actual_date, datetime.datetime) - else actual_date - ), - "site": kv.get("Site"), - "investigator": kv.get("Investigator"), - "location": kv.get("Location"), - "cohort": kv.get("Cohort"), - "irt_subject_status": kv.get("IRT Subject Status"), - "transaction_site_local": dt_site, - "transaction_system_local": dt_sys, - "transaction_by": kv.get("Transaction performed by"), - "medications": meds, - "fields": {k: v for k, v in kv.items() if k not in { - "Site", "Investigator", "Location", "Cohort", "IRT Subject Status", - "Subject", - "Transaction Date/Time (site local)", - "Transaction Date/Time (system local)", - "Transaction performed by", - }}, - "parsed_dates": parsed_dates, - "raw_text": text, - } - return doc - - -# ── main ───────────────────────────────────────────────────────────────────── - -def main(studies=None): - 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, - ) - cur = conn.cursor() - - if studies: - placeholders = ",".join(["%s"] * len(studies)) - cur.execute( - f"SELECT pk, study, subject, title, label, event, actual_date, text " - f"FROM iwrs_notifications WHERE study IN ({placeholders})", - studies, - ) - else: - cur.execute( - "SELECT pk, study, subject, title, label, event, actual_date, text " - "FROM iwrs_notifications" - ) - rows = cur.fetchall() - cur.close() - conn.close() - print(f" Nacteno {len(rows)} notifikaci z MySQL") - - mc = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000) - coll = mc[MONGO_DB][MONGO_COLL] - - # indexy - coll.create_index([("study", ASCENDING), ("subject", ASCENDING)]) - coll.create_index([("study", ASCENDING), ("title", ASCENDING)]) - coll.create_index([("actual_date", ASCENDING)]) - - upserts = 0 - for row in rows: - doc = build_document(row) - coll.replace_one({"_id": doc["_id"]}, doc, upsert=True) - upserts += 1 - - print(f" Upsert {upserts} dokumentu do {MONGO_DB}.{MONGO_COLL}") - - # stats - print("\n Statistika v Mongo:") - for r in coll.aggregate([ - {"$group": {"_id": {"study": "$study", "title": "$title"}, "count": {"$sum": 1}}}, - {"$sort": {"_id.study": 1, "_id.title": 1}}, - ]): - print(f" {r['_id']['study']} | {r['_id']['title']:30s} | {r['count']}") - - -if __name__ == "__main__": - studies = sys.argv[1:] if len(sys.argv) > 1 else None - main(studies) diff --git a/IWRS/Testing/format_accountability.py b/IWRS/Testing/format_accountability.py deleted file mode 100644 index a7ef956..0000000 --- a/IWRS/Testing/format_accountability.py +++ /dev/null @@ -1,118 +0,0 @@ -import pandas as pd -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -INPUT_FILE = "accountability_combined.xlsx" -OUTPUT_FILE = "accountability_formatted.xlsx" -SHEET_NAME = "CountryMedicationOverview" - -COLUMN_RENAMES = { - "Site": "Site", - "Medication ID": "Med ID", - "Packaged Lot number": "Lot No.", - "Original Expiration Date when Packaged Lot was Added": "Orig Exp Date", - "Expiration date": "Exp Date", - "Received Date": "Rcv Date", - "Shipment Receipt User": "Rcpt User", - "Subject Identifier": "Subject ID", - "Quantity Assigned": "Qty Asgn", - "IRT Transaction": "IRT Tx", - "Date Assigned": "Date Asgn", - "Assignment User": "Asgn User", - "Dispensation Status": "Disp Status", - "Dispensing Date": "Disp Date", - "Quantity Dispensed": "Qty Disp", - "Dispensing User": "Disp User", - "Quantity Returned": "Qty Ret", - "Date Returned": "Date Ret", - "Return User": "Ret User", - "DestroyedOn": "Destroyed", - "Basket number": "Basket No.", -} - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, -} - -# ── 1. Load with pandas and convert date columns ───────────────────────────── -df = pd.read_excel(INPUT_FILE) -df.rename(columns=COLUMN_RENAMES, inplace=True) - -for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], dayfirst=True, errors="coerce") - -df.sort_values(["Site", "Rcv Date", "Med ID"], inplace=True, ignore_index=True) -df.to_excel(OUTPUT_FILE, index=False, sheet_name=SHEET_NAME) - -# ── 2. Format with openpyxl ─────────────────────────────────────────────────── -wb = load_workbook(OUTPUT_FILE) -ws = wb[SHEET_NAME] - -header_fill = PatternFill("solid", start_color="1F4E79") -header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) -new_col_fill = PatternFill("solid", start_color="E2EFDA") -row_font = Font(name="Arial", size=10) - -thin = Side(style="thin", color="000000") -border = Border(left=thin, right=thin, top=thin, bottom=thin) - -headers = [cell.value for cell in ws[1]] -new_cols = {"Destroyed", "Basket No."} - -# Header row -for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - -# Data rows -max_row = ws.max_row -for row in ws.iter_rows(min_row=2, max_row=max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if col_name in new_cols: - cell.fill = new_col_fill - -# Column widths -for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - -ws.auto_filter.ref = ws.dimensions -ws.freeze_panes = "A2" - -wb.save(OUTPUT_FILE) -print(f"Saved: {OUTPUT_FILE} ({max_row - 1} rows, sheet '{SHEET_NAME}')") diff --git a/IWRS/Testing/list_reports.py b/IWRS/Testing/list_reports.py deleted file mode 100644 index ab0e876..0000000 --- a/IWRS/Testing/list_reports.py +++ /dev/null @@ -1,74 +0,0 @@ -from playwright.sync_api import sync_playwright -import json - -# ── CONFIG ────────────────────────────────────────────────────────────────── -BASE_URL = "https://janssen.4gclinical.com" -STUDY = "42847922MDD3003" - -EMAIL = "vbuzalka@its.jnj.com" -PASSWORD = "Vlado123++-" # doplň heslo -# ──────────────────────────────────────────────────────────────────────────── - - -def list_reports(): - with sync_playwright() as p: - browser = p.chromium.launch(headless=False) - page = browser.new_page() - - # Přihlášení - page.goto(BASE_URL) - page.wait_for_load_state("networkidle") - - page.get_by_label("Email *").fill(EMAIL) - page.get_by_label("Password *").fill(PASSWORD) - page.locator('#login__submit').click() - page.wait_for_load_state("networkidle") - - # Výběr studie — klikni na dropdown, vyber studii, klikni SELECT - page.get_by_label("Study *").click() - page.get_by_role("option", name=STUDY).click() - page.get_by_role("button", name="SELECT").click() - page.wait_for_load_state("networkidle") - - # Přejdi na seznam reportů - page.goto(f"{BASE_URL}/reports") - page.wait_for_load_state("networkidle") - page.wait_for_selector('[role="gridcell"] a', timeout=15000) - - # Získej názvy reportů - names = page.evaluate(""" - () => Array.from(document.querySelectorAll('[role="gridcell"] a')) - .map(a => a.innerText.trim()) - .filter(n => n) - """) - print(f"\nNalezeno {len(names)} reportů, zjišťuji URL...\n") - - # Pro každý report klikni, zaznamenej URL a vrať se zpět - reports = [] - for name in names: - with page.expect_navigation(timeout=15000): - page.locator('[role="gridcell"] a').filter(has_text=name).click() - page.wait_for_load_state("networkidle") - page.wait_for_timeout(2000) - path = page.url.replace(BASE_URL, "") - reports.append({"name": name, "href": path}) - print(f" {name:50s} {path}") - # Průběžné uložení po každém reportu - with open("reports.json", "w", encoding="utf-8") as f: - json.dump(reports, f, ensure_ascii=False, indent=2) - if page.url != f"{BASE_URL}/reports": - page.goto(f"{BASE_URL}/reports") - page.wait_for_load_state("networkidle") - page.wait_for_timeout(2000) - page.wait_for_selector('[role="gridcell"] a', timeout=30000) - - browser.close() - - with open("reports.json", "w", encoding="utf-8") as f: - json.dump(reports, f, ensure_ascii=False, indent=2) - print(f"\nUloženo do reports.json") - - return reports - - -list_reports() diff --git a/IWRS/Testing/sheet_assigned_not_dispensed.py b/IWRS/Testing/sheet_assigned_not_dispensed.py deleted file mode 100644 index 3b97d1a..0000000 --- a/IWRS/Testing/sheet_assigned_not_dispensed.py +++ /dev/null @@ -1,92 +0,0 @@ -import pandas as pd -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -SOURCE_FILE = "accountability_combined.xlsx" -OUTPUT_FILE = "sheet_assigned_not_dispensed.xlsx" -SHEET_NAME = "Assigned not dispensed" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, -} - -df = pd.read_excel(SOURCE_FILE) - -for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - -# Filter: Subject ID present AND Disp Date missing -mask = df["Subject ID"].notna() & df["Disp Date"].isna() -filtered = df[mask].copy().reset_index(drop=True) - -print(f"Assigned not dispensed: {len(filtered)}") - -filtered.to_excel(OUTPUT_FILE, index=False, sheet_name=SHEET_NAME) - -# Formatting -wb = load_workbook(OUTPUT_FILE) -ws = wb[SHEET_NAME] - -header_fill = PatternFill("solid", start_color="833C00") # dark orange -header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) -row_font = Font(name="Arial", size=10) -subj_fill = PatternFill("solid", start_color="FFF2CC") # light yellow highlight for Subject ID - -thin = Side(style="thin", color="000000") -border = Border(left=thin, right=thin, top=thin, bottom=thin) - -headers = [cell.value for cell in ws[1]] - -for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - -for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if col_name == "Subject ID": - cell.fill = subj_fill - -for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - -ws.auto_filter.ref = ws.dimensions -ws.freeze_panes = "A2" - -wb.save(OUTPUT_FILE) -print(f"Saved: {OUTPUT_FILE} (sheet: '{SHEET_NAME}')") diff --git a/IWRS/Testing/sheet_expired.py b/IWRS/Testing/sheet_expired.py deleted file mode 100644 index b41d353..0000000 --- a/IWRS/Testing/sheet_expired.py +++ /dev/null @@ -1,97 +0,0 @@ -import pandas as pd -from datetime import date -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -SOURCE_FILE = "accountability_combined.xlsx" -OUTPUT_FILE = "sheet_expired.xlsx" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, -} - -today = date.today() -sheet_name = f"Expired as of {today.strftime('%d-%b-%Y')}" - -# Load source -df = pd.read_excel(SOURCE_FILE) - -# Convert date columns -for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - -# Filter: not in basket AND not assigned to patient AND Exp Date < today -mask = df["Basket No."].isna() & df["Subject ID"].isna() & (df["Exp Date"] < pd.Timestamp(today)) -filtered = df[mask].copy().reset_index(drop=True) - -print(f"Expired kits not in basket: {len(filtered)}") - -filtered.to_excel(OUTPUT_FILE, index=False, sheet_name=sheet_name) - -# Formatting -wb = load_workbook(OUTPUT_FILE) -ws = wb[sheet_name] - -header_fill = PatternFill("solid", start_color="C00000") # dark red -header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) -row_font = Font(name="Arial", size=10) -exp_fill = PatternFill("solid", start_color="FFE0E0") # light red highlight for Exp Date - -thin = Side(style="thin", color="000000") -border = Border(left=thin, right=thin, top=thin, bottom=thin) - -headers = [cell.value for cell in ws[1]] - -for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - -for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if col_name == "Exp Date": - cell.fill = exp_fill - -for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - -ws.auto_filter.ref = ws.dimensions -ws.freeze_panes = "A2" - -wb.save(OUTPUT_FILE) -print(f"Saved: {OUTPUT_FILE} (sheet: '{sheet_name}')") diff --git a/IWRS/Testing/sheet_kits_for_destruction.py b/IWRS/Testing/sheet_kits_for_destruction.py deleted file mode 100644 index a6b6dd8..0000000 --- a/IWRS/Testing/sheet_kits_for_destruction.py +++ /dev/null @@ -1,99 +0,0 @@ -import pandas as pd -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -SOURCE_FILE = "accountability_combined.xlsx" -OUTPUT_FILE = "sheet_kits_for_destruction.xlsx" -SHEET_NAME = "Kits for destruction" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Date Ret", "Destroyed", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Qty Ret": 10, - "Date Ret": 14, - "Ret User": 18, - "Destroyed": 14, - "Basket No.": 12, -} - -df = pd.read_excel(SOURCE_FILE) - -for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - -# Filter: no basket AND (Date Ret filled OR Disp Status == NOT DISPENSED) -mask = ( - df["Basket No."].isna() & - ( - df["Date Ret"].notna() | - (df["Disp Status"].str.upper() == "NOT DISPENSED") - ) -) -filtered = df[mask].copy().sort_values(["Site", "Date Ret"], ascending=[True, True]) -filtered = filtered.drop(columns=["Destroyed", "Basket No."]).reset_index(drop=True) - -print(f"Kits for destruction: {len(filtered)}") - -filtered.to_excel(OUTPUT_FILE, index=False, sheet_name=SHEET_NAME) - -# Formatting -wb = load_workbook(OUTPUT_FILE) -ws = wb[SHEET_NAME] - -header_fill = PatternFill("solid", start_color="595959") # dark grey -header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) -row_font = Font(name="Arial", size=10) -basket_fill = PatternFill("solid", start_color="FFE0E0") # light red for empty Basket No. - -thin = Side(style="thin", color="000000") -border = Border(left=thin, right=thin, top=thin, bottom=thin) - -headers = [cell.value for cell in ws[1]] - -for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - -for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if col_name == "Basket No.": - cell.fill = basket_fill - -for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - -ws.auto_filter.ref = ws.dimensions -ws.freeze_panes = "A2" - -wb.save(OUTPUT_FILE) -print(f"Saved: {OUTPUT_FILE} (sheet: '{SHEET_NAME}')") diff --git a/IWRS/Testing/sheet_not_returned.py b/IWRS/Testing/sheet_not_returned.py deleted file mode 100644 index 79f68e2..0000000 --- a/IWRS/Testing/sheet_not_returned.py +++ /dev/null @@ -1,102 +0,0 @@ -import pandas as pd -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment, Border, Side -from openpyxl.utils import get_column_letter - -SOURCE_FILE = "accountability_combined.xlsx" -OUTPUT_FILE = "sheet_not_returned.xlsx" -SHEET_NAME = "Not returned" - -DATE_COLUMNS = { - "Orig Exp Date", "Exp Date", "Rcv Date", - "Date Asgn", "Disp Date", "Max Visit Date", -} - -COLUMN_WIDTHS = { - "Site": 14, - "Med ID": 10, - "Lot No.": 12, - "Orig Exp Date": 16, - "Exp Date": 14, - "Rcv Date": 14, - "Rcpt User": 22, - "Subject ID": 14, - "Qty Asgn": 9, - "IRT Tx": 8, - "Date Asgn": 14, - "Asgn User": 20, - "Disp Status": 16, - "Disp Date": 14, - "Qty Disp": 9, - "Disp User": 20, - "Max Visit Date": 16, -} - -df = pd.read_excel(SOURCE_FILE) - -for col in DATE_COLUMNS: - if col in df.columns: - df[col] = pd.to_datetime(df[col], errors="coerce") - -# Kits with no return date, assigned to a patient, and not "NOT DISPENSED" -no_ret = df[ - df["Date Ret"].isna() & - df["Subject ID"].notna() & - (df["Disp Status"].str.upper() != "NOT DISPENSED") -].copy() - -# Max Date Asgn per patient (from full dataset) -max_asgn = df.groupby("Subject ID")["Date Asgn"].max().rename("Max Visit Date") -no_ret = no_ret.join(max_asgn, on="Subject ID") - -# Keep only kits where Date Asgn is NOT the latest for that patient -filtered = no_ret[no_ret["Date Asgn"] < no_ret["Max Visit Date"]].copy() - -# Drop columns Q-U and keep Max Visit Date -filtered = filtered.drop(columns=["Qty Ret", "Date Ret", "Ret User", "Destroyed", "Basket No."]) -filtered = filtered.reset_index(drop=True) - -print(f"Not returned kits: {len(filtered)}") - -filtered.to_excel(OUTPUT_FILE, index=False, sheet_name=SHEET_NAME) - -# Formatting -wb = load_workbook(OUTPUT_FILE) -ws = wb[SHEET_NAME] - -header_fill = PatternFill("solid", start_color="375623") # dark green -header_font = Font(bold=True, color="FFFFFF", name="Arial", size=10) -row_font = Font(name="Arial", size=10) -ret_fill = PatternFill("solid", start_color="E2EFDA") # light green highlight for Date Ret - -thin = Side(style="thin", color="000000") -border = Border(left=thin, right=thin, top=thin, bottom=thin) - -headers = [cell.value for cell in ws[1]] - -for cell in ws[1]: - cell.fill = header_fill - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=False) - cell.border = border - -for row in ws.iter_rows(min_row=2, max_row=ws.max_row): - for cell in row: - col_name = headers[cell.column - 1] if cell.column <= len(headers) else None - cell.font = row_font - cell.border = border - cell.alignment = Alignment(horizontal="center") - if col_name in DATE_COLUMNS: - cell.number_format = "DD-MMM-YYYY" - if col_name == "Max Visit Date": - cell.fill = ret_fill - -for cell in ws[1]: - width = COLUMN_WIDTHS.get(cell.value, 14) - ws.column_dimensions[get_column_letter(cell.column)].width = width - -ws.auto_filter.ref = ws.dimensions -ws.freeze_panes = "A2" - -wb.save(OUTPUT_FILE) -print(f"Saved: {OUTPUT_FILE} (sheet: '{SHEET_NAME}')") diff --git a/IWRS/backfill_mysql_to_mongo.py b/IWRS/backfill_mysql_to_mongo.py deleted file mode 100644 index cc52b2e..0000000 --- a/IWRS/backfill_mysql_to_mongo.py +++ /dev/null @@ -1,272 +0,0 @@ -""" -Jednorázový backfill historických dat z MySQL do MongoDB. - -Pro každou snapshotovanou tabulku: - - všechny řádky všech import_id → snapshot kolekce - - řádky z MAX(import_id) per studie → hlavní kolekce (replace_one upsert) - -Pro idempotentní tabulky (notifications, destruction): - - všechno → hlavní kolekce (replace_one upsert) - -Notifikace jsou už v Mongo z parse_notifications_to_mongo.py — přeskočí se. -""" - -import os -import sys -import datetime - -import mysql.connector -from pymongo import ReplaceOne - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from common.mongo_writer import get_db, ensure_indexes, MONGO_DB - -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "Patients")) -import db_config - - -def conn(): - return 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, - ) - - -def dict_rows(cursor): - cols = [d[0] for d in cursor.description] - for row in cursor: - yield dict(zip(cols, row)) - - -def to_mongo_date(v): - if isinstance(v, datetime.datetime): - return v - if isinstance(v, datetime.date): - return datetime.datetime(v.year, v.month, v.day) - return v - - -def normalize(doc): - return {k: to_mongo_date(v) for k, v in doc.items() if v is not None} - - -# ── 1. iwrs_imports → iwrs_imports ─────────────────────────────────────────── - -def backfill_imports(): - print("[iwrs_imports]") - c = conn(); cur = c.cursor() - cur.execute("SELECT import_id, study, imported_at, source_file, report_type FROM iwrs_import") - db = get_db() - ops = [] - for r in dict_rows(cur): - d = normalize(r) - d["_id"] = d["import_id"] - ops.append(ReplaceOne({"_id": d["_id"]}, d, upsert=True)) - if ops: - db.iwrs_imports.bulk_write(ops, ordered=False) - print(f" -> {len(ops)} import logu") - cur.close(); c.close() - - -# ── 2. subject_summary (UCO + MDD sjednoceno) ──────────────────────────────── - -UCO_TABLE = "iwrs_uco3001_subject_summary" -MDD_TABLE = "iwrs_mdd3003_subject_summary" - - -def backfill_subject_summary(): - print("[iwrs_subject_summary]") - db = get_db() - # zjisti import_id → study mapování - c = conn(); cur = c.cursor() - cur.execute("SELECT import_id, study, imported_at FROM iwrs_import") - import_meta = {r[0]: {"study": r[1], "imported_at": r[2]} for r in cur.fetchall()} - cur.close(); c.close() - - total_snap = 0 - total_main = 0 - - for table, study in [(UCO_TABLE, "77242113UCO3001"), (MDD_TABLE, "42847922MDD3003")]: - c = conn(); cur = c.cursor() - cur.execute(f"SELECT * FROM {table}") - all_rows = list(dict_rows(cur)) - cur.close(); c.close() - - # MAX import_id per studie (pro hlavní kolekci) - import_ids = [r["import_id"] for r in all_rows if r.get("import_id") is not None] - if not import_ids: - continue - max_import = max(import_ids) - - # snapshoty: každý řádek → iwrs_subject_summary_snapshots - snap_docs = [] - main_ops = [] - for r in all_rows: - doc = normalize(r) - doc.pop("id", None) # MySQL autoincrement nezachováváme - doc["study"] = study - subject = doc.get("subject") - if not subject: - continue - natural = f"{study}:{subject}" - - snap = dict(doc) - snap["natural_id"] = natural - meta = import_meta.get(doc.get("import_id"), {}) - snap["imported_at"] = meta.get("imported_at") - snap_docs.append(snap) - - if doc["import_id"] == max_import: - main = dict(doc) - main["_id"] = natural - main["last_import_id"] = max_import - main["last_imported_at"] = meta.get("imported_at") - main_ops.append(ReplaceOne({"_id": natural}, main, upsert=True)) - - if snap_docs: - db.iwrs_subject_summary_snapshots.insert_many(snap_docs, ordered=False) - total_snap += len(snap_docs) - if main_ops: - db.iwrs_subject_summary.bulk_write(main_ops, ordered=False) - total_main += len(main_ops) - print(f" {study}: snap={len(snap_docs)} main={len(main_ops)}") - - print(f" TOTAL snap={total_snap} main={total_main}") - - -# ── 3. visits, shipments, items, inventory (per import_id) ─────────────────── - -def backfill_per_import(mysql_table, main_coll, snap_coll, id_fn, - drop_cols=("id",)): - print(f"[{mysql_table} -> {main_coll}/{snap_coll}]") - db = get_db() - c = conn(); cur = c.cursor() - - # import_id metadata - cur.execute("SELECT import_id, imported_at FROM iwrs_import") - import_meta = {r[0]: r[1] for r in cur.fetchall()} - - # MAX import_id per studie - cur.execute(f"SELECT study, MAX(import_id) FROM {mysql_table} GROUP BY study") - max_per_study = {r[0]: r[1] for r in cur.fetchall()} - - cur.execute(f"SELECT * FROM {mysql_table}") - all_rows = list(dict_rows(cur)) - cur.close(); c.close() - - snap_docs = [] - main_ops = [] - seen_main = set() - for r in all_rows: - doc = normalize(r) - for col in drop_cols: - doc.pop(col, None) - natural = id_fn(doc) - if not natural: - continue - imp_at = import_meta.get(doc.get("import_id")) - - snap = dict(doc) - snap["natural_id"] = natural - snap["imported_at"] = imp_at - snap_docs.append(snap) - - study = doc.get("study") - if study and doc.get("import_id") == max_per_study.get(study): - if natural in seen_main: - continue - seen_main.add(natural) - main = dict(doc) - main["_id"] = natural - main["last_import_id"] = doc["import_id"] - main["last_imported_at"] = imp_at - main_ops.append(ReplaceOne({"_id": natural}, main, upsert=True)) - - if snap_docs: - db[snap_coll].insert_many(snap_docs, ordered=False) - if main_ops: - db[main_coll].bulk_write(main_ops, ordered=False) - print(f" snap={len(snap_docs)} main={len(main_ops)}") - - -def visit_id(doc): - s, sub = doc.get("study"), doc.get("subject") - if not s or not sub: - return None - key = doc.get("irt_transaction_no") - if key is None: - sd = doc.get("scheduled_date") - key = sd.strftime("%Y%m%d") if sd else "noidx" - desc = (doc.get("irt_transaction_description") or "").replace(" ", "_")[:30] - return f"{s}:{sub}:{key}:{desc}" - - -def shipment_id_(doc): - return doc.get("shipment_id") - - -def shipment_item_id(doc): - s, m = doc.get("shipment_id"), doc.get("medication_id") - return f"{s}:{m}" if s and m else None - - -def inventory_id(doc): - s, m = doc.get("site"), doc.get("medication_id") - return f"{s}:{m}" if s and m else None - - -# ── 4. destruction (idempotentní, jen do main) ─────────────────────────────── - -def backfill_destruction(): - print("[iwrs_destruction]") - db = get_db() - c = conn(); cur = c.cursor() - cur.execute("SELECT * FROM iwrs_destruction") - rows = list(dict_rows(cur)) - cur.close(); c.close() - ops = [] - seen = set() - for r in rows: - doc = normalize(r) - doc.pop("id", None) - basket, med = doc.get("basket_id"), doc.get("medication_id") - if not basket or not med: - continue - nid = f"{basket}:{med}" - if nid in seen: - continue - seen.add(nid) - doc["_id"] = nid - ops.append(ReplaceOne({"_id": nid}, doc, upsert=True)) - if ops: - db.iwrs_destruction.bulk_write(ops, ordered=False) - print(f" -> {len(ops)} destrukci") - - -# ── main ───────────────────────────────────────────────────────────────────── - -def main(): - print(f"Cilova DB: {MONGO_DB}") - ensure_indexes() - backfill_imports() - backfill_subject_summary() - backfill_per_import("iwrs_subject_visits", "iwrs_visits", "iwrs_visits_snapshots", visit_id) - backfill_per_import("iwrs_shipments", "iwrs_shipments", "iwrs_shipments_snapshots", shipment_id_) - backfill_per_import("iwrs_shipment_items", "iwrs_shipment_items", "iwrs_shipment_items_snapshots", shipment_item_id) - backfill_per_import("iwrs_inventory", "iwrs_inventory", "iwrs_inventory_snapshots", inventory_id) - backfill_destruction() - - # finalni statistika - db = get_db() - print("\nFINALNI STAV V MONGO:") - for coll in ["iwrs_imports","iwrs_subject_summary","iwrs_visits","iwrs_notifications", - "iwrs_shipments","iwrs_shipment_items","iwrs_inventory","iwrs_destruction", - "iwrs_subject_summary_snapshots","iwrs_visits_snapshots", - "iwrs_shipments_snapshots","iwrs_shipment_items_snapshots","iwrs_inventory_snapshots"]: - n = db[coll].count_documents({}) - print(f" {coll:42s} {n}") - - -if __name__ == "__main__": - main() diff --git a/IWRS/Patients/download_subject_details.py b/IWRS/download_subject_details.py similarity index 99% rename from IWRS/Patients/download_subject_details.py rename to IWRS/download_subject_details.py index a156af0..410b857 100644 --- a/IWRS/Patients/download_subject_details.py +++ b/IWRS/download_subject_details.py @@ -7,7 +7,7 @@ import json import sys import pandas as pd -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from common.mongo_writer import get_db # ── CONFIG ────────────────────────────────────────────────────────────────── diff --git a/IWRS/Patients/import_notifications_to_mongo.py b/IWRS/import_notifications_to_mongo.py similarity index 97% rename from IWRS/Patients/import_notifications_to_mongo.py rename to IWRS/import_notifications_to_mongo.py index 651dabe..85c92ba 100644 --- a/IWRS/Patients/import_notifications_to_mongo.py +++ b/IWRS/import_notifications_to_mongo.py @@ -20,11 +20,10 @@ import datetime from bson.binary import Binary -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from common.mongo_writer import get_db, to_date -# parsery z původního skriptu -from parse_notifications_to_mongo import ( +from notification_parsers import ( parse_kv_lines, parse_medication_table, to_date as parse_to_date, to_datetime as parse_to_datetime, ) diff --git a/IWRS/Patients/import_to_mongo.py b/IWRS/import_patients_mongo.py similarity index 97% rename from IWRS/Patients/import_to_mongo.py rename to IWRS/import_patients_mongo.py index a93d542..e070f5c 100644 --- a/IWRS/Patients/import_to_mongo.py +++ b/IWRS/import_patients_mongo.py @@ -1,7 +1,8 @@ """ -Import Patients dat (subject_summary, visits) z XLSX do MongoDB. +import_patients_mongo.py — import Patients dat (subject_summary, visits) z XLSX do MongoDB. -Volá se z IWRS/Patients/run_all.py po stažení reportů. Hlavní kolekce + snapshoty. +Dříve Patients/import_to_mongo.py. Volá se z import_patients.py +(orchestrátor IWRS/run_all). Hlavní kolekce + snapshoty. """ import os @@ -12,7 +13,7 @@ import datetime import pandas as pd -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from common.mongo_writer import ( to_str, to_int, to_float, to_date, ensure_indexes, log_import, bulk_upsert_with_snapshot, diff --git a/IWRS/notification_parsers.py b/IWRS/notification_parsers.py new file mode 100644 index 0000000..97d5d4b --- /dev/null +++ b/IWRS/notification_parsers.py @@ -0,0 +1,110 @@ +""" +notification_parsers.py — parsery textů IWRS notifikací. +Verze: 1.0 | Datum: 2026-06-10 + +Vyčleněno z bývalého Patients/parse_notifications_to_mongo.py (MySQL→Mongo +legacy skript) — zůstaly jen čisté parsovací funkce, které používá +import_notifications_to_mongo.py. Žádná závislost na MySQL. +""" + +import re +import datetime + + +def parse_kv_lines(text): + """Vytáhne všechny řádky typu 'Klíč: Hodnota' do dictu. + Když je hodnota za dvojtečkou prázdná, vezme se první neprázdný následující řádek.""" + out = {} + lines = [l.strip() for l in text.splitlines()] + pending_key = None + for line in lines: + # čekáme na hodnotu pro klíč z předchozího řádku + if pending_key is not None: + if not line: + continue + if ":" not in line: + out.setdefault(pending_key, line) + pending_key = None + continue + # další řádek je sám "Klíč: Hodnota" → zahodíme pending a zpracujeme normálně + pending_key = None + + if not line or ":" not in line: + continue + if line.lower().startswith("http"): + continue + key, _, val = line.partition(":") + key = key.strip() + val = val.strip() + if not key or (" " in key and len(key.split()) > 8): + continue + if not val: + pending_key = key + continue + out.setdefault(key, val) + return out + + +DATE_RE = re.compile(r"^\d{2}-[A-Z][a-z]{2}-\d{4}$") +DATETIME_RE = re.compile(r"^(\d{2}-[A-Z][a-z]{2}-\d{4})\s+(\d{2}:\d{2}:\d{2})$") + + +def to_date(s): + if not s: + return None + s = s.strip() + if DATE_RE.match(s): + try: + return datetime.datetime.strptime(s, "%d-%b-%Y") + except ValueError: + return None + return None + + +def to_datetime(s): + if not s: + return None + s = re.sub(r"\s+", " ", s.strip()) + m = DATETIME_RE.match(s) + if m: + try: + return datetime.datetime.strptime(f"{m.group(1)} {m.group(2)}", "%d-%b-%Y %H:%M:%S") + except ValueError: + return None + return None + + +MED_ROW_RE = re.compile( + r"(?P\d{7})\s*[\s\n]*" + r"(?P[A-Za-z][A-Za-z0-9 /+\-]+?)\s*[\s\n]*" + r"(?P[A-Z0-9]{5,10})\s*[\s\n]*" + r"(?P\d{2}-[A-Z][a-z]{2}-\d{4})" +) + + +def parse_medication_table(text): + """Najde záznamy medikace (med_no, med_type, lot, expirace) v textu. + Pracuje s oběma formáty (UCO3001 multiline i MDD3003 concatenated).""" + rows = [] + # zkomprimuj whitespace pro snadnější regex + compact = re.sub(r"\s+", " ", text) + for m in MED_ROW_RE.finditer(compact): + med_type = m.group("type").strip() + # uřízni nadbytečné koncové fragmenty + med_type = re.sub(r"\s+(Packaged|Lot|Expiration|No|Date|Medication).*$", "", med_type).strip() + rows.append({ + "medication_no": m.group("no"), + "medication_type": med_type, + "lot_no": m.group("lot"), + "expiration_date": to_date(m.group("exp")), + }) + # dedupe + seen = set() + unique = [] + for r in rows: + key = (r["medication_no"], r["lot_no"]) + if key in seen: + continue + seen.add(key) + unique.append(r) + return unique diff --git a/IWRS/reports.json b/IWRS/reports.json deleted file mode 100644 index c66e6eb..0000000 --- a/IWRS/reports.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - {"name": "Drug Accountability Form - Multiple Subjects", "href": "/report/drug_accountability_form_multiple_subjects"}, - {"name": "Drug Accountability Form - Single Subject", "href": "/report/drug_accountability_form_single_subject"}, - {"name": "Janssen Pharmaceuticals IP Destruction Form", "href": "/report/ip_destruction_form"}, - {"name": "On-Site Drug Inventory and Accountability Details Form", "href": "/report/onsite_inventory_detail"}, - {"name": "On-Site Drug Inventory Form", "href": "/report/onsite_drug_inventory_form"}, - {"name": "Location Summary Report", "href": "/report/country_summary_report"}, - {"name": "Site Detail Report", "href": "/report/site_detail_report"}, - {"name": "Study Sites Report", "href": "/report/study_sites_report"}, - {"name": "Site Inventory Detail Report", "href": "/report/site_inventory_detail"}, - {"name": "Site Inventory Summary Report", "href": "/report/site_inventory_summary"}, - {"name": "Subject Data Changes Report", "href": "/report/patient_data_changes_report"}, - {"name": "Subject Detail Report", "href": "/report/patient_detail_report"}, - {"name": "Subject Summary Report", "href": "/report/patient_summary_report"}, - {"name": "Subject Visit Summary Report", "href": "/report/patient_visit_summary"}, - {"name": "Shipment Details Report", "href": "/report/shipment_details_report"}, - {"name": "Shipments Report", "href": "/report/shipments_report"}, - {"name": "Cohort History Report", "href": "/report/cohort_history_report"}, - {"name": "Cohort Summary Report", "href": "/report/cohort_summary_report"}, - {"name": "Site Activations Report", "href": "/report/site_activation_pivot"}, - {"name": "User Login History", "href": "/report/user_logins"}, - {"name": "Users List", "href": "/report/users"} -] diff --git a/IWRS/run_all_v1.0.md b/IWRS/run_all_v1.0.md deleted file mode 100644 index da699b4..0000000 --- a/IWRS/run_all_v1.0.md +++ /dev/null @@ -1,110 +0,0 @@ -# run_all_v1.0.py — IWRS: kompletní pipeline Pacienti + Léky - -**Verze:** 1.0 | **Datum:** 2026-06-10 - -Jeden vstupní skript na úrovni `IWRS/`, který stáhne z janssen.4gclinical.com -a naimportuje do MongoDB (db `studie`) data pacientů i léků pro obě studie -(77242113UCO3001, 42847922MDD3003). Nahrazuje dřívější `Drugs/run_all.py` -a `Patients/download_all.py` + `Patients/import_all.py` (přesunuty do `Trash/`). - -## Tok souborů - -``` -IWRS/Incoming/ ← sem padá vše stažené (pacienti i léky, datumované názvy) -IWRS/Incoming/Processed/ ← sem se přesouvá po úspěšném importu -``` - -- Při chybě importu soubor **zůstává v Incoming/** a zpracuje se při příštím běhu. -- Import jde vždy **nejstarší soubor napřed** (mtime) — chronologická správnost snapshotů. -- Kolize jména v Processed/ → přepíše se (Mongo už data má, soubor je jen archiv). -- Adresář `IWRS/Incoming/` je v `.gitignore` (stejně jako dříve `Patients/Incoming/`). -- Původní adresáře `Drugs/xls_*` zůstávají zmrazené na místě jako archiv — nový kód je nepoužívá. - -## Názvy souborů v Incoming/ - -| Typ | Vzor | -|---|---| -| Subject Summary | `YYYY-MM-DD {study} Subject Summary Report.xlsx` | -| Subject Detail | `YYYY-MM-DD {study} {subject} Subject Detail.xlsx` | -| Notifikace | `{datum}_{study}_{subject}_{label}.pdf` + `.json` | -| Onsite Inventory | `YYYY-MM-DD {study} Onsite Inventory {site}.xlsx` | -| IP Destruction | `YYYY-MM-DD {study} IP Destruction {basket}.xlsx` | -| Shipments Report | `YYYY-MM-DD {study} Shipments Report.xlsx` | -| Shipment Details | `YYYY-MM-DD {study} Shipment Details {shipment_id}.xlsx` | - -Při kolizi (druhý běh ve stejný den) se před příponu přidá ` HHMM`. -Metadata (site, basket, study) se při importu čtou primárně z **obsahu** souboru; -z názvu se bere jen `shipment_id` u Shipment Details. - -## Průběh - -### Fáze 1 — stahování (2 přihlášení, per studie jedna browser session) - -1. Login + výběr studie (`common/iwrs_portal.py`) -2. **Pacienti** (`Patients/download_patients.py`): - - Subject Summary Report - - per subjekt: Subject Detail XLSX + notifikace PDF+JSON (stahují se jen - notifikace, jejichž `pk` ještě není v Mongo `iwrs_notifications`) -3. **Léky** (`Drugs/download_drugs.py`): - - Onsite Inventory — všechna centra, vždy znovu - - IP Destruction — přeskočí košíky už importované v `iwrs_destruction` - (destrukce je immutable); dříve se přeskakovalo podle existence souboru - - Shipments Report — vždy znovu - - Shipment Details — jen CZ zásilky; přeskočí zásilky, jejichž položky - jsou v `iwrs_shipment_items` se statusem RECEIVED (finální stav); - dříve „soubor existuje a status RECEIVED“. CANCELLED zásilky se stahují - při každém běhu (záměrně zachováno z původní verze). - -### Fáze 2 — import (po stažení obou studií) - -1. `ensure_indexes()` (jednou) -2. **Pacienti** (`Patients/import_patients.py`): summary → detaily → notifikace; - per soubor, po úspěchu přesun do Processed/ -3. **Léky** (`Drugs/import_drugs.py`): jeden `import_id` per studie a běh; - parsuje všechny čekající soubory (nejstarší napřed, poslední vyhrává per `_id`), - pak hromadný zápis: - - `iwrs_shipments`, `iwrs_shipment_items`, `iwrs_inventory` — upsert + snapshot - - `iwrs_destruction` — upsert bez snapshotu - Po úspěšném zápisu se zparsované soubory přesunou do Processed/; - soubor s chybou parsování zůstává v Incoming/. - -## Použití - -``` -python run_all_v1.0.py # vše (download + import, obě studie) -python run_all_v1.0.py --download-only # jen stažení do Incoming/ -python run_all_v1.0.py --import-only # jen import čekajících souborů -python run_all_v1.0.py --only-patients # jen pacientská část -python run_all_v1.0.py --only-drugs # jen léková část -python run_all_v1.0.py --study 42847922MDD3003 # jen jedna studie -``` - -Prohlížeč běží s `headless=False` (viditelné okno) jako dosud. -Moduly `import_patients.py` a `import_drugs.py` lze spustit i samostatně. - -## Mapa modulů - -``` -IWRS/ - run_all_v1.0.py ← vstupní skript (CLI, orchestrace) - common/ - iwrs_portal.py ← BASE_URL, credentials, login(page, study) - paths.py ← INCOMING/PROCESSED, unique_path, move_done, sorted_by_mtime - mongo_writer.py ← beze změny (konvertory, upserty, snapshoty, import log) - Patients/ - download_patients.py ← summary + delegace na download_subject_details.run() - import_patients.py ← logika z bývalého import_all.py, nové cesty - download_subject_details.py, import_to_mongo.py, - import_notifications_to_mongo.py, parse_notifications_to_mongo.py ← beze změny - Trash/download_all.py, Trash/import_all.py ← nahrazeno - Drugs/ - download_drugs.py ← 4 typy reportů → Incoming/, skip-logika přes Mongo - import_drugs.py ← parsery z bývalého import_to_mongo.py, čte Incoming/ - Trash/run_all.py, Trash/import_to_mongo.py ← nahrazeno -``` - -## Jednorázová migrace (provedeno 2026-06-10) - -- `Patients/Incoming/Zpracováno/` (1343 souborů) → `IWRS/Incoming/Processed/` -- `.gitignore`: `IWRS/Patients/Incoming/` → `IWRS/Incoming/` -- Staré vstupní skripty → `Trash/` (viz mapa výše) diff --git a/IWRS/run_all_v1.0.py b/IWRS/run_all_v1.0.py deleted file mode 100644 index c17a3ce..0000000 --- a/IWRS/run_all_v1.0.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -================================================================================ - run_all_v1.0.py — IWRS: kompletní pipeline Pacienti + Léky (obě studie) - Verze: 1.0 - Datum: 2026-06-10 -================================================================================ - -Stáhne z janssen.4gclinical.com a naimportuje do MongoDB (db `studie`): - - Pacienti: Subject Summary, Subject Details, notifikace (PDF+JSON) - Léky: Onsite Inventory, IP Destruction, Shipments Report, Shipment Details - -Tok souborů: vše se stahuje do IWRS/Incoming/, po úspěšném importu se přesouvá -do IWRS/Incoming/Processed/. Při chybě soubor zůstává v Incoming/ a zpracuje -se při příštím běhu. - -Přihlášení: 2× (jednou per studie) — studie se vybírá až po přihlášení, takže -jedna browser session stáhne pacienty i léky pro jednu studii. - -Použití: - python run_all_v1.0.py # vše (download + import, obě studie) - python run_all_v1.0.py --download-only # jen stažení do Incoming/ - python run_all_v1.0.py --import-only # jen import čekajících souborů - python run_all_v1.0.py --only-patients # jen pacientská část - python run_all_v1.0.py --only-drugs # jen léková část - python run_all_v1.0.py --study 42847922MDD3003 # jen jedna studie - -Detaily v run_all_v1.0.md. -""" - -import os -import sys -import argparse -import traceback - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -for _p in (os.path.join(BASE_DIR, "Drugs"), os.path.join(BASE_DIR, "Patients"), BASE_DIR): - if _p not in sys.path: - sys.path.insert(0, _p) - -from playwright.sync_api import sync_playwright - -from common.iwrs_portal import login -from common.paths import STUDIES, INCOMING_DIR, PROCESSED_DIR, ensure_dirs -from common.mongo_writer import ensure_indexes - -import download_patients -import import_patients -import download_drugs -import import_drugs - - -def download_phase(studies, do_patients, do_drugs): - with sync_playwright() as p: - for study in studies: - print(f"\n{'='*60}") - print(f"[{study}] STAHOVÁNÍ") - print(f"{'='*60}") - - browser = p.chromium.launch(headless=False) - context = browser.new_context(accept_downloads=True) - page = context.new_page() - - try: - print(" Přihlášení...") - login(page, study) - - if do_patients: - print(f"\n ── PACIENTI [{study}] ──") - try: - download_patients.run(page, study) - except Exception as e: - print(f" CHYBA při stahování pacientů: {e}") - traceback.print_exc() - - if do_drugs: - print(f"\n ── LÉKY [{study}] ──") - try: - download_drugs.run(page, study) - except Exception as e: - print(f" CHYBA při stahování léků: {e}") - traceback.print_exc() - - except Exception as e: - print(f" CHYBA (login/session): {e}") - traceback.print_exc() - finally: - browser.close() - - -def import_phase(studies, do_patients, do_drugs): - print(f"\n{'='*60}") - print("IMPORT DO MongoDB") - print(f"{'='*60}") - ensure_indexes() - - if do_patients: - try: - import_patients.run(studies) - except Exception as e: - print(f" CHYBA při importu pacientů: {e}") - traceback.print_exc() - - if do_drugs: - try: - import_drugs.run(studies) - except Exception as e: - print(f" CHYBA při importu léků: {e}") - traceback.print_exc() - - -def main(): - ap = argparse.ArgumentParser( - description="IWRS pipeline: stažení + import pacientů a léků (obě studie)") - ap.add_argument("--download-only", action="store_true", help="jen stažení do Incoming/") - ap.add_argument("--import-only", action="store_true", help="jen import čekajících souborů") - ap.add_argument("--only-patients", action="store_true", help="jen pacientská část") - ap.add_argument("--only-drugs", action="store_true", help="jen léková část") - ap.add_argument("--study", choices=STUDIES, help="jen jedna studie") - args = ap.parse_args() - - if args.download_only and args.import_only: - ap.error("--download-only a --import-only nelze kombinovat") - if args.only_patients and args.only_drugs: - ap.error("--only-patients a --only-drugs nelze kombinovat") - - studies = [args.study] if args.study else STUDIES - do_patients = not args.only_drugs - do_drugs = not args.only_patients - - ensure_dirs() - - if not args.import_only: - download_phase(studies, do_patients, do_drugs) - - if not args.download_only: - import_phase(studies, do_patients, do_drugs) - - print(f"\n{'='*60}") - print("Vše hotovo.") - print(f" Incoming: {INCOMING_DIR}") - print(f" Processed: {PROCESSED_DIR}") - print(f"{'='*60}") - - -if __name__ == "__main__": - main()