ADR-0007: Decision Intelligence as a Separate Package Above analytics¶
| Status | Accepted |
| Date | 2026-07-04 |
| Deciders | Chief Software Architect, MineProductivity |
| Governs | mineproductivity.decision |
| Related documents | docs/architecture/07_Decision_Intelligence_Design_Specification.md; docs/design/07_Decision_Intelligence_Implementation_Checklist.md; docs/architecture/06_Analytics_Engine_Design_Specification.md §4, §37 (not yet merged to main - see feature/analytics-engine); ADR-0006-Analytics-Engine.md (not yet merged to main - see feature/analytics-engine) |
Context¶
MineProductivity's Foundation Layer and its first consumer package, analytics, are complete, tested, and locked as of analytics v0.8.0. The Analytics Engine spec itself already anticipated what comes next: its own §37 (Future Roadmap) names decision as the direct, first-named consumer of AnalyticsResult (particularly TrendResult, BenchmarkResult, and Baseline), and states plainly that decision "will consume AnalyticsResult objects... as evidence inputs to recommendation logic." ADR-0006 (governing analytics's own existence) likewise already names decision as the package directly above analytics in the locked dependency chain (core → ontology → events → kpis → analytics → decision). In other words, exactly as analytics formalized a package the Foundation Layer's own documents already assumed would exist, this ADR formalizes and designs decision, the package analytics's own documents already assumed would exist.
The immediate question this ADR answers: given a TrendResult, a BenchmarkResult, or a Baseline - a correct statistical judgment about an already-correct metric - operational users repeatedly ask a further question neither kpis nor analytics was ever meant to answer: what should the business do about it, how urgently, and why. Somewhere in the platform, exactly one package needs to own turning a statistical judgment into a recommended, ranked, explained action. This ADR decides where, and why it is not simply bolted onto analytics.
Decision¶
Create mineproductivity.decision as a new, independent package sitting directly above analytics and directly below the not-yet-designed digital_twin/simulation/optimization/visualization/agents packages:
decision is permitted to import core, events, ontology, registry, plugins, connectors, kpis, and analytics; it introduces its own public API (DecisionModel, DecisionContext, DecisionPipeline, RuleEngine built on core.BaseSpecification, Policy/Threshold as versioned governed artifacts, DecisionStrategy/RankingStrategy and their default concrete implementations, ActionPrioritizer/ActionPlanner, AlertGenerator, DecisionScorer/ConfidenceScorer, and interface-only RootCauseAnalyzer/WhatIfEngine contracts) without requiring any change to any Foundation Layer file, or to analytics, or to any of their public APIs or dependency rules. Its own registry (decision.REGISTRY) is a direct specialization of registry.Registry, exactly as analytics.REGISTRY and kpis.REGISTRY already are. Full design detail is in the governing specification linked above; this ADR records why the package exists and why it is shaped the way it is.
Why a Separate Package (Not Folded Into analytics)¶
- Distinct responsibility, distinct rate of change.
analyticsanswers "what is true about this series of values" (trend, benchmark, distribution) - a descriptive question.decisionanswers "given what is true, what should happen" - a prescriptive question. These are categorically different questions with different failure modes: a wrong trend calculation is a statistics bug; a wrong recommendation is a business-logic bug. Folding them into one package would force one test philosophy to cover two fundamentally different correctness properties (spec 07 §34 vs. spec 06 §35). analytics's own spec already drew this boundary. Spec 06 §37 explicitly identifiesdecisionas a consumer ofanalytics's outputs, never the reverse, and spec 06's own architectural principles (§3) scope it to "computation-only" - statistical treatment, not business judgment. Foldingdecision's rule-evaluation and recommendation logic intoanalyticswould violate that already-locked scope boundary.- Different governance weight for different artifacts. A
Policy(§12 of the governing spec) is a versioned, auditable business commitment - governed the way a KPI code is (spec 05 §17's "public contract" rule), not the way anAnalyticsModelimplementation is (spec 06's deliberately lighterAnalyticsMetadata).analyticshas no concept of a business policy at all; introducing one there would mean introducing KPI-grade governance ceremony into a package whose own ADR (ADR-0006, Architectural Principle 6) deliberately chose not to carry that weight.decisionis the first package in the platform that legitimately needs both a light, computational-strategy-grade metadata schema (DecisionMetadata) and a heavy, business-contract-grade governed artifact (Policy) side by side - a shapeanalyticswas never designed to hold. - Consistent with every other layer boundary already drawn in this platform, including the one
analyticsitself just established one layer down. A platform that has consistently drawn a package boundary at every "distinct responsibility, distinct rate of change, distinct governance weight" seam would be inconsistent to stop doing so at the one seam (analytics→decision) its own most recent specification already named.
Dependency Rationale¶
decision is permitted to import the entire Foundation Layer plus analytics, not merely analytics directly, for the same structural reason ADR-0006 already established one layer down: a DecisionContext (§8 of the governing spec) legitimately needs events.EventStore/EventBus directly (for audit-trail linkage and real-time evaluation, §25, §27) and ontology entity types (for scoping a decision to a specific piece of equipment, pit, or shift), not merely analytics.AnalyticsResult objects. Restricting decision to only import analytics would have forced analytics to grow pass-through re-exports of events/ontology/registry types it does not otherwise need, purely so decision could reach them indirectly - a strictly worse outcome than the direct permission this ADR grants.
connectors is included in the permitted-import list for platform-wide layering consistency, but this design deliberately does not exercise it, for exactly the reason ADR-0006 already established for analytics: decision operates on already-computed, already-validated KPIResult/AnalyticsResult objects, never on a vendor-specific wire format. This is recorded as a standing anti-pattern in the design spec (§33) rather than left ambiguous.
Architectural Trade-offs¶
- Trade-off accepted:
decisionreusescore.BaseSpecificationfor rule composition (§10, §11) rather than defining a purpose-built business-rule expression language. This constrains rule authors to whateverAndSpecification/OrSpecification/NotSpecification/PredicateSpecificationcan express - no free-form query syntax, no field-path DSL with its own parser. This trade-off was accepted because a bespoke rule language is exactly the kind of "second, subtly-different concept" this platform has consistently avoided (the same reasoninganalyticsapplied when it reusedkpis.Windowinstead of inventing a parallel windowing concept, ADR-0006), and becauseevents.EventFilteralready proved this exact composition mechanism sufficient for a comparably-shaped filtering problem. - Trade-off accepted:
ActionPlanner(§21) implements its own narrow topological-sort logic rather than reusingkpis.DependencyGraph, even though both solve "order items respecting declared dependencies." This means the platform now has two independent topological-sort implementations rather than one shared utility - a real, acknowledged duplication. It was accepted becausekpis.DependencyGraphis constructed againstRegistry[str, type[BaseKPI]]and readsKPIMetadata.dependenciesspecifically; forcingdecision's arbitrary action-to-action dependencies through that shape would have meant encoding actions as fake KPI registrations merely to reuse code - a worse outcome than a small, honest, independent implementation (§33's recorded anti-pattern makes this explicit). - Trade-off accepted: this package applies two different governance weights to two different artifact types (
Policy, heavy;DecisionMetadata, light) rather than one uniform weight. This is more conceptually complex than a single governance model, but the alternative - either KPI-grade ceremony for every pluggable strategy, or spreadsheet-grade informality for published business policies - was judged worse in both directions.
Alternatives Considered¶
- Fold decision-making logic directly into
analytics(e.g.AnalyticsResult.recommend()). Rejected - directly contradictsanalytics's own already-locked "computation-only" scope (spec 06 §3, §4) and its own Future Roadmap's explicit framing ofdecisionas a separate consumer (spec 06 §37). - Defer
decisionentirely untildigital_twin/optimizationneed it, treating recommendation logic as those future packages' own internal detail. Rejected - for the same reason ADR-0006 rejected deferringanalytics:digital_twin,simulation,optimization,visualization, and a futureagentspackage (design spec §36) are all independently expected to consume recommendations, rankings, and explanations; deferring this package would mean each reimplements its own, likely mutually inconsistent, notion of "what is a recommendation." - A single, larger "analytics + decision" package, reasoning that a recommendation is just a further transformation of a statistical result. Rejected -
digital_twinandvisualization(design spec §36) are expected to consumedecision's outputs without needinganalytics's full statistical surface exposed alongside it, and vice versa; bundling the two would also bundle their governance weights (§3.6 of the governing spec), which this ADR's "Architectural Trade-offs" section already establishes as deliberately distinct. - Ship concrete
RootCauseAnalyzer/WhatIfEngineimplementations now, on the reasoning that root-cause identification and what-if analysis are the most immediately valuable capabilities this package could offer. Rejected - see "Alternatives Rejected" below; this was the most seriously debated alternative, exactly mirroring the equivalent debate ADR-0006 recorded foranalytics's forecasting/anomaly/outlier interfaces.
Alternatives Rejected¶
Shipping at least one concrete RootCauseAnalyzer/WhatIfEngine implementation in this initial release is rejected for the same three compounding reasons ADR-0006 already established for analytics's interface-only capabilities, restated at this layer:
- Scope creep into causal inference and simulation. Identifying why a threshold was breached is a causal-inference problem; predicting the outcome of a hypothetical change is a simulation/forecasting problem. Both are explicitly excluded from this package's charter (design spec §3.1, §3.4, §4). Shipping either "just as a starting point" would establish a precedent that quietly widens
decision's scope every time a plausible-looking heuristic seems worth adding. - Premature commitment to an interface's shape via its first implementation. Defining
RootCauseAnalyzer/WhatIfEnginenow and deferring their first concrete implementations to independently-versioned future plugins keeps both contracts honest and general, rather than shaped by whichever heuristic happened to ship first. WhatIfEnginein particular already has a designated future owner.events.AsOf'sscenariofield was reserved, at the time the Event Framework was designed, "so a future scenario/what-if-forking capability (Digital Twin) can extend this type's usage without a breaking change." Shipping adecision-owned what-if implementation now would preempt the very capability (digital_twin) that field was reserved for; defining the interface and leaving the field's anticipated consumer to implement against it is the more consistent choice.
Reusing kpis.DependencyGraph for ActionPlanner by encoding actions as synthetic KPI registrations was also seriously considered - it would have avoided writing a second topological-sort implementation - and rejected outright once the coupling was examined closely: kpis.DependencyGraph's constructor binds it to a Registry[str, type[BaseKPI]] and its resolution logic reads KPIMetadata.dependencies specifically. Forcing action-planning through that shape would mean fabricating fake BaseKPI registrations purely to satisfy a type signature - an abuse of the KPI registry for a concept (arbitrary business actions) it was never designed to represent, and a much worse failure mode than a small, self-contained, independently-tested topological sort.
A bespoke business-rule expression language (a small parser/DSL for policy authors to write conditions in, rather than composing core.BaseSpecification objects in Python) was considered, since it would let non-Python-fluent policy authors write rules more directly. Rejected for this initial release - it would introduce a new parsing/evaluation surface with its own correctness and security considerations (arbitrary-condition parsers are a classic injection/DoS surface) for a capability core.BaseSpecification's composable predicates already provide programmatically; a future authoring-tool layer (outside this package, potentially part of a future visualization/tooling effort) could translate a friendlier syntax into Rule objects without decision itself ever needing to own a parser.
Long-Term Evolution¶
This package is expected to grow along the same two axes ADR-0006 already established for analytics, without requiring a new ADR:
- New concrete strategies within existing categories (new
DecisionStrategy/RankingStrategysubclasses, newPolicypublications) - ordinary, checklist-governed extension (design spec §31), not an architectural change. - The first concrete
RootCauseAnalyzer/WhatIfEngineimplementations, whenever they arrive - expected to land as plugins against the already-stable ABCs this design defines, most plausibly authored by whichever future package (digital_twinfor what-if, anagents-adjacent effort for root-cause) first has a concrete need.
A new ADR would be warranted if a future need arose to: change decision's position in the dependency chain; permit decision to be imported by a Foundation Layer package or by analytics (which would indicate a design error, not a legitimate evolution); introduce a second extension mechanism alongside the Registry Framework specialization this design already commits to; or generalize ActionPlanner's topological-sort logic into a shared core utility (which would revisit, not merely extend, the coupling-mismatch trade-off recorded above).
Future Compatibility¶
Every public type this package introduces (design spec §28, DecisionResult and its subclasses) is a plain, frozen core.BaseValueObject, serializable via the platform's existing core.serialization mechanism, with no decision-internal state leaking into any type a future package would need to consume - the identical compatibility guarantee ADR-0006 already established for analytics.AnalyticsResult, extended one layer up. This is a deliberate guarantee for the packages named in the design spec's Future Roadmap (§36) - digital_twin, simulation, optimization, visualization, and a future agents package - none of which are designed by this ADR or by the governing specification, but all of which are expected to consume decision's output types without requiring a breaking change to them. The RootCauseAnalyzer/WhatIfEngine interfaces (design spec §18, §19) exist specifically so that whichever future package first needs a concrete root-cause or what-if capability can implement against a contract that already exists, rather than this specification needing revision when that need arrives; WhatIfEngine's reuse of events.AsOf.scenario in particular means digital_twin's eventual what-if/scenario-forking implementation has a pre-established, already-locked type to build on rather than a new one to negotiate.
This ADR governs the existence and shape of mineproductivity.decision as a package. See 07_Decision_Intelligence_Design_Specification.md for the full object model and 07_Decision_Intelligence_Implementation_Checklist.md for the locked implementation contract.