@@ -158,6 +163,12 @@ export default function RunDetailPage() {
+
+
diff --git a/frontend/src/pages/visualize/forecast.tsx b/frontend/src/pages/visualize/forecast.tsx
index 611864ec..bed6cfe9 100644
--- a/frontend/src/pages/visualize/forecast.tsx
+++ b/frontend/src/pages/visualize/forecast.tsx
@@ -2,6 +2,8 @@ import { useState } from 'react'
import { Link } from 'react-router-dom'
import { BarChart3, Download, ExternalLink, Loader2, Play } from 'lucide-react'
import { useJob, useCreateJob } from '@/hooks/use-jobs'
+import { useJobExplanation } from '@/hooks/use-explanations'
+import { ExplanationPanel } from '@/components/explainability/explanation-panel'
import { TimeSeriesChart } from '@/components/charts/time-series-chart'
import { EmptyState } from '@/components/common/error-display'
import { JobPicker } from '@/components/common/job-picker'
@@ -58,6 +60,10 @@ export default function ForecastPage() {
(point) => point.lower_bound != null && point.upper_bound != null,
)
+ // Explain the loaded job only when it is a completed predict job.
+ const isPredictDone = job?.status === 'completed' && job?.job_type === 'predict'
+ const explanationQuery = useJobExplanation(job?.job_id ?? '', !!job && isPredictDone)
+
async function handleRunForecast() {
if (!trainRunId) return
setRunError(null)
@@ -246,6 +252,15 @@ export default function ForecastPage() {
)}
+
+ {/* Forecast explanation — only for a completed predict job */}
+ {isPredictDone && (
+
+ )}
>
)}
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index 80bc6da5..b29b23f1 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -860,3 +860,44 @@ export interface MultiScenarioComparison {
// entry per scenario name.
chart_series: Record[]
}
+
+// =============================================================================
+// Explainability — PRP-28 forecast explanation & driver attribution
+// =============================================================================
+
+// Qualitative confidence band for a forecast explanation.
+export type ConfidenceLevel = 'high' | 'medium' | 'low'
+
+// One named, interpretable demand driver behind a forecast. A driver with
+// contribution === 0 is informational context the model does not consume.
+export interface DriverContribution {
+ name: string
+ feature_value: number
+ contribution: number
+ direction: 'positive' | 'negative' | 'neutral'
+ description: string
+}
+
+// An advisory retail signal correlated with the forecast — never a causal claim.
+export interface ReasonCode {
+ code: string
+ severity: 'info' | 'warn'
+ detail: string
+}
+
+// A structured, rule-based explanation of a baseline h=1 forecast —
+// GET /explain/runs/{run_id}, GET /explain/jobs/{job_id}, POST /explain/forecast.
+export interface ForecastExplanation {
+ store_id: number
+ product_id: number
+ model_type: string
+ method: 'rule_based'
+ forecast_value: number
+ drivers: DriverContribution[]
+ reason_codes: ReasonCode[]
+ confidence: ConfidenceLevel
+ caveats: string[]
+ agent_summary: string
+ as_of_date: string // ISO date
+ generated_at: string // ISO datetime
+}