diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx new file mode 100644 index 0000000..1b93bdb Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1412.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx new file mode 100644 index 0000000..76e900f Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1414.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx new file mode 100644 index 0000000..00fe7fa Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1415.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx new file mode 100644 index 0000000..8836fdf Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1417.xlsx differ diff --git a/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx new file mode 100644 index 0000000..dfafbe2 Binary files /dev/null and b/Covance/CreatedReports/2026-05-05 42847922MDD3003 Covance Samples 1419.xlsx differ diff --git a/Covance/SourceData/Protocol 42847922MDD3003 sponsor-study-35472-activity-reports-documents-equery.csv b/Covance/SourceData/Protocol 42847922MDD3003 sponsor-study-35472-activity-reports-documents-equery.csv new file mode 100644 index 0000000..4e93cac --- /dev/null +++ b/Covance/SourceData/Protocol 42847922MDD3003 sponsor-study-35472-activity-reports-documents-equery.csv @@ -0,0 +1,37 @@ +Site,Country,Visit,Visit Collection Date,Accession,Subject,eQueryId,Create Date,Response Date Time,Issue Type,Status,Time Before Response,User Name,Email,Study Role +CZ10012 - Dr.Ales Urban,CZECH REPUBLIC,End of Phase/Treatment,"Mar 25, 2026 5:00 PM",6226560888,CZ100120005,2064594,"Mar 27, 2026 9:33 AM","Apr 1, 2026 4:32 PM",VST,Closed,5d,,, +CZ10012 - Dr.Ales Urban,CZECH REPUBLIC,Part 2 DB Every 12 Weeks,"Mar 6, 2026 2:00 PM",6226560891,CZ100120004,2035331,"Mar 10, 2026 11:18 AM","Mar 10, 2026 12:50 PM",ADT201,Closed,1h 31m,Marcela Sedlackova,marcelasedlackova@seznam.cz,Investigator +CZ10012 - Dr.Ales Urban,CZECH REPUBLIC,Part 2 OL Induction Day 29,"Jan 2, 2026 12:00 PM",6226976943,CZ100120004,1936950,"Jan 6, 2026 12:29 PM","Jan 9, 2026 8:41 AM",DOB,Closed,2d,Marcela Sedlackova,marcelasedlackova@seznam.cz,Investigator +CZ10012 - Dr.Ales Urban,CZECH REPUBLIC,End of Phase/Treatment,"Mar 25, 2026 3:00 PM",6226752555,CZ100120006,2064606,"Mar 27, 2026 9:48 AM","Apr 1, 2026 4:32 PM",VST,Closed,5d,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Retest,"Dec 11, 2025 9:05 AM",6226935606,CZ100110003,1930555,"Dec 31, 2025 12:41 PM","Jan 3, 2026 5:26 PM","VST, AMT4164",Closed,3d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Retest,"Dec 11, 2025 9:05 AM",6226935606,CZ100110003,1908988,"Dec 16, 2025 2:17 PM","Dec 19, 2025 3:55 PM","VST, AMT4164",Closed,3d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Baseline,"Jan 6, 2026 10:11 AM",6227261836,CZ100110006,1943384,"Jan 9, 2026 2:23 PM",,VST,Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Baseline,"Jan 6, 2026 10:11 AM",6227261836,CZ100110006,1953531,"Jan 16, 2026 7:52 AM",,VST,Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Baseline,"Jan 6, 2026 10:11 AM",6227261836,CZ100110006,1965291,"Jan 23, 2026 11:05 AM","Jan 26, 2026 6:18 PM",VST,Closed,3d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 1 Screening,"Jan 21, 2026 10:11 AM",6227542160,CZ100110009,1965641,"Jan 23, 2026 12:41 PM","Jan 26, 2026 6:18 PM",ADT201,Closed,3d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Retest,"Apr 10, 2026 8:16 AM",6227542182,CZ100110010,2105984,"Apr 22, 2026 4:51 PM",,"AMT4164, ADT201",Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Retest,"Apr 10, 2026 8:16 AM",6227542182,CZ100110010,2117124,"Apr 29, 2026 5:13 PM",,"AMT4164, ADT201",Open,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Retest,"Apr 10, 2026 8:16 AM",6227542182,CZ100110010,2094889,"Apr 15, 2026 7:25 PM",,"AMT4164, ADT201",Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Day 29,"Feb 3, 2026 10:23 AM",6226561517,CZ100110006,2040081,"Mar 12, 2026 12:52 PM","Mar 20, 2026 9:35 AM",VST,Closed,7d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Day 29,"Feb 3, 2026 10:23 AM",6226561517,CZ100110006,2015993,"Feb 25, 2026 2:26 PM","Mar 4, 2026 4:37 PM",VST,Closed,7d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Day 29,"Feb 3, 2026 10:23 AM",6226561517,CZ100110006,2005997,"Feb 18, 2026 3:01 PM",,VST,Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,Part 2 OL Induction Day 29,"Feb 3, 2026 10:23 AM",6226561517,CZ100110006,1984511,"Feb 5, 2026 10:36 AM","Feb 10, 2026 5:12 PM",VST,Closed,5d,Marta Lendlova,studie@medipa.org,Investigator +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,End of Phase/Treatment,,6228234300,CZ100110010,2112166,"Apr 27, 2026 1:26 PM",,SCD,Closed,,,, +CZ10011 - Dr. Marta Lendlova,CZECH REPUBLIC,End of Phase/Treatment,,6228234300,CZ100110010,2124850,"May 5, 2026 1:37 PM",,SCD,Open,,,, +CZ10008 - Dr.Zdenek Solle,CZECH REPUBLIC,Part 1 Screening,"Aug 5, 2025 11:58 AM",6225351045,CZ100080002,1712216,"Aug 7, 2025 12:05 PM","Aug 7, 2025 12:45 PM",SCT,Closed,40m,Roman Gregar,r.gregar@clintrial.cz,Investigator +CZ10008 - Dr.Zdenek Solle,CZECH REPUBLIC,Part 2 OL Induction Baseline,"Sep 10, 2025 11:35 AM",6225351056,CZ100080001,1769239,"Sep 15, 2025 5:00 PM","Sep 16, 2025 8:14 AM",SCT,Closed,15h 14m,Roman Gregar,r.gregar@clintrial.cz,Investigator +CZ10008 - Dr.Zdenek Solle,CZECH REPUBLIC,Early Withdrawal,"Oct 14, 2025 12:30 PM",6225351066,CZ100080004,1817448,"Oct 16, 2025 12:29 PM","Oct 16, 2025 1:37 PM",ADT201,Closed,1h 7m,Barbora Sollova,b.sollova@clintrial.cz,Investigator +CZ10008 - Dr.Zdenek Solle,CZECH REPUBLIC,Part 2 DB Baseline,"Mar 10, 2026 11:10 AM",6226804962,CZ100080006,2039863,"Mar 12, 2026 12:07 PM","Mar 16, 2026 8:11 AM",VST,Closed,3d,Barbora Sollova,b.sollova@clintrial.cz,Investigator +CZ10005 - Dr.Lubos Janu,CZECH REPUBLIC,Retest,"Sep 16, 2025 10:00 AM",6225351036,CZ100050001,1776942,"Sep 19, 2025 11:34 AM","Sep 23, 2025 2:21 PM",VST,Closed,4d,,, +CZ10005 - Dr.Lubos Janu,CZECH REPUBLIC,Retest,"Nov 11, 2025 10:04 AM",6225351037,CZ100050002,1859363,"Nov 13, 2025 12:27 PM","Nov 18, 2025 10:16 AM",ADT201,Closed,4d,,, +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,End of Phase/Treatment,"Jan 9, 2026 10:20 AM",6227178908,CZ100040002,1947467,"Jan 13, 2026 10:55 AM","Jan 16, 2026 10:51 AM","PCFCOM, ADT201",Closed,2d,Gabriela Novotna,gnovotna@email.cz,Investigator +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,End of Phase/Treatment,"Mar 6, 2026 9:50 AM",6227178911,CZ100040002,2034798,"Mar 10, 2026 12:00 AM","Mar 12, 2026 5:11 PM",ADT201,Closed,2d,Gabriela Novotna,gnovotna@email.cz,Investigator +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,Part 1 Screening,"Feb 27, 2026 8:00 AM",6227547329,CZ100040007,2024391,"Mar 3, 2026 12:47 PM","Mar 4, 2026 6:56 PM",PID,Closed,1d,Gabriela Novotna,gnovotna@email.cz,Investigator +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,Part 1 DB Baseline,"Feb 27, 2026 11:35 AM",6227547332,CZ100040006,2024264,"Mar 3, 2026 12:10 PM","Mar 4, 2026 6:57 PM",PID,Closed,1d,Gabriela Novotna,gnovotna@email.cz,Investigator +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,Part 2 OL Induction Baseline,"Apr 7, 2026 9:45 AM",6227541258,CZ100040005,2083848,"Apr 8, 2026 8:50 PM","Apr 13, 2026 5:16 PM",ADT201,Closed,4d,Erik Herman,erik.herman@seznam.cz,Investigator +CZ10004 - Dr. Erik Herman,CZECH REPUBLIC,Part 2 DB Week 13,"Apr 7, 2026 8:35 AM",6227541264,CZ100040001,2090583,"Apr 13, 2026 6:24 PM","Apr 28, 2026 2:16 PM","VST, ADT201",Closed,14d,Gabriela Novotna,gnovotna@email.cz,Investigator +title,value,,,,,,,,,,,,, +Search,,,,,,,,,,,,,, +Site,"CZ10005 - Dr.Lubos Janu, CZ10008 - Dr.Zdenek Solle, CZ10011 - Dr. Marta Lendlova, CZ10004 - Dr. Erik Herman, CZ10012 - Dr.Ales Urban",,,,,,,,,,,,, +Unresponded Only?,FALSE,,,,,,,,,,,,, +Posted,From - To - ,,,,,,,,,,,,, diff --git a/Covance/_create_tables_equeries.py b/Covance/_create_tables_equeries.py new file mode 100644 index 0000000..64630c9 --- /dev/null +++ b/Covance/_create_tables_equeries.py @@ -0,0 +1,49 @@ +""" +Vytvoří tabulku covance_equeries v databázi studie. +Spusť jednorázově pro inicializaci. +""" + +import mysql.connector +import db_config + +DDL = """ +CREATE TABLE IF NOT EXISTS covance_equeries ( + id INT AUTO_INCREMENT PRIMARY KEY, + import_id INT NOT NULL, + study VARCHAR(20) NOT NULL, + site_code VARCHAR(10), + investigator_name VARCHAR(100), + country VARCHAR(30), + visit VARCHAR(100), + visit_collection_date DATETIME, + accession VARCHAR(20), + subject VARCHAR(20), + equery_id INT, + create_date DATETIME, + response_datetime DATETIME, + issue_type VARCHAR(100), + status VARCHAR(20), + time_before_response VARCHAR(20), + user_name VARCHAR(100), + email VARCHAR(100), + study_role VARCHAR(50), + FOREIGN KEY (import_id) REFERENCES iwrs_import(import_id), + INDEX idx_import (import_id), + INDEX idx_site (site_code), + INDEX idx_subject (subject), + INDEX idx_equery (equery_id), + INDEX idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +""" + +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(DDL) +conn.commit() +cursor.close() +conn.close() +print("Tabulka covance_equeries OK.") diff --git a/Covance/create_report.py b/Covance/create_report.py index 7eb56bc..d1097d5 100644 --- a/Covance/create_report.py +++ b/Covance/create_report.py @@ -1,11 +1,13 @@ """ Covance samples report pro studii 42847922MDD3003. -Čte z MySQL (nejnovější import), generuje Excel s 5 listy: +Čte z MySQL (nejnovější import), generuje Excel s 7 listy: 1. Přehled — agregát per pacient+visit (Received / Not Received / Cancelled) 2. Chybějící — detail Not Received vzorků - 3. Kity — pivot kit inventory: centra × typy kitů - 4. ZDROJ — surová data samples - 5. ZDROJ Kity — surová data kit inventory + 3. Kity — kit inventory: centra × typy kitů s expirací + 4. eQueries — přehled eQuery dotazů (Open červeně) + 5. ZDROJ Vzorky — surová data samples + 6. ZDROJ Kity — surová data kit inventory + 7. ZDROJ eQuery — surová data eQueries """ import os @@ -37,6 +39,7 @@ EVEN_FILL = PatternFill("solid", fgColor="EBF3FB") ODD_FILL = PatternFill("solid", fgColor="FFFFFF") NOTRCV_FILL = PatternFill("solid", fgColor="FCE4D6") CANCELLED_FILL = PatternFill("solid", fgColor="F2F2F2") +OPEN_FILL = PatternFill("solid", fgColor="FFC7CE") CENTER = Alignment(horizontal="center", vertical="center") LEFT = Alignment(horizontal="left", vertical="center") diff --git a/Covance/import_equeries_to_mysql.py b/Covance/import_equeries_to_mysql.py new file mode 100644 index 0000000..25d7ece --- /dev/null +++ b/Covance/import_equeries_to_mysql.py @@ -0,0 +1,182 @@ +""" +Import Covance eQuery CSV do MySQL tabulky covance_equeries. +Strategie: versioning — každý import = nový import_id. +""" + +import os +import glob +import datetime + +import numpy as np +import pandas as pd +import mysql.connector + +import db_config + +STUDY = "42847922MDD3003" +SOURCE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "SourceData") + + +# ── type converters ────────────────────────────────────────────────────────── + +def _py(val): + if isinstance(val, np.generic): + return val.item() + return val + +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 + +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_datetime(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.to_pydatetime() + if isinstance(val, datetime.datetime): + return val + if isinstance(val, datetime.date): + return datetime.datetime(val.year, val.month, val.day) + s = str(val).strip() + if not s or s.lower() in ("nat", "nan", "none", ""): + return None + for fmt in ("%b %d, %Y %I:%M %p", "%b %d, %Y", + "%Y-%m-%d %H:%M:%S", "%Y-%m-%d"): + try: + return datetime.datetime.strptime(s, fmt) + except ValueError: + pass + return None + + +# ── 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_file): + cursor.execute( + "INSERT INTO iwrs_import (study, imported_at, source_file, report_type) VALUES (%s, %s, %s, %s)", + (study, datetime.datetime.now(), source_file, "covance_equeries"), + ) + return cursor.lastrowid + + +# ── parser ─────────────────────────────────────────────────────────────────── + +def find_csv(study): + pattern = os.path.join(SOURCE_DIR, f"Protocol {study} *equery*.csv") + files = glob.glob(pattern) + if not files: + raise FileNotFoundError(f"Nenalezen CSV soubor: {pattern}") + return sorted(files)[-1] + +def parse_site(site_str): + """'CZ10004 - Dr. Erik Herman' → ('CZ10004', 'Dr. Erik Herman')""" + if not site_str: + return None, None + parts = site_str.split(" - ", 1) + code = parts[0].strip() + name = parts[1].strip() if len(parts) > 1 else None + return code, name + +def parse_csv(path): + df = pd.read_csv(path, dtype=str) + # odstraň metadata řádky (eQueryId není číslo) + df = df[pd.to_numeric(df["eQueryId"], errors="coerce").notna()].copy() + + rows = [] + for _, r in df.iterrows(): + site_code, investigator_name = parse_site(to_str(r.get("Site"))) + rows.append({ + "site_code": site_code, + "investigator_name": investigator_name, + "country": to_str(r.get("Country")), + "visit": to_str(r.get("Visit")), + "visit_collection_date": to_datetime(r.get("Visit Collection Date")), + "accession": to_str(r.get("Accession")), + "subject": to_str(r.get("Subject")), + "equery_id": to_int(r.get("eQueryId")), + "create_date": to_datetime(r.get("Create Date")), + "response_datetime": to_datetime(r.get("Response Date Time")), + "issue_type": to_str(r.get("Issue Type")), + "status": to_str(r.get("Status")), + "time_before_response": to_str(r.get("Time Before Response")), + "user_name": to_str(r.get("User Name")), + "email": to_str(r.get("Email")), + "study_role": to_str(r.get("Study Role")), + }) + return rows + + +# ── insert ─────────────────────────────────────────────────────────────────── + +def insert_equeries(cursor, import_id, rows): + sql = """INSERT INTO covance_equeries + (import_id, study, site_code, investigator_name, country, + visit, visit_collection_date, accession, subject, equery_id, + create_date, response_datetime, issue_type, status, + time_before_response, user_name, email, study_role) + VALUES (%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_code"], r["investigator_name"], r["country"], + r["visit"], r["visit_collection_date"], r["accession"], + r["subject"], r["equery_id"], + r["create_date"], r["response_datetime"], + r["issue_type"], r["status"], + r["time_before_response"], r["user_name"], + r["email"], r["study_role"], + )) + + +# ── main ───────────────────────────────────────────────────────────────────── + +def main(): + csv_path = find_csv(STUDY) + print(f"Soubor: {os.path.basename(csv_path)}") + + rows = parse_csv(csv_path) + print(f"Načteno řádků: {len(rows)}") + + conn = get_conn() + cursor = conn.cursor() + + import_id = insert_import(cursor, STUDY, os.path.basename(csv_path)) + print(f"import_id = {import_id}") + + insert_equeries(cursor, import_id, rows) + conn.commit() + cursor.close() + conn.close() + + print(f"Hotovo — {len(rows)} eQuery záznamů importováno.") + + +main()