Skip to content

mineproductivity.kpis

Auto-generated API reference for the Kpis package, rendered from the package's own docstrings. For the narrative overview, dependency rules, and extension guide, see Packages -> Kpis.

kpis

mineproductivity.kpis -- the metric backbone of MineProductivity.

Makes every performance indicator a discoverable, versioned, self -describing object rather than a formula buried in a script or spreadsheet cell (KPI-as-object, the root README's engineering philosophy), guaranteeing the platform's central promise: two engineers, two sites, or two AI agents each compute "availability" must get the same number from the same events.

Implements docs/architecture/05_KPI_Engine_Design_Specification.md exactly. kpis depends on core, ontology, events, and registry -- and MUST NEVER import connectors (the single most load-bearing rule in the governing specification) -- 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.kpis, e.g.::

from mineproductivity.kpis import REGISTRY, KPIEngine

BaseKPI

Bases: ABC

The root of every KPI. A concrete leaf declares meta: ClassVar[KPIMetadata] and implements :meth:_compute -- everything else (validation, dependency declaration, result wrapping) is inherited.

This mirrors Cookbook Part I, Ch. 6 exactly: "The category base... and the metadata do most of the work; _compute is the only logic you write." Instances are stateless across :meth:compute calls and therefore safe to share and invoke concurrently from multiple threads.

compute

compute(rows, window=None)

Validate rows against :meth:_required_columns, then call :meth:_compute, then wrap in a :class:KPIResult.

This method is not overridden by leaf KPIs -- it is the one place the "qualify, don't coerce" stance (Cookbook Part I Ch. 6) is enforced uniformly: a missing required column becomes a warning-carrying result, never an exception.

ResultCache

ResultCache()

Memoizes :class:~mineproductivity.kpis.result.KPIResult per (code, window, scope, fingerprint).

fingerprint is a caller-supplied, cheap proxy for "has anything relevant changed" -- :class:~mineproductivity.kpis.engine.KPIEngine passes the count of envelopes matching the query backing a given computation, so the cache key naturally changes the moment a new matching event lands (design spec §22's "invalidated automatically... never silently stale" guarantee), without requiring a new versioning API on the locked EventStore contract.

Thread-safe: a single lock serializes reads-that-may-write, so concurrent :meth:get_or_compute calls for the same key never duplicate work or corrupt the cache -- the public contract only guarantees a consistent result, not this specific mechanism, mirroring registry.DiscoveryCache's concurrency contract.

get_or_compute

get_or_compute(code, window, scope, fingerprint, compute)

Return the cached result for this key, computing (and caching) it via compute() on first access.

invalidate

invalidate(code=None)

Explicitly drop every cached result for code, or every cached result if code is None.

CostKPI

Bases: BaseKPI, ABC

COST namespace: fuel per tonne, cost per tonne.

DelayKPI

Bases: BaseKPI, ABC

DISP namespace: delay hours by category, dispatch metrics, consuming :class:~mineproductivity.ontology.DelayCategory's six-value taxonomy.

EnergyKPI

Bases: BaseKPI, ABC

ENERGY/CARBON/WATER namespaces: consumption, emissions, and water-use indicators.

HaulageKPI

Bases: BaseKPI, ABC

HAUL namespace: cycle time context, match factor, TonKm.

MaintenanceKPI

Bases: BaseKPI, ABC

MAINT namespace: MTBF, MTTR, Ai.

ProductionKPI

Bases: BaseKPI, ABC

PROD namespace: throughput, cycle time, payload -- read directly from CycleEvent streams (Developer & Cookbook Guide Part III).

QualityKPI

Bases: BaseKPI, ABC

QUAL/GRADE namespaces: recovery, head grade.

SafetyKPI

Bases: BaseKPI, ABC

SAFE/AUTO namespaces: exposure-normalized leading indicators.

UtilizationKPI

Bases: BaseKPI, ABC

UTIL namespace: PA, UA, EU, OEE -- built on the canonical time model (design spec §19): calendar >= scheduled >= available >= operating.

CompositeKPI

Bases: BaseKPI, ABC

A KPI whose value is DERIVED from other KPIs' already-computed results, not directly from raw event rows -- e.g. UTIL.OEE = UTIL.PA x UTIL.Performance x UTIL.Quality (Part III, Canonical Semantics). Kept a distinct base class from leaf KPIs (design spec AD-KP-04) so "reads raw rows" and "reads other KPIs' results" are never conflated in one method signature.

combine

combine(component_results)

The composite counterpart of :meth:BaseKPI.compute: if any declared dependency's result has a None value, propagate None with a warning rather than calling :meth:_combine (which would otherwise have to special-case every missing dependency itself).

DependencyGraph

DependencyGraph(registry)

A DAG over KPI codes, built from KPIMetadata.dependencies. Topologically sorts so dependencies compute before dependents (Part III, Introduction: "the engine resolves them as a directed acyclic graph, computing dependencies before dependents").

Topological order per code is memoized (design spec §9's kpis.dependency_graph._topo_cache), invalidated only by an explicit :meth:invalidate call -- mirrors registry.DiscoveryCache's explicit-invalidation-only rule.

topological_order

topological_order(code)

Dependency-first order of every KPI code code transitively depends on, ending with code itself.

Raises:

Type Description
KPICircularDependencyError

If resolving code's dependency chain finds a cycle.

detect_cycle

detect_cycle()

Non-raising cycle detection across every code currently in the registry. KPIEngine/the @register decorator converts a Some result into :class:KPICircularDependencyError at registration time, not at first execution.

invalidate

invalidate()

Explicitly drop every memoized topological order -- call after the registry changes (a new KPI registered).

KPIEngine

KPIEngine(store, registry, backend, cache, *, shifts=None)

Orchestration ONLY -- holds no metric-specific logic (design spec §3.2, AD-KP-01). Resolves a requested KPI's dependency graph, assembles exactly the rows each node needs, executes leaves before composites, and returns the final :class:KPIResult.

shifts is an optional, injectable lookup of known :class:~mineproductivity.ontology.Shift instances -- when scope["shift"] names a known shift, its start_utc/end_utc resolve the query window directly (a genuine integration with ontology, not a placeholder). Without a matching shift, scope["since"]/scope["until"] (ISO-8601 strings) are used as a direct escape hatch; resolving a "day"/"week"/"month" window label against a site's calendar is Future Work (this reference engine's ontology integration is scoped to shift-level resolution for v0.7.0).

execute

execute(code, *, window, scope)

The .by(grouping) execution path (Cookbook Part I, Ch. 4).

summary

summary(codes, *, window, scope)

Batched multi-KPI execution (the mine.summary(...) equivalent): scans the event store once per distinct required_events set shared across the requested KPIs and their transitive dependencies, rather than once per KPI (design spec §22).

rows_for

rows_for(kpi, window, scope)

Assembles exactly the rows kpi needs: an :class:EventQuery scoped to kpi.meta.required_events and the window/scope, with every envelope flattened into a row dict and round-tripped through the active :class:~mineproductivity.kpis.backends.base_backend.ExecutionBackend (assemble then to_rows) -- this is the mechanical proof that BaseKPI._compute implementations are truly backend-independent (design spec §29's backend parity tests): swapping the backend never changes the row data, only how it was vectorized in transit.

Returns (rows, fingerprint) -- fingerprint is the matching envelope count, the cheap "has anything changed" proxy :class:~mineproductivity.kpis.caching.ResultCache keys on (design spec §22).

KPIAggregationError

KPIAggregationError(message, *, details=None)

Bases: MineProductivityError

An attempt was made to aggregate a RATIO/AVERAGE-kind KPI's already-computed sub-period results as if they were ADDITIVE (the "averaging shift TPHs" mistake, made structurally impossible by the engine rather than merely discouraged by documentation).

KPICircularDependencyError

KPICircularDependencyError(message, *, details=None)

Bases: MineProductivityError

DependencyGraph.detect_cycle() found a cycle -- raised at registration time (registering the KPI that completes the cycle), never deferred to first execution.

KPINotFoundError

KPINotFoundError(message, *, details=None)

Bases: NotFoundError

REGISTRY.get(code) found no registered, non-Retired KPI.

KPIValidationError

KPIValidationError(message, *, details=None)

Bases: ValidationError

KPIMetadata failed validation -- e.g. malformed identifier, missing mandatory field, aggregation/time-model violation.

KPIVersionConflictError

KPIVersionConflictError(message, *, details=None)

Bases: RegistrationError

A plugin attempted to re-register an existing, Active KPI code with metadata that changes its formula/units/semantics without a MAJOR version bump.

KPIStatus

Bases: Enum

Proposed -> Active -> Deprecated -> Retired. A Deprecated KPI remains resolvable via meta.aliases/deprecated_successor; a Retired identifier is never reused.

Aggregation

Bases: Enum

Governs how a KPI MAY be aggregated across periods/groups -- directly enforces the RATIO-not-averaged rule (Cookbook Part I Ch. 6).

DigitalMaturity

Bases: Enum

The site digital-maturity level a KPI requires to be computable.

Direction

Bases: Enum

Which way "better" points for a KPI's value.

KPIMetadata dataclass

KPIMetadata(name, code, *, description='', tags=frozenset(), attributes=dict(), official_name='', business_purpose='', operational_question='', business_meaning='', formula='', unit='', dimensions=(), required_events=(), required_ontology=(), dependencies=(), aggregation=Aggregation.ADDITIVE, visualization_hint='', benchmark_bands=dict(), edge_cases=(), leading_or_lagging='lagging', operational_or_strategic='operational', related_kpis=(), references=(), direction=Direction.HIGHER_IS_BETTER, status=(lambda: KPIStatus.PROPOSED)(), version='1.0.0', min_maturity=DigitalMaturity.L1_MANUAL, method_applicability=('open_pit', 'underground'), commodity_applicability=(), aliases=(), deprecated_successor=None)

Bases: BaseMetadata

The complete, governed metadata for one KPI -- fields 1-15 and 18-29 of the Standard Library's mandatory template are typed engine fields; fields 16-17 (worked example, sample dataset) are documentation artifacts carried in attributes since the engine does not execute prose (design spec §34).

No field is optional -- a blank field is a specification gap (Standard Library governance rule, enforced here at construction).

KPIIdentifier dataclass

KPIIdentifier(namespace, name, *, specialization=())

Bases: BaseValueObject

A parsed NAMESPACE.Name[.Specialization...] KPI code.

Examples:

>>> identifier = parse_identifier("PROD.TPH.Ore")
>>> identifier.namespace, identifier.name, identifier.specialization
('PROD', 'TPH', ('Ore',))
>>> str(identifier)
'PROD.TPH.Ore'

KPIResult dataclass

KPIResult(code, value, unit, *, n=0, warnings=(), scope=dict())

Bases: BaseValueObject

The outcome of one :class:~mineproductivity.kpis.base_kpi.BaseKPI computation -- a scalar value (or None if legitimately uncomputable), plus enough provenance (unit, source-row count, scope) to trace it back to its inputs.

Examples:

>>> result = KPIResult(code="PROD.TPH", value=1212.1, unit="t/h", n=48)
>>> result.value
1212.1
>>> KPIResult(code="PROD.TPH", value=None, unit="t/h", warnings=("zero operating hours",)).warnings
('zero operating hours',)

to_frame

to_frame()

Export to a DataFrame via the process's active :class:~mineproductivity.kpis.backends.base_backend.ExecutionBackend (design spec §9's kpis.backends._active_backend) -- "Familiar by design": .kpi(code).by(grouping).to_frame() mirrors df.groupby(...).agg(...).

plot

plot()

Delegates to the active backend's visualization-metadata hook.

pareto

pareto()

Delegates to the active backend's visualization-metadata hook.

CumulativeWindow dataclass

CumulativeWindow(kind, *, since_utc=None, until_utc=None, start_utc)

Bases: Window

Accumulates from a fixed start (e.g. month-to-date production).

RollingWindow dataclass

RollingWindow(kind, *, since_utc=None, until_utc=None, span, step)

Bases: Window

A moving window of fixed span, re-evaluated at each step (e.g. a 7-day rolling TPH trend).

Window dataclass

Window(kind, *, since_utc=None, until_utc=None)

Bases: BaseValueObject

A time-bounded scope for KPI computation -- "shift", "day", "week", "month", or a custom [since_utc, until_utc).

Examples:

>>> from datetime import timezone
>>> window = Window(
...     kind="custom",
...     since_utc=datetime(2026, 6, 25, tzinfo=timezone.utc),
...     until_utc=datetime(2026, 6, 26, tzinfo=timezone.utc),
... )
>>> window.kind
'custom'

register

register(cls)

Register cls into :data:REGISTRY, keyed by cls.meta.code.

Raises:

Type Description
KPIValidationError

If cls.meta itself is invalid (propagated from construction -- KPIMetadata.validate() already ran when cls.meta was assigned as a class attribute).

KPIVersionConflictError

If cls.meta.code is already registered -- a KPI code is a public contract; a genuine new meaning requires a MAJOR version bump under review, never silent re-registration (design spec §17, §20).

KPICircularDependencyError

If registering cls completes a dependency cycle -- checked immediately, not deferred to first :meth:~mineproductivity.kpis.engine.KPIEngine.execute call.

parse_identifier

parse_identifier(code)

Parse and validate code against the NAMESPACE.Name naming standard: a controlled uppercase namespace, a PascalCase name, and zero or more PascalCase dotted specializations (PROD.TPH.Ore).

Raises:

Type Description
KPIValidationError

If code does not conform.