tricc-oo 1.6.26__py3-none-any.whl → 1.7.0.dev1__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.
- tests/test_build.py +260 -0
- tests/test_cql.py +0 -9
- tricc_oo/converters/codesystem_to_ocl.py +4 -4
- tricc_oo/converters/cql_to_operation.py +0 -1
- tricc_oo/converters/datadictionnary.py +2 -2
- tricc_oo/converters/drawio_type_map.py +11 -11
- tricc_oo/converters/tricc_to_xls_form.py +4 -5
- tricc_oo/converters/xml_to_tricc.py +34 -55
- tricc_oo/models/base.py +1 -7
- tricc_oo/models/tricc.py +1 -5
- tricc_oo/serializers/xls_form.py +34 -90
- tricc_oo/strategies/output/base_output_strategy.py +0 -7
- tricc_oo/strategies/output/dhis2_form.py +8 -8
- tricc_oo/strategies/output/openmrs_form.py +2 -2
- tricc_oo/strategies/output/xls_form.py +49 -133
- tricc_oo/strategies/output/xlsform_cht.py +4 -129
- tricc_oo/visitors/tricc.py +112 -204
- {tricc_oo-1.6.26.dist-info → tricc_oo-1.7.0.dev1.dist-info}/METADATA +1 -1
- {tricc_oo-1.6.26.dist-info → tricc_oo-1.7.0.dev1.dist-info}/RECORD +22 -21
- {tricc_oo-1.6.26.dist-info → tricc_oo-1.7.0.dev1.dist-info}/WHEEL +1 -1
- {tricc_oo-1.6.26.dist-info → tricc_oo-1.7.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {tricc_oo-1.6.26.dist-info → tricc_oo-1.7.0.dev1.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,6 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
import pandas as pd
|
|
11
|
-
from pyxform import create_survey_from_xls
|
|
12
11
|
|
|
13
12
|
from tricc_oo.converters.utils import clean_name
|
|
14
13
|
from tricc_oo.models.base import (
|
|
@@ -19,16 +18,14 @@ from tricc_oo.models.ordered_set import OrderedSet
|
|
|
19
18
|
from tricc_oo.models.calculate import (
|
|
20
19
|
TriccNodeEnd,
|
|
21
20
|
TriccNodeDisplayCalculateBase,
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
)
|
|
24
23
|
from tricc_oo.models.tricc import (
|
|
25
24
|
TriccNodeCalculateBase,
|
|
26
25
|
TriccNodeBaseModel,
|
|
27
26
|
TriccNodeSelectOption,
|
|
28
|
-
TriccNodeSelect,
|
|
29
27
|
TriccNodeInputModel,
|
|
30
28
|
TriccNodeDisplayModel,
|
|
31
|
-
TriccNodeMainStart,
|
|
32
29
|
TRICC_FALSE_VALUE,
|
|
33
30
|
TRICC_TRUE_VALUE,
|
|
34
31
|
)
|
|
@@ -51,8 +48,6 @@ from tricc_oo.serializers.xls_form import (
|
|
|
51
48
|
SURVEY_MAP,
|
|
52
49
|
end_group,
|
|
53
50
|
generate_xls_form_export,
|
|
54
|
-
get_export_group_name,
|
|
55
|
-
get_export_group_required,
|
|
56
51
|
start_group,
|
|
57
52
|
)
|
|
58
53
|
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
@@ -91,8 +86,6 @@ OPERATOR_COALESCE_FALLBACK = {
|
|
|
91
86
|
TriccOperator.MORE_OR_EQUAL: -2147483648,
|
|
92
87
|
TriccOperator.LESS: 2147483647,
|
|
93
88
|
TriccOperator.LESS_OR_EQUAL: 2147483647,
|
|
94
|
-
TriccOperator.CONTAINS: "''",
|
|
95
|
-
TriccOperator.SELECTED: "''",
|
|
96
89
|
}
|
|
97
90
|
|
|
98
91
|
|
|
@@ -380,14 +373,12 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
380
373
|
|
|
381
374
|
def get_tricc_operation_expression(self, operation):
|
|
382
375
|
ref_expressions = []
|
|
383
|
-
original_references = []
|
|
384
376
|
if not hasattr(operation, "reference"):
|
|
385
377
|
return self.get_tricc_operation_operand(operation)
|
|
386
378
|
|
|
387
379
|
operator = getattr(operation, "operator", "")
|
|
388
380
|
coalesce_fallback = OPERATOR_COALESCE_FALLBACK[operator] if operator in OPERATOR_COALESCE_FALLBACK else "''"
|
|
389
381
|
for r in operation.reference:
|
|
390
|
-
original_references.append(r)
|
|
391
382
|
if isinstance(r, list):
|
|
392
383
|
r_expr = [
|
|
393
384
|
(
|
|
@@ -408,43 +399,43 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
408
399
|
# build lower level
|
|
409
400
|
if hasattr(self, f"tricc_operation_{operation.operator}"):
|
|
410
401
|
callable = getattr(self, f"tricc_operation_{operation.operator}")
|
|
411
|
-
return callable(ref_expressions
|
|
402
|
+
return callable(ref_expressions)
|
|
412
403
|
else:
|
|
413
404
|
raise NotImplementedError(
|
|
414
405
|
f"This type of opreation '{operation.operator}' is not supported in this strategy"
|
|
415
406
|
)
|
|
416
407
|
|
|
417
|
-
def tricc_operation_count(self, ref_expressions
|
|
408
|
+
def tricc_operation_count(self, ref_expressions):
|
|
418
409
|
return f"count-selected({self.clean_coalesce(ref_expressions[0])})"
|
|
419
410
|
|
|
420
|
-
def tricc_operation_multiplied(self, ref_expressions
|
|
421
|
-
return "*".join(map(str,
|
|
411
|
+
def tricc_operation_multiplied(self, ref_expressions):
|
|
412
|
+
return "*".join(map(str,ref_expressions))
|
|
422
413
|
|
|
423
|
-
def tricc_operation_divided(self, ref_expressions
|
|
414
|
+
def tricc_operation_divided(self, ref_expressions):
|
|
424
415
|
return f"{ref_expressions[0]} div {ref_expressions[1]}"
|
|
425
416
|
|
|
426
|
-
def tricc_operation_modulo(self, ref_expressions
|
|
417
|
+
def tricc_operation_modulo(self, ref_expressions):
|
|
427
418
|
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
428
419
|
|
|
429
|
-
def tricc_operation_coalesce(self, ref_expressions
|
|
420
|
+
def tricc_operation_coalesce(self, ref_expressions):
|
|
430
421
|
return f"coalesce({','.join(map(self.clean_coalesce, ref_expressions))})"
|
|
431
422
|
|
|
432
|
-
def tricc_operation_module(self, ref_expressions
|
|
423
|
+
def tricc_operation_module(self, ref_expressions):
|
|
433
424
|
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
434
425
|
|
|
435
|
-
def tricc_operation_minus(self, ref_expressions
|
|
426
|
+
def tricc_operation_minus(self, ref_expressions):
|
|
436
427
|
if len(ref_expressions) > 1:
|
|
437
428
|
return " - ".join(map(str, ref_expressions))
|
|
438
429
|
elif len(ref_expressions) == 1:
|
|
439
430
|
return f"-{ref_expressions[0]}"
|
|
440
431
|
|
|
441
|
-
def tricc_operation_plus(self, ref_expressions
|
|
442
|
-
return " + ".join(map(str,
|
|
432
|
+
def tricc_operation_plus(self, ref_expressions):
|
|
433
|
+
return " + ".join(map(str,ref_expressions))
|
|
443
434
|
|
|
444
|
-
def tricc_operation_not(self, ref_expressions
|
|
435
|
+
def tricc_operation_not(self, ref_expressions):
|
|
445
436
|
return f"not({ref_expressions[0]})"
|
|
446
437
|
|
|
447
|
-
def tricc_operation_and(self, ref_expressions
|
|
438
|
+
def tricc_operation_and(self, ref_expressions):
|
|
448
439
|
if len(ref_expressions) == 1:
|
|
449
440
|
return ref_expressions[0]
|
|
450
441
|
if len(ref_expressions) > 1:
|
|
@@ -456,7 +447,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
456
447
|
else:
|
|
457
448
|
return "1"
|
|
458
449
|
|
|
459
|
-
def tricc_operation_or(self, ref_expressions
|
|
450
|
+
def tricc_operation_or(self, ref_expressions):
|
|
460
451
|
if len(ref_expressions) == 1:
|
|
461
452
|
return ref_expressions[0]
|
|
462
453
|
if len(ref_expressions) > 1:
|
|
@@ -468,7 +459,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
468
459
|
else:
|
|
469
460
|
return "1"
|
|
470
461
|
|
|
471
|
-
def tricc_operation_native(self, ref_expressions
|
|
462
|
+
def tricc_operation_native(self, ref_expressions):
|
|
472
463
|
|
|
473
464
|
if len(ref_expressions) > 0:
|
|
474
465
|
if ref_expressions[0].startswith(("'", "`",)):
|
|
@@ -483,82 +474,77 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
483
474
|
return "0"
|
|
484
475
|
# return f"jr:choice-name({','.join(ref_expressions[1:])})"
|
|
485
476
|
else:
|
|
486
|
-
return f"{ref_expressions[0]}({','.join(map(str,
|
|
477
|
+
return f"{ref_expressions[0]}({','.join(map(str,ref_expressions[1:]))})"
|
|
487
478
|
|
|
488
|
-
def tricc_operation_istrue(self, ref_expressions
|
|
479
|
+
def tricc_operation_istrue(self, ref_expressions):
|
|
489
480
|
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
490
481
|
return f"{ref_expressions[0]}>={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
491
482
|
else:
|
|
492
483
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
493
484
|
|
|
494
|
-
def tricc_operation_isfalse(self, ref_expressions
|
|
485
|
+
def tricc_operation_isfalse(self, ref_expressions):
|
|
495
486
|
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
496
487
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
497
488
|
else:
|
|
498
489
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
499
490
|
|
|
500
|
-
def tricc_operation_parenthesis(self, ref_expressions
|
|
491
|
+
def tricc_operation_parenthesis(self, ref_expressions):
|
|
501
492
|
return f"({ref_expressions[0]})"
|
|
502
493
|
|
|
503
|
-
def tricc_operation_selected(self, ref_expressions
|
|
494
|
+
def tricc_operation_selected(self, ref_expressions):
|
|
504
495
|
parts = []
|
|
505
496
|
for s in ref_expressions[1:]:
|
|
506
497
|
# for option with numeric value
|
|
507
|
-
if isinstance(s, str)
|
|
508
|
-
cleaned_s = s
|
|
509
|
-
elif isinstance(s, TriccNodeSelectOption):
|
|
510
|
-
cleaned_s = s.name
|
|
511
|
-
else:
|
|
512
|
-
cleaned_s = "'" + str(s) + "'"
|
|
498
|
+
cleaned_s = s if isinstance(s, str) else "'" + str(s) + "'"
|
|
513
499
|
parts.append(f"selected({self.clean_coalesce(ref_expressions[0])}, {cleaned_s})")
|
|
514
500
|
if len(parts) == 1:
|
|
515
501
|
return parts[0]
|
|
516
502
|
else:
|
|
517
503
|
return self.tricc_operation_or(parts)
|
|
518
504
|
|
|
519
|
-
def tricc_operation_more_or_equal(self, ref_expressions
|
|
505
|
+
def tricc_operation_more_or_equal(self, ref_expressions):
|
|
520
506
|
return f"{ref_expressions[0]}>={ref_expressions[1]}"
|
|
521
507
|
|
|
522
|
-
def tricc_operation_less_or_equal(self, ref_expressions
|
|
508
|
+
def tricc_operation_less_or_equal(self, ref_expressions):
|
|
523
509
|
return f"{ref_expressions[0]}<={ref_expressions[1]}"
|
|
524
510
|
|
|
525
|
-
def tricc_operation_more(self, ref_expressions
|
|
511
|
+
def tricc_operation_more(self, ref_expressions):
|
|
526
512
|
return f"{ref_expressions[0]}>{ref_expressions[1]}"
|
|
527
513
|
|
|
528
|
-
def tricc_operation_less(self, ref_expressions
|
|
514
|
+
def tricc_operation_less(self, ref_expressions):
|
|
529
515
|
return f"{ref_expressions[0]}<{ref_expressions[1]}"
|
|
530
516
|
|
|
531
|
-
def tricc_operation_between(self, ref_expressions
|
|
517
|
+
def tricc_operation_between(self, ref_expressions):
|
|
532
518
|
return f"{ref_expressions[0]}>={ref_expressions[1]} and {ref_expressions[0]} < {ref_expressions[2]}"
|
|
533
519
|
|
|
534
|
-
def tricc_operation_equal(self, ref_expressions
|
|
520
|
+
def tricc_operation_equal(self, ref_expressions):
|
|
535
521
|
return f"{ref_expressions[0]}={ref_expressions[1]}"
|
|
536
522
|
|
|
537
|
-
def tricc_operation_not_equal(self, ref_expressions
|
|
523
|
+
def tricc_operation_not_equal(self, ref_expressions):
|
|
538
524
|
return f"{ref_expressions[0]}!={ref_expressions[1]}"
|
|
539
525
|
|
|
540
|
-
def tricc_operation_isnull(self, ref_expressions
|
|
526
|
+
def tricc_operation_isnull(self, ref_expressions):
|
|
541
527
|
return f"{ref_expressions[0]}=''"
|
|
542
528
|
|
|
543
|
-
def tricc_operation_isnotnull(self, ref_expressions
|
|
529
|
+
def tricc_operation_isnotnull(self, ref_expressions):
|
|
544
530
|
return f"{ref_expressions[0]}!=''"
|
|
545
531
|
|
|
546
|
-
def tricc_operation_isnottrue(self, ref_expressions
|
|
532
|
+
def tricc_operation_isnottrue(self, ref_expressions):
|
|
547
533
|
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
548
534
|
return f"{ref_expressions[0]}<{BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
549
535
|
else:
|
|
550
536
|
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
551
537
|
|
|
552
|
-
def tricc_operation_isnotfalse(self, ref_expressions
|
|
538
|
+
def tricc_operation_isnotfalse(self, ref_expressions):
|
|
553
539
|
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
554
540
|
return f"{ref_expressions[0]}>{BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
555
541
|
else:
|
|
556
542
|
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
557
543
|
|
|
558
|
-
def tricc_operation_notexist(self, ref_expressions
|
|
544
|
+
def tricc_operation_notexist(self, ref_expressions):
|
|
559
545
|
return f"{ref_expressions[0]}=''"
|
|
560
546
|
|
|
561
|
-
def tricc_operation_case(self, ref_expressions
|
|
547
|
+
def tricc_operation_case(self, ref_expressions):
|
|
562
548
|
ifs = 0
|
|
563
549
|
parts = []
|
|
564
550
|
else_found = False
|
|
@@ -581,7 +567,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
581
567
|
exp += ")"
|
|
582
568
|
return exp
|
|
583
569
|
|
|
584
|
-
def tricc_operation_ifs(self, ref_expressions
|
|
570
|
+
def tricc_operation_ifs(self, ref_expressions):
|
|
585
571
|
ifs = 0
|
|
586
572
|
parts = []
|
|
587
573
|
else_found = False
|
|
@@ -606,19 +592,19 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
606
592
|
def get_empty_label(self):
|
|
607
593
|
return "."
|
|
608
594
|
|
|
609
|
-
def tricc_operation_if(self, ref_expressions
|
|
595
|
+
def tricc_operation_if(self, ref_expressions):
|
|
610
596
|
return f"if({ref_expressions[0]},{ref_expressions[1]},{ref_expressions[2]})"
|
|
611
597
|
|
|
612
|
-
def tricc_operation_contains(self, ref_expressions
|
|
598
|
+
def tricc_operation_contains(self, ref_expressions):
|
|
613
599
|
return f"contains({self.clean_coalesce(ref_expressions[0])}, {self.clean_coalesce(ref_expressions[1])})"
|
|
614
600
|
|
|
615
|
-
def tricc_operation_exists(self, ref_expressions
|
|
601
|
+
def tricc_operation_exists(self, ref_expressions):
|
|
616
602
|
parts = []
|
|
617
603
|
for ref in ref_expressions:
|
|
618
604
|
parts.append(self.tricc_operation_not_equal([self.tricc_operation_coalesce([ref, "''"]), "''"]))
|
|
619
605
|
return self.tricc_operation_and(parts)
|
|
620
606
|
|
|
621
|
-
def tricc_operation_cast_number(self, ref_expressions
|
|
607
|
+
def tricc_operation_cast_number(self, ref_expressions):
|
|
622
608
|
if isinstance(
|
|
623
609
|
ref_expressions[0],
|
|
624
610
|
(
|
|
@@ -645,7 +631,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
645
631
|
else:
|
|
646
632
|
return f"number({ref_expressions[0]})"
|
|
647
633
|
|
|
648
|
-
def tricc_operation_cast_integer(self, ref_expressions
|
|
634
|
+
def tricc_operation_cast_integer(self, ref_expressions):
|
|
649
635
|
if isinstance(
|
|
650
636
|
ref_expressions[0],
|
|
651
637
|
(
|
|
@@ -672,18 +658,18 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
672
658
|
else:
|
|
673
659
|
return f"int({ref_expressions[0]})"
|
|
674
660
|
|
|
675
|
-
def tricc_operation_zscore(self, ref_expressions
|
|
661
|
+
def tricc_operation_zscore(self, ref_expressions):
|
|
676
662
|
y, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
677
663
|
# return ((Math.pow((y / m), l) - 1) / (s * l));
|
|
678
664
|
return f"(pow({y} div ({m}), {ll}) -1) div (({s}) div ({ll}))"
|
|
679
665
|
|
|
680
|
-
def tricc_operation_datetime_to_decimal(self, ref_expressions
|
|
666
|
+
def tricc_operation_datetime_to_decimal(self, ref_expressions):
|
|
681
667
|
return f"decimal-date-time({ref_expressions[0]})"
|
|
682
668
|
|
|
683
|
-
def tricc_operation_round(self, ref_expressions
|
|
669
|
+
def tricc_operation_round(self, ref_expressions):
|
|
684
670
|
return f"round({ref_expressions[0]})"
|
|
685
671
|
|
|
686
|
-
def tricc_operation_izscore(self, ref_expressions
|
|
672
|
+
def tricc_operation_izscore(self, ref_expressions):
|
|
687
673
|
z, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
688
674
|
# return (m * (z*s*l-1)^(1/l));
|
|
689
675
|
return f"pow({m} * ({z} * {s} * {ll} -1), 1 div {ll})"
|
|
@@ -702,7 +688,6 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
702
688
|
# @param left part
|
|
703
689
|
# @param right part
|
|
704
690
|
def generate_xls_form_calculate(self, node, processed_nodes, stashed_nodes, calculates, **kwargs):
|
|
705
|
-
self.codesystems = kwargs.get("codesystems", {})
|
|
706
691
|
if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(
|
|
707
692
|
node,
|
|
708
693
|
processed_nodes,
|
|
@@ -739,89 +724,20 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
739
724
|
# @param r reference to be translated
|
|
740
725
|
if isinstance(r, TriccOperation):
|
|
741
726
|
return self.get_tricc_operation_expression(r)
|
|
742
|
-
elif isinstance(r, (TriccStatic, str, int, float)):
|
|
743
|
-
return get_export_name(r)
|
|
744
727
|
elif isinstance(r, TriccReference):
|
|
745
728
|
logger.warning(f"reference `{r.value}` still used in a calculate")
|
|
746
729
|
return f"${{{get_export_name(r.value)}}}"
|
|
747
|
-
|
|
730
|
+
elif isinstance(r, (TriccStatic, str, int, float)):
|
|
731
|
+
return get_export_name(r)
|
|
748
732
|
elif isinstance(r, TriccNodeSelectOption):
|
|
749
733
|
logger.debug(f"select option {r.get_name()} from {r.select.get_name()} was used as a reference")
|
|
750
734
|
return get_export_name(r)
|
|
751
|
-
elif
|
|
752
|
-
if get_export_group_required(r.activity):
|
|
753
|
-
return f"${{{get_export_group_name(r.activity)}}}"
|
|
754
|
-
else:
|
|
755
|
-
return f"({self.get_tricc_operation_expression(r.relevance)})"
|
|
756
|
-
elif isinstance(r, TriccNodeMainStart):
|
|
757
|
-
return "1"
|
|
758
|
-
elif issubclass(r.__class__, (TriccNodeInputModel, TriccNodeSelect)):
|
|
735
|
+
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
759
736
|
return f"coalesce(${{{get_export_name(r)}}},{coalesce_fallback})"
|
|
760
737
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
761
738
|
return f"${{{get_export_name(r)}}}"
|
|
762
739
|
else:
|
|
763
740
|
raise NotImplementedError(f"This type of node {r.__class__} is not supported within an operation")
|
|
764
741
|
|
|
765
|
-
def tricc_operation_concatenate(self, ref_expressions
|
|
742
|
+
def tricc_operation_concatenate(self, ref_expressions):
|
|
766
743
|
return f"concat({','.join(map(str, ref_expressions))})"
|
|
767
|
-
|
|
768
|
-
def tricc_operation_diagnosis_list(self, ref_expressions, original_references=None):
|
|
769
|
-
from tricc_oo.converters.datadictionnary import lookup_codesystems_code
|
|
770
|
-
|
|
771
|
-
parts = []
|
|
772
|
-
for i, orig_ref in enumerate(original_references):
|
|
773
|
-
expr = ref_expressions[i] if ref_expressions and i < len(ref_expressions) else None
|
|
774
|
-
|
|
775
|
-
code = None
|
|
776
|
-
# Use only original references for code lookup
|
|
777
|
-
if isinstance(orig_ref, TriccReference):
|
|
778
|
-
code = orig_ref.value
|
|
779
|
-
elif issubclass(type(orig_ref), TriccNodeBaseModel):
|
|
780
|
-
code = orig_ref.name
|
|
781
|
-
else:
|
|
782
|
-
logger.critical(f"Unexpected reference type: {type(orig_ref)}, value: {orig_ref}")
|
|
783
|
-
exit(1)
|
|
784
|
-
|
|
785
|
-
concept = lookup_codesystems_code(self.project.code_systems, code)
|
|
786
|
-
if concept:
|
|
787
|
-
display = getattr(concept, 'display', code)
|
|
788
|
-
else:
|
|
789
|
-
logger.warning(f"Diagnosis code '{code}' not found in codesystems")
|
|
790
|
-
display = code
|
|
791
|
-
|
|
792
|
-
# Always include comma after diagnosis name
|
|
793
|
-
parts.append(f"if({expr} = 1, '{display},', '')")
|
|
794
|
-
|
|
795
|
-
if parts:
|
|
796
|
-
return f"concat({','.join(parts)})"
|
|
797
|
-
return "''"
|
|
798
|
-
|
|
799
|
-
def validate(self):
|
|
800
|
-
"""Validate the generated XLS form using pyxform."""
|
|
801
|
-
try:
|
|
802
|
-
# Determine the XLS file path
|
|
803
|
-
if self.project.start_pages["main"].root.form_id is not None:
|
|
804
|
-
form_id = str(self.project.start_pages["main"].root.form_id)
|
|
805
|
-
xls_path = os.path.join(self.output_path, form_id + ".xlsx")
|
|
806
|
-
|
|
807
|
-
if not os.path.exists(xls_path):
|
|
808
|
-
logger.error(f"XLS file not found: {xls_path}")
|
|
809
|
-
return False
|
|
810
|
-
|
|
811
|
-
# Validate using pyxform
|
|
812
|
-
survey = create_survey_from_xls(xls_path)
|
|
813
|
-
xml_output = survey.to_xml()
|
|
814
|
-
|
|
815
|
-
# Check if XML was generated successfully
|
|
816
|
-
if xml_output and len(xml_output.strip()) > 0:
|
|
817
|
-
logger.info("XLSForm validation successful")
|
|
818
|
-
return True
|
|
819
|
-
else:
|
|
820
|
-
logger.error("XLSForm validation failed: Empty XML output")
|
|
821
|
-
return False
|
|
822
|
-
else:
|
|
823
|
-
logger.error("Form ID not found for validation")
|
|
824
|
-
return False
|
|
825
|
-
except Exception as e:
|
|
826
|
-
logger.error(f"XLSForm validation failed: {str(e)}")
|
|
827
|
-
return False
|
|
@@ -2,13 +2,8 @@ import datetime
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
-
import subprocess
|
|
6
|
-
import tempfile
|
|
7
|
-
import zipfile
|
|
8
5
|
import pandas as pd
|
|
9
6
|
|
|
10
|
-
from pyxform.xls2xform import convert
|
|
11
|
-
|
|
12
7
|
from tricc_oo.models.lang import SingletonLangClass
|
|
13
8
|
from tricc_oo.models.calculate import TriccNodeEnd
|
|
14
9
|
from tricc_oo.models.tricc import TriccNodeDisplayModel
|
|
@@ -621,9 +616,6 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
621
616
|
newpath = os.path.join(self.output_path, newfilename)
|
|
622
617
|
media_path = os.path.join(self.output_path, form_id + "-media")
|
|
623
618
|
|
|
624
|
-
# Track all generated XLS files for validation
|
|
625
|
-
generated_files = [newpath]
|
|
626
|
-
|
|
627
619
|
settings = {
|
|
628
620
|
"form_title": title,
|
|
629
621
|
"form_id": form_id,
|
|
@@ -640,7 +632,6 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
640
632
|
df_settings.to_excel(writer, sheet_name="settings", index=False)
|
|
641
633
|
writer.close()
|
|
642
634
|
# pause
|
|
643
|
-
logger.info("generating the task and after pause questionnaires")
|
|
644
635
|
ends = []
|
|
645
636
|
for p in self.project.pages.values():
|
|
646
637
|
p_ends = list(
|
|
@@ -669,7 +660,6 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
669
660
|
new_form_id = f"{form_id}_{clean_name(e.name)}"
|
|
670
661
|
newfilename = f"{new_form_id}.xlsx"
|
|
671
662
|
newpath = os.path.join(self.output_path, newfilename)
|
|
672
|
-
generated_files.append(newpath) # Track additional XLS files
|
|
673
663
|
settings = {
|
|
674
664
|
"form_title": title,
|
|
675
665
|
"form_id": f"{new_form_id}",
|
|
@@ -718,122 +708,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
718
708
|
shutil.move(os.path.join(media_path_tmp, file_name), media_path)
|
|
719
709
|
shutil.rmtree(media_path_tmp)
|
|
720
710
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
def execute(self):
|
|
724
|
-
"""Override execute to handle multiple output files from CHT strategy."""
|
|
725
|
-
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
726
|
-
logger.info(f"build version: {version}")
|
|
727
|
-
if "main" in self.project.start_pages:
|
|
728
|
-
self.process_base(self.project.start_pages, pages=self.project.pages, version=version)
|
|
729
|
-
else:
|
|
730
|
-
logger.critical("Main process required")
|
|
731
|
-
|
|
732
|
-
logger.info("generate the relevance based on edges")
|
|
733
|
-
|
|
734
|
-
# create relevance Expression
|
|
735
|
-
|
|
736
|
-
# create calculate Expression
|
|
737
|
-
self.process_calculate(self.project.start_pages, pages=self.project.pages)
|
|
738
|
-
logger.info("generate the export format")
|
|
739
|
-
# create calculate Expression
|
|
740
|
-
self.process_export(self.project.start_pages, pages=self.project.pages)
|
|
741
|
-
|
|
742
|
-
logger.info("print the export")
|
|
743
|
-
|
|
744
|
-
# Export returns list of generated files for CHT strategy
|
|
745
|
-
generated_files = self.export(self.project.start_pages, version=version)
|
|
746
|
-
|
|
747
|
-
logger.info("validate the output")
|
|
748
|
-
if not self.validate(generated_files):
|
|
749
|
-
logger.error("CHT validation failed - aborting build")
|
|
750
|
-
exit(1)
|
|
751
|
-
|
|
752
|
-
def validate(self, generated_files=None):
|
|
753
|
-
"""Validate the generated XLS form(s) using pyxform conversion and ODK Validate JAR."""
|
|
754
|
-
if generated_files is None:
|
|
755
|
-
# Fallback for single file validation
|
|
756
|
-
if self.project.start_pages["main"].root.form_id is not None:
|
|
757
|
-
form_id = str(self.project.start_pages["main"].root.form_id)
|
|
758
|
-
generated_files = [os.path.join(self.output_path, form_id + ".xlsx")]
|
|
759
|
-
else:
|
|
760
|
-
logger.error("Form ID not found for validation")
|
|
761
|
-
return False
|
|
762
|
-
|
|
763
|
-
# Ensure ODK Validate JAR is available
|
|
764
|
-
jar_path = self._ensure_odk_validate_jar()
|
|
765
|
-
if not jar_path:
|
|
766
|
-
logger.error("ODK Validate JAR not available, skipping CHT validation")
|
|
767
|
-
return False
|
|
768
|
-
|
|
769
|
-
all_valid = True
|
|
770
|
-
for xls_file in generated_files:
|
|
771
|
-
if not os.path.exists(xls_file):
|
|
772
|
-
logger.error(f"XLS file not found: {xls_file}")
|
|
773
|
-
all_valid = False
|
|
774
|
-
continue
|
|
775
|
-
|
|
776
|
-
try:
|
|
777
|
-
# Convert XLS to XForm using pyxform (without validation)
|
|
778
|
-
xform_path = xls_file.replace('.xlsx', '.xml')
|
|
779
|
-
convert_result = convert(
|
|
780
|
-
xlsform=xls_file,
|
|
781
|
-
validate=False, # Don't validate during conversion
|
|
782
|
-
pretty_print=True
|
|
783
|
-
)
|
|
784
|
-
xform_content = convert_result.xform
|
|
785
|
-
|
|
786
|
-
# Write XForm to temporary file for validation
|
|
787
|
-
with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as temp_file:
|
|
788
|
-
temp_file.write(xform_content)
|
|
789
|
-
temp_xform_path = temp_file.name
|
|
790
|
-
|
|
791
|
-
try:
|
|
792
|
-
# Run ODK Validate JAR on the XForm
|
|
793
|
-
result = subprocess.run(
|
|
794
|
-
["java", "-Djava.awt.headless=true", "-jar", jar_path, temp_xform_path],
|
|
795
|
-
capture_output=True,
|
|
796
|
-
text=True,
|
|
797
|
-
cwd=self.output_path
|
|
798
|
-
)
|
|
799
|
-
|
|
800
|
-
if result.returncode == 0 or "Cycle detected" in result.stderr:
|
|
801
|
-
logger.info(f"CHT XLSForm validation successful: {os.path.basename(xls_file)}")
|
|
802
|
-
else:
|
|
803
|
-
logger.error(f"CHT XLSForm validation failed for {os.path.basename(xls_file)}: {result.stderr}")
|
|
804
|
-
all_valid = False
|
|
805
|
-
|
|
806
|
-
finally:
|
|
807
|
-
# Clean up temporary XForm file
|
|
808
|
-
os.unlink(temp_xform_path)
|
|
809
|
-
|
|
810
|
-
except Exception as e:
|
|
811
|
-
logger.error(f"CHT XLSForm validation error for {os.path.basename(xls_file)}: {str(e)}")
|
|
812
|
-
all_valid = False
|
|
813
|
-
|
|
814
|
-
logger.info(f"Extracted ODK Validate JAR to {jar_path}")
|
|
815
|
-
return jar_path
|
|
816
|
-
|
|
817
|
-
def _ensure_odk_validate_jar(self):
|
|
818
|
-
"""Ensure ODK Validate JAR is available by downloading from GitHub releases."""
|
|
819
|
-
jar_path = os.path.join(os.path.dirname(__file__), "ODK_Validate.jar")
|
|
820
|
-
|
|
821
|
-
# Check if JAR already exists
|
|
822
|
-
if os.path.exists(jar_path):
|
|
823
|
-
return jar_path
|
|
824
|
-
|
|
825
|
-
# Download JAR from GitHub releases
|
|
826
|
-
jar_url = "https://github.com/getodk/validate/releases/download/v1.20.0/ODK-Validate-v1.20.0.jar"
|
|
827
|
-
try:
|
|
828
|
-
import urllib.request
|
|
829
|
-
urllib.request.urlretrieve(jar_url, jar_path)
|
|
830
|
-
logger.info(f"Downloaded ODK Validate JAR to {jar_path}")
|
|
831
|
-
return jar_path
|
|
832
|
-
except Exception as e:
|
|
833
|
-
logger.error(f"Failed to download ODK Validate JAR: {str(e)}")
|
|
834
|
-
return None
|
|
835
|
-
|
|
836
|
-
def tricc_operation_zscore(self, ref_expressions, original_references=None):
|
|
711
|
+
def tricc_operation_zscore(self, ref_expressions):
|
|
837
712
|
y, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
838
713
|
# return ((Math.pow((y / m), l) - 1) / (s * l));
|
|
839
714
|
return f"""cht:extension-lib('{
|
|
@@ -846,7 +721,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
846
721
|
self.clean_coalesce(ref_expressions[3])
|
|
847
722
|
})"""
|
|
848
723
|
|
|
849
|
-
def tricc_operation_izscore(self, ref_expressions
|
|
724
|
+
def tricc_operation_izscore(self, ref_expressions):
|
|
850
725
|
z, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
851
726
|
# return (m * (z*s*l-1)^(1/l));
|
|
852
727
|
return f"""cht:extension-lib('{
|
|
@@ -857,9 +732,9 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
857
732
|
self.clean_coalesce(ref_expressions[2])
|
|
858
733
|
} ,{
|
|
859
734
|
self.clean_coalesce(ref_expressions[3])
|
|
860
|
-
}, true"""
|
|
735
|
+
}, true)"""
|
|
861
736
|
|
|
862
|
-
def tricc_operation_drug_dosage(self, ref_expressions
|
|
737
|
+
def tricc_operation_drug_dosage(self, ref_expressions):
|
|
863
738
|
# drug name
|
|
864
739
|
# age
|
|
865
740
|
# weight
|