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:
- Why is next week’s forecast 12% higher than this week’s?
- Is that because of the promotion, the season, or something the model picked up in recent data?
- Should I trust it?
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:
feature_metricsis a one-row DataFrame or a mapping of feature name to value. These are the lags, calendar measures, and exogenous drivers the model uses at the forecast origin, for example{"lag_1": 11.8, "lag_7": 10.9, "dow": 2, "promo": 1}.horizonselects the step. The default is0, the first step ahead. If the model returns several steps, such as[y1, y2, y3], the one athorizonis the forecast that gets explained.- The decision is a number, not a verdict. A forecast isn’t approved or declined, so the decision reads as a forecast value at a horizon (
forecast 12.40 at horizon 0).@model.explain()stays approve/decline and remains the path for classifiers. counterfactual=Trueis the same flagexplaintakes. [Add one line on what it adds to a forecast explanation or report.]
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.