speclogician 0.0.0b1__py3-none-any.whl → 0.0.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.
Files changed (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
@@ -1,153 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/shell/shell_view.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- from cmd2 import Cmd, with_argparser
11
- import argparse
12
-
13
- from speclogician.presentation.ctx import RenderCtx
14
- from speclogician.presentation.api import (
15
- present_state_instance,
16
- present_spec,
17
- present_domain_model,
18
- present_artifact_container,
19
- present_instances_list,
20
- )
21
- from speclogician.utils import console
22
-
23
-
24
- def _mk_ctx(args) -> RenderCtx:
25
- return RenderCtx(
26
- compact=getattr(args, "compact", False),
27
- show_stats_only=getattr(args, "stats_only", False),
28
- show_code=not getattr(args, "no_code", False),
29
- show_ids=getattr(args, "ids", False),
30
- show_art_ids=getattr(args, "art_ids", False),
31
- )
32
-
33
-
34
- class ViewShell(Cmd):
35
- prompt = "sl:view> "
36
- allow_cli_args = False
37
-
38
- def __init__(self, parent_shell, *args, **kwargs):
39
- super().__init__(*args, **kwargs)
40
- self.parent = parent_shell
41
- self.rich = console
42
-
43
- # ---- shared flags for all view commands ----
44
- def _add_common(self, p: argparse.ArgumentParser) -> None:
45
- p.add_argument("--idx", type=int, default=0)
46
- p.add_argument("--json", action="store_true")
47
- p.add_argument("--compact", action="store_true")
48
- p.add_argument("--stats-only", dest="stats_only", action="store_true")
49
- p.add_argument("--no-code", action="store_true")
50
- p.add_argument("--ids", action="store_true")
51
- p.add_argument("--art-ids", dest="art_ids", action="store_true")
52
-
53
- def _emit(self, x, *, json_only: bool) -> None:
54
- if json_only:
55
- self.rich.print_json(json.dumps(x, indent=2, default=str))
56
- else:
57
- self.rich.print(x)
58
-
59
- def do_back(self, _arg) -> bool:
60
- """Return to main shell."""
61
- return True
62
-
63
- do_exit = do_back
64
- do_quit = do_back
65
-
66
- # ----------------------
67
- # view state/spec/domain/artifacts/instances
68
- # ----------------------
69
-
70
- state_p = argparse.ArgumentParser()
71
- _ = state_p.add_argument
72
- # common
73
- # (cmd2 doesn't support easy composition; just call helper)
74
- # We'll patch it in __init__? easiest: do it here:
75
- # (but need self, so do it in method; use a helper below instead)
76
-
77
- @with_argparser(argparse.ArgumentParser())
78
- def do_state(self, args) -> None:
79
- """View full state instance."""
80
- if self.parent.state is None:
81
- self.perror("No state loaded.")
82
- return
83
-
84
- # build parser on the fly so we can reuse helper
85
- p = argparse.ArgumentParser()
86
- self._add_common(p)
87
- a = p.parse_args(args.argv)
88
-
89
- si = self.parent.state.instances[a.idx]
90
- rctx = _mk_ctx(a)
91
- out = present_state_instance(si, ctx=rctx, json_only=a.json)
92
- self._emit(out, json_only=a.json)
93
-
94
- @with_argparser(argparse.ArgumentParser())
95
- def do_spec(self, args) -> None:
96
- if self.parent.state is None:
97
- self.perror("No state loaded.")
98
- return
99
- p = argparse.ArgumentParser()
100
- self._add_common(p)
101
- a = p.parse_args(args.argv)
102
-
103
- si = self.parent.state.instances[a.idx]
104
- rctx = _mk_ctx(a)
105
- out = present_spec(si.spec, ctx=rctx, json_only=a.json)
106
- self._emit(out, json_only=a.json)
107
-
108
- @with_argparser(argparse.ArgumentParser())
109
- def do_domain(self, args) -> None:
110
- if self.parent.state is None:
111
- self.perror("No state loaded.")
112
- return
113
- p = argparse.ArgumentParser()
114
- self._add_common(p)
115
- a = p.parse_args(args.argv)
116
-
117
- si = self.parent.state.instances[a.idx]
118
- rctx = _mk_ctx(a)
119
- out = present_domain_model(si.spec.domain_model, ctx=rctx, json_only=a.json)
120
- self._emit(out, json_only=a.json)
121
-
122
- @with_argparser(argparse.ArgumentParser())
123
- def do_artifacts(self, args) -> None:
124
- if self.parent.state is None:
125
- self.perror("No state loaded.")
126
- return
127
- p = argparse.ArgumentParser()
128
- self._add_common(p)
129
- a = p.parse_args(args.argv)
130
-
131
- si = self.parent.state.instances[a.idx]
132
- rctx = _mk_ctx(a)
133
- out = present_artifact_container(si.art_container, ctx=rctx, json_only=a.json)
134
- self._emit(out, json_only=a.json)
135
-
136
- @with_argparser(argparse.ArgumentParser())
137
- def do_instances(self, args) -> None:
138
- if self.parent.state is None:
139
- self.perror("No state loaded.")
140
- return
141
- p = argparse.ArgumentParser()
142
- # instances view doesn’t need --idx but keep flags consistent:
143
- p.add_argument("--json", action="store_true")
144
- p.add_argument("--compact", action="store_true")
145
- p.add_argument("--stats-only", dest="stats_only", action="store_true")
146
- p.add_argument("--no-code", action="store_true")
147
- p.add_argument("--ids", action="store_true")
148
- p.add_argument("--art-ids", dest="art_ids", action="store_true")
149
- a = p.parse_args(args.argv)
150
-
151
- rctx = _mk_ctx(a)
152
- out = present_instances_list(self.parent.state.instances, ctx=rctx, json_only=a.json)
153
- self._emit(out, json_only=a.json)
@@ -1,32 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/state/change_result.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Literal, TypedDict
10
-
11
- from ..state.change import StateChange
12
- from ..state.inst import StateInstance
13
- from ..state.diff import StateDiff
14
- from typing import Union
15
-
16
- class ProcessChangeError(TypedDict):
17
- ok: Literal[False]
18
- stage: Literal["apply_change", "analyze_change", "calc_diff", "refresh_stats"]
19
- error: Exception
20
- change: StateChange
21
- old_state: StateInstance
22
-
23
- class ProcessChangeSuccess(TypedDict):
24
- ok: Literal[True]
25
- stage: Literal["ok"]
26
- change: StateChange
27
- old_state: StateInstance
28
- new_state: StateInstance
29
- state_diff: StateDiff | None # this is because it could potentially be the first state instance
30
- new_num_instances: int
31
-
32
- ProcessChangeResult = Union[ProcessChangeError, ProcessChangeSuccess]
@@ -1,191 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/state/diff.py
5
- #
6
-
7
- from enum import StrEnum
8
- from typing import TypeAlias, Callable, TypeVar, Generic
9
- from pydantic import BaseModel, Field
10
-
11
- from ..modeling.domain import BaseStatus
12
- from speclogician.modeling.complement import ScenarioComplementDiff
13
-
14
-
15
- class ComparisonOutcome(StrEnum):
16
- """ When we compare the before/after values, we'll use this as the comparison outcome """
17
- UNKNOWN = "unknown"
18
- NO_CHANGE = "no_change"
19
- NO_CHANGE_GOOD = "no_change_good"
20
- NO_CHANGE_BAD = "no_change_bad"
21
- IMPROVED = "improved"
22
- DECLINED = "declined"
23
-
24
-
25
- T = TypeVar("T")
26
-
27
- ComparisonFn: TypeAlias = Callable[[T, T], ComparisonOutcome]
28
-
29
- class ValueDiff(BaseModel, Generic[T]):
30
- """ Represents a change (before → after) with an explanatory label
31
- and an interpretation of whether increases/decreases are “good”. """
32
-
33
- label : str
34
- before: T
35
- after: T
36
- comp_func : ComparisonFn[T] | None = Field(default=None, exclude=True)
37
- hint : None | dict[ComparisonOutcome, str] = None
38
-
39
- def is_different(self) -> bool:
40
- if self.comp_func:
41
- return self.comp_func(self.before, self.after) in (ComparisonOutcome.IMPROVED, ComparisonOutcome.DECLINED)
42
- return self.before != self.after
43
-
44
-
45
- # comparison functions
46
-
47
- def base_status_comp (before : BaseStatus, after : BaseStatus) -> ComparisonOutcome:
48
- """ """
49
- # TODO there's prob a nice way to do this without explicit write out...
50
- match (before, after):
51
- case (BaseStatus.UNKNOWN, BaseStatus.UNKNOWN):
52
- return ComparisonOutcome.NO_CHANGE_BAD
53
- case (BaseStatus.UNKNOWN, _):
54
- return ComparisonOutcome.IMPROVED
55
- case (BaseStatus.INVALID_IML, BaseStatus.VALID):
56
- return ComparisonOutcome.IMPROVED
57
- case (BaseStatus.INVALID_IML, BaseStatus.INVALID_IML):
58
- return ComparisonOutcome.NO_CHANGE_BAD
59
- case (BaseStatus.VALID, BaseStatus.VALID):
60
- return ComparisonOutcome.NO_CHANGE_GOOD
61
- case (BaseStatus.VALID, _):
62
- return ComparisonOutcome.DECLINED
63
- case (_, _):
64
- return ComparisonOutcome.UNKNOWN
65
-
66
- def bool_true_is_good (before : bool, after : bool) -> ComparisonOutcome:
67
- """ Compare two boolean values where `true` is good """
68
- match (before, after):
69
- case (False, False):
70
- return ComparisonOutcome.NO_CHANGE_BAD
71
- case (False, True):
72
- return ComparisonOutcome.IMPROVED
73
- case (True, False):
74
- return ComparisonOutcome.DECLINED
75
- case (True, True):
76
- return ComparisonOutcome.NO_CHANGE_GOOD
77
-
78
- def bool_true_is_bad(before: bool, after: bool) -> ComparisonOutcome:
79
- match (before, after):
80
- case (False, False):
81
- return ComparisonOutcome.NO_CHANGE_GOOD
82
- case (False, True):
83
- return ComparisonOutcome.DECLINED
84
- case (True, False):
85
- return ComparisonOutcome.IMPROVED
86
- case (True, True):
87
- return ComparisonOutcome.NO_CHANGE_BAD
88
-
89
- def numeric_increase_good(before: int | float, after: int | float) -> ComparisonOutcome:
90
- """
91
- Increase is good (e.g. matched counts).
92
- """
93
- if before == after:
94
- return ComparisonOutcome.NO_CHANGE_GOOD if before > 0 else ComparisonOutcome.NO_CHANGE
95
- elif before < after:
96
- return ComparisonOutcome.IMPROVED
97
- else:
98
- return ComparisonOutcome.DECLINED
99
-
100
-
101
- def numeric_increase_bad(before: int | float, after: int | float) -> ComparisonOutcome:
102
- """
103
- Increase is bad (e.g. errors, inconsistencies).
104
- """
105
- if before == after:
106
- return ComparisonOutcome.NO_CHANGE_BAD if before > 0 else ComparisonOutcome.NO_CHANGE_GOOD
107
- elif before < after:
108
- return ComparisonOutcome.DECLINED
109
- else:
110
- return ComparisonOutcome.IMPROVED
111
-
112
- def complement_regions_decrease_good(before: int | None, after: int | None) -> ComparisonOutcome:
113
- """
114
- Complement represents uncovered space.
115
- Smaller complement => improved coverage.
116
- """
117
- if before is None or after is None:
118
- return ComparisonOutcome.UNKNOWN
119
- if after < before:
120
- return ComparisonOutcome.IMPROVED
121
- if after > before:
122
- return ComparisonOutcome.DECLINED
123
- return ComparisonOutcome.NO_CHANGE
124
-
125
-
126
- def complement_present_comp(before: bool, after: bool) -> ComparisonOutcome:
127
- """
128
- Presence itself isn't strictly good/bad; treat as UNKNOWN unless equal.
129
- (We separately judge region count where smaller is better.)
130
- """
131
- if before == after:
132
- return ComparisonOutcome.NO_CHANGE
133
- return ComparisonOutcome.UNKNOWN
134
-
135
-
136
- class StateDiff(BaseModel):
137
- """ Statistical summary of changes between two states """
138
-
139
- # Domain model-related value diffs
140
- base_status : ValueDiff[BaseStatus] # Base status
141
- base_has_state : ValueDiff[bool] # Does the base have state defined?
142
- base_has_action : ValueDiff[bool] # Does the base have action defined?
143
-
144
- # State predicates
145
- num_state_preds_total : ValueDiff[int] #
146
- num_state_preds_matched : ValueDiff[int] #
147
- num_state_preds_valid_logic : ValueDiff[int] #
148
- num_state_preds_errored : ValueDiff[int] #
149
-
150
- # state x action predicates
151
- num_action_preds_total : ValueDiff[int] #
152
- num_action_preds_matched : ValueDiff[int] #
153
- num_action_preds_valid_logic : ValueDiff[int] #
154
- num_action_preds_errored : ValueDiff[int] #
155
-
156
- # Predicates
157
- num_preds_total : ValueDiff[int] # Total number of predicates
158
- num_preds_matched : ValueDiff[int] # Predicates matched
159
- num_preds_valid_logic : ValueDiff[int] # Number of predicates with valid logic
160
- num_preds_errored : ValueDiff[int] # Number of predicates with errors
161
-
162
- # Transitions
163
- num_trans_total : ValueDiff[int] # Total number
164
- num_trans_matched : ValueDiff[int] # Number matched
165
- num_trans_valid_logic : ValueDiff[int] # Number with valid logic
166
- num_trans_errored : ValueDiff[int] # Number of transitions with errors
167
-
168
- # Scenarios
169
- num_sc_total : ValueDiff[int] # Total number of scenarios
170
- num_sc_missing : ValueDiff[int] # Missing predicates/transitions
171
- num_sc_matched : ValueDiff[int] # Scenarios matched vs artifacts
172
- num_sc_inconsistent : ValueDiff[int] # Scenarios inconsistent
173
-
174
- # Conflicts (spec-level)
175
- num_sc_conflicted : ValueDiff[int] # Total conflicts (all kinds)
176
- num_sc_overlap : ValueDiff[int] # Overlap conflicts
177
- num_sc_consumed : ValueDiff[int] # Consumed conflicts
178
-
179
- # Test traces
180
- num_test_traces_total : ValueDiff[int] # Total number of test traces
181
- num_test_traces_logic_good : ValueDiff[int] # Valid logic
182
- num_test_traces_matched : ValueDiff[int] # Matched traces
183
-
184
- # Log traces
185
- num_log_traces_total : ValueDiff[int] # Total number of log traces
186
- num_log_traces_logic_good : ValueDiff[int] # Number of log traces with valid logic
187
- num_log_traces_matched : ValueDiff[int] # Number of matched traces
188
-
189
- scenario_comp_present: ValueDiff[bool]
190
- num_comp_regions_total: ValueDiff[int | None]
191
- scenario_comp_diff: ScenarioComplementDiff | None = None