pyfcstm.diagnostics.inspect
Structured model inspection for pyfcstm.
This module provides inspect_model(), a single entry point that
walks a pyfcstm.model.StateMachine and produces a stable,
serialization-friendly view of its structure plus five derived
relational graphs (reachability, event emission, variable data flow,
aspect impact, action reference). The output is the foundation that
Layer 2 design-health warnings (W_* / I_* codes) and downstream
LLM / evaluation tooling consume.
The view shape is the single source of truth for the pyfcstm /
jsfcstm contract. Adding or renaming a field here must be mirrored on
the jsfcstm side (editors/jsfcstm/src/diagnostics/inspect.ts) and
in pyfcstm/diagnostics/schema.json.
The module exposes the following dataclasses:
StateInfo— per-state structural summaryTransitionInfo— per-transition structural summaryVariableInfo— per-variable structural summary plus guard-affect flags used byW_UNREFERENCED_VAREventInfo— per-event structural summaryModelMetrics— aggregate counts and ratiosModelInspect— top-level container including diagnostics
Examples:
>>> from pyfcstm.dsl import parse_with_grammar_entry
>>> from pyfcstm.model import parse_dsl_node_to_state_machine
>>> from pyfcstm.diagnostics import inspect_model
>>> source = '''
... def int counter = 0;
... state Root {
... state Idle;
... state Active;
... [*] -> Idle;
... Idle -> Active : if [counter > 0];
... }
... '''
>>> ast = parse_with_grammar_entry(source, 'state_machine_dsl')
>>> machine = parse_dsl_node_to_state_machine(ast)
>>> report = inspect_model(machine)
>>> report.metrics.n_states_leaf
2
DEFAULT_DEEP_HIERARCHY_THRESHOLD
- pyfcstm.diagnostics.inspect.DEFAULT_DEEP_HIERARCHY_THRESHOLD = 6
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4
DEFAULT_LARGE_COMPOSITE_THRESHOLD
- pyfcstm.diagnostics.inspect.DEFAULT_LARGE_COMPOSITE_THRESHOLD = 12
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4
DEFAULT_VAR_TO_LEAF_RATIO_THRESHOLD
- pyfcstm.diagnostics.inspect.DEFAULT_VAR_TO_LEAF_RATIO_THRESHOLD = 2.0
Convert a string or number to a floating point number, if possible.
KNOWN_SPANLESS_CODES
- pyfcstm.diagnostics.inspect.KNOWN_SPANLESS_CODES = frozenset({})
frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object
Build an immutable unordered collection of unique elements.
StateInfo
- class pyfcstm.diagnostics.inspect.StateInfo(path: str, name: str, parent_path: str | None, is_leaf: bool, is_pseudo: bool, is_composite: bool, substates: Tuple[str, ...], initial_targets: Tuple[Dict[str, Any], ...], entry_actions: Tuple[str, ...], during_actions: Tuple[str, ...], exit_actions: Tuple[str, ...], aspect_before: Tuple[str, ...], aspect_after: Tuple[str, ...], has_abstract_action: bool, span: Span | None = None)[source]
Structural summary of a single state.
- Parameters:
path (str) – Dotted hierarchical path, e.g.
'Root.SubSystem.Active'.name (str) – Short name of the state (last component of
path).parent_path (Optional[str]) – Dotted path of the parent state, or
Nonefor the root state.is_leaf (bool) –
Truewhen this state has no substates.is_pseudo (bool) –
Truewhen the state was declared withpseudo state.is_composite (bool) –
Truewhen this state has substates.substates (Tuple[str, ...]) – Direct-child state paths, in source order.
initial_targets (Tuple[Mapping[str, Any], ...]) – Each item describes one
[*] -> Xinitial transition declared inside this composite.targetis the target child path,guardis the source text of the guard orNone,eventis the qualified event name orNone,is_unconditionalisTrueonly when both guard and event are absent.entry_actions (Tuple[str, ...]) – Action labels (function name or
'<inline>') forenteractions on this state, in source order.during_actions (Tuple[str, ...]) – Action labels for
duringactions.exit_actions (Tuple[str, ...]) – Action labels for
exitactions.aspect_before (Tuple[str, ...]) – Aspect-action labels for
>> during before.aspect_after (Tuple[str, ...]) – Aspect-action labels for
>> during after.has_abstract_action (bool) –
Trueif any of the actions above is abstract. Used byVariableInfoconfidence judgements.
TransitionInfo
- class pyfcstm.diagnostics.inspect.TransitionInfo(from_path: str, to_path: str, event: str | None, event_scope: str | None, guard: str | None, effect: str | None, effect_self_assigns: ~typing.Tuple[str, ...], is_forced: bool, forced_origin: str | None, transition_index: int | None, span: ~pyfcstm.utils.validate.Span | None = None, effect_spans: ~typing.Tuple[~pyfcstm.utils.validate.Span, ...] = <factory>, effect_self_assign_spans: ~typing.Tuple[~pyfcstm.utils.validate.Span | None, ...] = <factory>)[source]
Structural summary of a single transition.
- Parameters:
from_path (str) – Dotted path of the source state, or the literal
'[*]'for an initial transition declared at the root.to_path (str) – Dotted path of the target state, or
'[*]'for an exit transition.event (Optional[str]) – Qualified event name (e.g.
'Root.SubA.E') orNoneif the transition has no event.event_scope (Optional[str]) –
'local','chain','absolute', orNonewhen there is no event.guard (Optional[str]) – Normalized guard expression text, or
None. Pyfcstm and jsfcstm share this inspect expression format so downstream range resolution can treatguard_textas a stable disambiguation hint.effect (Optional[str]) – Source text of the effect block, or
None.effect_self_assigns (Tuple[str, ...]) – Variable names assigned to themselves anywhere inside the transition effect block, including nested
ifbranches. Duplicate names are preserved so quick-fix emitters can detect ambiguous occurrences.effect_self_assign_spans (Tuple[Optional[pyfcstm.utils.validate.Span], ...]) – Source spans for the self-assign statements listed in
effect_self_assigns. The order matcheseffect_self_assignsand usesNonewhen a statement has no source span, preventing later spans from shifting to earlier names.is_forced (bool) –
Truewhen the transition was expanded from a!-prefixed forced transition.forced_origin (Optional[str]) – Raw source text of the original
!X -> Ydeclaration whenis_forcedisTrue, otherwiseNone.transition_index (Optional[int]) – Zero-based index in parent-first model transition order, including expanded forced transitions at their declaring state before ordinary transitions and descendant-state transitions. Downstream tooling may use this as a best-effort source-range disambiguation hint when spans are not available.
VariableInfo
- class pyfcstm.diagnostics.inspect.VariableInfo(name: str, type: str, init_value: str, read_in_states: ~typing.Tuple[str, ...], written_in_states: ~typing.Tuple[str, ...], read_in_guards: ~typing.Tuple[~typing.Tuple[str, str], ...], written_in_effects: ~typing.Tuple[~typing.Tuple[str, str], ...], affects_guard_directly: bool, affects_guard_indirectly: bool, abstract_actions_in_scope: ~typing.Tuple[str, ...], float_literal_assignments: ~typing.Tuple[str, ...] = <factory>, span: ~pyfcstm.utils.validate.Span | None = None, float_literal_assignment_spans: ~typing.Tuple[~pyfcstm.utils.validate.Span | None, ...] = <factory>)[source]
Structural summary of a variable definition plus guard-affect flags.
The
affects_guard_directlyandaffects_guard_indirectlyflags are precomputed here so that unreferenced-variable diagnostics can be expressed as a one-line filter against this object.- Parameters:
name (str) – Variable identifier.
type (str) – Declared type, currently
'int'or'float'.init_value (str) – Source text of the initializer expression.
read_in_states (Tuple[str, ...]) – State paths where the variable is read inside any action (
enter/during/exit/ aspect).written_in_states (Tuple[str, ...]) – State paths where the variable is written inside any action.
read_in_guards (Tuple[Tuple[str, str], ...]) – Tuples
(from_path, to_path)of transitions whose guard reads this variable.written_in_effects (Tuple[Tuple[str, str], ...]) – Tuples
(from_path, to_path)of transitions whose effect block writes this variable.affects_guard_directly (bool) –
Truewhen the variable is read by at least one transition guard.affects_guard_indirectly (bool) –
Truewhen the variable reaches a transition guard through the conservative use-def graph.abstract_actions_in_scope (Tuple[str, ...]) – Function names of abstract actions that may access this variable. FCSTM variables are global, so any abstract action in the machine is conservatively visible here. Downstream diagnostics can use this to distinguish high-confidence unused variables from variables that may be touched by abstract behavior.
float_literal_assignments (Tuple[str, ...]) – Source text of float literal assignments to this variable from lifecycle actions or transition effects.
EventInfo
- class pyfcstm.diagnostics.inspect.EventInfo(qualified_name: str, scope: str, used_by: Tuple[Tuple[str, str], ...], is_declared: bool, is_used: bool, span: Span | None = None)[source]
Structural summary of an event declaration.
- Parameters:
qualified_name (str) – Dotted fully qualified event name (e.g.
'Root.SubA.E').scope (str) –
'local','chain', or'absolute'.used_by (Tuple[Tuple[str, str], ...]) –
(from_path, to_path)tuples for every transition that references this event.is_declared (bool) –
Truewhen the event came from an expliciteventdeclaration.is_used (bool) –
Truewhen at least one transition references the event.
ActionInfo
ForcedTransitionInfo
ModelMetrics
- class pyfcstm.diagnostics.inspect.ModelMetrics(n_states_leaf: int, n_states_composite: int, n_states_pseudo: int, max_hierarchy_depth: int, n_transitions_normal: int, n_transitions_forced: int, n_events: int, n_variables: int, var_to_leaf_ratio: float, aspect_coverage: Dict[str, int], abstract_action_inventory: Tuple[str, ...])[source]
Aggregate model metrics.
- Parameters:
n_states_leaf (int) – Number of leaf states excluding pseudo states.
n_states_composite (int) – Number of composite states.
n_states_pseudo (int) – Number of pseudo states.
max_hierarchy_depth (int) – Maximum depth of state nesting, counted from the root (depth 0 = root).
n_transitions_normal (int) – Number of transitions that did not originate from a
!-forced declaration.n_transitions_forced (int) – Number of transitions expanded from
!-forced declarations.n_events (int) – Number of distinct qualified events exposed by the inspect surface, including explicitly declared events that no transition uses.
n_variables (int) – Number of variable definitions.
var_to_leaf_ratio (float) –
n_variables / max(n_states_leaf, 1).aspect_coverage (Dict[str, int]) – Mapping
composite_path -> n_descendant_leavesfor composite states that declare>> duringaspects.abstract_action_inventory (Tuple[str, ...]) – Function names of every abstract action across the model, sorted for stable output.
ModelInspect
- class pyfcstm.diagnostics.inspect.ModelInspect(root_state_path: str, states: ~typing.Tuple[~pyfcstm.diagnostics.inspect.StateInfo, ...], transitions: ~typing.Tuple[~pyfcstm.diagnostics.inspect.TransitionInfo, ...], variables: ~typing.Tuple[~pyfcstm.diagnostics.inspect.VariableInfo, ...], events: ~typing.Tuple[~pyfcstm.diagnostics.inspect.EventInfo, ...], actions: ~typing.Tuple[~pyfcstm.diagnostics.inspect.ActionInfo, ...], forced_transitions: ~typing.Tuple[~pyfcstm.diagnostics.inspect.ForcedTransitionInfo, ...], metrics: ~pyfcstm.diagnostics.inspect.ModelMetrics, reachability_graph: ~typing.Dict[str, ~typing.Tuple[str, ...]], event_emission_map: ~typing.Dict[str, ~typing.Tuple[str, ...]], var_dataflow: ~typing.Dict[str, ~typing.Dict[str, ~typing.Tuple[str, ...]]], aspect_impact_map: ~typing.Dict[str, ~typing.Tuple[str, ...]], action_ref_graph: ~typing.Dict[str, ~typing.Tuple[str, ...]], diagnostics: ~typing.Tuple[~pyfcstm.utils.validate.ModelDiagnostic, ...] = <factory>)[source]
Top-level structured view of a state machine model.
- Parameters:
root_state_path (str) – Dotted path of the root state.
states (Tuple[StateInfo, ...]) – All states walked from the root in pre-order.
transitions (Tuple[TransitionInfo, ...]) – All transitions, including expanded forced transitions, in source order.
variables (Tuple[VariableInfo, ...]) – All
defvariables, in declaration order.events (Tuple[EventInfo, ...]) – All qualified events exposed by the inspect surface, including explicitly declared events that no transition uses, sorted by qualified name.
metrics (ModelMetrics) – Aggregate model metrics.
reachability_graph (Dict[str, Tuple[str, ...]]) – Mapping from every state path to state paths reachable through normal transitions and composite initial edges. Guards are ignored;
[*]entry/exit markers are not exposed.event_emission_map (Dict[str, Tuple[str, ...]]) – Mapping event qualified name → list of source state paths that can emit it.
var_dataflow (Dict[str, Dict[str, Tuple[str, ...]]]) – Mapping variable name →
{'reads': [...], 'writes': [...]}of state paths.aspect_impact_map (Dict[str, Tuple[str, ...]]) – Mapping composite path → descendant leaf paths actually reached by its aspect actions.
action_ref_graph (Dict[str, Tuple[str, ...]]) – Mapping named-action function path → list of
refedges out of it.diagnostics (Tuple[ModelDiagnostic, ...]) – Layer 1
E_*plus design-healthW_*/I_*diagnostics derived from the inspect payload.
- to_json() Dict[str, Any][source]
Serialize this inspection report to a plain JSON-friendly dict.
Tuples are converted to lists; frozen dataclasses to dicts.
ModelDiagnosticinstances are serialized via their public attributes (code,severity,message,span,refs).- Returns:
A dict that round-trips through
json.dumps()without loss.- Return type:
Dict[str, Any]
Examples:
>>> from pyfcstm.dsl import parse_with_grammar_entry >>> from pyfcstm.model import parse_dsl_node_to_state_machine >>> ast = parse_with_grammar_entry('state Root;', 'state_machine_dsl') >>> machine = parse_dsl_node_to_state_machine(ast) >>> report = inspect_model(machine) >>> report.to_json()['root_state_path'] 'Root'
inspect_model
- pyfcstm.diagnostics.inspect.inspect_model(machine: StateMachine, *, deep_hierarchy_threshold: int = 6, large_composite_threshold: int = 12, var_to_leaf_ratio_threshold: float = 2.0, enable_verify: bool = False, max_complexity_tier: str = 'structural', max_call_count_scaling: str = 'linear_in_transitions', smt_timeout_ms: int | None = None) ModelInspect[source]
Build a structured inspection report for a state machine model.
The report combines the structural payload, the five derived view graphs, and design-health diagnostics that can be computed from the inspect surface.
- Parameters:
machine (pyfcstm.model.StateMachine) – The state machine model to inspect.
deep_hierarchy_threshold (int) – Maximum accepted hierarchy depth.
large_composite_threshold (int) – Maximum accepted number of direct child states in one composite.
var_to_leaf_ratio_threshold (float) – Maximum accepted variable to non-pseudo leaf-state ratio.
enable_verify (bool, optional) – Whether to run inspect-eligible
pyfcstm.verifyalgorithms and append their diagnostics. The defaultFalsepreserves the Layer 2 inspect contract.max_complexity_tier (str, optional) – Maximum verify complexity tier accepted by the inspect adapter when
enable_verifyis true."structural"keeps the default to graph-only verification.max_call_count_scaling (str, optional) – Maximum verify call-count scaling accepted by the inspect adapter when
enable_verifyis true.smt_timeout_ms (Optional[int], optional) – Optional solver timeout forwarded to SMT-local verify algorithms.
Nonepreserves the raw verify default of no configured timeout.
- Returns:
Structured view of the model.
- Return type:
Examples:
>>> from pyfcstm.dsl import parse_with_grammar_entry >>> from pyfcstm.model import parse_dsl_node_to_state_machine >>> source = ''' ... state Root { ... state A; ... state B; ... [*] -> A; ... A -> B; ... } ... ''' >>> ast = parse_with_grammar_entry(source, 'state_machine_dsl') >>> machine = parse_dsl_node_to_state_machine(ast) >>> report = inspect_model(machine) >>> report.reachability_graph['Root.A'] ('Root.B',) >>> verify_report = inspect_model(machine, enable_verify=True) >>> len(verify_report.diagnostics) >= len(report.diagnostics) True