From eb05691149c05a7e934a370b9b299cf464813c18 Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Fri, 5 Jun 2026 08:40:35 +0200 Subject: [PATCH] z230 --- .../{ => Trash}/janssenpc_file_send.py | 0 .../janssenpc_file_receive.py | 82 +++++ .../janssenpc_file_send (2).py | 300 ++++++++++++++++++ 3 files changed, 382 insertions(+) rename JanssenScripts/FileWatchAndSend/{ => Trash}/janssenpc_file_send.py (100%) create mode 100644 JanssenScripts/FileWatchAndSend/janssenpc_file_receive.py create mode 100644 JanssenScripts/FileWatchAndSend/janssenpc_file_send (2).py diff --git a/JanssenScripts/FileWatchAndSend/janssenpc_file_send.py b/JanssenScripts/FileWatchAndSend/Trash/janssenpc_file_send.py similarity index 100% rename from JanssenScripts/FileWatchAndSend/janssenpc_file_send.py rename to JanssenScripts/FileWatchAndSend/Trash/janssenpc_file_send.py diff --git a/JanssenScripts/FileWatchAndSend/janssenpc_file_receive.py b/JanssenScripts/FileWatchAndSend/janssenpc_file_receive.py new file mode 100644 index 0000000..6d0c520 --- /dev/null +++ b/JanssenScripts/FileWatchAndSend/janssenpc_file_receive.py @@ -0,0 +1,82 @@ +# Název: janssenpc_file_receive.py +# Verze: 1.0 +# Datum: 2026-06-05 +# Popis: Stáhne soubory čekající na serveru (msgs.buzalka.cz) do ##JNJPrenos\ZHovorcovic\. +# Spouštět ručně dle potřeby. + +import base64 +import hashlib +import requests +from pathlib import Path +from datetime import datetime +from cryptography.fernet import Fernet + +TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340" +PENDING_URL = "https://msgs.buzalka.cz/pending-files" +DOWNLOAD_URL = "https://msgs.buzalka.cz/download-file" +RECEIVE_DIR = Path(r"C:\Users\vbuzalka\OneDrive - JNJ\##JNJPrenos\ZHovorcovic") +LOG_FILE = Path(__file__).parent / "file_send.log" +_FERNET = Fernet(base64.urlsafe_b64encode(hashlib.sha256(TOKEN.encode()).digest())) + + +def log(msg: str): + ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + line = f"[{ts}] {msg}" + print(line) + with LOG_FILE.open("a", encoding="utf-8") as lf: + lf.write(line + "\n") + + +def resolve_dest(directory: Path, filename: str) -> Path: + """Přepíše existující soubor, pokud je zamčený → name (2), (3)...""" + dest = directory / filename + if not dest.exists(): + return dest + try: + dest.open('r+b').close() + return dest + except PermissionError: + pass + stem = Path(filename).stem + suffix = Path(filename).suffix + n = 2 + while True: + candidate = directory / f"{stem} ({n}){suffix}" + if not candidate.exists(): + return candidate + try: + candidate.open('r+b').close() + return candidate + except PermissionError: + n += 1 + + +log("=== file_receive: Spuštění ===") + +try: + resp = requests.get(PENDING_URL, headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30) + resp.raise_for_status() + pending = resp.json().get("files", []) + log(f"Souborů čeká na serveru: {len(pending)}{' — ' + str(pending) if pending else ''}") +except Exception as e: + log(f"CHYBA při dotazu na server: {e}") + pending = [] + +if pending: + RECEIVE_DIR.mkdir(parents=True, exist_ok=True) + for filename in pending: + try: + r = requests.get( + f"{DOWNLOAD_URL}/{filename}", + headers={"Authorization": f"Bearer {TOKEN}"}, + timeout=120, + ) + r.raise_for_status() + decrypted = _FERNET.decrypt(r.content) + dest = resolve_dest(RECEIVE_DIR, filename) + dest.write_bytes(decrypted) + log(f" STAŽENO | {filename}{' → ' + dest.name if dest.name != filename else ''}") + except Exception as e: + log(f" CHYBA | {filename} | {e}") + +log("=== file_receive: Hotovo ===") diff --git a/JanssenScripts/FileWatchAndSend/janssenpc_file_send (2).py b/JanssenScripts/FileWatchAndSend/janssenpc_file_send (2).py new file mode 100644 index 0000000..181fcfd --- /dev/null +++ b/JanssenScripts/FileWatchAndSend/janssenpc_file_send (2).py @@ -0,0 +1,300 @@ +# Název: janssenpc_file_send.py +# Verze: 2.4 +# Datum: 2026-06-05 +# Popis: Přejmenuje soubory ve složce ##JNJPrenos, zašifruje (Fernet/AES-128), +# odešle na msgs.buzalka.cz a přesune do podsložky Trash. +# Na konci stáhne soubory čekající na serveru do ##JNJPrenos\ZHovorcovic\. +# Loguje průběh do file_send.log vedle skriptu. +# Podporuje: PANORAMA Site Contacts (xlsx), Panorama Dashboard (xlsx), +# Site Visit Report (xlsx), Follow-Up Letter (xlsx), +# Clario MayoScore (csv), Clario MayoDiary (csv), +# Clario Data Corrections / DCRs (csv). + +import os +import time +import shutil +import base64 +import hashlib +import requests +import pandas as pd +from pathlib import Path +from datetime import datetime +from cryptography.fernet import Fernet + +TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340" +UPLOAD_URL = "https://msgs.buzalka.cz/upload-file" +_FERNET = Fernet(base64.urlsafe_b64encode(hashlib.sha256(TOKEN.encode()).digest())) +SOURCE_DIR = Path(r"C:\Users\vbuzalka\OneDrive - JNJ\##JNJPrenos") +TRASH_DIR = SOURCE_DIR / "Trash" +LOG_FILE = Path(__file__).parent / "file_send.log" + +MAYO_DIARY_COLUMNS = [ + 'Protocol', 'Country', 'Site', 'PI Name', 'Subject ID', + 'Report Date', 'Report Start Date/Time', 'Report End Date/Time', + 'Stool Frequency', 'Form Number', 'Role', 'Original Source', +] + +MAYO_SCORE_COLUMNS = [ + 'Protocol', 'Study Population', 'Country', 'Site', 'Principal Investigator', + 'Participant ID', 'Baseline Stool Frequency', 'Visit', 'Visit Date', + 'Endoscopy Completed?', 'Central Endoscopy Score', 'Local Endoscopy Score', + 'Partial Mayo Score', 'Full Mayo Score', +] + +DCR_ECOA_COLUMNS = [ + 'Protocol', 'Data Correction ID', 'Description', 'Query History', +] + +DCR_ECG_COLUMNS = [ + 'Protocol', 'Data Correction ID', 'Site ID', 'PI_NAME', 'Subject Number', 'Query History', +] + +PANORAMA_COLUMNS = [ + 'Part', 'Source', 'Sector', 'TA', 'Protocol ID', 'Interventional', + 'Region', 'Country Name', 'Institution Name', 'Site City', + 'Site Zip/Postal Code', 'Site Address', 'MSID', 'Site ID', + 'Site Status', 'SM Full Name', 'PI Name', 'St F Subj Enr Act', + 'ID', 'Category', 'Type', 'Priority', 'Severity', 'Description', + 'Brief Description - Subject ID', 'Comments', 'Created By', + 'Create Date', 'Last Modified Date', 'Start Date', 'Due Date', + 'End Date', 'Status', 'Days Outstanding', 'Action Taken', + 'Escalated To', 'Visit Report Status', 'Visit Report Approved', + 'Visit Report Type', 'Visit Report Status End Date', 'Active', + 'Association', 'Deviation', 'Deviation Closed Date', 'Reason For Exclusion' +] + + +def log(msg: str): + ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + line = f"[{ts}] {msg}" + print(line) + with LOG_FILE.open("a", encoding="utf-8") as lf: + lf.write(line + "\n") + + +def move_to_trash(f: Path): + TRASH_DIR.mkdir(exist_ok=True) + dest = TRASH_DIR / f.name + if dest.exists(): + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + dest = TRASH_DIR / f"{f.stem}_{ts}{f.suffix}" + shutil.move(str(f), dest) + + +def get_timestamp(file_path: str) -> str: + return datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d_%H-%M-%S') + + +def prejmenuj(directory: Path) -> None: + log(f"--- Přejmenování, adresář: {directory} ---") + files = [f for f in directory.iterdir() if f.is_file()] + log(f" Nalezeno souborů: {len(files)} — {[f.name for f in files]}") + + for f in files: + filename = f.name + file_path = str(f) + + # 0a. CLARIO MAYO DIARY (CSV) + if 'MAYO-DIARY' in filename and filename.endswith('.csv'): + log(f" Detekován MayoDiary: {filename}") + try: + df = pd.read_csv(file_path) + missing = set(MAYO_DIARY_COLUMNS) - set(df.columns) + if not missing: + protocols = df['Protocol'].dropna().unique() + log(f" Protocol: {list(protocols)}") + if len(protocols) > 0: + study = str(protocols[0]).strip() + new_name = f"{get_timestamp(file_path)} {study} Clario MayoDiary.csv" + f.rename(directory / new_name) + log(f" ÚSPĚCH: -> '{new_name}'") + else: + log(f" VAROVÁNÍ: Sloupec Protocol je prázdný.") + else: + log(f" PŘESKOČENO: Chybí sloupce: {missing}") + except Exception as e: + log(f" CHYBA: {e}") + continue + + # 0b. CLARIO MAYO SCORE (CSV) + if 'Custom.MayoScoreReport' in filename and filename.endswith('.csv'): + log(f" Detekován MayoScore: {filename}") + try: + df = pd.read_csv(file_path) + missing = set(MAYO_SCORE_COLUMNS) - set(df.columns) + if not missing: + protocols = df['Protocol'].dropna().unique() + log(f" Protocol: {list(protocols)}") + if len(protocols) > 0: + study = str(protocols[0]).strip() + new_name = f"{get_timestamp(file_path)} {study} Clario MayoScore.csv" + f.rename(directory / new_name) + log(f" ÚSPĚCH: -> '{new_name}'") + else: + log(f" VAROVÁNÍ: Sloupec Protocol je prázdný.") + else: + log(f" PŘESKOČENO: Chybí sloupce: {missing}") + except Exception as e: + log(f" CHYBA: {e}") + continue + + # 0c. CLARIO DATA CORRECTIONS (CSV) — ECG nebo eCOA + if filename.endswith('.csv'): + try: + df = pd.read_csv(file_path, nrows=2) + cols = set(df.columns) + log(f" CSV sloupce ({filename}): {sorted(cols)}") + + missing_ecg = set(DCR_ECG_COLUMNS) - cols + missing_ecoa = set(DCR_ECOA_COLUMNS) - cols + log(f" Chybí pro ECG: {missing_ecg or '—'}") + log(f" Chybí pro eCOA: {missing_ecoa or '—'}") + + if not missing_ecg: + label = "Clario ECG DCRs" + elif not missing_ecoa: + label = "Clario eCOA DCRs" + else: + log(f" Neznámý CSV typ — bude odeslán bez přejmenování: {filename}") + # nepokračujeme continue — soubor projde dál k odeslání + label = None + + if label: + log(f" Detekován {label}: {filename}") + protocols = df['Protocol'].dropna().unique() + log(f" Protocol: {list(protocols)}") + if len(protocols) > 0: + study = str(protocols[0]).strip() + new_name = f"{get_timestamp(file_path)} {study} {label}.csv" + f.rename(directory / new_name) + log(f" ÚSPĚCH přejmenování: -> '{new_name}'") + else: + log(f" VAROVÁNÍ: Sloupec Protocol je prázdný — odesílám pod původním názvem.") + except Exception as e: + log(f" CHYBA při zpracování CSV {filename}: {e}") + continue + + # Ostatní — jen xlsx + if not filename.endswith('.xlsx'): + log(f" Přeskočeno (neznámý typ): {filename}") + continue + + # 1a. PANORAMA SITE CONTACTS (XLSX) — soubor pojmenovaný "PANORAMA Dashboard" + if 'PANORAMA Dashboard' in filename: + log(f" Detekován PANORAMA Site Contacts: {filename}") + try: + with pd.ExcelFile(file_path) as xl: + sheet_names = xl.sheet_names + if 'Site Contacts' in sheet_names: + df_a1 = xl.parse('Site Contacts', nrows=1, header=None) + a1 = str(df_a1.iloc[0, 0]) if not df_a1.empty else '' + else: + a1 = None + # soubor je nyní zavřen — přejmenování proběhne bez chyby + if a1 is None: + log(f" PŘESKOČENO: List 'Site Contacts' nenalezen.") + elif 'Title: Site Contacts' in a1: + new_name = f"{get_timestamp(file_path)} PANORAMA Site Contacts.xlsx" + f.rename(directory / new_name) + log(f" ÚSPĚCH: -> '{new_name}'") + else: + log(f" PŘESKOČENO: A1 neodpovídá vzoru ({a1[:50]})") + except Exception as e: + log(f" CHYBA: {e}") + continue + + # 1. PANORAMA DASHBOARD (XLSX) + if 'Panorama Dashboard' in filename: + log(f" Detekován Panorama: {filename}") + try: + df = pd.read_excel(file_path, skiprows=5) + missing = set(PANORAMA_COLUMNS) - set(df.columns) + if not missing: + ids = df['Protocol ID'].dropna().unique() + log(f" Protocol ID: {list(ids)}") + if len(ids) > 0: + study = str(ids[0]).strip() + new_name = f"{get_timestamp(file_path)} {study} Panorama Deviations and Issues.xlsx" + f.rename(directory / new_name) + log(f" ÚSPĚCH: -> '{new_name}'") + else: + log(f" VAROVÁNÍ: Protocol ID je prázdný.") + else: + log(f" PŘESKOČENO: Chybí sloupce: {missing}") + except Exception as e: + log(f" CHYBA: {e}") + continue + + # 2. SITE VISIT REPORT A FOLLOW-UP LETTER (XLSX) + try: + df_a1 = pd.read_excel(file_path, nrows=1, header=None) + if not df_a1.empty: + a1 = str(df_a1.iloc[0, 0]) + log(f" A1: {a1[:80]}") + is_site_visit = "Title: Site Visit Report Details" in a1 + is_follow_up = "Title: Follow-Up Letter Details" in a1 + + if is_site_visit or is_follow_up: + suffix = "Site Visit Details.xlsx" if is_site_visit else "FUL details.xlsx" + log(f" Detekován {'Site Visit' if is_site_visit else 'Follow-Up Letter'}: {filename}") + df = pd.read_excel(file_path, skiprows=5) + if 'Protocol ID' in df.columns: + ids = df['Protocol ID'].dropna().unique() + log(f" Protocol ID: {list(ids)}") + if len(ids) > 0: + study = str(ids[0]).strip() + new_name = f"{get_timestamp(file_path)} {study} {suffix}" + f.rename(directory / new_name) + log(f" ÚSPĚCH: -> '{new_name}'") + else: + log(f" VAROVÁNÍ: Protocol ID je prázdný.") + else: + log(f" PŘESKOČENO: Chybí sloupec Protocol ID.") + else: + log(f" Přeskočeno (neznámý xlsx obsah): {filename}") + except Exception as e: + log(f" CHYBA: {e}") + + log("--- Přejmenování dokončeno ---") + + +# === HLAVNÍ LOGIKA === + +log("=== Spuštění ===") +log(f"Zdrojový adresář: {SOURCE_DIR} (existuje: {SOURCE_DIR.exists()})") + +# 1. Přejmenuj +prejmenuj(SOURCE_DIR) + +# 2. Počkej 10 vteřin +log("Čekám 10 vteřin...") +time.sleep(10) + +# 3. Odešli soubory +files = [f for f in SOURCE_DIR.iterdir() if f.is_file()] +log(f"Souborů k odeslání: {len(files)}") +for f in files: + log(f" Nalezen: {f.name}") + +if not files: + log("Žádné soubory k odeslání.") +else: + for f in files: + try: + encrypted = _FERNET.encrypt(f.read_bytes()) + enc_name = f.name + ".enc" + resp = requests.post( + UPLOAD_URL, + headers={"Authorization": f"Bearer {TOKEN}"}, + files={"file": (enc_name, encrypted, "application/octet-stream")}, + timeout=120, + ) + resp.raise_for_status() + status = resp.json().get('status', '?').upper() + log(f" {status:10} | {f.name} (zašifrováno)") + move_to_trash(f) + log(f" PŘESUNUTO | {f.name} -> Trash") + except Exception as e: + log(f" CHYBA | {f.name} | {e}") + +log("=== Hotovo ===")