speclogician 0.0.0b1__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.
- speclogician/__init__.py +0 -0
- speclogician/commands/__init__.py +15 -0
- speclogician/commands/cmd_ch.py +616 -0
- speclogician/commands/cmd_find.py +256 -0
- speclogician/commands/cmd_view.py +202 -0
- speclogician/commands/runner.py +149 -0
- speclogician/commands/utils.py +101 -0
- speclogician/data/__init__.py +0 -0
- speclogician/data/artifact.py +63 -0
- speclogician/data/container.py +402 -0
- speclogician/data/mapping.py +88 -0
- speclogician/data/refs.py +24 -0
- speclogician/data/traces.py +26 -0
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +278 -0
- speclogician/demos/loader.py +135 -0
- speclogician/demos/model.py +27 -0
- speclogician/demos/runner.py +51 -0
- speclogician/logic/__init__.py +11 -0
- speclogician/logic/api/__init__.py +29 -0
- speclogician/logic/api/client.py +606 -0
- speclogician/logic/api/decomp.py +67 -0
- speclogician/logic/api/scenario.py +102 -0
- speclogician/logic/api/traces.py +59 -0
- speclogician/logic/lib/__init__.py +19 -0
- speclogician/logic/lib/complement.py +107 -0
- speclogician/logic/lib/domain_model.py +59 -0
- speclogician/logic/lib/predicates.py +151 -0
- speclogician/logic/lib/scenarios.py +369 -0
- speclogician/logic/lib/traces.py +114 -0
- speclogician/logic/lib/transitions.py +104 -0
- speclogician/logic/main.py +246 -0
- speclogician/logic/strings.py +194 -0
- speclogician/logic/utils.py +135 -0
- speclogician/main.py +139 -0
- speclogician/modeling/__init__.py +31 -0
- speclogician/modeling/complement.py +104 -0
- speclogician/modeling/component.py +71 -0
- speclogician/modeling/conflict.py +26 -0
- speclogician/modeling/domain.py +349 -0
- speclogician/modeling/predicates.py +59 -0
- speclogician/modeling/scenario.py +162 -0
- speclogician/modeling/spec.py +306 -0
- speclogician/modeling/spec_stats.py +39 -0
- speclogician/presentation/__init__.py +0 -0
- speclogician/presentation/api.py +244 -0
- speclogician/presentation/builders/__init__.py +0 -0
- speclogician/presentation/builders/_links.py +44 -0
- speclogician/presentation/builders/container.py +53 -0
- speclogician/presentation/builders/data_artifact.py +42 -0
- speclogician/presentation/builders/domain.py +54 -0
- speclogician/presentation/builders/instances_list.py +38 -0
- speclogician/presentation/builders/predicate.py +51 -0
- speclogician/presentation/builders/recommendations.py +41 -0
- speclogician/presentation/builders/scenario.py +41 -0
- speclogician/presentation/builders/scenario_complement.py +82 -0
- speclogician/presentation/builders/smart_find.py +39 -0
- speclogician/presentation/builders/spec.py +39 -0
- speclogician/presentation/builders/state_diff.py +150 -0
- speclogician/presentation/builders/state_instance.py +42 -0
- speclogician/presentation/builders/state_instance_summary.py +84 -0
- speclogician/presentation/builders/trace.py +58 -0
- speclogician/presentation/ctx.py +38 -0
- speclogician/presentation/models/__init__.py +0 -0
- speclogician/presentation/models/container.py +44 -0
- speclogician/presentation/models/data_artifact.py +33 -0
- speclogician/presentation/models/domain.py +50 -0
- speclogician/presentation/models/instances_list.py +23 -0
- speclogician/presentation/models/predicate.py +60 -0
- speclogician/presentation/models/recommendations.py +34 -0
- speclogician/presentation/models/scenario.py +31 -0
- speclogician/presentation/models/scenario_complement.py +40 -0
- speclogician/presentation/models/smart_find.py +34 -0
- speclogician/presentation/models/spec.py +32 -0
- speclogician/presentation/models/state_diff.py +34 -0
- speclogician/presentation/models/state_instance.py +31 -0
- speclogician/presentation/models/state_instance_summary.py +102 -0
- speclogician/presentation/models/trace.py +42 -0
- speclogician/presentation/preview/__init__.py +13 -0
- speclogician/presentation/preview/cli.py +50 -0
- speclogician/presentation/preview/fixtures/__init__.py +205 -0
- speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
- speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
- speclogician/presentation/preview/fixtures/domain_model.py +162 -0
- speclogician/presentation/preview/fixtures/instances_list.py +162 -0
- speclogician/presentation/preview/fixtures/predicate.py +184 -0
- speclogician/presentation/preview/fixtures/scenario.py +84 -0
- speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
- speclogician/presentation/preview/fixtures/smart_find.py +140 -0
- speclogician/presentation/preview/fixtures/spec.py +95 -0
- speclogician/presentation/preview/fixtures/state_diff.py +158 -0
- speclogician/presentation/preview/fixtures/state_instance.py +128 -0
- speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
- speclogician/presentation/preview/fixtures/trace.py +206 -0
- speclogician/presentation/preview/registry.py +42 -0
- speclogician/presentation/renderers/__init__.py +24 -0
- speclogician/presentation/renderers/container.py +136 -0
- speclogician/presentation/renderers/data_artifact.py +144 -0
- speclogician/presentation/renderers/domain.py +123 -0
- speclogician/presentation/renderers/instances_list.py +120 -0
- speclogician/presentation/renderers/predicate.py +180 -0
- speclogician/presentation/renderers/recommendations.py +90 -0
- speclogician/presentation/renderers/scenario.py +94 -0
- speclogician/presentation/renderers/scenario_complement.py +59 -0
- speclogician/presentation/renderers/smart_find.py +307 -0
- speclogician/presentation/renderers/spec.py +105 -0
- speclogician/presentation/renderers/state_diff.py +102 -0
- speclogician/presentation/renderers/state_instance.py +82 -0
- speclogician/presentation/renderers/state_instance_summary.py +143 -0
- speclogician/presentation/renderers/trace.py +122 -0
- speclogician/py.typed +0 -0
- speclogician/shell/app.py +170 -0
- speclogician/shell/shell_ch.py +263 -0
- speclogician/shell/shell_view.py +153 -0
- speclogician/state/__init__.py +0 -0
- speclogician/state/change.py +428 -0
- speclogician/state/change_result.py +32 -0
- speclogician/state/diff.py +191 -0
- speclogician/state/inst.py +574 -0
- speclogician/state/recommendation.py +13 -0
- speclogician/state/recommender.py +577 -0
- speclogician/state/state.py +465 -0
- speclogician/state/state_stats.py +133 -0
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +257 -0
- speclogician/tui/app.tcss +160 -0
- speclogician/tui/demo.py +45 -0
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +454 -0
- speclogician/tui/splash_screen.py +51 -0
- speclogician/tui/stats_screen.py +125 -0
- speclogician/utils/__init__.py +78 -0
- speclogician/utils/load.py +166 -0
- speclogician/utils/prompt.md +325 -0
- speclogician/utils/testing.py +151 -0
- speclogician-0.0.0b1.dist-info/METADATA +116 -0
- speclogician-0.0.0b1.dist-info/RECORD +139 -0
- speclogician-0.0.0b1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/spec.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
from .domain import DomainModel
|
|
12
|
+
from .complement import ScenarioComplement
|
|
13
|
+
from .scenario import Scenario, ScenarioDelta
|
|
14
|
+
from .conflict import ScenarioConflict
|
|
15
|
+
|
|
16
|
+
def _scenario_guard_iml(scn) -> str:
|
|
17
|
+
"""
|
|
18
|
+
given predicates are assumed: pred(s)
|
|
19
|
+
when predicates are assumed: pred(s)(a) or pred(s, a) style; we generate pred s a.
|
|
20
|
+
"""
|
|
21
|
+
parts: list[str] = []
|
|
22
|
+
|
|
23
|
+
for p in (scn.given or []):
|
|
24
|
+
p = (p or "").strip()
|
|
25
|
+
if p:
|
|
26
|
+
parts.append(f"{p} s")
|
|
27
|
+
|
|
28
|
+
for p in (scn.when or []):
|
|
29
|
+
p = (p or "").strip()
|
|
30
|
+
if p:
|
|
31
|
+
parts.append(f"{p} s a")
|
|
32
|
+
|
|
33
|
+
return " && ".join(parts) if parts else "true"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _scenario_then_iml(scn) -> str:
|
|
37
|
+
"""
|
|
38
|
+
then is a list of transition function names.
|
|
39
|
+
Each transition is assumed: trans(s)(a) -> state; we generate trans s a.
|
|
40
|
+
Applied sequentially.
|
|
41
|
+
"""
|
|
42
|
+
thens = [(t or "").strip() for t in (scn.then or []) if (t or "").strip()]
|
|
43
|
+
if not thens:
|
|
44
|
+
return "s"
|
|
45
|
+
|
|
46
|
+
# Apply transitions in order:
|
|
47
|
+
# let s1 = t1 s a in let s2 = t2 s1 a in s2
|
|
48
|
+
lines: list[str] = []
|
|
49
|
+
cur = "s"
|
|
50
|
+
for i, tname in enumerate(thens, start=1):
|
|
51
|
+
nxt = f"s{i}"
|
|
52
|
+
lines.append(f"let {nxt} = {tname} {cur} a in")
|
|
53
|
+
cur = nxt
|
|
54
|
+
lines.append(cur)
|
|
55
|
+
return "\n ".join(lines)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Spec(BaseModel):
|
|
59
|
+
""" Spec is comprised of the Domain Model and a list of Scenarios """
|
|
60
|
+
|
|
61
|
+
# Domain model object also has all the relevant stats on the model
|
|
62
|
+
domain_model : DomainModel = Field(default_factory=DomainModel)
|
|
63
|
+
|
|
64
|
+
# The list of scenarios
|
|
65
|
+
scenarios : list[Scenario] = Field(default_factory=list[Scenario])
|
|
66
|
+
|
|
67
|
+
# Are there conflicts between?
|
|
68
|
+
scenario_conflicts : list[ScenarioConflict] = Field(default_factory=list[ScenarioConflict])
|
|
69
|
+
|
|
70
|
+
# Scenario complement
|
|
71
|
+
scenario_comp : ScenarioComplement | None = None
|
|
72
|
+
|
|
73
|
+
# Scenario stats
|
|
74
|
+
num_sc_total : int = 0 # Total number of scenarios
|
|
75
|
+
num_sc_missing : int = 0 # Missing predicates/transitions
|
|
76
|
+
num_sc_matched : int = 0 # Total number of scenarios matched vs artifacts
|
|
77
|
+
num_sc_inconsistent : int = 0 # Total number of scenarios inconsistent
|
|
78
|
+
|
|
79
|
+
# Conflicts (spec-level)
|
|
80
|
+
num_sc_conflicted: int = 0 # total conflicts (all kinds)
|
|
81
|
+
num_sc_overlap: int = 0 # overlap conflicts
|
|
82
|
+
num_sc_consumed: int = 0 # consumed conflicts
|
|
83
|
+
|
|
84
|
+
def conflicts_index(self) -> dict[str, list[ScenarioConflict]]:
|
|
85
|
+
idx: dict[str, list[ScenarioConflict]] = defaultdict(list)
|
|
86
|
+
for c in self.scenario_conflicts:
|
|
87
|
+
idx[c.scenario_name1].append(c)
|
|
88
|
+
idx[c.scenario_name2].append(c)
|
|
89
|
+
return dict(idx)
|
|
90
|
+
|
|
91
|
+
def conflicts_for(self, scenario_name: str) -> list[ScenarioConflict]:
|
|
92
|
+
return [
|
|
93
|
+
c for c in self.scenario_conflicts
|
|
94
|
+
if c.scenario_name1 == scenario_name or c.scenario_name2 == scenario_name
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def scenario_exists (self, name:str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Return True/False if scenario already exists
|
|
100
|
+
"""
|
|
101
|
+
return any(s.name == name for s in self.scenarios)
|
|
102
|
+
|
|
103
|
+
def find_scenario(self, name: str) -> Optional[Scenario]:
|
|
104
|
+
"""
|
|
105
|
+
Return the Scenario with the given name, or None if not found.
|
|
106
|
+
"""
|
|
107
|
+
for s in self.scenarios:
|
|
108
|
+
if s.name == name:
|
|
109
|
+
return s
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def _apply_delta_strict(
|
|
113
|
+
self,
|
|
114
|
+
current: list[str],
|
|
115
|
+
*,
|
|
116
|
+
delta: ScenarioDelta,
|
|
117
|
+
allowed_add: set[str],
|
|
118
|
+
section_label: str,
|
|
119
|
+
) -> list[str]:
|
|
120
|
+
cur = set(current)
|
|
121
|
+
|
|
122
|
+
# --- validate removals: must already be present ---
|
|
123
|
+
missing_removals = set(delta.remove) - cur
|
|
124
|
+
if missing_removals:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"{section_label}: cannot remove names that are not present: {sorted(missing_removals)}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# --- validate additions only ---
|
|
130
|
+
bad_adds = set(delta.add) - allowed_add
|
|
131
|
+
if bad_adds:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
f"{section_label}: cannot add unknown names: {sorted(bad_adds)}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# optional: forbid adding something already present
|
|
137
|
+
already_present = set(delta.add) & cur
|
|
138
|
+
if already_present:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"{section_label}: names already present: {sorted(already_present)}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# --- apply ---
|
|
144
|
+
cur.difference_update(delta.remove)
|
|
145
|
+
cur.update(delta.add)
|
|
146
|
+
|
|
147
|
+
return sorted(cur) # deterministic
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def add_scenario(
|
|
151
|
+
self,
|
|
152
|
+
name: str,
|
|
153
|
+
given: ScenarioDelta | None,
|
|
154
|
+
when: ScenarioDelta | None,
|
|
155
|
+
then: ScenarioDelta | None,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Add a new scenario. Deltas must be add-only."""
|
|
158
|
+
if self.scenario_exists(name):
|
|
159
|
+
raise ValueError(f"Scenario with this name already exists: [{name}]")
|
|
160
|
+
|
|
161
|
+
allowed_given = set(self.domain_model.state_pred_names())
|
|
162
|
+
allowed_when = set(self.domain_model.action_pred_names())
|
|
163
|
+
allowed_then = set(self.domain_model.transition_names())
|
|
164
|
+
|
|
165
|
+
def build_section(delta: ScenarioDelta | None, allowed: set[str], label: str) -> list[str]:
|
|
166
|
+
if delta is None:
|
|
167
|
+
return []
|
|
168
|
+
if not delta.is_add_only():
|
|
169
|
+
raise ValueError(f"{label}: 'remove' is not allowed when creating a scenario")
|
|
170
|
+
|
|
171
|
+
bad = set(delta.add) - allowed
|
|
172
|
+
if bad:
|
|
173
|
+
raise ValueError(f"{label}: cannot add unknown names: {sorted(bad)}")
|
|
174
|
+
|
|
175
|
+
return sorted(set(delta.add)) # set semantics, deterministic
|
|
176
|
+
|
|
177
|
+
new_given = build_section(given, allowed_given, f"Scenario '{name}' given")
|
|
178
|
+
new_when = build_section(when, allowed_when, f"Scenario '{name}' when")
|
|
179
|
+
new_then = build_section(then, allowed_then, f"Scenario '{name}' then")
|
|
180
|
+
|
|
181
|
+
self.scenarios.append(
|
|
182
|
+
Scenario(name=name, given=new_given, when=new_when, then=new_then)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def edit_scenario(
|
|
186
|
+
self,
|
|
187
|
+
name: str,
|
|
188
|
+
given: Optional[ScenarioDelta],
|
|
189
|
+
when: Optional[ScenarioDelta],
|
|
190
|
+
then: Optional[ScenarioDelta],
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Edit an existing scenario with strict validation (checks only on add)."""
|
|
193
|
+
scenario = self.find_scenario(name)
|
|
194
|
+
if scenario is None:
|
|
195
|
+
raise ValueError(f"Scenario '{name}' not found")
|
|
196
|
+
|
|
197
|
+
allowed_given = set(self.domain_model.state_pred_names())
|
|
198
|
+
allowed_when = set(self.domain_model.action_pred_names())
|
|
199
|
+
allowed_then = set(self.domain_model.transition_names())
|
|
200
|
+
|
|
201
|
+
if given is not None:
|
|
202
|
+
scenario.given = self._apply_delta_strict(
|
|
203
|
+
list(scenario.given),
|
|
204
|
+
delta=given,
|
|
205
|
+
allowed_add=allowed_given,
|
|
206
|
+
section_label=f"Scenario '{name}' given",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if when is not None:
|
|
210
|
+
scenario.when = self._apply_delta_strict(
|
|
211
|
+
list(scenario.when),
|
|
212
|
+
delta=when,
|
|
213
|
+
allowed_add=allowed_when,
|
|
214
|
+
section_label=f"Scenario '{name}' when",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if then is not None:
|
|
218
|
+
scenario.then = self._apply_delta_strict(
|
|
219
|
+
list(scenario.then),
|
|
220
|
+
delta=then,
|
|
221
|
+
allowed_add=allowed_then,
|
|
222
|
+
section_label=f"Scenario '{name}' then",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def rem_scenario(self, name: str) -> None:
|
|
226
|
+
"""Remove a scenario by name."""
|
|
227
|
+
for i, s in enumerate(self.scenarios):
|
|
228
|
+
if s.name == name:
|
|
229
|
+
del self.scenarios[i]
|
|
230
|
+
return
|
|
231
|
+
raise ValueError(f"Scenario '{name}' not found")
|
|
232
|
+
|
|
233
|
+
def scenario_model(self):
|
|
234
|
+
"""
|
|
235
|
+
Synthesize a model from the scenarios
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
scenario_logic = ""
|
|
239
|
+
for s in self.scenarios:
|
|
240
|
+
scenario_logic += f"{s.full_model_to_iml()}\n"
|
|
241
|
+
|
|
242
|
+
model = f"""
|
|
243
|
+
let scenarios (s : state) (a : action) =
|
|
244
|
+
{scenario_logic} else
|
|
245
|
+
false
|
|
246
|
+
"""
|
|
247
|
+
return model
|
|
248
|
+
|
|
249
|
+
def _scenarios_main_iml(self) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Compile scenarios into a single dispatcher function `main`.
|
|
252
|
+
Scenarios are tried in list order (priority order).
|
|
253
|
+
"""
|
|
254
|
+
if not self.scenarios:
|
|
255
|
+
return "let main (s: state) (_a: action) : state = s"
|
|
256
|
+
|
|
257
|
+
lines: list[str] = []
|
|
258
|
+
lines.append("let main (s: state) (a: action) : state =")
|
|
259
|
+
|
|
260
|
+
for idx, scn in enumerate(self.scenarios):
|
|
261
|
+
guard = _scenario_guard_iml(scn)
|
|
262
|
+
body = _scenario_then_iml(scn)
|
|
263
|
+
|
|
264
|
+
kw = "if" if idx == 0 else "else if"
|
|
265
|
+
lines.append(f" {kw} {guard} then")
|
|
266
|
+
# indent body nicely
|
|
267
|
+
body_lines = body.splitlines() or ["s"]
|
|
268
|
+
lines.append(" " + "\n ".join(body_lines))
|
|
269
|
+
|
|
270
|
+
lines.append(" else s")
|
|
271
|
+
return "\n".join(lines)
|
|
272
|
+
|
|
273
|
+
def full_model(self) -> str:
|
|
274
|
+
"""
|
|
275
|
+
Return the full model (base + predicates + transitions + scenarios)
|
|
276
|
+
as a single executable IML program.
|
|
277
|
+
"""
|
|
278
|
+
dm = self.domain_model
|
|
279
|
+
|
|
280
|
+
parts: list[str] = []
|
|
281
|
+
|
|
282
|
+
# 1. Base domain
|
|
283
|
+
if dm.base:
|
|
284
|
+
parts.append(dm.base.strip())
|
|
285
|
+
|
|
286
|
+
# 2. Predicates
|
|
287
|
+
if dm.state_preds or dm.action_preds:
|
|
288
|
+
parts.append("\n(* Predicates *)\n")
|
|
289
|
+
for p in dm.state_preds:
|
|
290
|
+
parts.append(p.to_iml().strip())
|
|
291
|
+
for p in dm.action_preds:
|
|
292
|
+
parts.append(p.to_iml().strip())
|
|
293
|
+
|
|
294
|
+
# 3. Transitions
|
|
295
|
+
if dm.transitions:
|
|
296
|
+
parts.append("\n(* Transitions *)\n")
|
|
297
|
+
for t in dm.transitions:
|
|
298
|
+
parts.append(t.to_iml().strip())
|
|
299
|
+
|
|
300
|
+
# 4. Scenarios
|
|
301
|
+
if self.scenarios:
|
|
302
|
+
parts.append("\n(* Scenario dispatcher *)\n")
|
|
303
|
+
parts.append(self._scenarios_main_iml().strip())
|
|
304
|
+
|
|
305
|
+
# Join with clean spacing
|
|
306
|
+
return "\n\n".join(p for p in parts if p.strip())
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/spec_stats.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from speclogician.data.mapping import ArtifactMap
|
|
11
|
+
from speclogician.modeling.conflict import ScenarioConsumed, ScenarioOverlap
|
|
12
|
+
from speclogician.modeling.scenario import Inconsistent, Missing
|
|
13
|
+
from speclogician.modeling.spec import Spec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def refresh_spec_stats(spec: Spec, *, art_map: ArtifactMap | None) -> None:
|
|
17
|
+
# ---- scenarios ----
|
|
18
|
+
spec.num_sc_total = len(spec.scenarios)
|
|
19
|
+
|
|
20
|
+
spec.num_sc_missing = sum(
|
|
21
|
+
1 for sc in spec.scenarios if isinstance(getattr(sc, "component_status", None), Missing)
|
|
22
|
+
)
|
|
23
|
+
spec.num_sc_inconsistent = sum(
|
|
24
|
+
1 for sc in spec.scenarios if isinstance(getattr(sc, "component_status", None), Inconsistent)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# “matched” should be keyed by scenario.comp_id (NOT name)
|
|
28
|
+
if art_map is None:
|
|
29
|
+
spec.num_sc_matched = 0
|
|
30
|
+
else:
|
|
31
|
+
comp_to_art = getattr(art_map, "comp_to_art_map", {}) or {}
|
|
32
|
+
spec.num_sc_matched = sum(
|
|
33
|
+
1 for sc in spec.scenarios if len(comp_to_art.get(sc.comp_id, [])) > 0
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# ---- conflicts (spec-level) ----
|
|
37
|
+
spec.num_sc_conflicted = len(spec.scenario_conflicts or [])
|
|
38
|
+
spec.num_sc_overlap = sum(isinstance(c, ScenarioOverlap) for c in (spec.scenario_conflicts or []))
|
|
39
|
+
spec.num_sc_consumed = sum(isinstance(c, ScenarioConsumed) for c in (spec.scenario_conflicts or []))
|
|
File without changes
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/api.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# --- Controls how we print things out (and also contains some data)
|
|
10
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
11
|
+
|
|
12
|
+
# --- Underlying models
|
|
13
|
+
from speclogician.modeling.predicates import ActionPredicate, StatePredicate, Transition
|
|
14
|
+
from speclogician.modeling.domain import DomainModel
|
|
15
|
+
from speclogician.modeling.spec import Spec
|
|
16
|
+
from speclogician.modeling.complement import ScenarioComplement
|
|
17
|
+
from speclogician.data.traces import TestTrace, LogTrace
|
|
18
|
+
from speclogician.data.refs import DocRef, SrcCodeRef
|
|
19
|
+
from speclogician.data.container import ArtifactContainer, SmartFindResults
|
|
20
|
+
from speclogician.state.inst import StateInstance
|
|
21
|
+
from speclogician.state.diff import StateDiff
|
|
22
|
+
from speclogician.modeling.scenario import Scenario
|
|
23
|
+
from speclogician.state.recommender import Recommendation
|
|
24
|
+
|
|
25
|
+
# --- Builders
|
|
26
|
+
from speclogician.presentation.builders.predicate import (
|
|
27
|
+
build_predicate_pm,
|
|
28
|
+
build_transition_pm,
|
|
29
|
+
)
|
|
30
|
+
from speclogician.presentation.builders.domain import build_domain_model_pm
|
|
31
|
+
from speclogician.presentation.builders.trace import build_test_trace_pm, build_log_trace_pm
|
|
32
|
+
from speclogician.presentation.builders.spec import build_spec_pm
|
|
33
|
+
from speclogician.presentation.builders.scenario import build_scenario_pm
|
|
34
|
+
from speclogician.presentation.builders.data_artifact import build_doc_ref_pm, build_src_code_ref_pm
|
|
35
|
+
from speclogician.presentation.builders.container import build_artifact_container_pm
|
|
36
|
+
from speclogician.presentation.builders.smart_find import build_smart_find_pm
|
|
37
|
+
from speclogician.presentation.builders.state_instance import build_state_instance_pm
|
|
38
|
+
from speclogician.presentation.builders.state_diff import build_state_diff_pm
|
|
39
|
+
from speclogician.presentation.builders.instances_list import build_instances_list_pm
|
|
40
|
+
from speclogician.presentation.builders.recommendations import build_recommendations_pm
|
|
41
|
+
from speclogician.presentation.builders.scenario_complement import build_scenario_complement_pm
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --- Renderers
|
|
45
|
+
from speclogician.presentation.renderers.predicate import (
|
|
46
|
+
render_predicate,
|
|
47
|
+
render_transition,
|
|
48
|
+
)
|
|
49
|
+
from speclogician.presentation.renderers.trace import render_test_trace, render_log_trace
|
|
50
|
+
from speclogician.presentation.renderers.domain import render_domain_model
|
|
51
|
+
from speclogician.presentation.renderers.spec import render_spec
|
|
52
|
+
from speclogician.presentation.renderers.scenario import render_scenario
|
|
53
|
+
from speclogician.presentation.renderers.data_artifact import render_doc_ref, render_src_code_ref
|
|
54
|
+
from speclogician.presentation.renderers.container import render_artifact_container
|
|
55
|
+
from speclogician.presentation.renderers.smart_find import render_smart_find
|
|
56
|
+
from speclogician.presentation.renderers.state_instance import render_state_instance
|
|
57
|
+
from speclogician.presentation.renderers.state_diff import render_state_diff
|
|
58
|
+
from speclogician.presentation.renderers.instances_list import render_instances_list
|
|
59
|
+
from speclogician.presentation.renderers.recommendations import render_recommendations
|
|
60
|
+
from speclogician.presentation.renderers.scenario_complement import render_scenario_complement
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def present_predicate(
|
|
64
|
+
p: StatePredicate | ActionPredicate,
|
|
65
|
+
*,
|
|
66
|
+
ctx: RenderCtx,
|
|
67
|
+
json_only: bool,
|
|
68
|
+
):
|
|
69
|
+
pm = build_predicate_pm(p, ctx)
|
|
70
|
+
if json_only:
|
|
71
|
+
return pm.model_dump()
|
|
72
|
+
return render_predicate(pm, ctx=ctx)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def present_transition(
|
|
76
|
+
t: Transition,
|
|
77
|
+
*,
|
|
78
|
+
ctx: RenderCtx,
|
|
79
|
+
json_only: bool,
|
|
80
|
+
):
|
|
81
|
+
pm = build_transition_pm(t, ctx)
|
|
82
|
+
if json_only:
|
|
83
|
+
return pm.model_dump()
|
|
84
|
+
return render_transition(pm, ctx=ctx)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def present_domain_model(
|
|
88
|
+
dm: DomainModel,
|
|
89
|
+
*,
|
|
90
|
+
ctx: RenderCtx,
|
|
91
|
+
json_only: bool,
|
|
92
|
+
):
|
|
93
|
+
pm = build_domain_model_pm(dm, ctx)
|
|
94
|
+
if json_only:
|
|
95
|
+
return pm.model_dump()
|
|
96
|
+
return render_domain_model(pm, ctx=ctx)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def present_spec(
|
|
100
|
+
spec: Spec,
|
|
101
|
+
*,
|
|
102
|
+
ctx: RenderCtx,
|
|
103
|
+
json_only: bool,
|
|
104
|
+
):
|
|
105
|
+
pm = build_spec_pm(spec, ctx)
|
|
106
|
+
if json_only:
|
|
107
|
+
return pm.model_dump()
|
|
108
|
+
return render_spec(pm, ctx=ctx)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def present_scenario_complement(
|
|
112
|
+
comp: ScenarioComplement,
|
|
113
|
+
*,
|
|
114
|
+
ctx: RenderCtx,
|
|
115
|
+
json_only: bool,
|
|
116
|
+
):
|
|
117
|
+
pm = build_scenario_complement_pm(comp, ctx)
|
|
118
|
+
if json_only:
|
|
119
|
+
return pm.model_dump()
|
|
120
|
+
return render_scenario_complement(pm, ctx=ctx)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def present_trace(
|
|
124
|
+
tr: TestTrace | LogTrace,
|
|
125
|
+
*,
|
|
126
|
+
ctx: RenderCtx,
|
|
127
|
+
json_only: bool,
|
|
128
|
+
):
|
|
129
|
+
if isinstance(tr, TestTrace):
|
|
130
|
+
pm = build_test_trace_pm(tr, ctx)
|
|
131
|
+
if json_only:
|
|
132
|
+
return pm.model_dump()
|
|
133
|
+
return render_test_trace(pm, ctx=ctx)
|
|
134
|
+
|
|
135
|
+
if isinstance(tr, LogTrace):
|
|
136
|
+
pm = build_log_trace_pm(tr, ctx)
|
|
137
|
+
if json_only:
|
|
138
|
+
return pm.model_dump()
|
|
139
|
+
return render_log_trace(pm, ctx=ctx)
|
|
140
|
+
|
|
141
|
+
raise TypeError(f"Unsupported trace type: {type(tr).__name__}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def present_data_artifact(
|
|
145
|
+
a: DocRef | SrcCodeRef,
|
|
146
|
+
*,
|
|
147
|
+
ctx: RenderCtx,
|
|
148
|
+
json_only: bool,
|
|
149
|
+
):
|
|
150
|
+
if isinstance(a, DocRef):
|
|
151
|
+
pm = build_doc_ref_pm(a, ctx)
|
|
152
|
+
if json_only:
|
|
153
|
+
return pm.model_dump()
|
|
154
|
+
return render_doc_ref(pm, ctx=ctx)
|
|
155
|
+
|
|
156
|
+
if isinstance(a, SrcCodeRef):
|
|
157
|
+
pm = build_src_code_ref_pm(a, ctx)
|
|
158
|
+
if json_only:
|
|
159
|
+
return pm.model_dump()
|
|
160
|
+
return render_src_code_ref(pm, ctx=ctx)
|
|
161
|
+
|
|
162
|
+
raise TypeError(f"Unsupported data artifact type: {type(a).__name__}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def present_artifact_container(
|
|
166
|
+
cont: ArtifactContainer,
|
|
167
|
+
*,
|
|
168
|
+
ctx: RenderCtx,
|
|
169
|
+
json_only: bool,
|
|
170
|
+
):
|
|
171
|
+
pm = build_artifact_container_pm(cont, ctx)
|
|
172
|
+
if json_only:
|
|
173
|
+
return pm.model_dump()
|
|
174
|
+
return render_artifact_container(pm, ctx=ctx)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def present_smart_find(
|
|
178
|
+
res: SmartFindResults,
|
|
179
|
+
*,
|
|
180
|
+
ctx: RenderCtx,
|
|
181
|
+
json_only: bool,
|
|
182
|
+
):
|
|
183
|
+
pm = build_smart_find_pm(res, ctx)
|
|
184
|
+
if json_only:
|
|
185
|
+
return pm.model_dump()
|
|
186
|
+
return render_smart_find(pm, ctx=ctx)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def present_state_diff(
|
|
190
|
+
sd: StateDiff,
|
|
191
|
+
*,
|
|
192
|
+
ctx: RenderCtx,
|
|
193
|
+
json_only: bool,
|
|
194
|
+
):
|
|
195
|
+
pm = build_state_diff_pm(sd, ctx)
|
|
196
|
+
if json_only:
|
|
197
|
+
return pm.model_dump()
|
|
198
|
+
return render_state_diff(pm, ctx=ctx)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def present_state_instance(
|
|
202
|
+
si: StateInstance,
|
|
203
|
+
*,
|
|
204
|
+
ctx: RenderCtx,
|
|
205
|
+
json_only: bool,
|
|
206
|
+
):
|
|
207
|
+
pm = build_state_instance_pm(si, ctx)
|
|
208
|
+
if json_only:
|
|
209
|
+
return pm.model_dump()
|
|
210
|
+
return render_state_instance(pm, ctx=ctx)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def present_scenario(
|
|
214
|
+
sc: Scenario,
|
|
215
|
+
*,
|
|
216
|
+
ctx: RenderCtx,
|
|
217
|
+
json_only: bool,
|
|
218
|
+
):
|
|
219
|
+
pm = build_scenario_pm(sc, ctx)
|
|
220
|
+
if json_only:
|
|
221
|
+
return pm.model_dump()
|
|
222
|
+
return render_scenario(pm, ctx=ctx)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def present_instances_list(
|
|
226
|
+
states: list[StateInstance],
|
|
227
|
+
*,
|
|
228
|
+
ctx: RenderCtx,
|
|
229
|
+
json_only: bool,
|
|
230
|
+
):
|
|
231
|
+
pm = build_instances_list_pm(states, ctx)
|
|
232
|
+
if json_only:
|
|
233
|
+
return pm.model_dump()
|
|
234
|
+
return render_instances_list(pm, ctx=ctx)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def present_recommendations(
|
|
238
|
+
recs: list[Recommendation],
|
|
239
|
+
*,
|
|
240
|
+
ctx: RenderCtx,
|
|
241
|
+
json_only: bool,
|
|
242
|
+
):
|
|
243
|
+
pm = build_recommendations_pm(recs, ctx)
|
|
244
|
+
return pm.model_dump() if json_only else render_recommendations(pm, ctx=ctx)
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/builders/_links.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
10
|
+
from speclogician.presentation.models.predicate import LinkedArtifactsPM
|
|
11
|
+
from speclogician.data.traces import TestTrace, LogTrace
|
|
12
|
+
from speclogician.data.refs import DocRef, SrcCodeRef
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_links_pm(comp_id: str, ctx: RenderCtx) -> LinkedArtifactsPM:
|
|
16
|
+
links = LinkedArtifactsPM()
|
|
17
|
+
|
|
18
|
+
if ctx.art_map is None:
|
|
19
|
+
return links
|
|
20
|
+
|
|
21
|
+
art_ids = list(ctx.art_map.get_arts_for_component(comp_id))
|
|
22
|
+
links.total = len(art_ids)
|
|
23
|
+
links.art_ids = art_ids if ctx.show_art_ids else []
|
|
24
|
+
|
|
25
|
+
if ctx.art_cont is None:
|
|
26
|
+
links.unresolved_ids = len(art_ids)
|
|
27
|
+
return links
|
|
28
|
+
|
|
29
|
+
unresolved = 0
|
|
30
|
+
for aid in art_ids:
|
|
31
|
+
a = ctx.art_cont.get_by_art_id(aid)
|
|
32
|
+
if a is None:
|
|
33
|
+
unresolved += 1
|
|
34
|
+
elif isinstance(a, TestTrace):
|
|
35
|
+
links.test_traces += 1
|
|
36
|
+
elif isinstance(a, LogTrace):
|
|
37
|
+
links.log_traces += 1
|
|
38
|
+
elif isinstance(a, DocRef):
|
|
39
|
+
links.doc_refs += 1
|
|
40
|
+
elif isinstance(a, SrcCodeRef):
|
|
41
|
+
links.src_code_refs += 1
|
|
42
|
+
|
|
43
|
+
links.unresolved_ids = unresolved
|
|
44
|
+
return links
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/builders/container.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from speclogician.data.container import ArtifactContainer
|
|
10
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
11
|
+
|
|
12
|
+
from speclogician.presentation.models.container import (
|
|
13
|
+
ArtifactContainerPM,
|
|
14
|
+
ArtifactContainerCountsPM,
|
|
15
|
+
ArtifactContainerItemsPM,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from speclogician.presentation.builders.trace import build_test_trace_pm, build_log_trace_pm
|
|
19
|
+
from speclogician.presentation.builders.data_artifact import build_doc_ref_pm, build_src_code_ref_pm
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_artifact_container_pm(cont: ArtifactContainer, ctx: RenderCtx) -> ArtifactContainerPM:
|
|
23
|
+
counts = ArtifactContainerCountsPM(
|
|
24
|
+
num_test_traces_total=cont.num_test_traces_total,
|
|
25
|
+
num_test_traces_matched=cont.num_test_traces_matched,
|
|
26
|
+
num_test_traces_logic_good=cont.num_test_traces_logic_good,
|
|
27
|
+
num_log_traces_total=cont.num_log_traces_total,
|
|
28
|
+
num_log_traces_matched=cont.num_log_traces_matched,
|
|
29
|
+
num_log_traces_logic_good=cont.num_log_traces_logic_good,
|
|
30
|
+
num_src_code_arts_total=cont.num_src_code_arts_total,
|
|
31
|
+
num_src_code_arts_matched=cont.num_src_code_arts_matched,
|
|
32
|
+
num_doc_arts_total=cont.num_doc_arts_total,
|
|
33
|
+
num_doc_arts_matched=cont.num_doc_arts_matched,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
items = ArtifactContainerItemsPM()
|
|
37
|
+
|
|
38
|
+
if not ctx.show_stats_only:
|
|
39
|
+
if ctx.show_traces:
|
|
40
|
+
items.test_traces = [build_test_trace_pm(t, ctx) for t in cont.test_traces]
|
|
41
|
+
items.log_traces = [build_log_trace_pm(t, ctx) for t in cont.log_traces]
|
|
42
|
+
else:
|
|
43
|
+
items.test_traces = []
|
|
44
|
+
items.log_traces = []
|
|
45
|
+
|
|
46
|
+
if ctx.show_artifacts:
|
|
47
|
+
items.doc_refs = [build_doc_ref_pm(d, ctx) for d in cont.doc_ref]
|
|
48
|
+
items.src_code_refs = [build_src_code_ref_pm(s, ctx) for s in cont.src_code]
|
|
49
|
+
else:
|
|
50
|
+
items.doc_refs = []
|
|
51
|
+
items.src_code_refs = []
|
|
52
|
+
|
|
53
|
+
return ArtifactContainerPM(counts=counts, items=items)
|