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.
Files changed (34) hide show
  1. {mathjson_solver-1.17.0/src/mathjson_solver.egg-info → mathjson_solver-1.18.0}/PKG-INFO +1 -1
  2. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/setup.cfg +1 -1
  3. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/setup.py +1 -1
  4. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver/__main__.py +53 -14
  5. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0/src/mathjson_solver.egg-info}/PKG-INFO +1 -1
  6. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/SOURCES.txt +1 -4
  7. mathjson_solver-1.18.0/tests/test_datetime.py +219 -0
  8. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_gail_model.py +1 -1
  9. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_state_tuples_reduce.py +67 -39
  10. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_with_pytest.py +10 -72
  11. mathjson_solver-1.17.0/tests/test_gail_model_nci_validated_no_beta.py +0 -619
  12. mathjson_solver-1.17.0/tests/test_gail_model_nci_validated_with_beta.py +0 -618
  13. mathjson_solver-1.17.0/tests/test_nci_discrete_implementation_no_beta.py +0 -480
  14. mathjson_solver-1.17.0/tests/test_nci_discrete_implementation_with_beta.py +0 -503
  15. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/LICENSE +0 -0
  16. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/README.md +0 -0
  17. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/pyproject.toml +0 -0
  18. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver/__init__.py +0 -0
  19. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/dependency_links.txt +0 -0
  20. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/src/mathjson_solver.egg-info/top_level.txt +0 -0
  21. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_TrapezoidalIntegratel.py +0 -0
  22. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_atindex.py +0 -0
  23. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_cumulative.py +0 -0
  24. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_equal.py +0 -0
  25. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_exceptions.py +0 -0
  26. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_extract_variables.py +0 -0
  27. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_findinterval.py +0 -0
  28. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_interpolate.py +0 -0
  29. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_range.py +0 -0
  30. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_reduce_simple.py +0 -0
  31. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_scalars.py +0 -0
  32. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_sets.py +0 -0
  33. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_simple_cases.py +0 -0
  34. {mathjson_solver-1.17.0 → mathjson_solver-1.18.0}/tests/test_slice.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.17.0
3
+ Version: 1.18.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = mathjson-solver
3
- version = 1.17.0
3
+ version = 1.18.0
4
4
  author = Martins Mednis
5
5
  author_email = mrt@mednis.info
6
6
  description = Utilities for MathJSON evaluation
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="mathjson-solver",
8
- version="1.17.0",
8
+ version="1.18.0",
9
9
  author="Martins Mednis",
10
10
  author_email="mrt@mednis.info",
11
11
  description="Utilities for MathJSON evaluation",
@@ -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
- # if isinstance(res, list):
303
- # l_res.append(sum([xx for xx in res[1:]]))
304
- # else:
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
- return any([f(x, c) for x in s[1][1:]])
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
- return all([f(x, c) for x in s[1][1:]])
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": lambda s: reduce(
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.17.0
3
+ Version: 1.18.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -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 test_gail_model1():
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], # Input array
30
- ["Array", 0, 0], # Initial state: [sum=0, count=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
- ["Appended",
33
- ["Appended",
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], # Input array
66
- ["Array", 0, 1], # Initial state: [sum=0, product=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
- ["Appended",
69
- ["Appended",
70
+ [
71
+ "Appended",
72
+ [
73
+ "Appended",
70
74
  ["Array"],
71
- ["Add", ["AtIndex", "accumulator", 0], "current_item"] # new_sum = old_sum + item
75
+ [
76
+ "Add",
77
+ ["AtIndex", "accumulator", 0],
78
+ "current_item",
79
+ ], # new_sum = old_sum + item
72
80
  ],
73
- ["Multiply", ["AtIndex", "accumulator", 1], "current_item"] # new_product = old_product * item
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], # Input array
96
- ["Array", 0, 0], # Initial state: [sum=0, max=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
- ["Appended",
99
- ["Appended",
110
+ [
111
+ "Appended",
112
+ [
113
+ "Appended",
100
114
  ["Array"],
101
- ["Add", ["AtIndex", "accumulator", 0], "current_item"] # new_sum = old_sum + item
115
+ [
116
+ "Add",
117
+ ["AtIndex", "accumulator", 0],
118
+ "current_item",
119
+ ], # new_sum = old_sum + item
102
120
  ],
103
- ["Max", ["Array", ["AtIndex", "accumulator", 1], "current_item"]] # new_max = max(old_max, item)
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
- ["state_result", [
128
- "Reduce",
129
- ["Array", 2, 3, 4],
130
- ["Array", 0, 1], # [sum=0, product=1]
131
- # Build new state using nested Appended pattern
132
- ["Appended",
133
- ["Appended",
134
- ["Array"],
135
- ["Add", ["AtIndex", "accumulator", 0], "current_item"] # sum
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
- ["Multiply", ["AtIndex", "accumulator", 1], "current_item"] # product
167
+ ["Variable", "accumulator"],
168
+ ["Variable", "current_item"],
169
+ ["Variable", "index"],
138
170
  ],
139
- ["Variable", "accumulator"],
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):