dakma-sdk 2.0: Every Forecasting AI Model Can Be Explained

dakma-sdk 2.0: Every Forecast Gets an Explanation

Your Forecasts Can Now Show Their Work

In October dakma-sdk reached 1.0, with the decision event as its foundation and explain_text for word-level explanations. Version 1.1 followed with a multi-class SHAP fix and degraded fallbacks that appear in decision and audit reports instead of failing quietly. Today dakma-sdk reaches 2.0, and it’s on PyPI. The headline is forecasting explainability, through a new decorator: explain_forecast.

This post explains what that means, why it’s harder than explaining a classifier, and how to use it.

Why forecasts are harder to explain

A classification has a natural “why”. These features pushed the score across the threshold, so the applicant was declined. The question is bounded, and the answer is a ranked list of factors.

A forecast has no threshold. Nobody asks why a number crossed a line. They ask:

The underlying model is often a regression over engineered features such as lags, rolling averages, calendar measures, and external drivers like price or promotions. SHAP applies to that setup, and that’s what dakma-sdk uses. The forecast is the thing being explained, and SHAP attributes it to the features.

explain_forecast

explain_forecast is for a regression model that returns a number, or one number per horizon. You pass the feature metrics for the forecast origin into the wrapped function. The decision is the forecast itself, and SHAP attributes it.

import dakma

dm = dakma.init(project="demand-forecast")

@dm.explain_forecast(horizon=0, counterfactual=True)
def forecast(model, feature_metrics):
    return model.predict(feature_metrics)

result = forecast(model, feature_metrics)
print(result.decision.to_text())          # forecast 12.40 at horizon 0
print(result.explanation.plain_language)
print(result.explanation.metadata["feature_metrics"])

A few details worth knowing:

The feature values are part of the record

result.explanation.metadata["feature_metrics"] stores the feature values the explanation was computed from. That matters for forecasts in particular.

A forecast is made at a point in time using only what was known then. An explanation is only trustworthy if it describes that same information. By tying the explanation to the feature metrics for the forecast origin, and keeping them on the record, the explanation can be checked against exactly what the model saw. When someone asks in three months why the model forecast what it did, the inputs are on file rather than reconstructed from memory.

Reading a forecast explanation

SHAP is additive: the baseline plus each feature’s contribution equals the forecast. That makes the explanation easy to reason about, but raw feature-level output has two problems with typical forecasting features:

1. Features don’t match how people think. “The forecast is high because of lag_7” is accurate, but a planner reasons in terms of recent history, seasonality, and the drivers they control.

2. Correlated features split credit arbitrarily. lag_1, lag_7, and rolling_mean_30 all describe the recent past and move together, so credit is divided among them in ways that can look random. Reading any single one as “the reason” is misleading.

Because contributions are additive, you can group them yourself and the groups still add up. Summing contributions across the lag features gives you one “recent history” number, and it no longer matters how credit was divided among the lags:

groups = {
    "recent history": ["lag_1", "lag_7", "roll_mean_30"],
    "seasonality":    ["dow", "month", "holiday"],
    "drivers":        ["price", "promo"],
}

# factors: feature name -> SHAP contribution for this forecast
grouped = {name: sum(factors[f] for f in cols if f in factors)
           for name, cols in groups.items()}

[Adjust this snippet to match how factors are exposed on result.explanation, or delete it if you’d rather not show grouping.]

Built on the decision event

Everything from 1.0 still holds. Every explained inference produces a decision event, and provenance, explanation, and governance hang off it:

Project → AI Decision Event → AuditEvent
             ├── Provenance   (Model, Dataset)
             ├── Explanation  (method, status, factors, quality)
             └── Governance   (documentation fields, evaluation)

A forecast is now one more decision that fits this shape. The forecast, its explanation, and the model that produced it arrive together, and the audit record exists whether or not anyone asks for it later.

[Paste the printed plain_language output, or a screenshot of the generated report with a caption in the style of the 1.0 post.]

What explanations can and can’t tell you

dakma-sdk is a documentation aid. It records and explains decisions, but it doesn’t provide legal advice or regulatory certification.

Forecast explanations also describe what the model relies on, not what causes the outcome in the real world. A large contribution from a promotion shows that the model leans on it, not that the promotion drove sales. This matters more for forecasts than for most predictions, because it’s tempting to read a driver’s contribution as proof of its effect. Treat the explanation as a view into the model’s reasoning, and treat causal claims as a separate question.

explain_forecast is for regression models that return a number or one number per horizon. Classifiers keep using @model.explain().

Upgrading from 1.x

[A major version invites the question “what breaks?”. Answer it directly. If nothing does, say so, as the 1.0 post did for 0.5.0. If something changed, list it here.]

Try it

pip install -U "dakma-sdk[ml]"           # core + pandas, scikit-learn, XGBoost, SHAP

SHAP comes with the ml extra. Import as dakma or dakma_sdk. If you explain forecasts in an audited or regulated setting, I’d like to hear what’s missing. Issues and ideas are welcome on GitHub.

Dakma: making AI decisions explainable and auditable.