tricc-oo 1.5.13__py3-none-any.whl → 1.6.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. tests/build.py +20 -28
  2. tests/test_build.py +260 -0
  3. tests/test_cql.py +48 -109
  4. tests/to_ocl.py +15 -17
  5. tricc_oo/__init__.py +0 -6
  6. tricc_oo/converters/codesystem_to_ocl.py +51 -40
  7. tricc_oo/converters/cql/cqlLexer.py +1 -0
  8. tricc_oo/converters/cql/cqlListener.py +1 -0
  9. tricc_oo/converters/cql/cqlParser.py +1 -0
  10. tricc_oo/converters/cql/cqlVisitor.py +1 -0
  11. tricc_oo/converters/cql_to_operation.py +129 -123
  12. tricc_oo/converters/datadictionnary.py +45 -54
  13. tricc_oo/converters/drawio_type_map.py +146 -65
  14. tricc_oo/converters/tricc_to_xls_form.py +58 -28
  15. tricc_oo/converters/utils.py +4 -4
  16. tricc_oo/converters/xml_to_tricc.py +296 -235
  17. tricc_oo/models/__init__.py +2 -1
  18. tricc_oo/models/base.py +333 -305
  19. tricc_oo/models/calculate.py +66 -51
  20. tricc_oo/models/lang.py +26 -27
  21. tricc_oo/models/ocl.py +146 -161
  22. tricc_oo/models/ordered_set.py +15 -19
  23. tricc_oo/models/tricc.py +149 -89
  24. tricc_oo/parsers/xml.py +15 -30
  25. tricc_oo/serializers/planuml.py +4 -6
  26. tricc_oo/serializers/xls_form.py +110 -153
  27. tricc_oo/strategies/input/base_input_strategy.py +28 -32
  28. tricc_oo/strategies/input/drawio.py +59 -71
  29. tricc_oo/strategies/output/base_output_strategy.py +151 -65
  30. tricc_oo/strategies/output/dhis2_form.py +908 -0
  31. tricc_oo/strategies/output/fhir_form.py +377 -0
  32. tricc_oo/strategies/output/html_form.py +224 -0
  33. tricc_oo/strategies/output/openmrs_form.py +694 -0
  34. tricc_oo/strategies/output/spice.py +106 -127
  35. tricc_oo/strategies/output/xls_form.py +322 -244
  36. tricc_oo/strategies/output/xlsform_cdss.py +627 -142
  37. tricc_oo/strategies/output/xlsform_cht.py +252 -125
  38. tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
  39. tricc_oo/visitors/tricc.py +1424 -1033
  40. tricc_oo/visitors/utils.py +16 -16
  41. tricc_oo/visitors/xform_pd.py +91 -89
  42. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/METADATA +128 -84
  43. tricc_oo-1.6.8.dist-info/RECORD +52 -0
  44. tricc_oo-1.6.8.dist-info/licenses/LICENSE +373 -0
  45. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/top_level.txt +0 -0
  46. tricc_oo-1.5.13.dist-info/RECORD +0 -46
  47. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/WHEEL +0 -0
@@ -2,7 +2,7 @@ import datetime
2
2
  import logging
3
3
  import os
4
4
  import shutil
5
- import re
5
+ import subprocess
6
6
  import pandas as pd
7
7
 
8
8
  from tricc_oo.models.lang import SingletonLangClass
@@ -24,24 +24,26 @@ logger = logging.getLogger("default")
24
24
 
25
25
  class XLSFormCHTStrategy(XLSFormCDSSStrategy):
26
26
  def process_export(self, start_pages, **kwargs):
27
- diags = []
28
27
  self.activity_export(start_pages[self.processes[0]], **kwargs)
29
28
  # self.add_tab_breaks_choice()
30
- cht_header = pd.DataFrame(columns=SURVEY_MAP.keys())
31
29
  cht_input_df = self.get_cht_input(start_pages, **kwargs)
32
- self.df_survey = self.df_survey[
33
- ~self.df_survey["name"].isin(cht_input_df["name"])
34
- ]
30
+ self.df_survey = self.df_survey[~self.df_survey["name"].isin(cht_input_df["name"])]
35
31
  self.df_survey.reset_index(drop=True, inplace=True)
36
32
 
37
- self.df_survey = pd.concat(
38
- [cht_input_df, self.df_survey, self.get_cht_summary()], ignore_index=True
39
- )
33
+ self.df_survey = pd.concat([cht_input_df, self.df_survey, self.get_cht_summary()], ignore_index=True)
34
+
35
+ self.inject_version()
36
+
37
+ def get_empty_label(self):
38
+ return "NO_LABEL"
40
39
 
41
40
  def get_cht_input(self, start_pages, **kwargs):
42
41
  empty = langs.get_trads("", force_dict=True)
43
42
  df_input = pd.DataFrame(columns=SURVEY_MAP.keys())
44
- # [ #type, '',#name ''#label, '',#hint '',#help '',#default '',#'appearance', '',#'constraint', '',#'constraint_message' '',#'relevance' '',#'disabled' '',#'required' '',#'required message' '',#'read only' '',#'expression' '',#'repeat_count' ''#'image' ],
43
+ # [ #type, '',#name ''#label, '',#hint '',#help '',#default '',#'appearance',
44
+ # '',#'constraint', '',#'constraint_message' '',#'relevance' '',#'disabled'
45
+ # '',#'required' '',#'required message' '',#'read only' '',#'expression' '',#
46
+ # 'repeat_count' ''#'image' ],
45
47
  df_input.loc[len(df_input)] = [
46
48
  "begin_group",
47
49
  "inputs",
@@ -61,6 +63,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
61
63
  "",
62
64
  "",
63
65
  "",
66
+ "",
64
67
  ]
65
68
  df_input.loc[len(df_input)] = [
66
69
  "hidden",
@@ -81,6 +84,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
81
84
  "",
82
85
  "",
83
86
  "",
87
+ "",
84
88
  ]
85
89
  df_input.loc[len(df_input)] = [
86
90
  "hidden",
@@ -101,6 +105,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
101
105
  "",
102
106
  "",
103
107
  "",
108
+ "",
104
109
  ]
105
110
 
106
111
  df_input.loc[len(df_input)] = [
@@ -122,6 +127,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
122
127
  "",
123
128
  "",
124
129
  "",
130
+ "",
125
131
  ]
126
132
  df_input.loc[len(df_input)] = [
127
133
  "string",
@@ -142,6 +148,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
142
148
  "",
143
149
  "",
144
150
  "",
151
+ "",
145
152
  ]
146
153
  df_input.loc[len(df_input)] = [
147
154
  "string",
@@ -162,6 +169,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
162
169
  "",
163
170
  "",
164
171
  "",
172
+ "",
165
173
  ]
166
174
  df_input.loc[len(df_input)] = [
167
175
  "string",
@@ -182,6 +190,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
182
190
  "",
183
191
  "",
184
192
  "",
193
+ "",
185
194
  ]
186
195
  df_input.loc[len(df_input)] = [
187
196
  "end_group",
@@ -202,6 +211,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
202
211
  "",
203
212
  "",
204
213
  "",
214
+ "",
205
215
  ]
206
216
  df_input.loc[len(df_input)] = [
207
217
  "begin_group",
@@ -222,11 +232,12 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
222
232
  "",
223
233
  "",
224
234
  "",
235
+ "",
225
236
  ]
226
237
  inputs = self.export_inputs(start_pages[self.processes[0]], **kwargs)
227
238
  for input in inputs:
228
239
  df_input.loc[len(df_input)] = get_input_line(input)
229
- self.get_contact_inputs(df_input)
240
+ self.get_contact_inputs(df_input)
230
241
  df_input.loc[len(df_input)] = [
231
242
  "hidden",
232
243
  "external_id",
@@ -246,6 +257,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
246
257
  "",
247
258
  "",
248
259
  "",
260
+ "",
249
261
  ]
250
262
 
251
263
  df_input.loc[len(df_input)] = [
@@ -267,6 +279,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
267
279
  "",
268
280
  "",
269
281
  "",
282
+ "",
270
283
  ]
271
284
 
272
285
  df_input.loc[len(df_input)] = [
@@ -288,6 +301,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
288
301
  "",
289
302
  "",
290
303
  "",
304
+ "",
291
305
  ]
292
306
 
293
307
  df_input.loc[len(df_input)] = [
@@ -309,6 +323,29 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
309
323
  "",
310
324
  "",
311
325
  "",
326
+ "",
327
+ ]
328
+
329
+ df_input.loc[len(df_input)] = [
330
+ "hidden",
331
+ "data_load",
332
+ *list(langs.get_trads("NO_LABEL", force_dict=True).values()),
333
+ *list(empty.values()),
334
+ *list(empty.values()),
335
+ "",
336
+ "hidden",
337
+ "",
338
+ *list(empty.values()),
339
+ "",
340
+ "",
341
+ "",
342
+ *list(empty.values()),
343
+ "",
344
+ "",
345
+ "",
346
+ "",
347
+ "",
348
+ "",
312
349
  ]
313
350
  self.get_contact_inputs_calculate(df_input)
314
351
  df_input.loc[len(df_input)] = [
@@ -318,17 +355,18 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
318
355
  *list(empty.values()), # hint
319
356
  *list(empty.values()), # help
320
357
  "", # default
321
- "", #'appearance', clean_name
322
- "", #'constraint',
323
- *list(empty.values()), #'constraint_message'
324
- "", #'relevance'
325
- "", #'disabled'
326
- "", #'required'
327
- *list(empty.values()), #'required message'
328
- "", #'read only'
329
- "../inputs/user/contact_id", #'expression'
330
- "", #'repeat_count'
331
- "", #'image'
358
+ "", # 'appearance', clean_name
359
+ "", # 'constraint',
360
+ *list(empty.values()), # 'constraint_message'
361
+ "", # 'relevance'
362
+ "", # 'disabled'
363
+ "", # 'required'
364
+ *list(empty.values()), # 'required message'
365
+ "", # 'read only'
366
+ "../inputs/user/contact_id", # 'expression'
367
+ "",
368
+ "", # 'repeat_count'
369
+ "", # 'image'
332
370
  "", # choice filter
333
371
  ]
334
372
  df_input.loc[len(df_input)] = [
@@ -338,17 +376,18 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
338
376
  *list(empty.values()), # hint
339
377
  *list(empty.values()), # help
340
378
  "", # default
341
- "", #'appearance', clean_name
342
- "", #'constraint',
343
- *list(empty.values()), #'constraint_message'
344
- "", #'relevance'
345
- "", #'disabled'
346
- "", #'required'
347
- *list(empty.values()), #'required message'
348
- "", #'read only'
349
- "../inputs/user/facility_id", #'expression'
350
- "", #'repeat_count'
351
- "", #'image'
379
+ "", # 'appearance', clean_name
380
+ "", # 'constraint',
381
+ *list(empty.values()), # 'constraint_message'
382
+ "", # 'relevance'
383
+ "", # 'disabled'
384
+ "", # 'required'
385
+ *list(empty.values()), # 'required message'
386
+ "", # 'read only'
387
+ "../inputs/user/facility_id", # 'expression'
388
+ "",
389
+ "", # 'repeat_count'
390
+ "", # 'image'
352
391
  "", # choice filter
353
392
  ]
354
393
  df_input.loc[len(df_input)] = [
@@ -358,17 +397,18 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
358
397
  *list(empty.values()), # hint
359
398
  *list(empty.values()), # help
360
399
  "", # default
361
- "", #'appearance', clean_name
362
- "", #'constraint',
363
- *list(empty.values()), #'constraint_message'
364
- "", #'relevance'
365
- "", #'disabled'
366
- "", #'required'
367
- *list(empty.values()), #'required message'
368
- "", #'read only'
369
- "../inputs/user/name", #'expression'
370
- "", #'repeat_count'
371
- "", #'image'
400
+ "", # 'appearance', clean_name
401
+ "", # 'constraint',
402
+ *list(empty.values()), # 'constraint_message'
403
+ "", # 'relevance'
404
+ "", # 'disabled'
405
+ "", # 'required'
406
+ *list(empty.values()), # 'required message'
407
+ "", # 'read only'
408
+ "../inputs/user/name", # 'expression'
409
+ "",
410
+ "", # 'repeat_count'
411
+ "", # 'image'
372
412
  "", # choice filter
373
413
  ]
374
414
  df_input.loc[len(df_input)] = [
@@ -378,17 +418,18 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
378
418
  *list(empty.values()), # hint
379
419
  *list(empty.values()), # help
380
420
  "", # default
381
- "", #'appearance', clean_name
382
- "", #'constraint',
383
- *list(empty.values()), #'constraint_message'
384
- "", #'relevance'
385
- "", #'disabled'
386
- "", #'required'
387
- *list(empty.values()), #'required message'
388
- "", #'read only'
389
- "../inputs/contact/_id", #'expression'
390
- "", #'repeat_count'
391
- "", #'image'
421
+ "", # 'appearance', clean_name
422
+ "", # 'constraint',
423
+ *list(empty.values()), # 'constraint_message'
424
+ "", # 'relevance'
425
+ "", # 'disabled'
426
+ "", # 'required'
427
+ *list(empty.values()), # 'required message'
428
+ "", # 'read only'
429
+ "../inputs/contact/_id", # 'expression'
430
+ "",
431
+ "", # 'repeat_count'
432
+ "", # 'image'
392
433
  "", # choice filter
393
434
  ]
394
435
 
@@ -399,17 +440,18 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
399
440
  *list(empty.values()), # hint
400
441
  *list(empty.values()), # help
401
442
  "", # default
402
- "", #'appearance', clean_name
403
- "", #'constraint',
404
- *list(empty.values()), #'constraint_message'
405
- "", #'relevance'
406
- "", #'disabled'
407
- "", #'required'
408
- *list(empty.values()), #'required message'
409
- "", #'read only'
410
- "../inputs/source_id", #'expression'
411
- "", #'repeat_count'
412
- "", #'image'
443
+ "", # 'appearance', clean_name
444
+ "", # 'constraint',
445
+ *list(empty.values()), # 'constraint_message'
446
+ "", # 'relevance'
447
+ "", # 'disabled'
448
+ "", # 'required'
449
+ *list(empty.values()), # 'required message'
450
+ "", # 'read only'
451
+ "../inputs/source_id", # 'expression'
452
+ "",
453
+ "", # 'repeat_count'
454
+ "", # 'image'
413
455
  "", # choice filter
414
456
  ]
415
457
  df_input.loc[len(df_input)] = [
@@ -419,39 +461,20 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
419
461
  *list(empty.values()), # hint
420
462
  *list(empty.values()), # help
421
463
  "", # default
422
- "", #'appearance', clean_name
423
- "", #'constraint',
424
- *list(empty.values()), #'constraint_message'
425
- "", #'relevance'
426
- "", #'disabled'
427
- "", #'required'
428
- *list(empty.values()), #'required message'
429
- "", #'read only'
430
- "../inputs/user/facility_id", #'expression'
431
- "", #'repeat_count'
432
- "", #'image'
464
+ "", # 'appearance', clean_name
465
+ "", # 'constraint',
466
+ *list(empty.values()), # 'constraint_message'
467
+ "", # 'relevance'
468
+ "", # 'disabled'
469
+ "", # 'required'
470
+ *list(empty.values()), # 'required message'
471
+ "", # 'read only'
472
+ "../inputs/user/facility_id", # 'expression'
473
+ "",
474
+ "", # 'repeat_count'
475
+ "", # 'image'
433
476
  "", # choice filter
434
477
  ]
435
- df_input.loc[len(df_input)] = [
436
- "hidden",
437
- "data_load",
438
- *list(langs.get_trads("NO_LABEL", force_dict=True).values()),
439
- *list(empty.values()),
440
- *list(empty.values()),
441
- "",
442
- "hidden",
443
- "",
444
- *list(empty.values()),
445
- "",
446
- "",
447
- "",
448
- *list(empty.values()),
449
- "",
450
- "",
451
- "",
452
- "",
453
- "",
454
- ]
455
478
 
456
479
  for input in inputs:
457
480
  df_input.loc[len(df_input)] = get_input_calc_line(input)
@@ -460,7 +483,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
460
483
 
461
484
  def get_contact_inputs(self, df_input):
462
485
  empty = langs.get_trads("", force_dict=True)
463
- if not len(df_input[df_input['name'] == 'sex']):
486
+ if not len(df_input[df_input["name"] == "sex"]):
464
487
  df_input.loc[len(df_input)] = [
465
488
  "hidden",
466
489
  "sex",
@@ -480,8 +503,9 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
480
503
  "",
481
504
  "",
482
505
  "",
506
+ "",
483
507
  ]
484
- if not len(df_input[df_input['name'] == 'date_of_birth']):
508
+ if not len(df_input[df_input["name"] == "date_of_birth"]):
485
509
  df_input.loc[len(df_input)] = [
486
510
  "hidden",
487
511
  "date_of_birth",
@@ -501,11 +525,11 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
501
525
  "",
502
526
  "",
503
527
  "",
528
+ "",
504
529
  ]
505
530
 
506
531
  return df_input
507
532
 
508
-
509
533
  def get_contact_inputs_calculate(self, df_input):
510
534
  empty = langs.get_trads("", force_dict=True)
511
535
  df_input.loc[len(df_input)] = [
@@ -527,6 +551,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
527
551
  "",
528
552
  "",
529
553
  "",
554
+ "",
530
555
  ]
531
556
  df_input.loc[len(df_input)] = [
532
557
  "calculate",
@@ -547,18 +572,13 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
547
572
  "",
548
573
  "",
549
574
  "",
575
+ "",
550
576
  ]
551
577
 
552
578
  return df_input
553
579
 
554
580
  def get_cht_summary(self):
555
581
  df_summary = pd.DataFrame(columns=SURVEY_MAP.keys())
556
- # [ #type, '',#name ''#label, '',#hint '',#help '',#default '',#'appearance', '',#'constraint', '',#'constraint_message' '',#'relevance' '',#'disabled' '',#'required' '',#'required message' '',#'read only' '',#'expression' '',#'repeat_count' ''#'image' ],
557
- # df_summary.loc[len(df_summary)] = [ 'begin group', 'group_summary' , 'Summary', '', '', '', 'field-list summary', '', '', '', '', '', '', '', '', '', '' ]
558
- # df_summary.loc[len(df_summary)] = [ 'note', 'r_patient_info', '**${patient_name}** ID: ${patient_id}', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ]
559
- # df_summary.loc[len(df_summary)] = [ 'note', 'r_followup', 'Follow Up <i class=“fa fa-flag”></i>', '', '', '', '', '', '','', '', '', '', '', '', '', '' ]
560
- # df_summary.loc[len(df_summary)] = [ 'note', 'r_followup_note' ,'FOLLOWUP instruction', '', '', '', '', '', '', '','', '', '', '', '', '', '' ]
561
- # df_summary.loc[len(df_summary)] = [ 'end group', '' ,'', '', '', '', '', '', '', '', '', '', '', '', '','', '' ]
562
582
  return df_summary
563
583
 
564
584
  def get_last_prev_index(self, df, e, depth=0):
@@ -573,7 +593,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
573
593
  latest = index[-1]
574
594
  if latest is None and depth > 5:
575
595
  for p in e.prev_nodes:
576
- index = get_last_prev_index(df, e, depth + 1)
596
+ index = self.get_last_prev_index(df, e, depth + 1)
577
597
  if not latest and index and index > latest:
578
598
  latest = index
579
599
  return latest
@@ -597,6 +617,9 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
597
617
  newpath = os.path.join(self.output_path, newfilename)
598
618
  media_path = os.path.join(self.output_path, form_id + "-media")
599
619
 
620
+ # Track all generated XLS files for validation
621
+ generated_files = [newpath]
622
+
600
623
  settings = {
601
624
  "form_title": title,
602
625
  "form_id": form_id,
@@ -606,10 +629,6 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
606
629
  }
607
630
  df_settings = pd.DataFrame(settings, index=indx)
608
631
  df_settings.head()
609
- if len(self.df_survey[self.df_survey["name"] == "version"]):
610
- self.df_survey.loc[self.df_survey["name"] == "version", "label"] = (
611
- f"v{version}"
612
- )
613
632
  # create a Pandas Excel writer using XlsxWriter as the engine
614
633
  writer = pd.ExcelWriter(newpath, engine="xlsxwriter")
615
634
  self.df_survey.to_excel(writer, sheet_name="survey", index=False)
@@ -621,8 +640,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
621
640
  for p in self.project.pages.values():
622
641
  p_ends = list(
623
642
  filter(
624
- lambda x: issubclass(x.__class__, TriccNodeEnd)
625
- and getattr(x, "process", "") == "pause",
643
+ lambda x: issubclass(x.__class__, TriccNodeEnd) and getattr(x, "process", "") == "pause",
626
644
  p.nodes.values(),
627
645
  )
628
646
  )
@@ -640,14 +658,13 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
640
658
  )
641
659
  )
642
660
  else:
643
- logger.critical(
644
- f"impossible to get last index before pause: {e.get_name()}"
645
- )
661
+ logger.critical(f"impossible to get last index before pause: {e.get_name()}")
646
662
  forms = [form_id]
647
663
  for i, e in ends_prev:
648
664
  new_form_id = f"{form_id}_{clean_name(e.name)}"
649
665
  newfilename = f"{new_form_id}.xlsx"
650
666
  newpath = os.path.join(self.output_path, newfilename)
667
+ generated_files.append(newpath) # Track additional XLS files
651
668
  settings = {
652
669
  "form_title": title,
653
670
  "form_id": f"{new_form_id}",
@@ -657,9 +674,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
657
674
  }
658
675
  df_settings = pd.DataFrame(settings, index=indx)
659
676
  df_settings.head()
660
- task_df, hidden_names = make_breakpoints(
661
- self.df_survey, i, e.name, replace_dots=True
662
- )
677
+ task_df, hidden_names = make_breakpoints(self.df_survey, i, e.name, replace_dots=True)
663
678
  # deactivate the end node
664
679
  task_df.loc[task_df["name"] == get_export_name(e), "calculation"] = 0
665
680
  # print fileds
@@ -688,9 +703,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
688
703
 
689
704
  media_path_tmp = os.path.join(self.output_path, "media-tmp")
690
705
  if os.path.isdir(media_path_tmp):
691
- if os.path.isdir(
692
- media_path
693
- ): # check if it exists, because if it does, error will be raised
706
+ if os.path.isdir(media_path): # check if it exists, because if it does, error will be raised
694
707
  shutil.rmtree(media_path)
695
708
  # (later change to make folder complaint to CHT)
696
709
  os.mkdir(media_path)
@@ -700,19 +713,133 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
700
713
  shutil.move(os.path.join(media_path_tmp, file_name), media_path)
701
714
  shutil.rmtree(media_path_tmp)
702
715
 
716
+ return generated_files
717
+
718
+ def execute(self):
719
+ """Override execute to handle multiple output files from CHT strategy."""
720
+ version = datetime.datetime.now().strftime("%Y%m%d%H%M")
721
+ logger.info(f"build version: {version}")
722
+ if "main" in self.project.start_pages:
723
+ self.process_base(self.project.start_pages, pages=self.project.pages, version=version)
724
+ else:
725
+ logger.critical("Main process required")
726
+
727
+ logger.info("generate the relevance based on edges")
728
+
729
+ # create relevance Expression
730
+
731
+ # create calculate Expression
732
+ self.process_calculate(self.project.start_pages, pages=self.project.pages)
733
+ logger.info("generate the export format")
734
+ # create calculate Expression
735
+ self.process_export(self.project.start_pages, pages=self.project.pages)
736
+
737
+ logger.info("print the export")
738
+
739
+ # Export returns list of generated files for CHT strategy
740
+ generated_files = self.export(self.project.start_pages, version=version)
741
+
742
+ logger.info("validate the output")
743
+ self.validate(generated_files)
744
+
745
+ def validate(self, generated_files=None):
746
+ """Validate the generated XLS form(s) using xls2xform-medic."""
747
+ if generated_files is None:
748
+ # Fallback for single file validation
749
+ if self.project.start_pages["main"].root.form_id is not None:
750
+ form_id = str(self.project.start_pages["main"].root.form_id)
751
+ generated_files = [os.path.join(self.output_path, form_id + ".xlsx")]
752
+ else:
753
+ logger.error("Form ID not found for validation")
754
+ return False
755
+
756
+ # Ensure xls2xform-medic is available
757
+ medic_tool = self._ensure_xls2xform_medic()
758
+ if not medic_tool:
759
+ logger.error("xls2xform-medic tool not available, skipping CHT validation")
760
+ return False
761
+
762
+ all_valid = True
763
+ for xls_file in generated_files:
764
+ if not os.path.exists(xls_file):
765
+ logger.error(f"XLS file not found: {xls_file}")
766
+ all_valid = False
767
+ continue
768
+
769
+ try:
770
+ # Run xls2xform-medic validation
771
+ result = subprocess.run(
772
+ [medic_tool, xls_file],
773
+ capture_output=True,
774
+ text=True,
775
+ cwd=self.output_path
776
+ )
777
+
778
+ if result.returncode == 0:
779
+ logger.info(f"CHT XLSForm validation successful: {os.path.basename(xls_file)}")
780
+ else:
781
+ logger.error(f"CHT XLSForm validation failed for {os.path.basename(xls_file)}: {result.stderr}")
782
+ all_valid = False
783
+
784
+ except Exception as e:
785
+ logger.error(f"CHT XLSForm validation error for {os.path.basename(xls_file)}: {str(e)}")
786
+ all_valid = False
787
+
788
+ return all_valid
789
+
790
+ def _ensure_xls2xform_medic(self):
791
+ """Ensure xls2xform-medic tool is available."""
792
+ # Check if it's in PATH
793
+ medic_tool = shutil.which("xls2xform-medic")
794
+ if medic_tool:
795
+ return medic_tool
796
+
797
+ # Check if we need to download it
798
+ medic_path = os.path.join(os.path.dirname(__file__), "xls2xform-medic")
799
+ if os.path.exists(medic_path):
800
+ return medic_path
801
+
802
+ # Try to download from the provided URL
803
+ try:
804
+ import urllib.request
805
+ medic_url = "https://github.com/medic/pyxform/releases/download/v4.0.0-medic/xls2xform-medic"
806
+ logger.info(f"Downloading xls2xform-medic from {medic_url}")
807
+ urllib.request.urlretrieve(medic_url, medic_path)
808
+ # Make executable
809
+ os.chmod(medic_path, 0o755)
810
+ return medic_path
811
+ except Exception as e:
812
+ logger.error(f"Failed to download xls2xform-medic: {str(e)}")
813
+ return None
814
+
703
815
  def tricc_operation_zscore(self, ref_expressions):
704
816
  y, ll, m, s = self.get_zscore_params(ref_expressions)
705
817
  # return ((Math.pow((y / m), l) - 1) / (s * l));
706
- return f"cht:extension-lib('{ref_expressions[0][1:-1]}.js',{self.clean_coalesce(ref_expressions[1])} ,{self.clean_coalesce(ref_expressions[2])} ,{self.clean_coalesce(ref_expressions[3])})"
818
+ return f"""cht:extension-lib('{
819
+ ref_expressions[0][1:-1]
820
+ }.js',{
821
+ self.clean_coalesce(ref_expressions[1])
822
+ } ,{
823
+ self.clean_coalesce(ref_expressions[2])
824
+ } ,{
825
+ self.clean_coalesce(ref_expressions[3])
826
+ })"""
707
827
 
708
828
  def tricc_operation_izscore(self, ref_expressions):
709
829
  z, ll, m, s = self.get_zscore_params(ref_expressions)
710
830
  # return (m * (z*s*l-1)^(1/l));
711
- return f"cht:extension-lib('{ref_expressions[0][1:-1]}.js',{self.clean_coalesce(ref_expressions[1])} ,{self.clean_coalesce(ref_expressions[2])} ,{self.clean_coalesce(ref_expressions[3])}, true)"
831
+ return f"""cht:extension-lib('{
832
+ ref_expressions[0][1:-1]
833
+ }.js',{
834
+ self.clean_coalesce(ref_expressions[1])
835
+ } ,{
836
+ self.clean_coalesce(ref_expressions[2])
837
+ } ,{
838
+ self.clean_coalesce(ref_expressions[3])
839
+ }, true)"""
712
840
 
713
841
  def tricc_operation_drug_dosage(self, ref_expressions):
714
842
  # drug name
715
843
  # age
716
844
  # weight
717
845
  return f"cht:extension-lib('drugs.js',{','.join(map(self.clean_coalesce, ref_expressions))})"
718
-