mineproductivity.events¶
Auto-generated API reference for the Events package, rendered from the package's own docstrings. For the narrative overview, dependency rules, and extension guide, see Packages -> Events.
events ¶
mineproductivity.events -- the immutable, append-only event model
every derived state in the platform is computed from.
Implements Event Sourcing: the event log is the single system of record; every other piece of derived state (a KPI value, a Digital Twin snapshot, an analytics aggregate) is a pure function of the event log up to some point in time.
events depends on core and ontology (ontology.DelayCategory
and ontology.SafetyEventType -- see Documentation Governance Rule
005 and ontology/README.md) and nothing else -- see README.md¶
for the full set of architectural rules this package must satisfy.
Everything documented here is part of the public API and can be imported
directly from mineproductivity.events, e.g.::
from mineproductivity.events import CycleEvent, EventEnvelope, EventID
EventFilter ¶
Reuses :class:~mineproductivity.core.specification.BaseSpecification
(&/|/~ composable) rather than a bespoke query DSL.
BaseEvent
dataclass
¶
Bases: BaseValueObject, ABC
Abstract root of every canonical event payload type.
A BaseEvent carries only the business-domain fact; identity,
version, and the three timestamps live one level up, on
:class:~mineproductivity.events.envelope.EventEnvelope. This
separation is deliberate: two envelopes with different event_id\ s
can legitimately wrap payload instances that are field-for-field
identical (e.g. two trucks completing structurally similar cycles) --
BaseEvent equality is a :class:~mineproductivity.core.value_object.BaseValueObject\ 's
field equality, while :class:~mineproductivity.events.envelope.EventEnvelope
equality is additionally scoped by EventID.
Every concrete event type declares equipment_id/shift_id
(references into the future Ontology Framework, held as plain
strings -- see :class:~mineproductivity.events.base_event.BaseEvent's
module docstring and Documentation Governance Rule #005) and a class
-level event_type_code registry key.
duration_h
abstractmethod
¶
Return this event's duration in hours, however duration is
defined for the concrete type (a cycle's total time, a delay's
span, an instantaneous reading's 0.0, ...).
EventBus ¶
Bases: ABC
Near-real-time publish/subscribe distribution of newly appended envelopes, for consumers that cannot wait for a query (streaming KPIs, a future Digital Twin's live-state sync).
publish
abstractmethod
¶
Called by an :class:~mineproductivity.events.store.EventStore
after a successful, durable write. Never called before durability
is confirmed.
subscribe
abstractmethod
¶
Register handler to be called for every published envelope
matching filter. Returns a :class:Subscription whose
cancel() unregisters it.
Subscription ¶
Bases: ABC
A handle to one active :meth:EventBus.subscribe registration.
ConsumptionEvent
dataclass
¶
Bases: BaseEvent
One resource-consumption reading -- e.g. a single fuel-dispensing
event. Instantaneous by nature (a metering reading, not a span), so
:meth:duration_h is always 0.0.
Examples:
>>> event = ConsumptionEvent(
... equipment_id="T-101", shift_id="PIL_2026-01-15_D",
... resource_type=ResourceType.FUEL, quantity=1840.0, unit="L",
... )
>>> event.duration_h()
0.0
CycleEvent
dataclass
¶
CycleEvent(equipment_id, shift_id, queue_min, spot_min, load_min, haul_min, dump_min, return_min, payload_t, *, route_id=None, operator_id=None, material_type='unspecified')
Bases: BaseEvent
One completed haul cycle: queue → spot → load → haul → dump → return.
Examples:
>>> cycle = CycleEvent(
... equipment_id="HT-214", shift_id="A-2026-06-25",
... queue_min=1.5, spot_min=0.5, load_min=2.5,
... haul_min=8.0, dump_min=1.0, return_min=6.0,
... payload_t=220.0,
... )
>>> cycle.cycle_min
19.5
DelayEvent
dataclass
¶
Bases: BaseEvent
One delay event, classified into exactly one of the six canonical,
mutually-exclusive :class:~mineproductivity.ontology.DelayCategory
values (Developer & Cookbook Guide Part III, "Canonical Semantics").
Examples:
>>> delay = DelayEvent(
... equipment_id="CR-01", shift_id="A-2026-06-25",
... delay_category=DelayCategory.EQUIPMENT,
... delay_reason="crusher_down", duration_min=252.0,
... )
>>> delay.duration_h()
4.2
MaintenanceEvent
dataclass
¶
MaintenanceEvent(equipment_id, shift_id, failure_start_utc, return_to_service_utc, total_downtime_h, is_planned, failure_mode_code)
Bases: BaseEvent
One equipment failure event, from failure to return-to-service.
Examples:
>>> from datetime import datetime, timezone
>>> event = MaintenanceEvent(
... equipment_id="T-101", shift_id="PIL_2026-01-15_D",
... failure_start_utc=datetime(2026, 1, 15, 4, tzinfo=timezone.utc),
... return_to_service_utc=datetime(2026, 1, 15, 7, tzinfo=timezone.utc),
... total_downtime_h=3.0, is_planned=False, failure_mode_code="HYD-001",
... )
>>> event.duration_h()
3.0
ProductionEvent
dataclass
¶
ProductionEvent(equipment_id, shift_id, pit_code, material_type, tonnes_moved, planned_tonnes, operating_h)
Bases: BaseEvent
A shift-level production summary for one pit/material combination.
Examples:
>>> event = ProductionEvent(
... equipment_id="PIT-NORTH", shift_id="PIL_2026-01-15_D",
... pit_code="PIT-NORTH", material_type="Ore",
... tonnes_moved=38400.0, planned_tonnes=40000.0, operating_h=29.7,
... )
>>> event.duration_h()
29.7
ResourceType ¶
Bases: Enum
The resource kinds a :class:ConsumptionEvent can meter.
SafetyEvent
dataclass
¶
Bases: BaseEvent
One leading-safety-indicator observation (speed violation, fatigue
flag, proximity alert, seatbelt non-compliance). Instantaneous by
nature, so :meth:duration_h is always 0.0.
Examples:
>>> event = SafetyEvent(
... equipment_id="HT-214", shift_id="A-2026-06-25",
... safety_event_type=SafetyEventType.SPEED_VIOLATION,
... severity=SafetySeverity.MEDIUM, zone_id="B7N_CR1",
... )
>>> event.duration_h()
0.0
SafetyEventType ¶
Bases: Enum
The leading safety-indicator kinds a safety event can record.
SafetySeverity ¶
Bases: Enum
The severity of one observed safety event.
EventEnvelope
dataclass
¶
EventEnvelope(event_id, version, payload, event_time_utc, processing_time_utc, ingestion_time_utc, *, metadata=(lambda: EventMetadata(name='event'))())
Bases: BaseValueObject, Generic[TEvent]
The stored/transmitted unit: identity + version + times + payload.
Implements the three-time-concept temporal model mandated by the
Learning & Benchmark Suite v1.0 ("Temporal Data Philosophy"):
event_time_utc (when it happened -- the canonical calculation
basis), processing_time_utc (when the source system processed it
-- diagnostic only, never a calculation basis), and
ingestion_time_utc (when this platform accepted it -- the audit
trail). In normal operation event_time_utc <= processing_time_utc
<= ingestion_time_utc, and that ordering is enforced here.
Examples:
>>> from datetime import datetime, timezone
>>> from mineproductivity.events.canonical import CycleEvent
>>> now = datetime(2026, 6, 25, 6, 0, tzinfo=timezone.utc)
>>> envelope = EventEnvelope(
... event_id=EventID.generate(), version=EventVersion(),
... payload=CycleEvent(
... equipment_id="HT-214", shift_id="A-2026-06-25",
... queue_min=1.5, spot_min=0.5, load_min=2.5,
... haul_min=8.0, dump_min=1.0, return_min=6.0, payload_t=220.0,
... ),
... event_time_utc=now, processing_time_utc=now, ingestion_time_utc=now,
... )
>>> envelope.payload.payload_t
220.0
EventMetadata
dataclass
¶
EventMetadata(name, *, description='', tags=frozenset(), attributes=dict(), confidence=1.0, source_system='unknown', late_arrival=False)
Bases: BaseMetadata
Descriptive and provenance metadata attached to every envelope.
Deliberately kept out of :class:~mineproductivity.events.base_event.BaseEvent's
own fields so an event's business payload never mixes with its
trust/provenance data.
DuplicateEventError ¶
Bases: MineProductivityError
Raised when an implementation chooses to reject a duplicate rather than silently no-op it. Not raised by the reference in-memory store, which treats an identical re-append as an idempotent no-op.
EventNotFoundError ¶
Bases: NotFoundError
EventStore.get() found no envelope for the given EventID
(optionally, at the given version).
EventValidationError ¶
Bases: ValidationError
A :class:~mineproductivity.events.base_event.BaseEvent or
:class:~mineproductivity.events.envelope.EventEnvelope failed
structural or schema validation.
EventVersionConflictError ¶
Bases: MineProductivityError
An append specified a version that does not extend the store's
current version chain for that EventID (e.g. appending version 1
again with different field values).
ReplayError ¶
Bases: MineProductivityError
A replay() or snapshot() request could not be satisfied
(e.g. as_of predates the store's retention horizon).
EventID
dataclass
¶
Bases: BaseIdentifier[str]
Globally unique identifier for one event instance.
Generated values are 26-character, Crockford Base32-encoded,
time-sortable identifiers (ULID-shaped): the leading characters
encode a millisecond timestamp, so EventID values generated later
sort lexicographically after ones generated earlier -- a property
:class:~mineproductivity.events.store.EventStore range-scans can
rely on. A caller may also construct an EventID directly from any
string (e.g. EventID(value="csv-0001")) when a source system
supplies its own stable identifier.
Examples:
>>> first = EventID.generate()
>>> second = EventID.generate()
>>> first != second
True
>>> len(first.value)
26
AsOf
dataclass
¶
Bases: BaseValueObject
A point-in-time (or named-scenario) reference for replay.
Exactly one of utc/scenario is expected to be meaningful for
a given replay call; both are optional so a future
scenario/what-if-forking capability (Digital Twin) can extend this
type's usage without a breaking change.
Examples:
>>> from datetime import datetime, timezone
>>> AsOf(utc=datetime(2026, 6, 18, 6, tzinfo=timezone.utc)).utc.year
2026
ReplayHandle
dataclass
¶
Bases: BaseValueObject, Generic[TEnvelope]
The materialized result of an :meth:~mineproductivity.events.store.EventStore.replay call:
the highest-version envelope per EventID as of as_of.
Examples:
>>> handle: ReplayHandle[EventEnvelope] = ReplayHandle(as_of=AsOf(scenario="genesis"), envelopes=())
>>> handle.envelopes
()
EventSchema
dataclass
¶
Bases: BaseValueObject
The machine-readable shape of one event type -- used for
validation, documentation generation, and JSON Schema export (parity
with the ontology's to_schema() pattern).
Examples:
>>> schema = EventSchema(
... event_type_code="CYCLE",
... version=EventVersion(),
... required_fields=("payload_t",),
... field_types={"payload_t": "float"},
... invariants=("payload_t >= 0",),
... )
>>> schema.to_json_schema()["required"]
['payload_t']
to_json_schema ¶
Export this schema as a (draft-2020-12-flavored) JSON Schema document.
EventSnapshot
dataclass
¶
Bases: BaseValueObject
A materialized checkpoint of a store's logical state as of as_of.
Snapshotting is a performance extension point, never a correctness
requirement: a conformant :class:~mineproductivity.events.store.EventStore
implementation MAY refuse to produce snapshots (falling back to full
replay from genesis) and still be conformant, provided replay
produces an identical result. The equivalence law a conformant
snapshot strategy must satisfy is::
replay(as_of) == fold(query(since=snapshot.as_of, until=as_of), initial=snapshot.state)
Examples:
EventQuery
dataclass
¶
EventQuery(*, event_types=None, equipment_ids=None, shift_ids=None, since_utc=None, until_utc=None, filters=(), as_of_version_policy='latest')
Bases: BaseValueObject
A query over a store's envelopes.
as_of_version_policy governs version resolution: "latest"
(the default) collapses to the highest stored version per
EventID; "as_of_ingestion" returns every stored version, as
ingested, with no collapsing -- useful for audit/replay tooling that
needs the full correction history rather than only the current view.
EventStore ¶
Bases: ABC, Generic[TEnvelope]
The append-only, immutable system of record.
Specializes the shape of :class:~mineproductivity.core.repository.BaseRepository
(add/get/find/list) with event-sourcing-specific operations:
append-only writes (no update, no delete), version-aware retrieval,
range queries by event_time_utc, and replay. An EventStore
never mutates a stored envelope.
append
abstractmethod
¶
Append one envelope. Idempotent on (event_id, version): a
second append of an identical envelope is a no-op success; an
append of a different envelope under an already-stored
(event_id, version) is a conflict.
append_batch
abstractmethod
¶
Append many envelopes, short-circuiting on the first rejection.
get
abstractmethod
¶
Return the envelope for event_id, at its highest stored
version unless as_of_version pins an earlier one.
Raises:
| Type | Description |
|---|---|
EventNotFoundError
|
If no such envelope (at that version, if specified) exists. |
query
abstractmethod
¶
Stream envelopes matching query. MUST be a generator (or
otherwise lazy Iterator) -- never a materialized list.
replay
abstractmethod
¶
Reconstruct the store's logical state as of a point in time or a named scenario.
snapshot
abstractmethod
¶
Materialize a snapshot to accelerate future replay to or past as_of.
ConfidenceScore
dataclass
¶
Bases: BaseValueObject
How much a downstream consumer should trust one event, in [0.0, 1.0].
Examples:
>>> ConfidenceScore(value=1.0).value
1.0
>>> ConfidenceScore(value=0.5, reasons=("missing operator_id",)).reasons
('missing operator_id',)
EventValidator ¶
Bases: BaseValidator[BaseEvent]
Contextual validation for a constructed :class:~mineproductivity.events.base_event.BaseEvent.
Separate from the structural validation every BaseEvent already
enforces on construction (its own validate(), inherited from
:class:~mineproductivity.core.value_object.BaseValueObject).
EventValidator checks cross-cutting concerns that do not depend
on any single event type's fields: that identity references are
present, and -- via the optional entity_resolver hook -- that
they resolve against the (future) Ontology Framework's entity
registry. Per Documentation Governance Rule #005, this package does
not implement that registry itself; entity_resolver lets a caller
inject real resolution once it exists, without events depending
on it.
Examples:
>>> validator = EventValidator()
>>> from mineproductivity.events.canonical import CycleEvent
>>> event = CycleEvent(
... equipment_id="HT-214", shift_id="A-2026-06-25",
... queue_min=1.5, spot_min=0.5, load_min=2.5,
... haul_min=8.0, dump_min=1.0, return_min=6.0, payload_t=220.0,
... )
>>> validator.validate(event).is_valid
True
validate_with_confidence ¶
Validate candidate and derive its :class:ConfidenceScore in one call.
ValidationOutcome
dataclass
¶
Bases: BaseValueObject
The complete result of validating one event: structural/contextual validation combined with a confidence score.
Examples:
>>> outcome = ValidationOutcome(result=ValidationResult.success(), confidence=ConfidenceScore(1.0))
>>> outcome.is_valid
True
EventVersion
dataclass
¶
Bases: BaseVersionedObject
The correction/revision counter for one :class:~mineproductivity.events.identifier.EventID.
An (EventID, EventVersion) pair is the true primary key of a
stored envelope -- see the Learning & Benchmark Suite v1.0, "Event
corrections and idempotency". version=1 (the inherited default)
is the original ingestion; version=2 and beyond are corrections,
reached only via :meth:~mineproductivity.core.versioning.BaseVersionedObject.next_version.
Never reset.
Examples:
>>> original = EventVersion()
>>> original.version
1
>>> correction = original.next_version()
>>> correction.version
2