Skip to content

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

EventFilter = BaseSpecification[EventEnvelope[Any]]

Reuses :class:~mineproductivity.core.specification.BaseSpecification (&/|/~ composable) rather than a bespoke query DSL.

BaseEvent dataclass

BaseEvent(equipment_id, shift_id)

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

duration_h()

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

publish(envelope)

Called by an :class:~mineproductivity.events.store.EventStore after a successful, durable write. Never called before durability is confirmed.

subscribe abstractmethod

subscribe(filter, handler)

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.

is_active abstractmethod property

is_active

Whether this subscription's handler is still registered.

cancel abstractmethod

cancel()

Unregister the associated handler. Idempotent: cancelling an already-cancelled subscription is a no-op.

ConsumptionEvent dataclass

ConsumptionEvent(equipment_id, shift_id, resource_type, quantity, unit)

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

cycle_min property

cycle_min

The total cycle duration: the sum of all six legs.

DelayEvent dataclass

DelayEvent(equipment_id, shift_id, delay_category, delay_reason, duration_min)

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

SafetyEvent(equipment_id, shift_id, safety_event_type, severity, *, zone_id=None)

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

DuplicateEventError(message, *, details=None)

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

EventNotFoundError(message, *, details=None)

Bases: NotFoundError

EventStore.get() found no envelope for the given EventID (optionally, at the given version).

EventValidationError

EventValidationError(message, *, details=None)

Bases: ValidationError

A :class:~mineproductivity.events.base_event.BaseEvent or :class:~mineproductivity.events.envelope.EventEnvelope failed structural or schema validation.

EventVersionConflictError

EventVersionConflictError(message, *, details=None)

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

ReplayError(message, *, details=None)

Bases: MineProductivityError

A replay() or snapshot() request could not be satisfied (e.g. as_of predates the store's retention horizon).

EventID dataclass

EventID(value)

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

generate classmethod

generate()

Return a new, randomly generated, time-sortable identifier.

AsOf dataclass

AsOf(*, utc=None, scenario=None)

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

ReplayHandle(as_of, envelopes)

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
()

get

get(event_id)

Return the envelope for event_id in this replayed state, or None.

EventSchema dataclass

EventSchema(event_type_code, version, required_fields, field_types, *, invariants=())

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

to_json_schema()

Export this schema as a (draft-2020-12-flavored) JSON Schema document.

EventSnapshot dataclass

EventSnapshot(as_of, state)

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:

>>> snapshot = EventSnapshot(as_of=AsOf(scenario="genesis"), state={})
>>> snapshot.state
{}

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(envelope)

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_batch(envelopes)

Append many envelopes, short-circuiting on the first rejection.

get abstractmethod

get(event_id, *, as_of_version=None)

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.

find abstractmethod

find(event_id)

Non-raising counterpart to :meth:get.

query abstractmethod

query(query)

Stream envelopes matching query. MUST be a generator (or otherwise lazy Iterator) -- never a materialized list.

replay abstractmethod

replay(as_of)

Reconstruct the store's logical state as of a point in time or a named scenario.

snapshot abstractmethod

snapshot(as_of)

Materialize a snapshot to accelerate future replay to or past as_of.

ConfidenceScore dataclass

ConfidenceScore(value, *, reasons=())

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

EventValidator(*, entity_resolver=None)

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_with_confidence(candidate)

Validate candidate and derive its :class:ConfidenceScore in one call.

ValidationOutcome dataclass

ValidationOutcome(result, confidence)

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

EventVersion(*, version=1)

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