z230
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"Protocol","Study Population","Country","Site","Principal Investigator","Participant ID","Baseline Stool Frequency","Visit","Visit Date","Endoscopy Completed?","Endoscopy Date","Bowel Preparation Start Date 1","Bowel Preparation End Date 1","Bowel Preparation Start Date 2","Bowel Preparation End Date 2","Central Endoscopy Score","Local Endoscopy Score","PGA Score","Eligible Day (-1)","Day (-1) Excluded Reason(s)","Eligible Day (-2)","Day (-2) Excluded Reason(s)","Eligible Day (-3)","Day (-3) Excluded Reason(s)","Eligible Day (-4)","Day (-4) Excluded Reason(s)","Eligible Day (-5)","Day (-5) Excluded Reason(s)","Eligible Day (-6)","Day (-6) Excluded Reason(s)","Eligible Day (-7)","Day (-7) Excluded Reason(s)","Eligible Day (-8)","Day (-8) Excluded Reason(s)","Eligible Day (-9)","Day (-9) Excluded Reason(s)","Eligible Day (-10)","Day (-10) Excluded Reason(s)","Eligible Day (-1) Stool Count","Eligible Day (-2) Stool Count","Eligible Day (-3) Stool Count","Eligible Day (-4) Stool Count","Eligible Day (-5) Stool Count","Eligible Day (-6) Stool Count","Eligible Day (-7) Stool Count","Eligible Day (-8) Stool Count","Eligible Day (-9) Stool Count","Eligible Day (-10) Stool Count","Stool Frequency Sub-score","Eligible Day (-1) Rectal Bleeding Score","Eligible Day (-2) Rectal Bleeding Score","Eligible Day (-3) Rectal Bleeding Score","Eligible Day (-4) Rectal Bleeding Score","Eligible Day (-5) Rectal Bleeding Score","Eligible Day (-6) Rectal Bleeding Score","Eligible Day (-7) Rectal Bleeding Score","Eligible Day (-8) Rectal Bleeding Score","Eligible Day (-9) Rectal Bleeding Score","Eligible Day (-10) Rectal Bleeding Score","Rectal Bleeding Sub-score","Partial Mayo Score","Modified Mayo Score","Full Mayo Score","Site Action","Last Mayo Score Submission","Week I-12 Clinical Responder","Week I-12 Clinical Remission","Clinical Flare","Loss of Response","Partial Mayo Response Post Loss of Response","Partial Mayo Response for Clinical Non-Responders"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","I-0","19 Feb 2026","Yes","05 Feb 2026","04 Feb 2026","04 Feb 2026","-","-","2","-","3","18 Feb 2026","-","17 Feb 2026","-","16 Feb 2026","-","15 Feb 2026","-","14 Feb 2026","-","13 Feb 2026","-","12 Feb 2026","-","11 Feb 2026","Day Not Applicable for Calculation","10 Feb 2026","Day Not Applicable for Calculation","09 Feb 2026","Day Not Applicable for Calculation","10","8","7","5","7","8","8","-","-","-","3","1","1","1","0","1","1","1","-","-","-","1","7","6","9","-","08 Apr 2026 07:11:25","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","I-2","04 Mar 2026","-","-","-","-","-","-","-","-","3","03 Mar 2026","-","02 Mar 2026","-","01 Mar 2026","-","28 Feb 2026","-","27 Feb 2026","-","26 Feb 2026","-","25 Feb 2026","-","24 Feb 2026","Day Not Applicable for Calculation","23 Feb 2026","Day Not Applicable for Calculation","22 Feb 2026","Day Not Applicable for Calculation","5","4","5","4","5","6","6","-","-","-","2","1","0","1","0","1","0","1","-","-","-","1","6","","","-","28 May 2026 10:04:05","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","I-4","18 Mar 2026","-","-","-","-","-","-","-","-","2","17 Mar 2026","-","16 Mar 2026","-","15 Mar 2026","-","14 Mar 2026","-","13 Mar 2026","-","12 Mar 2026","-","11 Mar 2026","-","10 Mar 2026","Day Not Applicable for Calculation","09 Mar 2026","Day Not Applicable for Calculation","08 Mar 2026","Day Not Applicable for Calculation","5","5","5","4","5","4","5","-","-","-","2","1","0","0","1","1","1","0","-","-","-","1","5","","","-","08 Apr 2026 11:04:49","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","I-8","05 May 2026","-","-","-","-","-","-","-","-","1","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","Day Not Applicable for Calculation","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","3","3","4","4","5","4","4","-","-","-","2","1","1","1","1","1","1","1","-","-","-","1","4","","","-","28 May 2026 14:42:53","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","I-12","13 May 2026","Yes","06 May 2026","05 May 2026","05 May 2026","-","-","1","-","1","12 May 2026","-","11 May 2026","-","10 May 2026","-","09 May 2026","-","08 May 2026","-","07 May 2026","-","06 May 2026","Endoscopy","05 May 2026","Bowel Preparation for Procedure;Day Not Applicable for Calculation","04 May 2026","-","03 May 2026","Day Not Applicable for Calculation","5","4","6","5","5","5","-","-","3","-","2","1","0","1","1","1","1","-","-","1","-","1","4","4","5","-","28 May 2026 14:43:11","Clinical Responder","No","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","1","M-4","10 Jun 2026","-","-","-","-","-","-","-","-","1","09 Jun 2026","-","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","4","5","3","4","5","4","5","-","-","-","2","0","0","0","0","1","0","1","-","-","-","0","3","","","-","10 Jun 2026 07:15:50","N/A","N/A","No","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012002","1","I-0","08 Apr 2026","Yes","18 Mar 2026","17 Mar 2026","18 Mar 2026","-","-","2","-","2","07 Apr 2026","-","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","Missing Diary","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","-","31 Mar 2026","Day Not Applicable for Calculation","30 Mar 2026","Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","3","3","4","-","3","3","4","-","-","-","1","0","0","0","-","0","0","1","-","-","-","0","3","3","5","-","10 Jun 2026 08:42:08","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012002","1","I-2","23 Apr 2026","-","-","-","-","-","-","-","-","2","22 Apr 2026","Missing Diary","21 Apr 2026","-","20 Apr 2026","-","19 Apr 2026","-","18 Apr 2026","-","17 Apr 2026","-","16 Apr 2026","-","15 Apr 2026","Day Not Applicable for Calculation","14 Apr 2026","Day Not Applicable for Calculation","13 Apr 2026","Day Not Applicable for Calculation","-","3","3","6","5","5","4","-","-","-","2","-","0","0","1","1","1","1","-","-","-","1","5","","","-","10 Jun 2026 08:42:33","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012002","1","I-4","06 May 2026","-","-","-","-","-","-","-","-","1","05 May 2026","-","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","Day Not Applicable for Calculation","27 Apr 2026","Day Not Applicable for Calculation","26 Apr 2026","Day Not Applicable for Calculation","6","3","2","3","3","3","3","-","-","-","1","1","0","0","0","1","1","0","-","-","-","0","2","","","-","04 Jun 2026 07:39:06","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012002","1","I-8","04 Jun 2026","-","-","-","-","-","-","-","-","1","03 Jun 2026","-","02 Jun 2026","-","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","Day Not Applicable for Calculation","26 May 2026","Day Not Applicable for Calculation","25 May 2026","Day Not Applicable for Calculation","3","4","3","3","3","3","4","-","-","-","1","0","0","0","0","0","0","1","-","-","-","0","2","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012003","1","I-0","27 May 2026","Yes","13 May 2026","12 May 2026","12 May 2026","-","-","3","-","2","26 May 2026","-","25 May 2026","-","24 May 2026","-","23 May 2026","-","22 May 2026","-","21 May 2026","-","20 May 2026","-","19 May 2026","Day Not Applicable for Calculation","18 May 2026","Day Not Applicable for Calculation","17 May 2026","Day Not Applicable for Calculation","6","9","7","8","9","7","8","-","-","-","3","2","2","2","2","1","1","1","-","-","-","2","7","8","10","-","27 May 2026 07:24:39","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012003","1","I-2","10 Jun 2026","-","-","-","-","-","-","-","-","2","09 Jun 2026","-","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","7","8","8","7","6","8","6","-","-","-","3","2","2","1","2","2","2","1","-","-","-","2","7","","","-","10 Jun 2026 07:30:18","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10003","Leksa Vaclav","CZ100032001","2","I-0","10 Jun 2026","Yes","27 May 2026","26 May 2026","26 May 2026","-","-","2","-","2","09 Jun 2026","-","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","4","4","4","4","5","4","5","-","-","-","1","2","2","2","2","2","2","2","-","-","-","2","5","5","7","-","10 Jun 2026 08:48:09","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","1","I-0","20 Mar 2026","Yes","19 Feb 2026","-","-","-","-","3","-","3","19 Mar 2026","-","18 Mar 2026","-","17 Mar 2026","-","16 Mar 2026","-","15 Mar 2026","-","14 Mar 2026","-","13 Mar 2026","-","12 Mar 2026","Day Not Applicable for Calculation","11 Mar 2026","Day Not Applicable for Calculation","10 Mar 2026","Day Not Applicable for Calculation","7","7","8","8","7","8","5","-","-","-","3","2","1","1","1","1","1","0","-","-","-","1","7","7","10","-","20 Mar 2026 07:02:44","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","1","I-2","08 Apr 2026","-","-","-","-","-","-","-","-","2","07 Apr 2026","Medication For Diarrhea","06 Apr 2026","Medication For Diarrhea","05 Apr 2026","Medication For Diarrhea","04 Apr 2026","Medication For Diarrhea","03 Apr 2026","Medication For Diarrhea","02 Apr 2026","Medication For Diarrhea","01 Apr 2026","Medication For Diarrhea","31 Mar 2026","Medication For Diarrhea;Day Not Applicable for Calculation","30 Mar 2026","Medication For Diarrhea;Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","-","-","-","-","-","-","-","-","-","-","Non-Evaluable","-","-","-","-","-","-","-","-","-","-","Non-Evaluable","Non-Evaluable","Non-Evaluable","Non-Evaluable","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","1","I-4","15 Apr 2026","-","-","-","-","-","-","-","-","3","14 Apr 2026","-","13 Apr 2026","-","12 Apr 2026","-","11 Apr 2026","-","10 Apr 2026","-","09 Apr 2026","-","08 Apr 2026","-","07 Apr 2026","Medication For Diarrhea;Day Not Applicable for Calculation","06 Apr 2026","Medication For Diarrhea;Day Not Applicable for Calculation","05 Apr 2026","Medication For Diarrhea;Day Not Applicable for Calculation","9","22","20","19","17","18","18","-","-","-","3","1","3","2","2","2","2","2","-","-","-","2","8","","","-","04 May 2026 22:06:03","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","1","I-8","18 May 2026","-","-","-","-","-","-","-","-","2","17 May 2026","-","16 May 2026","-","15 May 2026","-","14 May 2026","-","13 May 2026","-","12 May 2026","-","11 May 2026","-","10 May 2026","Day Not Applicable for Calculation","09 May 2026","Day Not Applicable for Calculation","08 May 2026","Day Not Applicable for Calculation","7","5","9","7","7","8","8","-","-","-","3","1","1","1","1","1","1","1","-","-","-","1","6","","","-","04 Jun 2026 21:46:30","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","1","I-12","08 Jun 2026","Yes","28 May 2026","-","-","-","-","3","-","3","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","-","01 Jun 2026","Missing Diary","31 May 2026","Day Not Applicable for Calculation","30 May 2026","Day Not Applicable for Calculation","29 May 2026","Day Not Applicable for Calculation","6","5","5","5","7","6","-","-","-","-","3","1","1","0","0","1","0","-","-","-","-","1","7","7","10","-","11 Jun 2026 22:12:05","Clinical Nonresponder","No","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062002","1","I-0","26 May 2026","Yes","14 May 2026","13 May 2026","13 May 2026","-","-","2","-","2","25 May 2026","-","24 May 2026","-","23 May 2026","-","22 May 2026","-","21 May 2026","-","20 May 2026","-","19 May 2026","-","18 May 2026","Day Not Applicable for Calculation","17 May 2026","Day Not Applicable for Calculation","16 May 2026","Day Not Applicable for Calculation","8","8","6","7","7","6","7","-","-","-","3","2","2","2","2","2","2","2","-","-","-","2","7","7","9","-","29 May 2026 15:45:00","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062002","1","I-2","09 Jun 2026","-","-","-","-","-","-","-","-","2","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","-","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","30 May 2026","Day Not Applicable for Calculation","7","8","7","7","7","5","7","-","-","-","3","2","1","1","1","2","2","2","-","-","-","2","7","","","-","11 Jun 2026 22:12:40","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10009","Jiri Pumprla","CZ100092001","1","I-0","05 May 2026","Yes","24 Apr 2026","23 Apr 2026","23 Apr 2026","-","-","2","-","2","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","Day Not Applicable for Calculation","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","5","5","5","5","5","5","5","-","-","-","2","1","1","1","1","1","1","1","-","-","-","1","5","5","7","-","05 May 2026 11:19:40","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10009","Jiri Pumprla","CZ100092001","1","I-2","19 May 2026","-","-","-","-","-","-","-","-","1","18 May 2026","-","17 May 2026","-","16 May 2026","-","15 May 2026","-","14 May 2026","-","13 May 2026","-","12 May 2026","-","11 May 2026","Day Not Applicable for Calculation","10 May 2026","Day Not Applicable for Calculation","09 May 2026","Day Not Applicable for Calculation","5","4","5","5","5","4","6","-","-","-","2","1","1","1","1","1","1","1","-","-","-","1","4","","","-","19 May 2026 10:38:25","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10009","Jiri Pumprla","CZ100092001","1","I-4","04 Jun 2026","-","-","-","-","-","-","-","-","1","03 Jun 2026","-","02 Jun 2026","-","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","Day Not Applicable for Calculation","26 May 2026","Day Not Applicable for Calculation","25 May 2026","Day Not Applicable for Calculation","2","3","2","3","3","2","3","-","-","-","1","0","0","0","0","0","0","0","-","-","-","0","2","","","-","04 Jun 2026 09:24:54","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10012","Stefan Konecny","CZ100122001","5","I-0","07 Apr 2026","Yes","24 Mar 2026","22 Mar 2026","22 Mar 2026","-","-","2","-","2","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","-","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","-","31 Mar 2026","-","30 Mar 2026","Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","28 Mar 2026","Day Not Applicable for Calculation","8","11","5","9","11","10","13","-","-","-","3","1","2","2","2","2","2","2","-","-","-","2","7","7","9","-","04 May 2026 08:44:52","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10012","Stefan Konecny","CZ100122001","5","I-2","22 Apr 2026","-","-","-","-","-","-","-","-","2","21 Apr 2026","-","20 Apr 2026","-","19 Apr 2026","-","18 Apr 2026","-","17 Apr 2026","-","16 Apr 2026","-","15 Apr 2026","-","14 Apr 2026","Day Not Applicable for Calculation","13 Apr 2026","Day Not Applicable for Calculation","12 Apr 2026","Day Not Applicable for Calculation","7","5","6","6","7","8","2","-","-","-","1","1","0","1","1","1","2","0","-","-","-","1","4","","","-","04 May 2026 08:45:07","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10012","Stefan Konecny","CZ100122001","5","I-4","07 May 2026","-","-","-","-","-","-","-","-","1","06 May 2026","-","05 May 2026","-","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","Day Not Applicable for Calculation","28 Apr 2026","Day Not Applicable for Calculation","27 Apr 2026","Day Not Applicable for Calculation","8","7","7","8","4","11","7","-","-","-","1","2","1","1","1","0","1","1","-","-","-","1","3","","","-","01 Jun 2026 00:57:35","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10012","Stefan Konecny","CZ100122001","5","I-8","03 Jun 2026","-","-","-","-","-","-","-","-","2","02 Jun 2026","-","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","-","26 May 2026","Day Not Applicable for Calculation","25 May 2026","Day Not Applicable for Calculation","24 May 2026","Day Not Applicable for Calculation","5","9","7","5","5","9","7","-","-","-","1","1","1","1","0","3","0","1","-","-","-","1","4","","","-","03 Jun 2026 17:47:25","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132001","1","I-0","24 Mar 2026","Yes","12 Mar 2026","11 Mar 2026","11 Mar 2026","-","-","2","-","2","23 Mar 2026","-","22 Mar 2026","-","21 Mar 2026","-","20 Mar 2026","-","19 Mar 2026","-","18 Mar 2026","-","17 Mar 2026","-","16 Mar 2026","Day Not Applicable for Calculation","15 Mar 2026","Day Not Applicable for Calculation","14 Mar 2026","Day Not Applicable for Calculation","8","6","5","7","6","7","6","-","-","-","3","1","1","1","0","1","1","1","-","-","-","1","6","6","8","-","05 Apr 2026 22:41:27","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132001","1","I-2","08 Apr 2026","-","-","-","-","-","-","-","-","2","07 Apr 2026","-","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","-","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","-","31 Mar 2026","Day Not Applicable for Calculation","30 Mar 2026","Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","5","2","3","6","5","5","5","-","-","-","2","0","0","0","0","1","1","0","-","-","-","0","4","","","-","28 May 2026 23:19:03","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132001","1","I-4","21 Apr 2026","-","-","-","-","-","-","-","-","0","20 Apr 2026","-","19 Apr 2026","-","18 Apr 2026","-","17 Apr 2026","-","16 Apr 2026","-","15 Apr 2026","-","14 Apr 2026","-","13 Apr 2026","Day Not Applicable for Calculation","12 Apr 2026","Day Not Applicable for Calculation","11 Apr 2026","Day Not Applicable for Calculation","4","3","4","3","3","4","4","-","-","-","2","0","0","0","0","0","0","0","-","-","-","0","2","","","-","27 May 2026 12:54:41","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132002","1","I-0","12 May 2026","Yes","21 Apr 2026","20 Apr 2026","21 Apr 2026","-","-","2","-","2","11 May 2026","-","10 May 2026","-","09 May 2026","-","08 May 2026","-","07 May 2026","-","06 May 2026","-","05 May 2026","Missing Diary","04 May 2026","Day Not Applicable for Calculation","03 May 2026","Day Not Applicable for Calculation","02 May 2026","Day Not Applicable for Calculation","2","1","1","1","1","2","-","-","-","-","0","0","0","0","0","0","0","-","-","-","-","0","2","2","4","-","28 May 2026 23:19:30","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132002","1","I-2","26 May 2026","-","-","-","-","-","-","-","-","1","25 May 2026","-","24 May 2026","Missing Diary","23 May 2026","-","22 May 2026","-","21 May 2026","-","20 May 2026","-","19 May 2026","-","18 May 2026","Missing Diary;Day Not Applicable for Calculation","17 May 2026","Day Not Applicable for Calculation","16 May 2026","Day Not Applicable for Calculation","1","-","1","2","1","2","2","-","-","-","1","0","-","0","0","0","0","0","-","-","-","0","2","","","-","28 May 2026 23:19:51","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132002","1","I-4","10 Jun 2026","-","-","-","-","-","-","-","-","2","09 Jun 2026","-","08 Jun 2026","Missing Diary","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","Missing Diary;Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","4","-","1","1","2","2","1","-","-","-","1","0","-","0","0","0","0","0","-","-","-","0","3","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132003","1","I-0","02 Jun 2026","Yes","25 May 2026","24 May 2026","24 May 2026","-","-","2","-","2","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","-","26 May 2026","-","25 May 2026","Endoscopy;Missing Diary;Day Not Applicable for Calculation","24 May 2026","Bowel Preparation for Procedure;Missing Diary;Day Not Applicable for Calculation","23 May 2026","Missing Diary;Day Not Applicable for Calculation","8","8","11","10","10","11","6","-","-","-","3","2","2","1","2","1","2","2","-","-","-","2","7","7","9","-","02 Jun 2026 08:17:40","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10013","David Stepek","CZ100132003","1","I-2","10 Jun 2026","-","-","-","-","-","-","-","-","2","09 Jun 2026","-","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","-","02 Jun 2026","Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","31 May 2026","Day Not Applicable for Calculation","9","2","1","4","2","4","2","-","-","-","1","1","1","0","1","1","1","0","-","-","-","1","4","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10016","Robert Mudr","CZ100162001","1","I-0","28 May 2026","Yes","19 May 2026","18 May 2026","19 May 2026","-","-","3","-","3","27 May 2026","-","26 May 2026","-","25 May 2026","-","24 May 2026","-","23 May 2026","-","22 May 2026","-","21 May 2026","-","20 May 2026","Day Not Applicable for Calculation","19 May 2026","Endoscopy;Bowel Preparation for Procedure;Day Not Applicable for Calculation","18 May 2026","Bowel Preparation for Procedure;Day Not Applicable for Calculation","14","15","15","15","15","15","15","-","-","-","3","2","3","3","2","2","3","3","-","-","-","3","9","9","12","-","28 May 2026 10:22:48","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10016","Robert Mudr","CZ100162001","1","I-2","11 Jun 2026","-","-","-","-","-","-","-","-","3","10 Jun 2026","-","09 Jun 2026","-","08 Jun 2026","-","07 Jun 2026","-","06 Jun 2026","-","05 Jun 2026","-","04 Jun 2026","-","03 Jun 2026","Day Not Applicable for Calculation","02 Jun 2026","Day Not Applicable for Calculation","01 Jun 2026","Day Not Applicable for Calculation","10","9","9","8","13","9","8","-","-","-","3","2","1","1","1","2","1","1","-","-","-","1","7","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adolescent","Czech Republic","DD5-CZ10020","Lucie Gonsorcikova","CZ100201001","1","Unscheduled 1","04 May 2026","Yes","20 Apr 2026","12 Apr 2026","15 Apr 2026","-","-","2","-","3","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","-","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","24 Apr 2026","Day Not Applicable for Calculation","5","6","6","7","6","3","3","-","-","-","2","0","0","0","0","0","0","0","-","-","-","0","5","4","7","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adolescent","Czech Republic","DD5-CZ10020","Lucie Gonsorcikova","CZ100201001","1","I-0","18 May 2026","Yes","01 May 2026","01 May 2026","01 May 2026","-","-","2","-","3","17 May 2026","-","16 May 2026","-","15 May 2026","-","14 May 2026","-","13 May 2026","-","12 May 2026","-","11 May 2026","-","10 May 2026","Day Not Applicable for Calculation","09 May 2026","Day Not Applicable for Calculation","08 May 2026","Day Not Applicable for Calculation","6","6","6","6","6","6","6","-","-","-","3","0","0","0","0","0","0","0","-","-","-","0","6","5","8","-","18 May 2026 08:39:27","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adolescent","Czech Republic","DD5-CZ10020","Lucie Gonsorcikova","CZ100201001","1","I-2","01 Jun 2026","-","-","-","-","-","-","-","-","3","31 May 2026","-","30 May 2026","Missing Diary","29 May 2026","Missing Diary","28 May 2026","Missing Diary","27 May 2026","-","26 May 2026","-","25 May 2026","-","24 May 2026","Day Not Applicable for Calculation","23 May 2026","Day Not Applicable for Calculation","22 May 2026","Day Not Applicable for Calculation","6","-","-","-","6","6","6","-","-","-","3","0","-","-","-","0","0","0","-","-","-","0","6","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10021","Martin Bortlik","CZ100212001","1","I-0","07 Apr 2026","Yes","16 Mar 2026","15 Mar 2026","16 Mar 2026","-","-","3","-","3","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","-","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","-","31 Mar 2026","-","30 Mar 2026","Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","28 Mar 2026","Day Not Applicable for Calculation","11","11","10","11","11","10","9","-","-","-","3","2","2","2","2","2","2","2","-","-","-","2","8","8","11","-","20 Apr 2026 09:27:58","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10021","Martin Bortlik","CZ100212001","1","I-2","20 Apr 2026","-","-","-","-","-","-","-","-","3","19 Apr 2026","-","18 Apr 2026","-","17 Apr 2026","-","16 Apr 2026","-","15 Apr 2026","-","14 Apr 2026","-","13 Apr 2026","-","12 Apr 2026","Day Not Applicable for Calculation","11 Apr 2026","Day Not Applicable for Calculation","10 Apr 2026","Day Not Applicable for Calculation","8","7","9","8","8","7","8","-","-","-","3","2","2","1","1","1","2","1","-","-","-","1","7","","","-","20 Apr 2026 09:29:01","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10021","Martin Bortlik","CZ100212001","1","I-4","05 May 2026","-","-","-","-","-","-","-","-","1","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","Day Not Applicable for Calculation","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","6","6","6","6","7","7","6","-","-","-","3","0","0","1","1","1","1","1","-","-","-","1","5","","","-","-","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10021","Martin Bortlik","CZ100212001","1","I-8","02 Jun 2026","-","-","-","-","-","-","-","-","1","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","-","26 May 2026","-","25 May 2026","Day Not Applicable for Calculation","24 May 2026","Day Not Applicable for Calculation","23 May 2026","Day Not Applicable for Calculation","3","4","4","4","5","5","5","-","-","-","2","0","0","0","0","0","1","1","-","-","-","0","3","","","-","02 Jun 2026 14:44:34","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222002","1","I-0","19 Feb 2026","Yes","11 Feb 2026","10 Feb 2026","11 Feb 2026","-","-","2","-","2","18 Feb 2026","-","17 Feb 2026","-","16 Feb 2026","-","15 Feb 2026","-","14 Feb 2026","-","13 Feb 2026","-","12 Feb 2026","-","11 Feb 2026","Endoscopy;Bowel Preparation for Procedure;Day Not Applicable for Calculation","10 Feb 2026","Bowel Preparation for Procedure;Day Not Applicable for Calculation","09 Feb 2026","Day Not Applicable for Calculation","3","2","2","3","4","3","2","-","-","-","1","1","1","0","0","0","2","2","-","-","-","1","4","4","6","-","19 Feb 2026 15:24:43","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","1","I-0","09 Mar 2026","Yes","11 Feb 2026","10 Feb 2026","11 Feb 2026","-","-","2","-","2","08 Mar 2026","-","07 Mar 2026","-","06 Mar 2026","-","05 Mar 2026","-","04 Mar 2026","-","03 Mar 2026","Missing Diary","02 Mar 2026","Missing Diary","01 Mar 2026","Missing Diary;Day Not Applicable for Calculation","28 Feb 2026","Missing Diary;Day Not Applicable for Calculation","27 Feb 2026","Missing Diary;Day Not Applicable for Calculation","7","7","6","6","7","-","-","-","-","-","3","2","2","2","2","2","-","-","-","-","-","2","7","7","9","-","22 Mar 2026 18:34:58","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","1","I-2","27 Mar 2026","-","-","-","-","-","-","-","-","2","26 Mar 2026","-","25 Mar 2026","-","24 Mar 2026","-","23 Mar 2026","-","22 Mar 2026","-","21 Mar 2026","-","20 Mar 2026","-","19 Mar 2026","Day Not Applicable for Calculation","18 Mar 2026","Day Not Applicable for Calculation","17 Mar 2026","Day Not Applicable for Calculation","7","3","3","3","5","5","5","-","-","-","2","0","0","1","1","1","1","2","-","-","-","1","5","","","-","08 Apr 2026 07:36:56","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","1","I-4","08 Apr 2026","-","-","-","-","-","-","-","-","2","07 Apr 2026","-","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","-","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","-","31 Mar 2026","Day Not Applicable for Calculation","30 Mar 2026","Day Not Applicable for Calculation","29 Mar 2026","Day Not Applicable for Calculation","3","3","4","4","5","4","3","-","-","-","2","1","0","0","2","1","1","2","-","-","-","1","5","","","-","08 Apr 2026 07:59:35","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","1","I-8","04 May 2026","-","-","-","-","-","-","-","-","2","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","-","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","24 Apr 2026","Missing Diary;Day Not Applicable for Calculation","3","5","3","3","3","2","3","-","-","-","1","0","0","0","0","0","0","0","-","-","-","0","3","","","-","04 May 2026 07:52:47","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","1","I-12","01 Jun 2026","Yes","20 May 2026","19 May 2026","20 May 2026","-","-","3","-","2","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","-","26 May 2026","-","25 May 2026","-","24 May 2026","Day Not Applicable for Calculation","23 May 2026","Day Not Applicable for Calculation","22 May 2026","Day Not Applicable for Calculation","4","4","6","3","3","3","3","-","-","-","2","1","1","2","1","1","1","2","-","-","-","1","5","6","8","-","01 Jun 2026 14:25:57","Clinical Nonresponder","No","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","1","I-0","09 Apr 2026","Yes","08 Apr 2026","31 Mar 2026","01 Apr 2026","-","-","2","-","2","08 Apr 2026","Endoscopy","07 Apr 2026","-","06 Apr 2026","-","05 Apr 2026","-","04 Apr 2026","-","03 Apr 2026","-","02 Apr 2026","-","01 Apr 2026","Bowel Preparation for Procedure;Day Not Applicable for Calculation","31 Mar 2026","Bowel Preparation for Procedure;Day Not Applicable for Calculation","30 Mar 2026","-","-","3","3","4","3","4","3","-","-","3","1","-","2","2","2","2","2","2","-","-","2","2","5","5","7","-","29 May 2026 11:07:08","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","1","I-2","22 Apr 2026","-","-","-","-","-","-","-","-","2","21 Apr 2026","-","20 Apr 2026","-","19 Apr 2026","-","18 Apr 2026","-","17 Apr 2026","-","16 Apr 2026","-","15 Apr 2026","-","14 Apr 2026","Day Not Applicable for Calculation","13 Apr 2026","Day Not Applicable for Calculation","12 Apr 2026","Day Not Applicable for Calculation","3","3","5","3","2","3","2","-","-","-","1","1","2","2","1","1","1","2","-","-","-","1","4","","","-","05 May 2026 07:29:35","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","1","I-4","05 May 2026","-","-","-","-","-","-","-","-","2","04 May 2026","-","03 May 2026","-","02 May 2026","-","01 May 2026","-","30 Apr 2026","-","29 Apr 2026","-","28 Apr 2026","-","27 Apr 2026","Day Not Applicable for Calculation","26 Apr 2026","Day Not Applicable for Calculation","25 Apr 2026","Day Not Applicable for Calculation","4","2","2","2","2","2","2","-","-","-","1","1","1","1","1","2","1","1","-","-","-","1","4","","","-","05 May 2026 07:28:55","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
"77242113UCO3001","Adult","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","1","I-8","02 Jun 2026","-","-","-","-","-","-","-","-","2","01 Jun 2026","-","31 May 2026","-","30 May 2026","-","29 May 2026","-","28 May 2026","-","27 May 2026","-","26 May 2026","-","25 May 2026","Day Not Applicable for Calculation","24 May 2026","Day Not Applicable for Calculation","23 May 2026","Day Not Applicable for Calculation","2","2","2","2","2","4","10","-","-","-","1","2","1","2","1","2","2","2","-","-","-","2","5","","","-","02 Jun 2026 08:18:08","N/A","N/A","N/A","N/A","N/A","N/A"
|
||||
|
@@ -0,0 +1,219 @@
|
||||
"Protocol","Country","Site","PI Name","Subject ID","Age at Informed Consent","Baseline Stool Count","Confirm Baseline Stool Count","Data Correction ID","Creation Date UTC","Status","Description","Date of Last Action UTC","Total Open Period","Total Open Time (Days)","Current Status Time (Days)","Type","Next Action Required","Category","Query History","Reason for Change","Resolution"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012001","48","1","","SW00703544","13-May-2026","Submitted","Please change answer to clinical remision from no to YES (week 12). Entry erros ","20-May-2026","15-21 Days","21","16","Query Active ","Site","New","(1) 20 May 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification Request.
|
||||
|
||||
For us to process your request, please let us know the name of the form (with date) with question.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
","Entry Error",""
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10001","Matej Falc","CZ100012002","79","1","","SW00696586","09-Apr-2026","ReadyForQC","Please correct date of endoscopy to date: 18 March 2026 (from 25 March 2026)","15-Apr-2026","Over 28 Days","43","40","Query Active ","Site","Site-Entered Data","","Entry Error","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In Mayo Subscore (1) dated 08 Apr 2026 for I-0 visit, CLARIO to make the following changes:
|
||||
- What was the date of endoscopy? (ENDODT1D): from 25 Mar 2026 to 18 Mar 2026
|
||||
- Data Flag (QSDFLG1B): from blank to check
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10006","Michal Konecny","CZ100062001","19","1","","SW00704536","19-May-2026","ReadyForQC","Please change the endoscopy date to 19-FEB-2026. 06-MAR-2026 was entered in error. ","26-May-2026","15-21 Days","18","13","Query Active ","Site","Site-Entered Data","","Entry Error","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In Mayo Subscore (1) dated 20 Mar 2026 for I-0 visit, CLARIO to make the following changes:
|
||||
-What was the date of endoscopy? (ENDODT1D): from 06 Mar 2026 to 19 Feb 2026
|
||||
- Data Flag (QSDFLG1B): from blank to check
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10012","Stefan Konecny","CZ100122001","22","5","Yes, I confirm this is the correct stool count.","SW00706684","01-Jun-2026","Submitted","The right endoscopy date is 23MAR2026, please change the date","15-Jun-2026","8-14 Days","9","","","Clario DM","New","(1) 05 Jun 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification.
|
||||
|
||||
Please confirm that if you are requesting following.
|
||||
|
||||
Mayo Subscore (1) dated 07 Apr 2026 for I-0
|
||||
What was the date of endoscopy? (ENDODT1D): from 24 Mar 2026 to 23 Mar 2026
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team.
|
||||
|
||||
|
||||
(2) 15 Jun 2026 hosova.kristyna@fnbrno.cz (Site User): The endoscopy was performed 23MAR2026
|
||||
|
||||
","Entry Error",""
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10013","David Stepek","CZ100132002","29","1","","SW00705646","26-May-2026","ReadyForQC","Correct visit date I-O is 12-May-2026. All questionaries were filled on paper and entered in tablet later.
|
||||
Log-in issue. ","09-Jun-2026","8-14 Days","13","3","","Clario DM","Visit Data","(1) 01 Jun 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification.
|
||||
|
||||
Please provide the timestamps for each of the assessments if you used paper forms and transcribed into the device.
|
||||
If unknown, ERT will use a dummy timestamp.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team.
|
||||
|
||||
(2) 01 Jun 2026 dstepek@vnbrno.cz (Site User): time is unknown
|
||||
|
||||
","Changed Information","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In the following forms for I-0, CLARIO to make the following changes:
|
||||
-Report Date: from 26May 2026 to 12 May 2026
|
||||
-Report Start Date and time: from 26 May 2026 to 12 May 2026 23:59:59
|
||||
-Event End Date: from 26 May 2026 08:27:57 to 12 May 2026 23:59:59
|
||||
|
||||
+Tablet Training Module (1)
|
||||
+Participant Start Instructions (1)
|
||||
+IBDQ (1)
|
||||
+PROMIS Fatigue – Short Form 7a (1)
|
||||
+BASDAI (1)
|
||||
+Participant End Instructions (1)
|
||||
+Visit End (122)
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10013","David Stepek","CZ100132003","49","1","","SW00708623","10-Jun-2026","Cancelled","Correct date of I-2 is 26.5.2026. all questionaries were entered on paper at 07,45 and transmited later. ","10-Jun-2026","1 Day","1","","","","New","","yes, subject mishmasch",""
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10013","David Stepek","CZ100132003","49","1","","SW00706581","29-May-2026","Completed","baseline stool count reported by subject is 0, please change to 1 as per CRA request (subject has 1 stool in 2-3 days if in remission)","10-Jun-2026","4-7 Days","7","","","","Demographic","","Changed Information","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In System Variables form, CLARIO to make the following changes:
|
||||
- Baseline Stool Count (PT.Custom4): from 0 to 1
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10016","Robert Mudr","CZ100162001","48","1","","SW00705916","27-May-2026","Completed","As per ATS investigation (ATS26040111), please remove the below form which was entered as a duplicate
|
||||
|
||||
- MAYO Diary (5) 24 Apr 2026","10-Jun-2026","8-14 Days","9","","","","Technical Revision","","Technical Revision - Other","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: CLARIO to delete MAYO Diary (5) dated 24 Apr 2026
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10020","Lucie Gonsorcikova","CZ100201001","15","1","","SW00701729","06-May-2026","Completed","Dears, please delete data from visit I-0 (reported as 4th of May 2026) as this visit had to be postponed - see the previous DCR of this patient and change data request that was corrected. Patient has left the site before it was resolved and and new date of I-0 was planned. Patient continues to fill in his diary and patient is coming to I=0 visit within allowed window. We need the system and tablet to be ready to run new Mayo Score Report with updated and recent data (e.g. reflect new I-0 visit date, new eligible days -1 to -7.).
|
||||
thank you, Jiri Skopek","19-May-2026","8-14 Days","8","","","","Visit Data","(1) 11 May 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification.
|
||||
|
||||
Please note that the delete forms are allowed if the reason is one of the following.
|
||||
If not, forms will move to unscheduled visit.
|
||||
|
||||
Data collected by the wrong patient.
|
||||
Data collected by someone other than the patient.
|
||||
Data collected prior to informed consent, or after withdrawal from the study.
|
||||
Duplicate data erroneously entered at an Unscheduled visit via paper transcription.
|
||||
Data collected that is not expected per protocol.
|
||||
|
||||
Also, I-0 visit is still ongoing. Please close the visit.
|
||||
Once the visit was closed, we will process accoridngly.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
(2) 11 May 2026 jskopek (Site User): Dears,
|
||||
I do not see any option that is adequate -from the list. Data are not needed to be deleted fully, they reflect the situation at May4th. Please mark it as unscheduled visit - as exactly that is the case. We need the system to be ready for I-0 visit planned for next week.
|
||||
I will close the visit tomorrow - do you mean in tablet/ipad?
|
||||
Thank you very much for your help! Jiri
|
||||
|
||||
(3) 12 May 2026 venkata.ramana (Clario): Thank you for your response.
|
||||
Please note that the visit I-0 was still ongoing but not closed yet.
|
||||
So please close the visit.
|
||||
Kind Regards, Clario Data Coordination Team.
|
||||
|
||||
(4) 12 May 2026 jskopek (Site User): If I try to close the I-O visit in TABLET, it asks me if patient fulfils eligibility criteria to proceed to next visit based on these old data – if I answer NO, it asks me to DEACTIVATE patient. I do not want to DEACTIVATE patient – can you help WHERE and HOW to close this visit for you to change it to UNSCHEDULED and not to de-activate patient?
|
||||
Thank you Jiri
|
||||
|
||||
|
||||
","Other-delete visit I-0","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In the following forms dated 04 May 2026, CLARIO to make the following changes:
|
||||
-Event ID: from I-0 to Unscheduled Visit 1
|
||||
-Event At Entry: from I-0 to Unscheduled Visit 1
|
||||
|
||||
+Visit Start (49)
|
||||
+ePRO Availability (1)
|
||||
+Mayo Subscore (1)
|
||||
+PGA (1)
|
||||
|
||||
Part 2: CLARIO to delete the following forms dated 04 May 2026 for I-0 visit.
|
||||
|
||||
+C-SSRS Since Last Visit (1)
|
||||
+C-SSRS Since Last Visit Findings Report (1)
|
||||
|
||||
Part 3: CLARIO to manually enter Visit End form for Unscheduled visit 1 with the following information:
|
||||
-Protocol: 77242113UCO3001
|
||||
-Report Date: 04 May 2026
|
||||
-Report Start Date and Time: 04 May 2026 23:59:59
|
||||
-Event ID: Unscheduled Visit 1
|
||||
-Event End Date: 04 May 2026 23:59:59
|
||||
-Visit Status: Incomplete
|
||||
-Phase At Entry: Screening
|
||||
-Phase At Entry Timestamp: 13 Apr 2026 12:32:20
|
||||
-Event At Entry: Unscheduled visit 1
|
||||
-Event Start Date: 04 May 2026 23:59:59
|
||||
-Event Time Zone Offset in Milliseconds: 7200000
|
||||
-Session Repeat Number (SESREP1N): 0
|
||||
-Session Instance Id (SESINST1S): 3f1214f0-4788-11f1-a0cf-bb403212adce
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10020","Lucie Gonsorcikova","CZ100201001","15","1","","SW00701226","04-May-2026","Completed","Dears, we would like ask you to change the information I read on assignment form given by patient on April 13, 2026 (Visit 1), Baseline Stool Count (PT.Custom4) as 3 that should be reported as 1.
|
||||
Patient has entered wrong number as he did not understood it should be number of stools when illness is in remission or absent. He is a child and did not reflected this question correctly. Therefore, please change Baseline Stool Count = 1.
|
||||
Thank you, Jiri Skopek ","04-May-2026","1 Day","1","","","","Demographic","","Changed Information","(Clario instructions)
|
||||
|
||||
1. Please make below changes in the assignment form:
|
||||
|
||||
Baseline Stool Count (PT. Custom4): 03 to 01."
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10021","Martin Bortlik","CZ100212001","61","1","","SW00699492","23-Apr-2026","ReadyForQC","Please correct the date of endoscopy done during screening visit of patient CZ100212001 to correct date 16-MAR-2026.","29-Apr-2026","Over 28 Days","34","30","Query Active ","Site","Site-Entered Data","","Changed Information","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In the Mayo Subscore (1) dated 07 Apr 2026 for I-0 visit, CLARIO to make the following changes:
|
||||
-What was the date of endoscopy? (ENDODT1D): from 24 Mar 2026 to 16 Mar 2026
|
||||
- Data Flag (QSDFLG1B): from blank to check
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","39","1","","SW00703322","12-May-2026","Completed","As per ATS investigation (ATS26040111), please remove the below form that's been entered as a duplicate
|
||||
|
||||
- MAYO Diary (16) - 18 Mar 2026
|
||||
","20-May-2026","4-7 Days","6","","","","Technical Revision","","Technical Revision - Other","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: CLARIO to delete the MAYO Diary (16) dated 18 Mar 2026.
|
||||
"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222003","39","1","","SW00689748","09-Mar-2026","Completed","Dear all,
|
||||
|
||||
Patient CZ 100222003 was randomized on 9 Mar 2026. Kindly correct the colonoscopy date to 11 Feb 2025.
|
||||
|
||||
The date was initially entered as 21 Feb 2025 because the earlier date could not be entered in the system. The patient was rescreened.","02-Apr-2026","15-21 Days","17","","","","Site-Entered Data","(1) 13 Mar 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification.
|
||||
|
||||
Could you please conform that if you are requesting following?
|
||||
|
||||
Mayo Subscore (1) dated 09 Mar 2026 for I-0 visit
|
||||
-What was the date of endoscopy? (ENDODT1D): from 23 Feb 2026 to 11 Feb 2025
|
||||
|
||||
Could you please confirm the year? This subject was assigned on 02 Mar 2026, you are providing that correct date is 11 Feb 2025 which a year ago.
|
||||
If you are not requesting above, please provide us the name of the form with question.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
|
||||
(2) 13 Mar 2026 katerina.havlikova@clinoxus.com (Site User): confirm date of colonoscopy 11Feb2026
|
||||
|
||||
(3) 21 Mar 2026 msullivan (Clario): Dear Site,
|
||||
|
||||
The requested changes to the Mayo data have been updated. Please navigate to the Mayo Score Report and resubmit the form for visit to log the updated Mayo Score form. Once done, please respond to this query confirming that the Mayo Score has been resubmitted.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
(4) 24 Mar 2026 jana.pomahacova@clinoxus.com (Site User): Thank you and sent
|
||||
|
||||
","New Information","CLARIO RESOLUTION:
|
||||
|
||||
Part 1: In the Mayo Subscore (1) dated 09 Mar 2026 for I-0 visit, CLARIO to make the following changes:
|
||||
-What was the date of endoscopy? (ENDODT1D): from 23 Feb 2026 to 11 Feb 2025
|
||||
-Data Flag (QSDFLG1B): from blank to check"
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","33","1","","SW00705372","22-May-2026","Submitted","Dear all, please change Colonoscopz date from 8April2026 to date 01Apr2026 Thank you in advance","12-Jun-2026","8-14 Days","14","","Query Active ","Site","New","(1) 29 May 2026 msullivan (Clario): Please confirm your request
|
||||
|
||||
Dear Site. Thank you for submitting this Data Clarification.
|
||||
|
||||
Please provide us the name of the form for this request.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
(2) 02 Jun 2026 katerina.havlikova@clinoxus.com (Site User): Dear all, please change Colonoscopy for Week I-12 date from 8April2026 to date 01Apr2026 Thank you in advance
|
||||
|
||||
(3) 12 Jun 2026 msullivan (Clario): Dear Site,
|
||||
Please note that there is no I-12 visit in StudyWorks.
|
||||
If you completed visit and stored, please submit all stored reports.
|
||||
Until we see the data in StudyWorks, we are unable to confirm your request.
|
||||
Also, please provide us the name of the form for this request.
|
||||
|
||||
Thank you. ERT/CLARIO Data Coordination Team
|
||||
|
||||
","Changed Information",""
|
||||
"77242113UCO3001","Czech Republic","DD5-CZ10022","Petr Hrabak","CZ100222005","33","1","","SW00702538","08-May-2026","Completed","This TRR is to document the correction to the Mayo Subscore (1) form, where the following variables were populated with NULL values, due to a known core defect:
|
||||
Event At Entry, Event Start Date, Event Time Zone Offset in Milliseconds.","12-May-2026","2-3 Days","2","","","","Technical Revision","","Technical Revision - Other","Please make the below changes in Mayo Subscore (1) dated 22 Apr 2026:
|
||||
|
||||
-Event At Entry: I-0
|
||||
-Event Start Date: 09 Apr 2026 08:09:19
|
||||
-Event Time Zone Offset in Milliseconds: 7200000"
|
||||
|
+1420
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
"Protocol","Country","Site ID","PI_NAME","Subject Number","Age","Data Correction ID","Creation Date UTC","Status","Date of Last Action UTC","Total Open Period","Total Open Time (Days)","Current Status Time (Days)","Type","Next Action Required","Category","Query History","Reason for Change"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10001","Falc, Matej","CZ100012001","48 Years","16923867","14-May-2026","Escalated","14-Jun-2026","15-21 Days","20","","QUERY","Clario DM","Patient","(8) 14 Jun 2026 Clario: what should I do now? I have send you 1 ecg by normal way, 2 by pdf.","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10001","Falc, Matej","CZ100012001","48 Years","16567067","22-Jan-2026","Resolved","28-Jan-2026","4-7 Days","4","","QUERY","","Patient","MD Falc","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10009","Pumprla, Jiri","CZ100092001","49 Years","16776685","31-Mar-2026","Resolved","13-May-2026","Over 28 Days","29","","QUERY","","Patient","(2) 13 May 2026 Clario: I confirm, that only ONE ECG was collected by mistake.","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10013","Stepek, David","CZ100132001","29 Years","16990554","04-Jun-2026","Resolved","08-Jun-2026","2-3 Days","2","","QUERY","","Patient","(2) 07 Jun 2026 Clario: by mistake only one strip was taken","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10013","Stepek, David","CZ100132001","29 Years","16981256","02-Jun-2026","Resolved","04-Jun-2026","2-3 Days","2","","QUERY","","Transmittal","Visit: SCREENING/","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10013","Stepek, David","CZ100132002","29 Years","16985014","03-Jun-2026","Resolved","04-Jun-2026","1 Day","1","","QUERY","","Patient","(2) 04 Jun 2026 Clario: by mistake only one strip was expected","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10013","Stepek, David","CZ100132003","49 Years","16988974","04-Jun-2026","Resolved","05-Jun-2026","1 Day","1","","DCR","","Transmittal","Affected Event: 'SCREENING'",""
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10013","Stepek, David","CZ100132003","49 Years","16985006","03-Jun-2026","Resolved","04-Jun-2026","1 Day","1","","QUERY","","Patient","(2) 04 Jun 2026 Clario: by mistake only one strip was taken","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10021","Bortlik, Martin","CZ100212001","61 Years","16717619","11-Mar-2026","Resolved","28-Apr-2026","Over 28 Days","32","","QUERY","","Patient","(2) 28 Apr 2026 Clario: I confirmed that due to technical problems, the ECG was done only twice","Data Checks"
|
||||
"77242113UCO3001_ANALYSIS","Czech Republic The","CZ10022","Hrabak, Petr","CZ100222003","39 Years","16945114","21-May-2026","Resolved","04-Jun-2026","8-14 Days","10","","DCR","","Patient","(7) 04 Jun 2026 Portal, EXPeRT: It was mistake NO ECG for this date 20May2026 was done",""
|
||||
|
@@ -0,0 +1,302 @@
|
||||
"""
|
||||
import_to_mongo.py
|
||||
Verze: 1.2
|
||||
Datum: 2026-06-02
|
||||
|
||||
Import Clario CSV do MongoDB (databáze: Clario).
|
||||
|
||||
Kolekce: Clario.MayoDiary / Clario.MayoScore / Clario.eCOA_DCRs / Clario.ECG_DCRs
|
||||
Filtr: pouze řádky s Country == "Czech Republic"
|
||||
Klíč: MayoDiary → Subject ID + Form Number
|
||||
MayoScore → Participant ID + Visit
|
||||
eCOA_DCRs → Data Correction ID
|
||||
ECG_DCRs → Data Correction ID
|
||||
Historie: při změně fields se stará verze uloží do pole history[]
|
||||
Po importu přesune zpracované CSV do downloads/Zpracovano/
|
||||
|
||||
Použití:
|
||||
python import_to_mongo.py # importuje všechny CSV z downloads/
|
||||
python import_to_mongo.py downloads/konkretni.csv # jeden soubor
|
||||
"""
|
||||
|
||||
import csv
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from pymongo import MongoClient, ASCENDING
|
||||
|
||||
MONGO_URI = "mongodb://192.168.1.76:27017"
|
||||
DB_NAME = "Clario"
|
||||
DOWNLOADS_DIR = Path(__file__).parent / "downloads"
|
||||
PROCESSED_DIR = DOWNLOADS_DIR / "Zpracovano"
|
||||
|
||||
COUNTRY_FILTER = "Czech Republic"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Konfigurace kolekcí
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
COLLECTION_CONFIG = {
|
||||
"MayoDiary": {
|
||||
"collection": "Clario.MayoDiary",
|
||||
"subject_col": "Subject ID",
|
||||
"key_cols": ("Subject ID", "Form Number"),
|
||||
},
|
||||
"MayoScore": {
|
||||
"collection": "Clario.MayoScore",
|
||||
"subject_col": "Participant ID",
|
||||
"key_cols": ("Participant ID", "Visit"),
|
||||
"outcome_cols": (
|
||||
"Site Action",
|
||||
"Last Mayo Score Submission",
|
||||
"Week I-12 Clinical Responder",
|
||||
"Week I-12 Clinical Remission",
|
||||
"Clinical Flare",
|
||||
"Loss of Response",
|
||||
"Partial Mayo Response Post Loss of Response",
|
||||
"Partial Mayo Response for Clinical Non-Responders",
|
||||
),
|
||||
},
|
||||
"eCOA DCRs": {
|
||||
"collection": "Clario.eCOA_DCRs",
|
||||
"subject_col": "Subject ID",
|
||||
"key_cols": ("Data Correction ID",),
|
||||
},
|
||||
"ECG DCRs": {
|
||||
"collection": "Clario.ECG_DCRs",
|
||||
"subject_col": "Subject Number",
|
||||
"key_cols": ("Data Correction ID",),
|
||||
},
|
||||
}
|
||||
|
||||
DATE_FORMATS = [
|
||||
"%d-%b-%Y ",
|
||||
"%d-%b-%Y",
|
||||
"%d-%b-%Y %H:%M:%S",
|
||||
"%d %b %Y %H:%M:%S",
|
||||
"%d %b %Y %H:%M:%S:%f",
|
||||
"%d %b %Y",
|
||||
"%d %B %Y",
|
||||
"%Y%m%d %H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%m/%d/%Y %I:%M:%S %p",
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def clean_colname(name: str) -> str:
|
||||
"""Odstraní BOM a okolní uvozovky/mezery z názvu sloupce."""
|
||||
return name.lstrip("").strip().strip('"')
|
||||
|
||||
|
||||
def parse_date(value: str) -> str | None:
|
||||
v = value.strip()
|
||||
for fmt in DATE_FORMATS:
|
||||
try:
|
||||
dt = datetime.strptime(v, fmt.strip())
|
||||
return dt.replace(tzinfo=timezone.utc).isoformat()
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def extract_snapshot_date(filename: str) -> str:
|
||||
match = re.match(r"(\d{4}-\d{2}-\d{2})", Path(filename).name)
|
||||
return match.group(1) if match else datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def detect_collection_type(filename: str) -> str | None:
|
||||
"""Vrátí klíč do COLLECTION_CONFIG nebo None."""
|
||||
stem = Path(filename).stem
|
||||
for key in COLLECTION_CONFIG:
|
||||
if key in stem:
|
||||
return key
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CSV → dokument
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def map_row(row: dict, col_type: str) -> dict:
|
||||
cfg = COLLECTION_CONFIG[col_type]
|
||||
doc: dict = {}
|
||||
fields: dict = {}
|
||||
|
||||
cleaned = {clean_colname(k): v.strip() if v else "" for k, v in row.items()}
|
||||
|
||||
subject_col = cfg["subject_col"]
|
||||
doc["subject"] = {"id": cleaned.get(subject_col, "")}
|
||||
# ECG DCRs používají "Site ID" místo "Site"
|
||||
site_name = cleaned.get("Site") or cleaned.get("Site ID", "")
|
||||
doc["site"] = {"name": site_name}
|
||||
doc["country"] = cleaned.get("Country", "")
|
||||
doc["study"] = cleaned.get("Protocol", "")
|
||||
|
||||
key_parts = [cleaned.get(c, "") for c in cfg["key_cols"]]
|
||||
doc["recordKey"] = "_".join(key_parts)
|
||||
|
||||
outcome_cols = set(cfg.get("outcome_cols", ()))
|
||||
for col in outcome_cols:
|
||||
value = cleaned.get(col, "")
|
||||
if value and value != "-":
|
||||
parsed = parse_date(value)
|
||||
doc[col] = parsed if parsed else value
|
||||
else:
|
||||
doc[col] = None
|
||||
|
||||
skip_top = {"Protocol", "Country", "Site", subject_col} | outcome_cols
|
||||
for col, value in cleaned.items():
|
||||
if col in skip_top:
|
||||
continue
|
||||
if not value or value == "-":
|
||||
continue
|
||||
parsed = parse_date(value)
|
||||
fields[col] = parsed if parsed else value
|
||||
|
||||
doc["fields"] = fields
|
||||
return doc
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Import jednoho souboru
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def import_file(csv_path: str, db) -> dict:
|
||||
filename = Path(csv_path).name
|
||||
col_type = detect_collection_type(filename)
|
||||
if col_type is None:
|
||||
print(f" Preskakuji (neznamy typ): {filename}")
|
||||
return {"skipped": True}
|
||||
|
||||
cfg = COLLECTION_CONFIG[col_type]
|
||||
col_name = cfg["collection"]
|
||||
snapshot_date = extract_snapshot_date(filename)
|
||||
collection = db[col_name]
|
||||
|
||||
inserted = changed = unchanged = filtered_out = 0
|
||||
|
||||
with open(csv_path, encoding="utf-8-sig", newline="") as f:
|
||||
reader = csv.DictReader(f, delimiter=",", quotechar='"')
|
||||
|
||||
for row in reader:
|
||||
cleaned_row = {clean_colname(k): v for k, v in row.items()}
|
||||
country = cleaned_row.get("Country", "").strip()
|
||||
if COUNTRY_FILTER not in country:
|
||||
filtered_out += 1
|
||||
continue
|
||||
|
||||
doc = map_row(row, col_type)
|
||||
record_key = doc.get("recordKey")
|
||||
if not record_key:
|
||||
continue
|
||||
|
||||
doc["sourceFile"] = filename
|
||||
|
||||
existing = collection.find_one({"recordKey": record_key})
|
||||
|
||||
if existing is None:
|
||||
doc["firstSeen"] = snapshot_date
|
||||
doc["lastSeen"] = snapshot_date
|
||||
doc["history"] = []
|
||||
collection.insert_one(doc)
|
||||
inserted += 1
|
||||
|
||||
elif existing.get("fields") != doc["fields"]:
|
||||
old_entry = {
|
||||
"date": existing.get("lastSeen", snapshot_date),
|
||||
"fields": existing["fields"],
|
||||
}
|
||||
update_doc = {k: v for k, v in doc.items()}
|
||||
update_doc["lastSeen"] = snapshot_date
|
||||
collection.update_one(
|
||||
{"_id": existing["_id"]},
|
||||
{
|
||||
"$push": {"history": old_entry},
|
||||
"$set": update_doc,
|
||||
},
|
||||
)
|
||||
changed += 1
|
||||
|
||||
else:
|
||||
collection.update_one(
|
||||
{"_id": existing["_id"]},
|
||||
{"$set": {"lastSeen": snapshot_date, "sourceFile": filename}},
|
||||
)
|
||||
unchanged += 1
|
||||
|
||||
collection.create_index([("recordKey", ASCENDING)], unique=True)
|
||||
collection.create_index([("subject.id", ASCENDING)])
|
||||
collection.create_index([("site.name", ASCENDING)])
|
||||
if col_type == "MayoScore":
|
||||
collection.create_index([("Site Action", ASCENDING)])
|
||||
if col_type in ("eCOA DCRs", "ECG DCRs"):
|
||||
collection.create_index([("fields.Status", ASCENDING)])
|
||||
collection.create_index([("fields.Type", ASCENDING)])
|
||||
|
||||
stats = {
|
||||
"collection": col_name,
|
||||
"snapshot": snapshot_date,
|
||||
"inserted": inserted,
|
||||
"changed": changed,
|
||||
"unchanged": unchanged,
|
||||
"filtered_out": filtered_out,
|
||||
}
|
||||
print(f" {col_name} [{snapshot_date}]: +{inserted} new, ~{changed} changed, ={unchanged} same, -{filtered_out} non-CZ")
|
||||
return stats
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
paths: list[Path] = []
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
for arg in sys.argv[1:]:
|
||||
p = Path(arg)
|
||||
if p.is_file():
|
||||
paths.append(p)
|
||||
else:
|
||||
print(f"Soubor nenalezen: {arg}")
|
||||
else:
|
||||
paths = sorted(DOWNLOADS_DIR.glob("*.csv"))
|
||||
|
||||
if not paths:
|
||||
print("Zadne CSV soubory k importu.")
|
||||
return
|
||||
|
||||
print(f"Nalezeno {len(paths)} souboru.\n")
|
||||
|
||||
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
|
||||
client.admin.command("ping")
|
||||
db = client[DB_NAME]
|
||||
|
||||
PROCESSED_DIR.mkdir(exist_ok=True)
|
||||
|
||||
total = {"inserted": 0, "changed": 0, "unchanged": 0}
|
||||
|
||||
for csv_path in paths:
|
||||
print(f"Import: {csv_path.name}")
|
||||
stats = import_file(str(csv_path), db)
|
||||
if not stats.get("skipped"):
|
||||
for k in total:
|
||||
total[k] += stats.get(k, 0)
|
||||
|
||||
dest = PROCESSED_DIR / csv_path.name
|
||||
shutil.move(str(csv_path), str(dest))
|
||||
print(f" -> presunut do Zpracovano/")
|
||||
|
||||
client.close()
|
||||
|
||||
print(f"\nCelkem: +{total['inserted']} new, ~{total['changed']} changed, ={total['unchanged']} same")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,776 @@
|
||||
"""
|
||||
create_report.py
|
||||
Verze: 1.6
|
||||
Datum: 2026-06-02
|
||||
|
||||
Generuje Excel report (.xlsm) pro studii 77242113UCO3001 z MongoDB databáze Clario.
|
||||
Výstup: U:/Dropbox/!!!Days/Downloads Z230/YYYY-MM-DD 77242113UCO3001 Clario Reports.xlsm
|
||||
|
||||
Zdroj dat:
|
||||
MongoDB 192.168.1.76, databáze Clario
|
||||
Kolekce Clario.MayoScore — skóre Mayo per pacient × visit
|
||||
Kolekce Clario.MayoDiary — denní záznamy deníku pacienta
|
||||
Kolekce Clario.eCOA_DCRs — data correction requests eCOA
|
||||
Kolekce Clario.ECG_DCRs — data correction requests ECG
|
||||
|
||||
Listy:
|
||||
MayoScore — jeden řádek = pacient × visit
|
||||
sloupec „KLIKNI SEM" naviguje na filtrovaný EligibleDays
|
||||
řádky I-0 s Modified Mayo < 5 červeně tučně
|
||||
MayoDiary — jeden řádek = denní záznam deníku pacienta
|
||||
Compliance — jeden řádek = pacient × visit; kolik dní v okně mezi návštěvami
|
||||
mělo být vyplněno v MayoDiary a kolik jich pacient skutečně
|
||||
vyplnil + procento. Okno I-0 = od první diary po I-0; ostatní
|
||||
= od (předchozí visit +1) po aktuální visit. Unscheduled se
|
||||
ignorují. Řádky s compliance ≥ 100 % zeleně.
|
||||
EligibleDays — jeden řádek = jeden eligible day z MayoScore obohacený o data z MayoDiary;
|
||||
included/excluded flag, excluded dny šedě na žlutém pozadí
|
||||
eCOA_DCRs — všechna pole z kolekce Clario.eCOA_DCRs
|
||||
ECG_DCRs — všechna pole z kolekce Clario.ECG_DCRs
|
||||
|
||||
VBA makro (Worksheet_SelectionChange na listu MayoScore):
|
||||
Klik na sloupec „KLIKNI SEM" → přepne na EligibleDays a vyfiltruje záznamy
|
||||
pro daného pacienta a visit. Vyžaduje povolení maker při otevření souboru.
|
||||
"""
|
||||
|
||||
VERSION = "1.7"
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
from pymongo import MongoClient
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
import xlwings as xw
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Konfigurace
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
MONGO_URI = "mongodb://192.168.1.76:27017"
|
||||
DB_NAME = "Clario"
|
||||
OUTPUT_DIR = Path(r"U:\Dropbox\!!!Days\Downloads Z230")
|
||||
|
||||
VISIT_ORDER = ["I-0", "I-2", "I-4", "I-8", "I-12", "M-4"]
|
||||
|
||||
COLUMNS_SCORE = [
|
||||
("KLIKNI SEM", lambda d: "▶ klikni sem"),
|
||||
("Site", lambda d: d.get("site", {}).get("name", "")),
|
||||
("Subject ID", lambda d: d.get("subject", {}).get("id", "")),
|
||||
("Visit", lambda d: d["fields"].get("Visit", "")),
|
||||
("Visit Date", lambda d: d["fields"].get("Visit Date", "")),
|
||||
("Baseline Stool Frequency", lambda d: _num(d["fields"].get("Baseline Stool Frequency", ""))),
|
||||
("Central Endoscopy Score", lambda d: _num(d["fields"].get("Central Endoscopy Score", ""))),
|
||||
("PGA Score", lambda d: _num(d["fields"].get("PGA Score", ""))),
|
||||
("Stool Frequency Sub-score", lambda d: _num(d["fields"].get("Stool Frequency Sub-score", ""))),
|
||||
("Rectal Bleeding Sub-score", lambda d: _num(d["fields"].get("Rectal Bleeding Sub-score", ""))),
|
||||
("Partial Mayo Score", lambda d: _num(d["fields"].get("Partial Mayo Score", ""))),
|
||||
("Modified Mayo Score", lambda d: _num(d["fields"].get("Modified Mayo Score", ""))),
|
||||
("Full Mayo Score", lambda d: _num(d["fields"].get("Full Mayo Score", ""))),
|
||||
("Site Action", lambda d: d.get("Site Action") or ""),
|
||||
("Last Mayo Score Submission", lambda d: d.get("Last Mayo Score Submission") or ""),
|
||||
("Wk I-12 Responder", lambda d: d.get("Week I-12 Clinical Responder") or ""),
|
||||
("Wk I-12 Remission", lambda d: d.get("Week I-12 Clinical Remission") or ""),
|
||||
("Clinical Flare", lambda d: d.get("Clinical Flare") or ""),
|
||||
("Loss of Response", lambda d: d.get("Loss of Response") or ""),
|
||||
("Partial Mayo Post LoR", lambda d: d.get("Partial Mayo Response Post Loss of Response") or ""),
|
||||
("Partial Mayo Non-Resp", lambda d: d.get("Partial Mayo Response for Clinical Non-Responders") or ""),
|
||||
]
|
||||
|
||||
COLUMNS_DIARY = [
|
||||
("Subject ID", lambda d: d.get("subject", {}).get("id", "")),
|
||||
("Report Date", lambda d: d["fields"].get("Report Date", "")),
|
||||
("Baseline Stool Count", lambda d: _num(d["fields"].get("Baseline Stool Count", ""))),
|
||||
("Stool Frequency", lambda d: _num(d["fields"].get("Stool Frequency", ""))),
|
||||
("MAYO050", lambda d: d["fields"].get("MAYO050", "")),
|
||||
("Not Applicable", lambda d: d["fields"].get("Not Applicable", "")),
|
||||
("Constipation", lambda d: d["fields"].get("Constipation", "")),
|
||||
("Diarrhea", lambda d: d["fields"].get("Diarrhea", "")),
|
||||
("Irregularity", lambda d: d["fields"].get("Irregularity", "")),
|
||||
]
|
||||
|
||||
COLUMNS_ECOA_DCRS = [
|
||||
("Site", lambda d: d.get("site", {}).get("name", "")),
|
||||
("Subject ID", lambda d: d.get("subject", {}).get("id", "")),
|
||||
("Data Correction ID", lambda d: d["fields"].get("Data Correction ID", "")),
|
||||
("PI Name", lambda d: d["fields"].get("PI Name", "")),
|
||||
("Creation Date UTC", lambda d: d["fields"].get("Creation Date UTC", "")),
|
||||
("Date of Last Action UTC", lambda d: d["fields"].get("Date of Last Action UTC", "")),
|
||||
("Status", lambda d: d["fields"].get("Status", "")),
|
||||
("Type", lambda d: d["fields"].get("Type", "")),
|
||||
("Next Action Required", lambda d: d["fields"].get("Next Action Required", "")),
|
||||
("Category", lambda d: d["fields"].get("Category", "")),
|
||||
("Total Open Period", lambda d: d["fields"].get("Total Open Period", "")),
|
||||
("Total Open Time (Days)", lambda d: _num(d["fields"].get("Total Open Time (Days)", ""))),
|
||||
("Current Status Time (Days)", lambda d: _num(d["fields"].get("Current Status Time (Days)", ""))),
|
||||
("Reason for Change", lambda d: d["fields"].get("Reason for Change", "")),
|
||||
("Description", lambda d: d["fields"].get("Description", "")),
|
||||
("Resolution", lambda d: d["fields"].get("Resolution", "")),
|
||||
("Query History", lambda d: d["fields"].get("Query History", "")),
|
||||
("Age at Informed Consent", lambda d: d["fields"].get("Age at Informed Consent", "")),
|
||||
("Baseline Stool Count", lambda d: _num(d["fields"].get("Baseline Stool Count", ""))),
|
||||
("firstSeen", lambda d: d.get("firstSeen", "")),
|
||||
("lastSeen", lambda d: d.get("lastSeen", "")),
|
||||
]
|
||||
|
||||
COLUMNS_ECG_DCRS = [
|
||||
("Site ID", lambda d: d.get("site", {}).get("name", "")),
|
||||
("Subject Number", lambda d: d.get("subject", {}).get("id", "")),
|
||||
("Data Correction ID", lambda d: d["fields"].get("Data Correction ID", "")),
|
||||
("PI Name", lambda d: d["fields"].get("PI_NAME", "")),
|
||||
("Age", lambda d: d["fields"].get("Age", "")),
|
||||
("Creation Date UTC", lambda d: d["fields"].get("Creation Date UTC", "")),
|
||||
("Date of Last Action UTC", lambda d: d["fields"].get("Date of Last Action UTC", "")),
|
||||
("Status", lambda d: d["fields"].get("Status", "")),
|
||||
("Type", lambda d: d["fields"].get("Type", "")),
|
||||
("Next Action Required", lambda d: d["fields"].get("Next Action Required", "")),
|
||||
("Category", lambda d: d["fields"].get("Category", "")),
|
||||
("Total Open Period", lambda d: d["fields"].get("Total Open Period", "")),
|
||||
("Total Open Time (Days)", lambda d: _num(d["fields"].get("Total Open Time (Days)", ""))),
|
||||
("Current Status Time (Days)", lambda d: _num(d["fields"].get("Current Status Time (Days)", ""))),
|
||||
("Reason for Change", lambda d: d["fields"].get("Reason for Change", "")),
|
||||
("Query History", lambda d: d["fields"].get("Query History", "")),
|
||||
("firstSeen", lambda d: d.get("firstSeen", "")),
|
||||
("lastSeen", lambda d: d.get("lastSeen", "")),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _num(value):
|
||||
"""Převede číselný string na int, jinak vrátí původní hodnotu nebo None."""
|
||||
if value == "" or value is None:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, TypeError):
|
||||
try:
|
||||
return float(value)
|
||||
except (ValueError, TypeError):
|
||||
return value
|
||||
|
||||
|
||||
def _visit_sort_key(doc):
|
||||
visit = doc["fields"].get("Visit", "")
|
||||
try:
|
||||
idx = VISIT_ORDER.index(visit)
|
||||
except ValueError:
|
||||
idx = len(VISIT_ORDER)
|
||||
return (doc.get("site", {}).get("name", ""), doc.get("subject", {}).get("id", ""), idx, visit)
|
||||
|
||||
|
||||
def _iso_to_date(value):
|
||||
"""ISO string → Python date pro Excel."""
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
try:
|
||||
return datetime.fromisoformat(value).date()
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Styly
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
HEADER_FILL = PatternFill("solid", fgColor="1F497D")
|
||||
HEADER_FONT = Font(bold=True, color="FFFFFF", size=10)
|
||||
CELL_FONT = Font(size=10)
|
||||
ALIGN_CTR = Alignment(horizontal="center", vertical="center", wrap_text=False)
|
||||
ALIGN_LEFT = Alignment(horizontal="left", vertical="center")
|
||||
|
||||
THIN = Side(style="thin", color="BFBFBF")
|
||||
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||
|
||||
# zebra
|
||||
FILL_ODD = PatternFill("solid", fgColor="FFFFFF")
|
||||
FILL_EVEN = PatternFill("solid", fgColor="EBF1DE")
|
||||
|
||||
# DCR status barvy
|
||||
FILL_DCR_SITE = PatternFill("solid", fgColor="FFFF00") # žlutá — čeká lékař
|
||||
FILL_DCR_CLARIO = PatternFill("solid", fgColor="BDD7EE") # modrá — čeká Clario
|
||||
FILL_DCR_QC = PatternFill("solid", fgColor="F4B942") # oranžová — ReadyForQC
|
||||
FILL_DCR_DONE = PatternFill("solid", fgColor="FFFFFF") # bílá — Completed
|
||||
|
||||
SCORE_COLS = {"Partial Mayo Score", "Modified Mayo Score", "Full Mayo Score"}
|
||||
SCORE_FILL = PatternFill("solid", fgColor="FFC7CE") # červená pro skóre ≥ 5 (placeholder — nepoužíváme podmíněné formátování)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sestavení sheetu
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_sheet(ws, docs, columns, date_cols, center_cols, col_widths, row_font_fn=None, wrap_cols=None, header_row=1):
|
||||
headers = [c[0] for c in columns]
|
||||
|
||||
for col_idx, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=header_row, column=col_idx, value=header)
|
||||
cell.font = HEADER_FONT
|
||||
cell.fill = HEADER_FILL
|
||||
cell.alignment = ALIGN_CTR
|
||||
cell.border = BORDER
|
||||
ws.row_dimensions[header_row].height = 28
|
||||
|
||||
data_start = header_row + 1
|
||||
for row_idx, doc in enumerate(docs, data_start):
|
||||
fill = FILL_EVEN if (row_idx - header_row) % 2 == 0 else FILL_ODD
|
||||
font = row_font_fn(doc) if row_font_fn else CELL_FONT
|
||||
for col_idx, (col_name, getter) in enumerate(columns, 1):
|
||||
value = getter(doc)
|
||||
if col_name in date_cols and isinstance(value, str):
|
||||
value = _iso_to_date(value)
|
||||
cell = ws.cell(row=row_idx, column=col_idx, value=value)
|
||||
cell.font = font
|
||||
cell.fill = fill
|
||||
cell.border = BORDER
|
||||
if wrap_cols and col_name in wrap_cols:
|
||||
cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
|
||||
else:
|
||||
cell.alignment = ALIGN_CTR if col_name in center_cols else ALIGN_LEFT
|
||||
|
||||
for col_idx, (col_name, _) in enumerate(columns, 1):
|
||||
ws.column_dimensions[get_column_letter(col_idx)].width = col_widths.get(col_name, 14)
|
||||
|
||||
for col_name in date_cols:
|
||||
if col_name in headers:
|
||||
letter = get_column_letter(headers.index(col_name) + 1)
|
||||
for row_idx in range(data_start, len(docs) + data_start):
|
||||
ws[f"{letter}{row_idx}"].number_format = "DD-MMM-YYYY"
|
||||
|
||||
ws.freeze_panes = f"A{data_start}"
|
||||
ws.auto_filter.ref = f"A{header_row}:{get_column_letter(len(headers))}{header_row}"
|
||||
|
||||
|
||||
def _score_row_font(doc):
|
||||
visit = doc["fields"].get("Visit", "")
|
||||
try:
|
||||
mod_mayo = int(doc["fields"].get("Modified Mayo Score", ""))
|
||||
except (ValueError, TypeError):
|
||||
mod_mayo = None
|
||||
if visit == "I-0" and mod_mayo is not None and mod_mayo < 5:
|
||||
return Font(size=10, bold=True, color="FF0000")
|
||||
return CELL_FONT
|
||||
|
||||
|
||||
def build_mayo_score_sheet(ws, docs):
|
||||
_build_sheet(
|
||||
ws, docs, COLUMNS_SCORE,
|
||||
date_cols={"Visit Date", "Last Mayo Score Submission"},
|
||||
center_cols={"KLIKNI SEM", "Visit", "Central Endoscopy Score", "PGA Score",
|
||||
"Stool Frequency Sub-score", "Rectal Bleeding Sub-score",
|
||||
"Partial Mayo Score", "Modified Mayo Score", "Full Mayo Score",
|
||||
"Baseline Stool Frequency",
|
||||
"Wk I-12 Responder", "Wk I-12 Remission", "Clinical Flare",
|
||||
"Loss of Response", "Partial Mayo Post LoR", "Partial Mayo Non-Resp",
|
||||
"Last Mayo Score Submission"},
|
||||
col_widths={
|
||||
"KLIKNI SEM": 14,
|
||||
"Site": 18, "Subject ID": 16, "Visit": 12, "Visit Date": 14,
|
||||
"Baseline Stool Frequency": 14, "Central Endoscopy Score": 14,
|
||||
"PGA Score": 10, "Stool Frequency Sub-score": 14,
|
||||
"Rectal Bleeding Sub-score": 14, "Partial Mayo Score": 14,
|
||||
"Modified Mayo Score": 14, "Full Mayo Score": 13,
|
||||
"Site Action": 22, "Last Mayo Score Submission": 16,
|
||||
"Wk I-12 Responder": 14, "Wk I-12 Remission": 14,
|
||||
"Clinical Flare": 14, "Loss of Response": 14,
|
||||
"Partial Mayo Post LoR": 20, "Partial Mayo Non-Resp": 20,
|
||||
},
|
||||
row_font_fn=_score_row_font,
|
||||
)
|
||||
# Speciální styl pro sloupec KLIKNI SEM — vypadá jako tlačítko/odkaz
|
||||
link_font = Font(size=10, bold=True, color="FFFFFF")
|
||||
link_fill = PatternFill("solid", fgColor="2E75B6")
|
||||
for row in range(2, len(docs) + 2):
|
||||
cell = ws.cell(row=row, column=1)
|
||||
cell.font = link_font
|
||||
cell.fill = link_fill
|
||||
cell.alignment = ALIGN_CTR
|
||||
|
||||
|
||||
def build_mayo_diary_sheet(ws, docs):
|
||||
_build_sheet(
|
||||
ws, docs, COLUMNS_DIARY,
|
||||
date_cols={"Report Date"},
|
||||
center_cols={"Baseline Stool Count", "Stool Frequency", "Not Applicable",
|
||||
"Constipation", "Diarrhea", "Irregularity"},
|
||||
col_widths={
|
||||
"Subject ID": 16, "Report Date": 14, "Baseline Stool Count": 14,
|
||||
"Stool Frequency": 14, "MAYO050": 48, "Not Applicable": 14,
|
||||
"Constipation": 14, "Diarrhea": 12, "Irregularity": 14,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def build_eligible_days_sheet(ws, score_docs, diary_docs):
|
||||
# Lookup diary records by (subject_id, date_part YYYY-MM-DD)
|
||||
diary_lookup: dict[tuple, dict] = {}
|
||||
for d in diary_docs:
|
||||
subj = d.get("subject", {}).get("id", "")
|
||||
date_iso = d["fields"].get("Report Date", "")
|
||||
date_part = date_iso[:10] if date_iso else ""
|
||||
if subj and date_part:
|
||||
diary_lookup[(subj, date_part)] = d
|
||||
|
||||
headers = [
|
||||
"Included", "Subject ID", "Visit", "Visit Date", "Day",
|
||||
"Report Date", "Baseline Stool Count", "Stool Frequency",
|
||||
"MAYO050", "Not Applicable", "Constipation", "Diarrhea", "Irregularity",
|
||||
]
|
||||
col_widths = {
|
||||
"Included": 10, "Subject ID": 16, "Visit": 10, "Visit Date": 14, "Day": 8,
|
||||
"Report Date": 14, "Baseline Stool Count": 14, "Stool Frequency": 14,
|
||||
"MAYO050": 48, "Not Applicable": 14, "Constipation": 14,
|
||||
"Diarrhea": 12, "Irregularity": 14,
|
||||
}
|
||||
center_cols = {"Included", "Visit", "Day", "Baseline Stool Count", "Stool Frequency",
|
||||
"Not Applicable", "Constipation", "Diarrhea", "Irregularity"}
|
||||
date_cols = {"Visit Date", "Report Date"}
|
||||
no_fill = PatternFill("solid", fgColor="FFF2CC") # žlutá pro excluded dny
|
||||
|
||||
for col_idx, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col_idx, value=header)
|
||||
cell.font = HEADER_FONT
|
||||
cell.fill = HEADER_FILL
|
||||
cell.alignment = ALIGN_CTR
|
||||
cell.border = BORDER
|
||||
ws.row_dimensions[1].height = 28
|
||||
|
||||
row_idx = 2
|
||||
for score_doc in score_docs:
|
||||
subj = score_doc.get("subject", {}).get("id", "")
|
||||
visit = score_doc["fields"].get("Visit", "")
|
||||
visit_date = score_doc["fields"].get("Visit Date", "")
|
||||
|
||||
for n in range(1, 11):
|
||||
day_date_iso = score_doc["fields"].get(f"Eligible Day (-{n})")
|
||||
if not day_date_iso or day_date_iso == "-":
|
||||
continue
|
||||
date_part = day_date_iso[:10]
|
||||
excl_reason = score_doc["fields"].get(f"Day (-{n}) Excluded Reason(s)", "")
|
||||
included = "No" if excl_reason and excl_reason != "-" else "Yes"
|
||||
|
||||
diary = diary_lookup.get((subj, date_part), {})
|
||||
df = diary.get("fields", {})
|
||||
|
||||
fill = no_fill if included == "No" else (FILL_EVEN if row_idx % 2 == 0 else FILL_ODD)
|
||||
font = Font(size=10, color="808080") if included == "No" else CELL_FONT
|
||||
|
||||
values = [
|
||||
included,
|
||||
subj,
|
||||
visit,
|
||||
_iso_to_date(visit_date) if isinstance(visit_date, str) else visit_date,
|
||||
f"-{n}",
|
||||
_iso_to_date(day_date_iso),
|
||||
_num(df.get("Baseline Stool Count", "")),
|
||||
_num(df.get("Stool Frequency", "")),
|
||||
df.get("MAYO050", ""),
|
||||
df.get("Not Applicable", ""),
|
||||
df.get("Constipation", ""),
|
||||
df.get("Diarrhea", ""),
|
||||
df.get("Irregularity", ""),
|
||||
]
|
||||
|
||||
for col_idx, (header, value) in enumerate(zip(headers, values), 1):
|
||||
cell = ws.cell(row=row_idx, column=col_idx, value=value)
|
||||
cell.font = font
|
||||
cell.fill = fill
|
||||
cell.border = BORDER
|
||||
if header in date_cols:
|
||||
cell.number_format = "DD-MMM-YYYY"
|
||||
cell.alignment = ALIGN_CTR if header in center_cols else ALIGN_LEFT
|
||||
|
||||
row_idx += 1
|
||||
|
||||
for col_idx, header in enumerate(headers, 1):
|
||||
ws.column_dimensions[get_column_letter(col_idx)].width = col_widths.get(header, 14)
|
||||
|
||||
ws.freeze_panes = "A2"
|
||||
ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1"
|
||||
|
||||
|
||||
def _build_dcr_legend(ws):
|
||||
"""Vloží legendu do řádků 1–4, prázdný řádek 5. Data začínají od řádku 6."""
|
||||
legend = [
|
||||
(FILL_DCR_SITE, "Čeká lékař — Next Action Required = Site (lékař musí odpovědět nebo potvrdit)"),
|
||||
(FILL_DCR_CLARIO, "Čeká Clario — Next Action Required = Clario DM (Clario dostalo podklady, provede změnu)"),
|
||||
(FILL_DCR_QC, "ReadyForQC — Clario provedlo změny, čeká na finální QC kontrolu"),
|
||||
(FILL_DCR_DONE, "Completed / Resolved — DCR je uzavřen"),
|
||||
]
|
||||
for i, (fill, text) in enumerate(legend, 1):
|
||||
a = ws.cell(row=i, column=1, value="")
|
||||
a.fill = fill
|
||||
a.border = BORDER
|
||||
b = ws.cell(row=i, column=2, value=text)
|
||||
b.font = Font(size=10, bold=True)
|
||||
b.alignment = ALIGN_LEFT
|
||||
# řádek 5 prázdný — nic nedělat
|
||||
|
||||
|
||||
def _dcr_row_fill(doc):
|
||||
"""Vrátí fill barvu dle stavu DCR."""
|
||||
status = doc["fields"].get("Status", "")
|
||||
next_action = doc["fields"].get("Next Action Required", "")
|
||||
if status in ("Completed", "Resolved"):
|
||||
return FILL_DCR_DONE
|
||||
if status == "ReadyForQC":
|
||||
return FILL_DCR_QC
|
||||
if "Site" in next_action:
|
||||
return FILL_DCR_SITE
|
||||
if "Clario" in next_action or next_action == "":
|
||||
return FILL_DCR_CLARIO
|
||||
return FILL_ODD
|
||||
|
||||
|
||||
def build_ecoa_dcrs_sheet(ws, docs):
|
||||
_build_dcr_legend(ws)
|
||||
docs_sorted = sorted(docs, key=lambda d: (
|
||||
d.get("site", {}).get("name", ""),
|
||||
d.get("subject", {}).get("id", ""),
|
||||
d["fields"].get("Creation Date UTC", ""),
|
||||
))
|
||||
_build_sheet(
|
||||
ws, docs_sorted, COLUMNS_ECOA_DCRS,
|
||||
date_cols={"Creation Date UTC", "Date of Last Action UTC"},
|
||||
center_cols={"Status", "Type", "Next Action Required", "Category",
|
||||
"Total Open Time (Days)", "Current Status Time (Days)",
|
||||
"Baseline Stool Count", "firstSeen", "lastSeen"},
|
||||
col_widths={
|
||||
"Site": 16, "Subject ID": 16, "Data Correction ID": 18,
|
||||
"PI Name": 18, "Creation Date UTC": 14, "Date of Last Action UTC": 14,
|
||||
"Status": 14, "Type": 16, "Next Action Required": 16, "Category": 20,
|
||||
"Total Open Period": 14, "Total Open Time (Days)": 14,
|
||||
"Current Status Time (Days)": 16, "Reason for Change": 20,
|
||||
"Description": 50, "Resolution": 50, "Query History": 60,
|
||||
"Age at Informed Consent": 14, "Baseline Stool Count": 14,
|
||||
"firstSeen": 12, "lastSeen": 12,
|
||||
},
|
||||
wrap_cols={"Reason for Change", "Description", "Resolution", "Query History"},
|
||||
header_row=6,
|
||||
row_font_fn=lambda doc: CELL_FONT,
|
||||
)
|
||||
# Přebarvení řádků dle DCR stavu (přepíše zebra fill)
|
||||
data_start = 7
|
||||
for row_idx, doc in enumerate(docs_sorted, data_start):
|
||||
fill = _dcr_row_fill(doc)
|
||||
for col_idx in range(1, len(COLUMNS_ECOA_DCRS) + 1):
|
||||
ws.cell(row=row_idx, column=col_idx).fill = fill
|
||||
|
||||
|
||||
def build_ecg_dcrs_sheet(ws, docs):
|
||||
_build_dcr_legend(ws)
|
||||
docs_sorted = sorted(docs, key=lambda d: (
|
||||
d.get("site", {}).get("name", ""),
|
||||
d.get("subject", {}).get("id", ""),
|
||||
d["fields"].get("Creation Date UTC", ""),
|
||||
))
|
||||
_build_sheet(
|
||||
ws, docs_sorted, COLUMNS_ECG_DCRS,
|
||||
date_cols={"Creation Date UTC", "Date of Last Action UTC"},
|
||||
center_cols={"Status", "Type", "Next Action Required", "Category",
|
||||
"Total Open Time (Days)", "Current Status Time (Days)",
|
||||
"firstSeen", "lastSeen"},
|
||||
col_widths={
|
||||
"Site ID": 14, "Subject Number": 16, "Data Correction ID": 16,
|
||||
"PI Name": 18, "Age": 10, "Creation Date UTC": 14,
|
||||
"Date of Last Action UTC": 14, "Status": 14, "Type": 12,
|
||||
"Next Action Required": 16, "Category": 14,
|
||||
"Total Open Period": 14, "Total Open Time (Days)": 14,
|
||||
"Current Status Time (Days)": 16, "Reason for Change": 20,
|
||||
"Query History": 60, "firstSeen": 12, "lastSeen": 12,
|
||||
},
|
||||
wrap_cols={"Query History"},
|
||||
header_row=6,
|
||||
row_font_fn=lambda doc: CELL_FONT,
|
||||
)
|
||||
# Přebarvení řádků dle DCR stavu
|
||||
data_start = 7
|
||||
for row_idx, doc in enumerate(docs_sorted, data_start):
|
||||
fill = _dcr_row_fill(doc)
|
||||
for col_idx in range(1, len(COLUMNS_ECG_DCRS) + 1):
|
||||
ws.cell(row=row_idx, column=col_idx).fill = fill
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# List Compliance
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Pořadí návštěv pro výpočet oken (Unscheduled apod. se ignorují)
|
||||
COMPLIANCE_VISIT_ORDER = ["I-0", "I-2", "I-4", "I-8", "I-12", "M-4"]
|
||||
|
||||
FILL_COMPLIANCE_OK = PatternFill("solid", fgColor="C6EFCE") # zelená — compliance ≥ 100 %
|
||||
FONT_COMPLIANCE_OK = Font(size=10, color="006100")
|
||||
|
||||
|
||||
def build_compliance_sheet(ws, score_docs, diary_docs):
|
||||
"""Compliance vyplňování MayoDiary mezi návštěvami.
|
||||
|
||||
Okno pro I-0 = od první MayoDiary daného pacienta po datum I-0.
|
||||
Okno pro ostatní = od (datum předchozí návštěvy + 1 den) po datum návštěvy.
|
||||
Vyplněno = počet MayoDiary záznamů pacienta s Report Date uvnitř okna.
|
||||
Dní v okně = počet kalendářních dní okna (včetně obou krajů).
|
||||
"""
|
||||
# -- MayoDiary datumy per pacient (jeden průchod) ------------------------
|
||||
diary_by_subj: dict[str, list] = {}
|
||||
for d in diary_docs:
|
||||
subj = d.get("subject", {}).get("id", "")
|
||||
rd = d["fields"].get("Report Date", "")
|
||||
dt = _iso_to_date(rd) if isinstance(rd, str) else rd
|
||||
if subj and hasattr(dt, "year"):
|
||||
diary_by_subj.setdefault(subj, []).append(dt)
|
||||
first_diary = {s: min(dts) for s, dts in diary_by_subj.items() if dts}
|
||||
|
||||
def _vidx(v):
|
||||
try:
|
||||
return COMPLIANCE_VISIT_ORDER.index(v)
|
||||
except ValueError:
|
||||
return len(COMPLIANCE_VISIT_ORDER)
|
||||
|
||||
# -- Návštěvy per pacient (jen známé visity) -----------------------------
|
||||
by_subj: dict[str, list] = {}
|
||||
for sd in score_docs:
|
||||
if sd["fields"].get("Visit", "") not in COMPLIANCE_VISIT_ORDER:
|
||||
continue
|
||||
subj = sd.get("subject", {}).get("id", "")
|
||||
by_subj.setdefault(subj, []).append(sd)
|
||||
|
||||
rows = []
|
||||
for subj in sorted(by_subj):
|
||||
visits = sorted(by_subj[subj], key=lambda d: _vidx(d["fields"].get("Visit", "")))
|
||||
prev_end = None
|
||||
for sd in visits:
|
||||
visit = sd["fields"].get("Visit", "")
|
||||
vdate = _iso_to_date(sd["fields"].get("Visit Date", ""))
|
||||
if not hasattr(vdate, "year"):
|
||||
continue
|
||||
if visit == "I-0":
|
||||
start = first_diary.get(subj)
|
||||
else:
|
||||
start = (prev_end + timedelta(days=1)) if prev_end else first_diary.get(subj)
|
||||
prev_end = vdate
|
||||
if not start or not hasattr(start, "year"):
|
||||
continue
|
||||
days = (vdate - start).days + 1
|
||||
if days <= 0:
|
||||
continue
|
||||
filled = sum(1 for dt in diary_by_subj.get(subj, []) if start <= dt <= vdate)
|
||||
pct = round(filled / days * 100)
|
||||
rows.append({
|
||||
"site": sd.get("site", {}).get("name", ""),
|
||||
"subj": subj,
|
||||
"visit": visit,
|
||||
"start": start,
|
||||
"end": vdate,
|
||||
"days": days,
|
||||
"filled": filled,
|
||||
"pct": pct,
|
||||
})
|
||||
|
||||
# -- Zápis listu ---------------------------------------------------------
|
||||
headers = ["Site", "Subject ID", "Visit", "Okno od", "Okno do",
|
||||
"Dní v okně", "Vyplněno", "Compliance %"]
|
||||
col_widths = {"Site": 18, "Subject ID": 16, "Visit": 10, "Okno od": 14,
|
||||
"Okno do": 14, "Dní v okně": 12, "Vyplněno": 12, "Compliance %": 14}
|
||||
center_cols = {"Visit", "Dní v okně", "Vyplněno", "Compliance %"}
|
||||
date_cols = {"Okno od", "Okno do"}
|
||||
|
||||
for col_idx, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col_idx, value=header)
|
||||
cell.font = HEADER_FONT
|
||||
cell.fill = HEADER_FILL
|
||||
cell.alignment = ALIGN_CTR
|
||||
cell.border = BORDER
|
||||
ws.row_dimensions[1].height = 28
|
||||
|
||||
for row_idx, r in enumerate(rows, 2):
|
||||
is_ok = r["pct"] >= 100
|
||||
if is_ok:
|
||||
fill = FILL_COMPLIANCE_OK
|
||||
font = FONT_COMPLIANCE_OK
|
||||
else:
|
||||
fill = FILL_EVEN if row_idx % 2 == 0 else FILL_ODD
|
||||
font = CELL_FONT
|
||||
values = [r["site"], r["subj"], r["visit"], r["start"], r["end"],
|
||||
r["days"], r["filled"], r["pct"]]
|
||||
for col_idx, (header, value) in enumerate(zip(headers, values), 1):
|
||||
cell = ws.cell(row=row_idx, column=col_idx, value=value)
|
||||
cell.font = font
|
||||
cell.fill = fill
|
||||
cell.border = BORDER
|
||||
if header in date_cols:
|
||||
cell.number_format = "DD-MMM-YYYY"
|
||||
if header == "Compliance %":
|
||||
cell.number_format = '0"%"'
|
||||
cell.alignment = ALIGN_CTR if header in center_cols else ALIGN_LEFT
|
||||
|
||||
for col_idx, header in enumerate(headers, 1):
|
||||
ws.column_dimensions[get_column_letter(col_idx)].width = col_widths.get(header, 14)
|
||||
|
||||
ws.freeze_panes = "A2"
|
||||
ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers: výstupní cesta
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _unique_path(directory: Path, stem: str, suffix: str) -> Path:
|
||||
candidate = directory / f"{stem}{suffix}"
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
n = 2
|
||||
while True:
|
||||
candidate = directory / f"{stem} ({n}){suffix}"
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
n += 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Timing helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _tick(label: str, t0: float) -> float:
|
||||
"""Vypíše dobu od t0 a vrátí aktuální čas jako nový t0."""
|
||||
elapsed = time.perf_counter() - t0
|
||||
print(f" {label:<30} {elapsed:6.2f} s")
|
||||
return time.perf_counter()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
t_total = time.perf_counter()
|
||||
print("Spouštím generování reportu...")
|
||||
print()
|
||||
|
||||
# -- 1. MongoDB: připojení + načtení + seřazení --------------------------
|
||||
t = time.perf_counter()
|
||||
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
|
||||
client.admin.command("ping")
|
||||
db = client[DB_NAME]
|
||||
score_docs = list(db["Clario.MayoScore"].find({}))
|
||||
diary_docs = list(db["Clario.MayoDiary"].find({}))
|
||||
ecoa_dcr_docs = list(db["Clario.eCOA_DCRs"].find({}))
|
||||
ecg_dcr_docs = list(db["Clario.ECG_DCRs"].find({}))
|
||||
client.close()
|
||||
score_docs.sort(key=_visit_sort_key)
|
||||
diary_docs.sort(key=lambda d: (
|
||||
d.get("subject", {}).get("id", ""),
|
||||
d["fields"].get("Report Date", ""),
|
||||
))
|
||||
t = _tick(f"MongoDB (ping, fetch, sort → {len(score_docs)} + {len(diary_docs)} + {len(ecoa_dcr_docs)} + {len(ecg_dcr_docs)} záznamů)", t)
|
||||
|
||||
# -- 2–4. Tvorba listů ---------------------------------------------------
|
||||
wb = Workbook()
|
||||
ws_score = wb.active
|
||||
ws_score.title = "MayoScore"
|
||||
build_mayo_score_sheet(ws_score, score_docs)
|
||||
t = _tick("List MayoScore (KLIKNI SEM, zebra, červené I-0, autofilter)", t)
|
||||
|
||||
ws_diary = wb.create_sheet("MayoDiary")
|
||||
build_mayo_diary_sheet(ws_diary, diary_docs)
|
||||
t = _tick("List MayoDiary (zebra, formátování dat, autofilter)", t)
|
||||
|
||||
ws_comp = wb.create_sheet("Compliance")
|
||||
build_compliance_sheet(ws_comp, score_docs, diary_docs)
|
||||
t = _tick("List Compliance (okna mezi visitami, % vyplnění, zelená ≥100 %)", t)
|
||||
|
||||
ws_days = wb.create_sheet("EligibleDays")
|
||||
build_eligible_days_sheet(ws_days, score_docs, diary_docs)
|
||||
t = _tick("List EligibleDays (diary lookup, included/excluded flag, autofilter)", t)
|
||||
|
||||
ws_ecoa = wb.create_sheet("eCOA_DCRs")
|
||||
build_ecoa_dcrs_sheet(ws_ecoa, ecoa_dcr_docs)
|
||||
t = _tick(f"List eCOA_DCRs ({len(ecoa_dcr_docs)} záznamů)", t)
|
||||
|
||||
ws_ecg = wb.create_sheet("ECG_DCRs")
|
||||
build_ecg_dcrs_sheet(ws_ecg, ecg_dcr_docs)
|
||||
t = _tick(f"List ECG_DCRs ({len(ecg_dcr_docs)} záznamů)", t)
|
||||
|
||||
# -- 5. Uložení XLSX -----------------------------------------------------
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
base_stem = f"{today} 77242113UCO3001 Clario Reports"
|
||||
xlsm_path = _unique_path(OUTPUT_DIR, base_stem, ".xlsm")
|
||||
xlsx_path = xlsm_path.with_suffix(".xlsx")
|
||||
wb.save(str(xlsx_path))
|
||||
t = _tick("Uložení XLSX (openpyxl, dočasný soubor)", t)
|
||||
|
||||
# -- 6. Injektování VBA --------------------------------------------------
|
||||
inject_vba(xlsx_path, xlsm_path)
|
||||
xlsx_path.unlink(missing_ok=True)
|
||||
_tick("Injektování VBA (xlwings: open → AddFromString → SaveAs .xlsm)", t)
|
||||
|
||||
# -- Souhrn --------------------------------------------------------------
|
||||
total = time.perf_counter() - t_total
|
||||
print()
|
||||
print(f" {'Celkem':<30} {total:6.2f} s")
|
||||
print()
|
||||
print(f"Uloženo: {xlsm_path}")
|
||||
|
||||
|
||||
def inject_vba(xlsx_path: Path, xlsm_path: Path) -> None:
|
||||
vba_code = '''\
|
||||
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
|
||||
If Target.Row < 2 Then Exit Sub
|
||||
If Target.Rows.Count > 1 Then Exit Sub
|
||||
If Target.Column <> 1 Then Exit Sub
|
||||
|
||||
Dim subjectId As String
|
||||
Dim visit As String
|
||||
subjectId = CStr(Me.Cells(Target.Row, 3).Value)
|
||||
visit = CStr(Me.Cells(Target.Row, 4).Value)
|
||||
|
||||
If subjectId = "" Or visit = "" Then Exit Sub
|
||||
|
||||
Dim ws As Worksheet
|
||||
On Error Resume Next
|
||||
Set ws = ThisWorkbook.Sheets("EligibleDays")
|
||||
On Error GoTo 0
|
||||
If ws Is Nothing Then Exit Sub
|
||||
|
||||
Application.ScreenUpdating = False
|
||||
|
||||
ws.AutoFilterMode = False
|
||||
ws.Range("A1").AutoFilter
|
||||
ws.Range("A1").AutoFilter Field:=2, Criteria1:=subjectId
|
||||
ws.Range("A1").AutoFilter Field:=3, Criteria1:=visit
|
||||
|
||||
ws.Activate
|
||||
ws.Range("A2").Select
|
||||
|
||||
Application.ScreenUpdating = True
|
||||
End Sub
|
||||
'''
|
||||
|
||||
app = xw.App(visible=False)
|
||||
try:
|
||||
wb = app.books.open(str(xlsx_path))
|
||||
# Najdi VBComponent odpovídající listu "MayoScore" podle tab názvu
|
||||
vb_comp = None
|
||||
for comp in wb.api.VBProject.VBComponents:
|
||||
if comp.Type == 100: # xlSheet
|
||||
try:
|
||||
if comp.Properties("Name").Value == "MayoScore":
|
||||
vb_comp = comp
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if vb_comp is None:
|
||||
# fallback: první sheet (Sheet1)
|
||||
vb_comp = wb.api.VBProject.VBComponents("Sheet1")
|
||||
vb_comp.CodeModule.AddFromString(vba_code)
|
||||
wb.api.SaveAs(str(xlsm_path), FileFormat=52) # 52 = xlOpenXMLWorkbookMacroEnabled
|
||||
wb.close()
|
||||
finally:
|
||||
app.quit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
import_to_mongo.py
|
||||
Verze: 1.2
|
||||
Datum: 2026-06-02
|
||||
Verze: 1.3
|
||||
Datum: 2026-06-15
|
||||
|
||||
Import Clario CSV do MongoDB (databáze: Clario).
|
||||
|
||||
@@ -11,7 +11,8 @@ Klíč: MayoDiary → Subject ID + Form Number
|
||||
MayoScore → Participant ID + Visit
|
||||
eCOA_DCRs → Data Correction ID
|
||||
ECG_DCRs → Data Correction ID
|
||||
Historie: při změně fields se stará verze uloží do pole history[]
|
||||
Historie: při změně jakéhokoliv datového sloupce (fields + outcome cols) se stará
|
||||
verze uloží do pole history[] spolu s outcome poli
|
||||
Po importu přesune zpracované CSV do downloads/Zpracovano/
|
||||
|
||||
Použití:
|
||||
@@ -119,6 +120,14 @@ def detect_collection_type(filename: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def data_snapshot(doc: dict, outcome_cols: tuple) -> dict:
|
||||
"""Porovnatelný snapshot všech datových polí: fields{} + outcome cols."""
|
||||
snap = {"fields": doc.get("fields", {})}
|
||||
for col in outcome_cols:
|
||||
snap[col] = doc.get(col)
|
||||
return snap
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CSV → dokument
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -176,6 +185,7 @@ def import_file(csv_path: str, db) -> dict:
|
||||
|
||||
cfg = COLLECTION_CONFIG[col_type]
|
||||
col_name = cfg["collection"]
|
||||
outcome_cols = tuple(cfg.get("outcome_cols", ()))
|
||||
snapshot_date = extract_snapshot_date(filename)
|
||||
collection = db[col_name]
|
||||
|
||||
@@ -207,11 +217,13 @@ def import_file(csv_path: str, db) -> dict:
|
||||
collection.insert_one(doc)
|
||||
inserted += 1
|
||||
|
||||
elif existing.get("fields") != doc["fields"]:
|
||||
old_entry = {
|
||||
"date": existing.get("lastSeen", snapshot_date),
|
||||
"fields": existing["fields"],
|
||||
}
|
||||
elif data_snapshot(existing, outcome_cols) != data_snapshot(doc, outcome_cols):
|
||||
# Uložíme kompletní snapshot starého stavu (fields + outcome cols)
|
||||
old_entry = {"date": existing.get("lastSeen", snapshot_date)}
|
||||
for col in outcome_cols:
|
||||
old_entry[col] = existing.get(col)
|
||||
old_entry["fields"] = existing.get("fields", {})
|
||||
|
||||
update_doc = {k: v for k, v in doc.items()}
|
||||
update_doc["lastSeen"] = snapshot_date
|
||||
collection.update_one(
|
||||
|
||||
Reference in New Issue
Block a user