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,574 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/state/inst.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from .change import StateChange
|
|
15
|
+
from .diff import (
|
|
16
|
+
StateDiff,
|
|
17
|
+
ValueDiff,
|
|
18
|
+
base_status_comp,
|
|
19
|
+
bool_true_is_good,
|
|
20
|
+
numeric_increase_bad,
|
|
21
|
+
numeric_increase_good,
|
|
22
|
+
)
|
|
23
|
+
from .recommendation import Recommendation
|
|
24
|
+
from ..data.container import ArtifactContainer
|
|
25
|
+
from ..data.mapping import ArtifactMap
|
|
26
|
+
from ..modeling.domain import BaseStatus
|
|
27
|
+
from ..modeling.spec import Spec
|
|
28
|
+
from ..utils import JSONObject
|
|
29
|
+
|
|
30
|
+
def _safe_iso(dt: Optional[datetime]) -> str | None:
|
|
31
|
+
if dt is None:
|
|
32
|
+
return None
|
|
33
|
+
return dt.isoformat()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _comp_regions(comp: object | None) -> list[object]:
|
|
37
|
+
"""
|
|
38
|
+
Best-effort extraction of complement regions.
|
|
39
|
+
|
|
40
|
+
Supports a couple of plausible layouts:
|
|
41
|
+
- comp.regions
|
|
42
|
+
- comp.cover_regions
|
|
43
|
+
"""
|
|
44
|
+
if comp is None:
|
|
45
|
+
return []
|
|
46
|
+
regs = getattr(comp, "regions", None)
|
|
47
|
+
if regs is None:
|
|
48
|
+
regs = getattr(comp, "cover_regions", None)
|
|
49
|
+
try:
|
|
50
|
+
return list(regs or [])
|
|
51
|
+
except Exception:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StateInstance(BaseModel):
|
|
56
|
+
"""
|
|
57
|
+
State Instance class
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
61
|
+
|
|
62
|
+
spec: Spec = Field(default_factory=Spec) # `Spec` is the domain model + scenarios
|
|
63
|
+
art_container: ArtifactContainer = Field(default_factory=ArtifactContainer)
|
|
64
|
+
art_map: ArtifactMap = Field(default_factory=ArtifactMap)
|
|
65
|
+
|
|
66
|
+
# These describe the changes from the previous state
|
|
67
|
+
changes: list[StateChange] = Field(default_factory=list[StateChange])
|
|
68
|
+
state_diff: StateDiff | None = None
|
|
69
|
+
|
|
70
|
+
# The generated recommendations
|
|
71
|
+
recommendations: list[Recommendation] = Field(default_factory=list[Recommendation])
|
|
72
|
+
|
|
73
|
+
def update_created_at(self) -> None:
|
|
74
|
+
"""Update the `created_at` timestamp field"""
|
|
75
|
+
self.created_at = datetime.now(timezone.utc)
|
|
76
|
+
|
|
77
|
+
def json_summary(self) -> JSONObject:
|
|
78
|
+
"""
|
|
79
|
+
JSON snapshot of the *inputs* used by calc_diff() to compute StateDiff.
|
|
80
|
+
|
|
81
|
+
Goal: stable, machine-readable summary suitable for --json output and tests.
|
|
82
|
+
"""
|
|
83
|
+
dm = self.spec.domain_model
|
|
84
|
+
spec = self.spec
|
|
85
|
+
art = self.art_container
|
|
86
|
+
|
|
87
|
+
comp = getattr(spec, "scenario_comp", None)
|
|
88
|
+
regions = _comp_regions(comp)
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"created_at": _safe_iso(self.created_at),
|
|
92
|
+
"changes": [c.model_dump(mode="json") for c in (self.changes or [])],
|
|
93
|
+
# ---- Domain model (base) ----
|
|
94
|
+
"base_status": getattr(dm, "base_status", None),
|
|
95
|
+
"base_has_state": getattr(dm, "base_has_state", None),
|
|
96
|
+
"base_has_action": getattr(dm, "base_has_action", None),
|
|
97
|
+
# ---- Domain model (state predicates) ----
|
|
98
|
+
"num_state_preds_total": getattr(dm, "num_state_preds_total", None),
|
|
99
|
+
"num_state_preds_matched": getattr(dm, "num_state_preds_matched", None),
|
|
100
|
+
"num_state_preds_valid_logic": getattr(dm, "num_state_preds_valid_logic", None),
|
|
101
|
+
"num_state_preds_errored": getattr(dm, "num_state_preds_errored", None),
|
|
102
|
+
# ---- Domain model (action predicates) ----
|
|
103
|
+
"num_action_preds_total": getattr(dm, "num_action_preds_total", None),
|
|
104
|
+
"num_action_preds_matched": getattr(dm, "num_action_preds_matched", None),
|
|
105
|
+
"num_action_preds_valid_logic": getattr(dm, "num_action_preds_valid_logic", None),
|
|
106
|
+
"num_action_preds_errored": getattr(dm, "num_action_preds_errored", None),
|
|
107
|
+
# ---- Domain model (predicates totals) ----
|
|
108
|
+
"num_preds_total": getattr(dm, "num_preds_total", None),
|
|
109
|
+
"num_preds_matched": getattr(dm, "num_preds_matched", None),
|
|
110
|
+
"num_preds_valid_logic": getattr(dm, "num_preds_valid_logic", None),
|
|
111
|
+
"num_preds_errored": getattr(dm, "num_preds_errored", None),
|
|
112
|
+
# ---- Domain model (transitions) ----
|
|
113
|
+
"num_trans_total": getattr(dm, "num_trans_total", None),
|
|
114
|
+
"num_trans_matched": getattr(dm, "num_trans_matched", None),
|
|
115
|
+
"num_trans_valid_logic": getattr(dm, "num_trans_valid_logic", None),
|
|
116
|
+
"num_trans_errored": getattr(dm, "num_trans_errored", None),
|
|
117
|
+
# ---- Scenarios (Spec-level) ----
|
|
118
|
+
"num_sc_total": getattr(spec, "num_sc_total", None),
|
|
119
|
+
"num_sc_missing": getattr(spec, "num_sc_missing", None),
|
|
120
|
+
"num_sc_matched": getattr(spec, "num_sc_matched", None),
|
|
121
|
+
"num_sc_inconsistent": getattr(spec, "num_sc_inconsistent", None),
|
|
122
|
+
# Conflicts (total + by kind).
|
|
123
|
+
"num_sc_conflicted": getattr(spec, "num_sc_conflicted", None),
|
|
124
|
+
"num_sc_overlap": getattr(spec, "num_sc_overlap", 0),
|
|
125
|
+
"num_sc_consumed": getattr(spec, "num_sc_consumed", 0),
|
|
126
|
+
# ---- Complement (Spec-level) ----
|
|
127
|
+
# NOTE: match StateDiff field names (new ones).
|
|
128
|
+
"scenario_comp_present": comp is not None,
|
|
129
|
+
"num_comp_regions_total": len(regions),
|
|
130
|
+
# ---- ArtifactContainer: test traces ----
|
|
131
|
+
"num_test_traces_total": getattr(art, "num_test_traces_total", None),
|
|
132
|
+
"num_test_traces_logic_good": getattr(art, "num_test_traces_logic_good", None),
|
|
133
|
+
"num_test_traces_matched": getattr(art, "num_test_traces_matched", None),
|
|
134
|
+
# ---- ArtifactContainer: log traces ----
|
|
135
|
+
"num_log_traces_total": getattr(art, "num_log_traces_total", None),
|
|
136
|
+
"num_log_traces_logic_good": getattr(art, "num_log_traces_logic_good", None),
|
|
137
|
+
"num_log_traces_matched": getattr(art, "num_log_traces_matched", None),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class InstancesList(BaseModel):
|
|
142
|
+
"""A helper container for pretty printing a list of instances"""
|
|
143
|
+
|
|
144
|
+
states: list[StateInstance]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def calc_diff(new: StateInstance, old: StateInstance) -> StateDiff:
|
|
148
|
+
"""
|
|
149
|
+
Generate a diff between two StateInstances.
|
|
150
|
+
|
|
151
|
+
Assumes `old` and `new` have fully computed stats on:
|
|
152
|
+
- new.spec.domain_model
|
|
153
|
+
- new.spec (scenario stats)
|
|
154
|
+
- new.art_container
|
|
155
|
+
- new.spec.scenario_comp (optional)
|
|
156
|
+
"""
|
|
157
|
+
vals: list[tuple[str, ValueDiff]] = []
|
|
158
|
+
|
|
159
|
+
dm_old = old.spec.domain_model
|
|
160
|
+
dm_new = new.spec.domain_model
|
|
161
|
+
spec_old = old.spec
|
|
162
|
+
spec_new = new.spec
|
|
163
|
+
art_old = old.art_container
|
|
164
|
+
art_new = new.art_container
|
|
165
|
+
|
|
166
|
+
# -------------------------------------------------------------------------
|
|
167
|
+
# Helper: only emit diffs for fields that actually exist on StateDiff.
|
|
168
|
+
# This makes calc_diff resilient while you evolve StateDiff.
|
|
169
|
+
# -------------------------------------------------------------------------
|
|
170
|
+
state_diff_fields = getattr(StateDiff, "model_fields", {}) # pydantic v2
|
|
171
|
+
|
|
172
|
+
def _emit(key: str, vd: ValueDiff) -> None:
|
|
173
|
+
if key in state_diff_fields:
|
|
174
|
+
vals.append((key, vd))
|
|
175
|
+
|
|
176
|
+
# >>> Base
|
|
177
|
+
_emit(
|
|
178
|
+
"base_status",
|
|
179
|
+
ValueDiff[BaseStatus](
|
|
180
|
+
label="Base code IML status",
|
|
181
|
+
before=dm_old.base_status,
|
|
182
|
+
after=dm_new.base_status,
|
|
183
|
+
comp_func=base_status_comp,
|
|
184
|
+
hint={},
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
_emit(
|
|
189
|
+
"base_has_state",
|
|
190
|
+
ValueDiff[bool](
|
|
191
|
+
label="State type is present",
|
|
192
|
+
before=dm_old.base_has_state,
|
|
193
|
+
after=dm_new.base_has_state,
|
|
194
|
+
comp_func=bool_true_is_good,
|
|
195
|
+
hint={},
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
_emit(
|
|
200
|
+
"base_has_action",
|
|
201
|
+
ValueDiff[bool](
|
|
202
|
+
label="Action type is present",
|
|
203
|
+
before=dm_old.base_has_action,
|
|
204
|
+
after=dm_new.base_has_action,
|
|
205
|
+
comp_func=bool_true_is_good,
|
|
206
|
+
hint={},
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# >>> State predicates
|
|
211
|
+
_emit(
|
|
212
|
+
"num_state_preds_total",
|
|
213
|
+
ValueDiff[int](
|
|
214
|
+
label="Number of state predicates",
|
|
215
|
+
before=dm_old.num_state_preds_total,
|
|
216
|
+
after=dm_new.num_state_preds_total,
|
|
217
|
+
comp_func=numeric_increase_good,
|
|
218
|
+
hint={},
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
_emit(
|
|
223
|
+
"num_state_preds_matched",
|
|
224
|
+
ValueDiff[int](
|
|
225
|
+
label="Number of matched state predicates",
|
|
226
|
+
before=dm_old.num_state_preds_matched,
|
|
227
|
+
after=dm_new.num_state_preds_matched,
|
|
228
|
+
comp_func=numeric_increase_good,
|
|
229
|
+
hint={},
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
_emit(
|
|
234
|
+
"num_state_preds_valid_logic",
|
|
235
|
+
ValueDiff[int](
|
|
236
|
+
label="Number of state predicates with valid logic",
|
|
237
|
+
before=dm_old.num_state_preds_valid_logic,
|
|
238
|
+
after=dm_new.num_state_preds_valid_logic,
|
|
239
|
+
comp_func=numeric_increase_good,
|
|
240
|
+
hint={},
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
_emit(
|
|
245
|
+
"num_state_preds_errored",
|
|
246
|
+
ValueDiff[int](
|
|
247
|
+
label="Number of state predicates with errors",
|
|
248
|
+
before=dm_old.num_state_preds_errored,
|
|
249
|
+
after=dm_new.num_state_preds_errored,
|
|
250
|
+
comp_func=numeric_increase_bad,
|
|
251
|
+
hint={},
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# >>> Action predicates
|
|
256
|
+
_emit(
|
|
257
|
+
"num_action_preds_total",
|
|
258
|
+
ValueDiff[int](
|
|
259
|
+
label="Number of action predicates",
|
|
260
|
+
before=dm_old.num_action_preds_total,
|
|
261
|
+
after=dm_new.num_action_preds_total,
|
|
262
|
+
comp_func=numeric_increase_good,
|
|
263
|
+
hint={},
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
_emit(
|
|
268
|
+
"num_action_preds_matched",
|
|
269
|
+
ValueDiff[int](
|
|
270
|
+
label="Number of matched action predicates",
|
|
271
|
+
before=dm_old.num_action_preds_matched,
|
|
272
|
+
after=dm_new.num_action_preds_matched,
|
|
273
|
+
comp_func=numeric_increase_good,
|
|
274
|
+
hint={},
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
_emit(
|
|
279
|
+
"num_action_preds_valid_logic",
|
|
280
|
+
ValueDiff[int](
|
|
281
|
+
label="Number of action predicates with valid logic",
|
|
282
|
+
before=dm_old.num_action_preds_valid_logic,
|
|
283
|
+
after=dm_new.num_action_preds_valid_logic,
|
|
284
|
+
comp_func=numeric_increase_good,
|
|
285
|
+
hint={},
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
_emit(
|
|
290
|
+
"num_action_preds_errored",
|
|
291
|
+
ValueDiff[int](
|
|
292
|
+
label="Number of action predicates with errors",
|
|
293
|
+
before=dm_old.num_action_preds_errored,
|
|
294
|
+
after=dm_new.num_action_preds_errored,
|
|
295
|
+
comp_func=numeric_increase_bad,
|
|
296
|
+
hint={},
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# >>> Predicates (totals)
|
|
301
|
+
_emit(
|
|
302
|
+
"num_preds_total",
|
|
303
|
+
ValueDiff[int](
|
|
304
|
+
label="Number of predicates (total)",
|
|
305
|
+
before=dm_old.num_preds_total,
|
|
306
|
+
after=dm_new.num_preds_total,
|
|
307
|
+
comp_func=numeric_increase_good,
|
|
308
|
+
hint={},
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
_emit(
|
|
313
|
+
"num_preds_matched",
|
|
314
|
+
ValueDiff[int](
|
|
315
|
+
label="Number of matched predicates (total)",
|
|
316
|
+
before=dm_old.num_preds_matched,
|
|
317
|
+
after=dm_new.num_preds_matched,
|
|
318
|
+
comp_func=numeric_increase_good,
|
|
319
|
+
hint={},
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
_emit(
|
|
324
|
+
"num_preds_valid_logic",
|
|
325
|
+
ValueDiff[int](
|
|
326
|
+
label="Number of predicates with valid logic (total)",
|
|
327
|
+
before=dm_old.num_preds_valid_logic,
|
|
328
|
+
after=dm_new.num_preds_valid_logic,
|
|
329
|
+
comp_func=numeric_increase_good,
|
|
330
|
+
hint={},
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
_emit(
|
|
335
|
+
"num_preds_errored",
|
|
336
|
+
ValueDiff[int](
|
|
337
|
+
label="Number of predicates with errors (total)",
|
|
338
|
+
before=dm_old.num_preds_errored,
|
|
339
|
+
after=dm_new.num_preds_errored,
|
|
340
|
+
comp_func=numeric_increase_bad,
|
|
341
|
+
hint={},
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# >>> Transitions
|
|
346
|
+
_emit(
|
|
347
|
+
"num_trans_total",
|
|
348
|
+
ValueDiff[int](
|
|
349
|
+
label="Number of transitions",
|
|
350
|
+
before=dm_old.num_trans_total,
|
|
351
|
+
after=dm_new.num_trans_total,
|
|
352
|
+
comp_func=numeric_increase_good,
|
|
353
|
+
hint={},
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
_emit(
|
|
358
|
+
"num_trans_matched",
|
|
359
|
+
ValueDiff[int](
|
|
360
|
+
label="Number of matched transitions",
|
|
361
|
+
before=dm_old.num_trans_matched,
|
|
362
|
+
after=dm_new.num_trans_matched,
|
|
363
|
+
comp_func=numeric_increase_good,
|
|
364
|
+
hint={},
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
_emit(
|
|
369
|
+
"num_trans_valid_logic",
|
|
370
|
+
ValueDiff[int](
|
|
371
|
+
label="Number of transitions with valid logic",
|
|
372
|
+
before=dm_old.num_trans_valid_logic,
|
|
373
|
+
after=dm_new.num_trans_valid_logic,
|
|
374
|
+
comp_func=numeric_increase_good,
|
|
375
|
+
hint={},
|
|
376
|
+
),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
_emit(
|
|
380
|
+
"num_trans_errored",
|
|
381
|
+
ValueDiff[int](
|
|
382
|
+
label="Number of transitions with errors",
|
|
383
|
+
before=dm_old.num_trans_errored,
|
|
384
|
+
after=dm_new.num_trans_errored,
|
|
385
|
+
comp_func=numeric_increase_bad,
|
|
386
|
+
hint={},
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# >>> Scenarios
|
|
391
|
+
_emit(
|
|
392
|
+
"num_sc_total",
|
|
393
|
+
ValueDiff[int](
|
|
394
|
+
label="Number of scenarios",
|
|
395
|
+
before=spec_old.num_sc_total,
|
|
396
|
+
after=spec_new.num_sc_total,
|
|
397
|
+
comp_func=numeric_increase_good,
|
|
398
|
+
hint={},
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
_emit(
|
|
403
|
+
"num_sc_missing",
|
|
404
|
+
ValueDiff[int](
|
|
405
|
+
label="Number of scenarios with missing model components",
|
|
406
|
+
before=spec_old.num_sc_missing,
|
|
407
|
+
after=spec_new.num_sc_missing,
|
|
408
|
+
comp_func=numeric_increase_bad,
|
|
409
|
+
hint={},
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
_emit(
|
|
414
|
+
"num_sc_matched",
|
|
415
|
+
ValueDiff[int](
|
|
416
|
+
label="Number of scenarios matched",
|
|
417
|
+
before=spec_old.num_sc_matched,
|
|
418
|
+
after=spec_new.num_sc_matched,
|
|
419
|
+
comp_func=numeric_increase_good,
|
|
420
|
+
hint={},
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
_emit(
|
|
425
|
+
"num_sc_inconsistent",
|
|
426
|
+
ValueDiff[int](
|
|
427
|
+
label="Number of inconsistent scenarios",
|
|
428
|
+
before=spec_old.num_sc_inconsistent,
|
|
429
|
+
after=spec_new.num_sc_inconsistent,
|
|
430
|
+
comp_func=numeric_increase_bad,
|
|
431
|
+
hint={},
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# >>> Conflicts
|
|
436
|
+
_emit(
|
|
437
|
+
"num_sc_conflicted",
|
|
438
|
+
ValueDiff[int](
|
|
439
|
+
label="Number of scenario conflicts (total)",
|
|
440
|
+
before=spec_old.num_sc_conflicted,
|
|
441
|
+
after=spec_new.num_sc_conflicted,
|
|
442
|
+
comp_func=numeric_increase_bad,
|
|
443
|
+
hint={},
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
_emit(
|
|
448
|
+
"num_sc_overlap",
|
|
449
|
+
ValueDiff[int](
|
|
450
|
+
label="Number of scenario overlap conflicts",
|
|
451
|
+
before=getattr(spec_old, "num_sc_overlap", 0),
|
|
452
|
+
after=getattr(spec_new, "num_sc_overlap", 0),
|
|
453
|
+
comp_func=numeric_increase_bad,
|
|
454
|
+
hint={},
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
_emit(
|
|
459
|
+
"num_sc_consumed",
|
|
460
|
+
ValueDiff[int](
|
|
461
|
+
label="Number of scenario consumed conflicts",
|
|
462
|
+
before=getattr(spec_old, "num_sc_consumed", 0),
|
|
463
|
+
after=getattr(spec_new, "num_sc_consumed", 0),
|
|
464
|
+
comp_func=numeric_increase_bad,
|
|
465
|
+
hint={},
|
|
466
|
+
),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# -------------------------------------------------------------------------
|
|
470
|
+
# >>> Complement (Spec-level): scenario_comp
|
|
471
|
+
# NOTE: match the new StateDiff field names you mentioned:
|
|
472
|
+
# - scenario_comp_present
|
|
473
|
+
# - num_comp_regions_total
|
|
474
|
+
# -------------------------------------------------------------------------
|
|
475
|
+
comp_old = getattr(spec_old, "scenario_comp", None)
|
|
476
|
+
comp_new = getattr(spec_new, "scenario_comp", None)
|
|
477
|
+
|
|
478
|
+
old_regions = _comp_regions(comp_old)
|
|
479
|
+
new_regions = _comp_regions(comp_new)
|
|
480
|
+
|
|
481
|
+
# Presence: having a computed complement is generally "good" (enables coverage reasoning).
|
|
482
|
+
_emit(
|
|
483
|
+
"scenario_comp_present",
|
|
484
|
+
ValueDiff[bool](
|
|
485
|
+
label="Scenario complement is present",
|
|
486
|
+
before=(comp_old is not None),
|
|
487
|
+
after=(comp_new is not None),
|
|
488
|
+
comp_func=bool_true_is_good,
|
|
489
|
+
hint={},
|
|
490
|
+
),
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# Region count: smaller complement is better (more coverage), so increases are "bad".
|
|
494
|
+
_emit(
|
|
495
|
+
"num_comp_regions_total",
|
|
496
|
+
ValueDiff[int](
|
|
497
|
+
label="Complement region count",
|
|
498
|
+
before=len(old_regions),
|
|
499
|
+
after=len(new_regions),
|
|
500
|
+
comp_func=numeric_increase_bad,
|
|
501
|
+
hint={},
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# -------------------------------------------------------------------------
|
|
506
|
+
# >>> Traces
|
|
507
|
+
# -------------------------------------------------------------------------
|
|
508
|
+
_emit(
|
|
509
|
+
"num_test_traces_total",
|
|
510
|
+
ValueDiff[int](
|
|
511
|
+
label="Number of test traces",
|
|
512
|
+
before=art_old.num_test_traces_total,
|
|
513
|
+
after=art_new.num_test_traces_total,
|
|
514
|
+
comp_func=numeric_increase_good,
|
|
515
|
+
hint={},
|
|
516
|
+
),
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
_emit(
|
|
520
|
+
"num_test_traces_logic_good",
|
|
521
|
+
ValueDiff[int](
|
|
522
|
+
label="Number of test traces logically valid",
|
|
523
|
+
before=art_old.num_test_traces_logic_good,
|
|
524
|
+
after=art_new.num_test_traces_logic_good,
|
|
525
|
+
comp_func=numeric_increase_good,
|
|
526
|
+
hint={},
|
|
527
|
+
),
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
_emit(
|
|
531
|
+
"num_test_traces_matched",
|
|
532
|
+
ValueDiff[int](
|
|
533
|
+
label="Number of test traces matched",
|
|
534
|
+
before=art_old.num_test_traces_matched,
|
|
535
|
+
after=art_new.num_test_traces_matched,
|
|
536
|
+
comp_func=numeric_increase_good,
|
|
537
|
+
hint={},
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
_emit(
|
|
542
|
+
"num_log_traces_total",
|
|
543
|
+
ValueDiff[int](
|
|
544
|
+
label="Number of log traces",
|
|
545
|
+
before=art_old.num_log_traces_total,
|
|
546
|
+
after=art_new.num_log_traces_total,
|
|
547
|
+
comp_func=numeric_increase_good,
|
|
548
|
+
hint={},
|
|
549
|
+
),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
_emit(
|
|
553
|
+
"num_log_traces_logic_good",
|
|
554
|
+
ValueDiff[int](
|
|
555
|
+
label="Number of log traces logically valid",
|
|
556
|
+
before=art_old.num_log_traces_logic_good,
|
|
557
|
+
after=art_new.num_log_traces_logic_good,
|
|
558
|
+
comp_func=numeric_increase_good,
|
|
559
|
+
hint={},
|
|
560
|
+
),
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
_emit(
|
|
564
|
+
"num_log_traces_matched",
|
|
565
|
+
ValueDiff[int](
|
|
566
|
+
label="Number of log traces matched",
|
|
567
|
+
before=art_old.num_log_traces_matched,
|
|
568
|
+
after=art_new.num_log_traces_matched,
|
|
569
|
+
comp_func=numeric_increase_good,
|
|
570
|
+
hint={},
|
|
571
|
+
),
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
return StateDiff(**dict(vals))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/state/recommendation.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
class Recommendation(BaseModel):
|
|
11
|
+
text: str
|
|
12
|
+
kind: Literal["error", "warning", "next", "info"] = "next"
|
|
13
|
+
priority: int = 50 # lower = higher priority
|