mathjson-solver 1.17.0__tar.gz → 1.18.0__tar.gz
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.
- {mathjson_solver-1.17.0/src/mathjson_solver.egg-info → mathjson_solver-1.18.0}/PKG-INFO +1 -1
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/setup.cfg +1 -1
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/setup.py +1 -1
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver/__main__.py +53 -14
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0/src/mathjson_solver.egg-info}/PKG-INFO +1 -1
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/SOURCES.txt +1 -4
- mathjson_solver-1.18.0/tests/test_datetime.py +219 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_gail_model.py +1 -1
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_state_tuples_reduce.py +67 -39
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_with_pytest.py +10 -72
- mathjson_solver-1.17.0/tests/test_gail_model_nci_validated_no_beta.py +0 -619
- mathjson_solver-1.17.0/tests/test_gail_model_nci_validated_with_beta.py +0 -618
- mathjson_solver-1.17.0/tests/test_nci_discrete_implementation_no_beta.py +0 -480
- mathjson_solver-1.17.0/tests/test_nci_discrete_implementation_with_beta.py +0 -503
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/LICENSE +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/README.md +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/pyproject.toml +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver/__init__.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/dependency_links.txt +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/top_level.txt +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_TrapezoidalIntegratel.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_atindex.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_cumulative.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_equal.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_exceptions.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_extract_variables.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_findinterval.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_interpolate.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_range.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_reduce_simple.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_scalars.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_sets.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_simple_cases.py +0 -0
- {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_slice.py +0 -0
|
@@ -17,6 +17,22 @@ except ImportError:
|
|
|
17
17
|
NoneType = type(None)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _try_parse_datetime(value):
|
|
21
|
+
"""Try to parse a string as datetime or date. Returns original value if not parseable."""
|
|
22
|
+
if isinstance(value, (datetime.datetime, datetime.date)):
|
|
23
|
+
return value
|
|
24
|
+
if isinstance(value, str):
|
|
25
|
+
try:
|
|
26
|
+
return datetime.datetime.fromisoformat(value)
|
|
27
|
+
except ValueError:
|
|
28
|
+
pass
|
|
29
|
+
try:
|
|
30
|
+
return datetime.date.fromisoformat(value)
|
|
31
|
+
except ValueError:
|
|
32
|
+
pass
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
|
|
20
36
|
# def find_interpolation_bounds(
|
|
21
37
|
# l: list, target: int | float
|
|
22
38
|
# ) -> Union[Union[int, float], tuple[Union[int, float], Union[int, float]]]:
|
|
@@ -294,16 +310,19 @@ def create_mathjson_solver(solver_parameters):
|
|
|
294
310
|
if i == 0:
|
|
295
311
|
tmp = res
|
|
296
312
|
else:
|
|
313
|
+
# Handle datetime string + timedelta
|
|
314
|
+
if isinstance(res, datetime.timedelta):
|
|
315
|
+
tmp = _try_parse_datetime(tmp)
|
|
316
|
+
elif isinstance(tmp, datetime.timedelta):
|
|
317
|
+
res = _try_parse_datetime(res)
|
|
297
318
|
try:
|
|
298
319
|
tmp = tmp + res
|
|
299
320
|
except TypeError:
|
|
300
321
|
pass
|
|
301
322
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
# l_res.append(res)
|
|
306
|
-
|
|
323
|
+
# Convert datetime result back to string
|
|
324
|
+
if isinstance(tmp, (datetime.datetime, datetime.date)):
|
|
325
|
+
return tmp.isoformat()
|
|
307
326
|
return tmp
|
|
308
327
|
|
|
309
328
|
# tmp = 0
|
|
@@ -334,6 +353,22 @@ def create_mathjson_solver(solver_parameters):
|
|
|
334
353
|
l_res.append(res)
|
|
335
354
|
return Add(l_res)
|
|
336
355
|
|
|
356
|
+
def Subtract(s):
|
|
357
|
+
values = [f(x, c) for x in s[1:]]
|
|
358
|
+
# Convert datetime strings if we're dealing with timedelta
|
|
359
|
+
converted = []
|
|
360
|
+
for i, v in enumerate(values):
|
|
361
|
+
if isinstance(v, datetime.timedelta):
|
|
362
|
+
converted.append(v)
|
|
363
|
+
elif any(isinstance(other, datetime.timedelta) for other in values):
|
|
364
|
+
converted.append(_try_parse_datetime(v))
|
|
365
|
+
else:
|
|
366
|
+
converted.append(v)
|
|
367
|
+
result = reduce(lambda a, b: a - b, converted)
|
|
368
|
+
if isinstance(result, (datetime.datetime, datetime.date)):
|
|
369
|
+
return result.isoformat()
|
|
370
|
+
return result
|
|
371
|
+
|
|
337
372
|
def Max(s):
|
|
338
373
|
if isinstance(s[1], str):
|
|
339
374
|
return max([f(x, c) for x in f(s[1], c) if is_numeric(f(x, c))])
|
|
@@ -370,10 +405,16 @@ def create_mathjson_solver(solver_parameters):
|
|
|
370
405
|
return len([x for x in s[1][1:]])
|
|
371
406
|
|
|
372
407
|
def Any(s):
|
|
373
|
-
|
|
408
|
+
evaluated = f(s[1], c)
|
|
409
|
+
if isinstance(evaluated, list) and evaluated[0] == "Array":
|
|
410
|
+
return any([f(x, c) for x in evaluated[1:]])
|
|
411
|
+
raise ValueError("Parameter 1 must be an array.")
|
|
374
412
|
|
|
375
413
|
def All(s):
|
|
376
|
-
|
|
414
|
+
evaluated = f(s[1], c)
|
|
415
|
+
if isinstance(evaluated, list) and evaluated[0] == "Array":
|
|
416
|
+
return all([f(x, c) for x in evaluated[1:]])
|
|
417
|
+
raise ValueError("Parameter 1 must be an array.")
|
|
377
418
|
|
|
378
419
|
def Int(s):
|
|
379
420
|
try:
|
|
@@ -572,18 +613,18 @@ def create_mathjson_solver(solver_parameters):
|
|
|
572
613
|
def Strptime(s):
|
|
573
614
|
datetime_str = f(s[1], c)
|
|
574
615
|
parameters = f(s[2], c)
|
|
575
|
-
return datetime.datetime.strptime(datetime_str, parameters)
|
|
616
|
+
return datetime.datetime.strptime(datetime_str, parameters).isoformat()
|
|
576
617
|
|
|
577
618
|
def Strftime(s):
|
|
578
|
-
dt = f(s[1], c)
|
|
619
|
+
dt = _try_parse_datetime(f(s[1], c))
|
|
579
620
|
parameters = f(s[2], c)
|
|
580
621
|
return dt.strftime(parameters)
|
|
581
622
|
|
|
582
623
|
def Now(s):
|
|
583
|
-
return datetime.datetime.now()
|
|
624
|
+
return datetime.datetime.now().isoformat()
|
|
584
625
|
|
|
585
626
|
def Today(s):
|
|
586
|
-
return datetime.date.today()
|
|
627
|
+
return datetime.date.today().isoformat()
|
|
587
628
|
|
|
588
629
|
def TimeDeltaDays(s):
|
|
589
630
|
return datetime.timedelta(days=f(s[1], c))
|
|
@@ -994,9 +1035,7 @@ def create_mathjson_solver(solver_parameters):
|
|
|
994
1035
|
constructs = {
|
|
995
1036
|
"Sum": Sum,
|
|
996
1037
|
"Add": Add,
|
|
997
|
-
"Subtract":
|
|
998
|
-
lambda a, b: a - b, [f(x, c) for x in s[1:]]
|
|
999
|
-
),
|
|
1038
|
+
"Subtract": Subtract,
|
|
1000
1039
|
"Constants": Constants,
|
|
1001
1040
|
"Switch": Switch,
|
|
1002
1041
|
"StrictSwitch": StrictSwitch,
|
|
@@ -12,16 +12,13 @@ src/mathjson_solver.egg-info/top_level.txt
|
|
|
12
12
|
tests/test_TrapezoidalIntegratel.py
|
|
13
13
|
tests/test_atindex.py
|
|
14
14
|
tests/test_cumulative.py
|
|
15
|
+
tests/test_datetime.py
|
|
15
16
|
tests/test_equal.py
|
|
16
17
|
tests/test_exceptions.py
|
|
17
18
|
tests/test_extract_variables.py
|
|
18
19
|
tests/test_findinterval.py
|
|
19
20
|
tests/test_gail_model.py
|
|
20
|
-
tests/test_gail_model_nci_validated_no_beta.py
|
|
21
|
-
tests/test_gail_model_nci_validated_with_beta.py
|
|
22
21
|
tests/test_interpolate.py
|
|
23
|
-
tests/test_nci_discrete_implementation_no_beta.py
|
|
24
|
-
tests/test_nci_discrete_implementation_with_beta.py
|
|
25
22
|
tests/test_range.py
|
|
26
23
|
tests/test_reduce_simple.py
|
|
27
24
|
tests/test_scalars.py
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import pytest
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), "../src/"))
|
|
7
|
+
|
|
8
|
+
from mathjson_solver import create_solver
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestDatetimeReturnTypes:
|
|
12
|
+
"""Test that datetime functions return strings by default."""
|
|
13
|
+
|
|
14
|
+
def test_today_returns_string(self):
|
|
15
|
+
solver = create_solver({})
|
|
16
|
+
result = solver(["Today"])
|
|
17
|
+
assert isinstance(result, str)
|
|
18
|
+
assert result == datetime.date.today().isoformat()
|
|
19
|
+
|
|
20
|
+
def test_now_returns_string(self):
|
|
21
|
+
solver = create_solver({})
|
|
22
|
+
result = solver(["Now"])
|
|
23
|
+
assert isinstance(result, str)
|
|
24
|
+
# Check format is ISO datetime (contains T)
|
|
25
|
+
assert "T" in result
|
|
26
|
+
|
|
27
|
+
def test_strptime_returns_string(self):
|
|
28
|
+
solver = create_solver({})
|
|
29
|
+
result = solver(["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"])
|
|
30
|
+
assert isinstance(result, str)
|
|
31
|
+
assert result == "2025-01-10T10:05:00"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestDatetimeArithmetic:
|
|
35
|
+
"""Test datetime arithmetic with string inputs/outputs."""
|
|
36
|
+
|
|
37
|
+
def test_today_plus_timedelta_returns_string(self):
|
|
38
|
+
solver = create_solver({})
|
|
39
|
+
result = solver(["Add", ["Today"], ["TimeDeltaDays", 7]])
|
|
40
|
+
assert isinstance(result, str)
|
|
41
|
+
expected = (datetime.date.today() + datetime.timedelta(days=7)).isoformat()
|
|
42
|
+
# Result includes time component since date becomes datetime after arithmetic
|
|
43
|
+
assert result.startswith(expected) or "T" in result
|
|
44
|
+
|
|
45
|
+
def test_today_minus_timedelta_returns_string(self):
|
|
46
|
+
solver = create_solver({})
|
|
47
|
+
result = solver(["Subtract", ["Today"], ["TimeDeltaDays", 3]])
|
|
48
|
+
assert isinstance(result, str)
|
|
49
|
+
|
|
50
|
+
def test_now_plus_timedelta_returns_string(self):
|
|
51
|
+
solver = create_solver({})
|
|
52
|
+
result = solver(["Add", ["Now"], ["TimeDeltaHours", 2]])
|
|
53
|
+
assert isinstance(result, str)
|
|
54
|
+
assert "T" in result
|
|
55
|
+
|
|
56
|
+
def test_strptime_plus_timedelta_days(self):
|
|
57
|
+
solver = create_solver({})
|
|
58
|
+
result = solver([
|
|
59
|
+
"Strftime",
|
|
60
|
+
["Add", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], ["TimeDeltaDays", 3]],
|
|
61
|
+
"%d",
|
|
62
|
+
])
|
|
63
|
+
assert result == "13"
|
|
64
|
+
|
|
65
|
+
def test_strptime_minus_timedelta_days(self):
|
|
66
|
+
solver = create_solver({})
|
|
67
|
+
result = solver([
|
|
68
|
+
"Strftime",
|
|
69
|
+
["Subtract", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], ["TimeDeltaDays", 3]],
|
|
70
|
+
"%d",
|
|
71
|
+
])
|
|
72
|
+
assert result == "07"
|
|
73
|
+
|
|
74
|
+
def test_strptime_plus_timedelta_minutes(self):
|
|
75
|
+
solver = create_solver({})
|
|
76
|
+
result = solver([
|
|
77
|
+
"Strftime",
|
|
78
|
+
["Add", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], ["TimeDeltaMinutes", 5]],
|
|
79
|
+
"%M",
|
|
80
|
+
])
|
|
81
|
+
assert result == "10"
|
|
82
|
+
|
|
83
|
+
def test_strptime_plus_timedelta_hours(self):
|
|
84
|
+
solver = create_solver({})
|
|
85
|
+
result = solver([
|
|
86
|
+
"Strftime",
|
|
87
|
+
["Add", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], ["TimeDeltaHours", 2]],
|
|
88
|
+
"%H",
|
|
89
|
+
])
|
|
90
|
+
assert result == "12"
|
|
91
|
+
|
|
92
|
+
def test_strptime_plus_timedelta_weeks(self):
|
|
93
|
+
solver = create_solver({})
|
|
94
|
+
result = solver([
|
|
95
|
+
"Strftime",
|
|
96
|
+
["Add", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], ["TimeDeltaWeeks", 1]],
|
|
97
|
+
"%d",
|
|
98
|
+
])
|
|
99
|
+
assert result == "17"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestStrftime:
|
|
103
|
+
"""Test Strftime with various inputs."""
|
|
104
|
+
|
|
105
|
+
def test_strftime_with_strptime(self):
|
|
106
|
+
solver = create_solver({})
|
|
107
|
+
result = solver(["Strftime", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], "%Y"])
|
|
108
|
+
assert result == "2025"
|
|
109
|
+
|
|
110
|
+
def test_strftime_with_today(self):
|
|
111
|
+
solver = create_solver({})
|
|
112
|
+
result = solver(["Strftime", ["Today"], "%Y"])
|
|
113
|
+
assert result == str(datetime.date.today().year)
|
|
114
|
+
|
|
115
|
+
def test_strftime_with_now(self):
|
|
116
|
+
solver = create_solver({})
|
|
117
|
+
result = solver(["Strftime", ["Now"], "%Y"])
|
|
118
|
+
assert result == str(datetime.datetime.now().year)
|
|
119
|
+
|
|
120
|
+
def test_strftime_accepts_string_datetime(self):
|
|
121
|
+
"""Test that Strftime can parse ISO string input."""
|
|
122
|
+
solver = create_solver({})
|
|
123
|
+
result = solver(["Strftime", "2025-06-15T14:30:00", "%Y-%m-%d"])
|
|
124
|
+
assert result == "2025-06-15"
|
|
125
|
+
|
|
126
|
+
def test_strftime_accepts_string_date(self):
|
|
127
|
+
"""Test that Strftime can parse ISO date string input."""
|
|
128
|
+
solver = create_solver({})
|
|
129
|
+
result = solver(["Strftime", "2025-06-15", "%Y-%m-%d"])
|
|
130
|
+
assert result == "2025-06-15"
|
|
131
|
+
|
|
132
|
+
def test_strftime_full_format(self):
|
|
133
|
+
solver = create_solver({})
|
|
134
|
+
result = solver(["Strftime", "2025-06-15T14:30:45", "%Y-%m-%d %H:%M:%S"])
|
|
135
|
+
assert result == "2025-06-15 14:30:45"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestDatetimeChaining:
|
|
139
|
+
"""Test complex datetime operation chains."""
|
|
140
|
+
|
|
141
|
+
def test_add_then_strftime(self):
|
|
142
|
+
solver = create_solver({})
|
|
143
|
+
result = solver([
|
|
144
|
+
"Strftime",
|
|
145
|
+
["Add", ["Today"], ["TimeDeltaDays", 10]],
|
|
146
|
+
"%Y-%m-%d",
|
|
147
|
+
])
|
|
148
|
+
expected = (datetime.date.today() + datetime.timedelta(days=10)).strftime("%Y-%m-%d")
|
|
149
|
+
assert result == expected
|
|
150
|
+
|
|
151
|
+
def test_subtract_then_strftime(self):
|
|
152
|
+
solver = create_solver({})
|
|
153
|
+
result = solver([
|
|
154
|
+
"Strftime",
|
|
155
|
+
["Subtract", ["Now"], ["TimeDeltaHours", 5]],
|
|
156
|
+
"%H",
|
|
157
|
+
])
|
|
158
|
+
# Just verify it returns a valid hour string
|
|
159
|
+
assert result.isdigit()
|
|
160
|
+
assert 0 <= int(result) <= 23
|
|
161
|
+
|
|
162
|
+
def test_multiple_timedelta_additions(self):
|
|
163
|
+
solver = create_solver({})
|
|
164
|
+
result = solver([
|
|
165
|
+
"Strftime",
|
|
166
|
+
["Add", ["Add", ["Strptime", "2025-01-01T00:00", "%Y-%m-%dT%H:%M"], ["TimeDeltaDays", 1]], ["TimeDeltaHours", 12]],
|
|
167
|
+
"%Y-%m-%d %H:%M",
|
|
168
|
+
])
|
|
169
|
+
assert result == "2025-01-02 12:00"
|
|
170
|
+
|
|
171
|
+
def test_datetime_in_conditional(self):
|
|
172
|
+
"""Test using datetime comparison in If statement."""
|
|
173
|
+
solver = create_solver({})
|
|
174
|
+
# This tests that string datetimes work in other constructs
|
|
175
|
+
today = solver(["Today"])
|
|
176
|
+
assert isinstance(today, str)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestTimeDeltaFunctions:
|
|
180
|
+
"""Test TimeDelta functions."""
|
|
181
|
+
|
|
182
|
+
def test_timedelta_days(self):
|
|
183
|
+
solver = create_solver({})
|
|
184
|
+
result = solver(["Add", "2025-01-10", ["TimeDeltaDays", 5]])
|
|
185
|
+
assert "2025-01-15" in result
|
|
186
|
+
|
|
187
|
+
def test_timedelta_hours(self):
|
|
188
|
+
solver = create_solver({})
|
|
189
|
+
result = solver(["Add", "2025-01-10T10:00:00", ["TimeDeltaHours", 3]])
|
|
190
|
+
assert "13:00:00" in result
|
|
191
|
+
|
|
192
|
+
def test_timedelta_minutes(self):
|
|
193
|
+
solver = create_solver({})
|
|
194
|
+
result = solver(["Add", "2025-01-10T10:00:00", ["TimeDeltaMinutes", 30]])
|
|
195
|
+
assert "10:30:00" in result
|
|
196
|
+
|
|
197
|
+
def test_timedelta_weeks(self):
|
|
198
|
+
solver = create_solver({})
|
|
199
|
+
result = solver(["Add", "2025-01-10", ["TimeDeltaWeeks", 2]])
|
|
200
|
+
assert "2025-01-24" in result
|
|
201
|
+
|
|
202
|
+
def test_negative_timedelta(self):
|
|
203
|
+
solver = create_solver({})
|
|
204
|
+
result = solver(["Add", "2025-01-10", ["TimeDeltaDays", -5]])
|
|
205
|
+
assert "2025-01-05" in result
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestDatetimeWithVariables:
|
|
209
|
+
"""Test datetime operations with variables."""
|
|
210
|
+
|
|
211
|
+
def test_today_in_variable(self):
|
|
212
|
+
solver = create_solver({})
|
|
213
|
+
result = solver(["Constants", ["d", ["Today"]], ["Strftime", "d", "%Y-%m-%d"]])
|
|
214
|
+
assert result == datetime.date.today().strftime("%Y-%m-%d")
|
|
215
|
+
|
|
216
|
+
def test_datetime_string_in_parameters(self):
|
|
217
|
+
solver = create_solver({"start_date": "2025-06-01"})
|
|
218
|
+
result = solver(["Strftime", ["Add", "start_date", ["TimeDeltaDays", 10]], "%Y-%m-%d"])
|
|
219
|
+
assert result == "2025-06-11"
|
|
@@ -18,7 +18,7 @@ from mathjson_solver import create_solver
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@pytest.mark.skipif(not NUMPY_AVAILABLE, reason="NumPy not available")
|
|
21
|
-
def
|
|
21
|
+
def test_1989_gail_model1():
|
|
22
22
|
parameters = {
|
|
23
23
|
"AGEMEN": 1, # Age at menarche (12-13 years)
|
|
24
24
|
"NBIOPS": 1, # 1 previous biopsy
|
|
@@ -26,19 +26,21 @@ def test_simple_state_tuple_accumulation():
|
|
|
26
26
|
|
|
27
27
|
expression = [
|
|
28
28
|
"Reduce",
|
|
29
|
-
["Array", 1, 2, 3, 4],
|
|
30
|
-
["Array", 0, 0],
|
|
29
|
+
["Array", 1, 2, 3, 4], # Input array
|
|
30
|
+
["Array", 0, 0], # Initial state: [sum=0, count=0]
|
|
31
31
|
# Build a new array with updated values
|
|
32
|
-
[
|
|
33
|
-
|
|
32
|
+
[
|
|
33
|
+
"Appended",
|
|
34
|
+
[
|
|
35
|
+
"Appended",
|
|
34
36
|
["Array"], # Start with empty array
|
|
35
|
-
["Add", ["AtIndex", "accumulator", 0], "current_item"] # Add new sum
|
|
37
|
+
["Add", ["AtIndex", "accumulator", 0], "current_item"], # Add new sum
|
|
36
38
|
],
|
|
37
|
-
["Add", ["AtIndex", "accumulator", 1], 1] # Add new count
|
|
39
|
+
["Add", ["AtIndex", "accumulator", 1], 1], # Add new count
|
|
38
40
|
],
|
|
39
41
|
["Variable", "accumulator"],
|
|
40
42
|
["Variable", "current_item"],
|
|
41
|
-
["Variable", "index"]
|
|
43
|
+
["Variable", "index"],
|
|
42
44
|
]
|
|
43
45
|
|
|
44
46
|
solver = create_solver({})
|
|
@@ -62,19 +64,29 @@ def test_running_sum_and_product():
|
|
|
62
64
|
|
|
63
65
|
expression = [
|
|
64
66
|
"Reduce",
|
|
65
|
-
["Array", 2, 3, 4],
|
|
66
|
-
["Array", 0, 1],
|
|
67
|
+
["Array", 2, 3, 4], # Input array
|
|
68
|
+
["Array", 0, 1], # Initial state: [sum=0, product=1]
|
|
67
69
|
# Build new state using nested Appended pattern
|
|
68
|
-
[
|
|
69
|
-
|
|
70
|
+
[
|
|
71
|
+
"Appended",
|
|
72
|
+
[
|
|
73
|
+
"Appended",
|
|
70
74
|
["Array"],
|
|
71
|
-
[
|
|
75
|
+
[
|
|
76
|
+
"Add",
|
|
77
|
+
["AtIndex", "accumulator", 0],
|
|
78
|
+
"current_item",
|
|
79
|
+
], # new_sum = old_sum + item
|
|
72
80
|
],
|
|
73
|
-
[
|
|
81
|
+
[
|
|
82
|
+
"Multiply",
|
|
83
|
+
["AtIndex", "accumulator", 1],
|
|
84
|
+
"current_item",
|
|
85
|
+
], # new_product = old_product * item
|
|
74
86
|
],
|
|
75
87
|
["Variable", "accumulator"],
|
|
76
88
|
["Variable", "current_item"],
|
|
77
|
-
["Variable", "index"]
|
|
89
|
+
["Variable", "index"],
|
|
78
90
|
]
|
|
79
91
|
|
|
80
92
|
solver = create_solver({})
|
|
@@ -92,19 +104,28 @@ def test_simple_dual_accumulation():
|
|
|
92
104
|
|
|
93
105
|
expression = [
|
|
94
106
|
"Reduce",
|
|
95
|
-
["Array", 3, 1, 4, 2],
|
|
96
|
-
["Array", 0, 0],
|
|
107
|
+
["Array", 3, 1, 4, 2], # Input array
|
|
108
|
+
["Array", 0, 0], # Initial state: [sum=0, max=0]
|
|
97
109
|
# Build new state using nested Appended pattern
|
|
98
|
-
[
|
|
99
|
-
|
|
110
|
+
[
|
|
111
|
+
"Appended",
|
|
112
|
+
[
|
|
113
|
+
"Appended",
|
|
100
114
|
["Array"],
|
|
101
|
-
[
|
|
115
|
+
[
|
|
116
|
+
"Add",
|
|
117
|
+
["AtIndex", "accumulator", 0],
|
|
118
|
+
"current_item",
|
|
119
|
+
], # new_sum = old_sum + item
|
|
102
120
|
],
|
|
103
|
-
[
|
|
121
|
+
[
|
|
122
|
+
"Max",
|
|
123
|
+
["Array", ["AtIndex", "accumulator", 1], "current_item"],
|
|
124
|
+
], # new_max = max(old_max, item)
|
|
104
125
|
],
|
|
105
126
|
["Variable", "accumulator"],
|
|
106
127
|
["Variable", "current_item"],
|
|
107
|
-
["Variable", "index"]
|
|
128
|
+
["Variable", "index"],
|
|
108
129
|
]
|
|
109
130
|
|
|
110
131
|
solver = create_solver({})
|
|
@@ -122,31 +143,38 @@ def test_extract_final_value_from_state_tuple():
|
|
|
122
143
|
|
|
123
144
|
expression = [
|
|
124
145
|
"Constants",
|
|
125
|
-
|
|
126
146
|
# Use Reduce to accumulate state tuple [sum, product]
|
|
127
|
-
[
|
|
128
|
-
"
|
|
129
|
-
[
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
[
|
|
148
|
+
"state_result",
|
|
149
|
+
[
|
|
150
|
+
"Reduce",
|
|
151
|
+
["Array", 2, 3, 4],
|
|
152
|
+
["Array", 0, 1], # [sum=0, product=1]
|
|
153
|
+
# Build new state using nested Appended pattern
|
|
154
|
+
[
|
|
155
|
+
"Appended",
|
|
156
|
+
[
|
|
157
|
+
"Appended",
|
|
158
|
+
["Array"],
|
|
159
|
+
["Add", ["AtIndex", "accumulator", 0], "current_item"], # sum
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
"Multiply",
|
|
163
|
+
["AtIndex", "accumulator", 1],
|
|
164
|
+
"current_item",
|
|
165
|
+
], # product
|
|
136
166
|
],
|
|
137
|
-
["
|
|
167
|
+
["Variable", "accumulator"],
|
|
168
|
+
["Variable", "current_item"],
|
|
169
|
+
["Variable", "index"],
|
|
138
170
|
],
|
|
139
|
-
|
|
140
|
-
["Variable", "current_item"],
|
|
141
|
-
["Variable", "index"]
|
|
142
|
-
]],
|
|
143
|
-
|
|
171
|
+
],
|
|
144
172
|
# Extract just the sum (index 0) - this is what we'll do for NCI risk
|
|
145
|
-
["AtIndex", "state_result", 0]
|
|
173
|
+
["AtIndex", "state_result", 0],
|
|
146
174
|
]
|
|
147
175
|
|
|
148
176
|
solver = create_solver({})
|
|
149
177
|
result = solver(expression)
|
|
150
178
|
|
|
151
179
|
# Should return just the sum: 2 + 3 + 4 = 9
|
|
152
|
-
assert result == 9, f"Expected 9, got {result}"
|
|
180
|
+
assert result == 9, f"Expected 9, got {result}"
|
|
@@ -179,6 +179,16 @@ from mathjson_solver import create_solver
|
|
|
179
179
|
["Sum", ["Map", ["Array", 1, 2, 3, 4, 1, 1, 0, 1], ["GreaterEqual"], 2]],
|
|
180
180
|
3,
|
|
181
181
|
),
|
|
182
|
+
(
|
|
183
|
+
{},
|
|
184
|
+
["All", ["Map", ["Array", 1, 2, 3, 4, 1, 1, 0, 1], ["GreaterEqual"], 2]],
|
|
185
|
+
False,
|
|
186
|
+
),
|
|
187
|
+
(
|
|
188
|
+
{},
|
|
189
|
+
["Any", ["Map", ["Array", 1, 2, 3, 4, 1, 1, 0, 1], ["GreaterEqual"], 2]],
|
|
190
|
+
True,
|
|
191
|
+
),
|
|
182
192
|
({}, ["Sum", ["Array", True, False, True, False, False]], 2),
|
|
183
193
|
# ["HasMatchingSublist", list, required_match_count, position, contiguous, function, more parameters]
|
|
184
194
|
(
|
|
@@ -259,78 +269,6 @@ from mathjson_solver import create_solver
|
|
|
259
269
|
],
|
|
260
270
|
False,
|
|
261
271
|
),
|
|
262
|
-
(
|
|
263
|
-
{},
|
|
264
|
-
["Strftime", ["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"], "%Y"],
|
|
265
|
-
"2025",
|
|
266
|
-
),
|
|
267
|
-
({}, ["Strftime", ["Today"], "%Y"], "2025"),
|
|
268
|
-
({}, ["Strftime", ["Now"], "%Y"], "2025"),
|
|
269
|
-
(
|
|
270
|
-
{},
|
|
271
|
-
[
|
|
272
|
-
"Strftime",
|
|
273
|
-
[
|
|
274
|
-
"Add",
|
|
275
|
-
["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"],
|
|
276
|
-
["TimeDeltaDays", 3],
|
|
277
|
-
],
|
|
278
|
-
"%d",
|
|
279
|
-
],
|
|
280
|
-
"13",
|
|
281
|
-
),
|
|
282
|
-
(
|
|
283
|
-
{},
|
|
284
|
-
[
|
|
285
|
-
"Strftime",
|
|
286
|
-
[
|
|
287
|
-
"Subtract",
|
|
288
|
-
["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"],
|
|
289
|
-
["TimeDeltaDays", 3],
|
|
290
|
-
],
|
|
291
|
-
"%d",
|
|
292
|
-
],
|
|
293
|
-
"07",
|
|
294
|
-
),
|
|
295
|
-
(
|
|
296
|
-
{},
|
|
297
|
-
[
|
|
298
|
-
"Strftime",
|
|
299
|
-
[
|
|
300
|
-
"Add",
|
|
301
|
-
["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"],
|
|
302
|
-
["TimeDeltaMinutes", 5],
|
|
303
|
-
],
|
|
304
|
-
"%M",
|
|
305
|
-
],
|
|
306
|
-
"10",
|
|
307
|
-
),
|
|
308
|
-
(
|
|
309
|
-
{},
|
|
310
|
-
[
|
|
311
|
-
"Strftime",
|
|
312
|
-
[
|
|
313
|
-
"Add",
|
|
314
|
-
["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"],
|
|
315
|
-
["TimeDeltaHours", 2],
|
|
316
|
-
],
|
|
317
|
-
"%H",
|
|
318
|
-
],
|
|
319
|
-
"12",
|
|
320
|
-
),
|
|
321
|
-
(
|
|
322
|
-
{},
|
|
323
|
-
[
|
|
324
|
-
"Strftime",
|
|
325
|
-
[
|
|
326
|
-
"Add",
|
|
327
|
-
["Strptime", "2025-01-10T10:05", "%Y-%m-%dT%H:%M"],
|
|
328
|
-
["TimeDeltaWeeks", 1],
|
|
329
|
-
],
|
|
330
|
-
"%d",
|
|
331
|
-
],
|
|
332
|
-
"17",
|
|
333
|
-
),
|
|
334
272
|
],
|
|
335
273
|
)
|
|
336
274
|
def test_solver_simple(parameters, expression, expected_result):
|