Discovery (Python API)¶
Discovery covers inferred FSM candidates, observation ingestion, shadow diffs, and pluggable stores. Conceptual guide: Discovery (inferred FSM) and Classification.
Below are the main service and store entrypoints; protocols and data models are available in the pystator.discovery package.
Engine and thresholds¶
InferenceEngine
¶
InferenceEngine(observation_store: ObservationStore, inference_store: InferenceStore, built_candidate_store: BuiltDiscoveryCandidateStore, shadow_store: ShadowResultStore, thresholds: InferenceThresholds | None = None, *, draft_store: DraftStore | None = None)
Primary discovery API: ingest, infer, and shadow-compare.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observation_store
|
ObservationStore
|
Store for observed state events. |
required |
inference_store
|
InferenceStore
|
Store for inferred edges. |
required |
built_candidate_store
|
BuiltDiscoveryCandidateStore
|
Store for built (persisted) candidate FSM snapshots. |
required |
shadow_store
|
ShadowResultStore
|
Store for shadow comparison results. |
required |
thresholds
|
InferenceThresholds | None
|
Optional edge-count and confidence thresholds. |
None
|
draft_store
|
DraftStore | None
|
Draft scopes before build; defaults to observation_store if it implements draft persistence (same combined store). |
None
|
Source code in src/pystator/discovery/service.py
record_observation
¶
record_observation(machine_name: str, event: ObservedStateEvent) -> bool
Record an observed state event for a machine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
machine_name
|
str
|
Expected machine name (must match event). |
required |
event
|
ObservedStateEvent
|
The observed state event to record. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the observation was successfully stored. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the event's machine_name does not match. |
Source code in src/pystator/discovery/service.py
preview_inference
¶
preview_inference(machine_name: str, *, mode: Literal['inference', 'manual'] = 'inference', entity_ids: list[str] | None = None, min_edge_count: int | None = None, min_confidence: float | None = None) -> dict[str, Any]
Preview inferred edges without creating a candidate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
machine_name
|
str
|
Machine to infer edges for. |
required |
mode
|
Literal['inference', 'manual']
|
Inference mode ( |
'inference'
|
entity_ids
|
list[str] | None
|
Optional filter to specific entities. |
None
|
min_edge_count
|
int | None
|
Override minimum edge count threshold. |
None
|
min_confidence
|
float | None
|
Override minimum confidence threshold. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with inferred edges, selected edges, and metadata. |
Source code in src/pystator/discovery/service.py
build_candidate
¶
build_candidate(machine_name: str, version: str | None = None, *, mode: Literal['inference', 'manual'] = 'inference', entity_ids: list[str] | None = None, edge_selection: list[tuple[str, str]] | None = None, min_edge_count: int | None = None, min_confidence: float | None = None, draft_id: str | None = None) -> BuiltDiscoveryCandidate
Build and store a candidate machine from observations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
machine_name
|
str
|
Machine to build a candidate for. |
required |
version
|
str | None
|
Explicit version string (auto-incremented if omitted). |
None
|
mode
|
Literal['inference', 'manual']
|
|
'inference'
|
entity_ids
|
list[str] | None
|
Optional filter to specific entities. |
None
|
edge_selection
|
list[tuple[str, str]] | None
|
Required for manual mode; list of
|
None
|
min_edge_count
|
int | None
|
Override minimum edge count threshold. |
None
|
min_confidence
|
float | None
|
Override minimum confidence threshold. |
None
|
draft_id
|
str | None
|
Optional originating draft id (recorded in metadata). |
None
|
Returns:
| Type | Description |
|---|---|
BuiltDiscoveryCandidate
|
The persisted built discovery candidate. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If manual mode is used without edge_selection or selection contains unknown edge pairs. |
Source code in src/pystator/discovery/service.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
list_built_candidates
¶
list_built_candidates(machine_name: str) -> list[BuiltDiscoveryCandidate]
Return all saved built candidates for machine_name, store-defined order.
get_built_candidate
¶
get_built_candidate(machine_name: str, version: str) -> BuiltDiscoveryCandidate | None
Return a specific built candidate version, if present.
Source code in src/pystator/discovery/service.py
InferenceThresholds
dataclass
¶
Store factory¶
create_discovery_store
¶
create_discovery_store(backend: str, *, connection_string: str | None = None, options: dict[str, Any] | None = None) -> InMemoryDiscoveryStore | SQLiteDiscoveryStore | PostgresDiscoveryStore | MongoDBDiscoveryStore | RedisDiscoveryStore
Source code in src/pystator/discovery/stores/factory.py
Classification¶
StateClassifier
¶
Evaluates state when rules against data records to determine states.
States are evaluated in their definition order within the rule set.
The first state whose when checks all pass wins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rule_set
|
ClassificationRuleSet
|
The classification rule set containing states and settings. |
required |
Source code in src/pystator/discovery/classification/_classifier.py
classify
¶
Classify a single data record into a state.
Iterates classifiable states in order. For each state, all
top-level when items are AND'd together. The first state
where all checks pass wins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
dict[str, Any]
|
Dict of field values from the data source. |
required |
Returns:
| Type | Description |
|---|---|
ClassificationResult
|
ClassificationResult with the matched state, or |
ClassificationResult
|
default_state if no rule matched. |
Source code in src/pystator/discovery/classification/_classifier.py
from_machine
classmethod
¶
from_machine(machine: StateMachine, *, entity_id_field: str | None = None, timestamp_field: str | None = None, source: str | None = None, default_state: str | None = None) -> StateClassifier
Create a classifier from a loaded StateMachine.
Extracts states that have when clauses in their definition
order. Classification settings are read from
machine.meta["classification"] and can be overridden via
keyword arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
machine
|
StateMachine
|
A loaded StateMachine instance. |
required |
entity_id_field
|
str | None
|
Override for entity_id_field. |
None
|
timestamp_field
|
str | None
|
Override for timestamp_field. |
None
|
source
|
str | None
|
Override for source label. |
None
|
default_state
|
str | None
|
Override for default_state. |
None
|
Returns:
| Type | Description |
|---|---|
StateClassifier
|
A configured StateClassifier. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no states have |
Source code in src/pystator/discovery/classification/_classifier.py
DataIngester
¶
DataIngester(classifier: StateClassifier, engine: InferenceEngine, machine_name: str, *, skip_unclassified: bool = True)
Ingests raw data records through classification into InferenceEngine.
Supports record-by-record streaming and batch (list) ingestion. For each record: classify state -> create ObservedStateEvent -> feed to InferenceEngine.record_observation().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classifier
|
StateClassifier
|
StateClassifier with rules for state determination. |
required |
engine
|
InferenceEngine
|
InferenceEngine to feed observations into. |
required |
machine_name
|
str
|
Target machine name for observations. |
required |
skip_unclassified
|
bool
|
If True, silently skip records with no state. If False, raise ValueError for unclassified records. |
True
|
Source code in src/pystator/discovery/classification/_ingester.py
ingest_record
¶
Classify and ingest a single record (streaming).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
dict[str, Any]
|
Raw data record with field values. |
required |
Returns:
| Type | Description |
|---|---|
ClassificationResult
|
ClassificationResult for this record. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If state is None and skip_unclassified is False. |
ValueError
|
If entity_id field is missing from record. |
Source code in src/pystator/discovery/classification/_ingester.py
ingest_batch
¶
Classify and ingest a batch of records.
Each record is processed independently; one bad record does not abort the batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
list[dict[str, Any]]
|
List of raw data records. |
required |
Returns:
| Type | Description |
|---|---|
IngestionResult
|
IngestionResult with counts of accepted, skipped, and errors. |
Source code in src/pystator/discovery/classification/_ingester.py
Key models (selected)¶
ObservedStateEvent
dataclass
¶
ObservedStateEvent(entity_id: str, machine_name: str, state: str, observed_at: datetime, source: str = 'unknown', source_event_id: str | None = None, metadata: dict[str, Any] = dict(), ingested_at: datetime = _utc_now(), ingest_seq: int = 0)
One observed state for an entity.
Attributes:
| Name | Type | Description |
|---|---|---|
entity_id |
str
|
Identifier for the entity being observed. |
machine_name |
str
|
FSM machine this observation belongs to. |
state |
str
|
The observed state name. |
observed_at |
datetime
|
When the state was observed. |
source |
str
|
Origin system or label for this observation. |
source_event_id |
str | None
|
Optional upstream event ID for traceability. |
metadata |
dict[str, Any]
|
Additional user-defined metadata. |
ingested_at |
datetime
|
When this event was ingested into the store. |
ingest_seq |
int
|
Monotonic sequence number within an ingestion session. |
BuiltDiscoveryCandidate
dataclass
¶
BuiltDiscoveryCandidate(machine_name: str, version: str, status: str, config: dict[str, Any], created_at: datetime = _utc_now(), metadata: dict[str, Any] = dict(), candidate_sequence: int = 0)
Persisted built FSM snapshot from discovery (a versioned candidate machine).
InferredEdge
dataclass
¶
InferredEdge(source_state: str, destination_state: str, count: int, unique_entities: int, unique_sources: int, confidence: float, last_seen_at: datetime | None = None)
Weighted transition inferred from observed sequences.
Imports¶
from pystator.discovery import (
InferenceEngine,
InferenceThresholds,
create_discovery_store,
StateClassifier,
DataIngester,
ObservedStateEvent,
BuiltDiscoveryCandidate,
)
Additional exports (DiscoveryDraft, ShadowDiff, PostgresDiscoveryStore, ClassificationResult, …) are listed in pystator.discovery.__all__.