feat(ui): add prediction state management and PredictionPanel component

- Create prediction type definitions in src/types/predictions.ts
- Add prediction state management to page.tsx with caching
- Implement PredictionPanel component with:
  - Master visibility toggle
  - Model info display (name, version, type, metrics)
  - Action buttons (Run on Visible, Predict All)
  - Confidence threshold slider
  - Label filter checkboxes with per-class metrics
  - Disagreement filter toggle
  - Prediction summary display
  - Model server offline banner
- Add on-demand and batch prediction fetching
- Implement prediction caching by chart and model version
- Add health polling for inference API (30s interval when offline)
- Ensure annotation tools work independently of prediction API

Tasks completed: 9.1-9.5, 12.1-12.3 (59/78 total)
This commit is contained in:
Marko Djordjevic 2026-02-15 16:20:07 +01:00
parent bb1b6d573f
commit 28ebe2c5d1
4 changed files with 608 additions and 8 deletions

118
src/types/predictions.ts Normal file
View file

@ -0,0 +1,118 @@
/**
* Prediction types for ML model inference
*/
export interface PredictionSpan {
label: string;
start_time: number;
end_time: number;
avg_confidence: number;
candle_count: number;
}
export interface PerCandlePrediction {
time: number;
label: string;
confidence: number;
}
export interface ModelInfo {
model_name: string;
model_version: string;
model_type: string;
experiment_name: string;
run_id: string;
trained_at: string;
feature_count: number;
label_names: string[];
}
export interface PerClassMetrics {
[label: string]: {
precision: number;
recall: number;
f1_score: number;
support: number;
};
}
export interface ModelMetrics {
accuracy: number;
f1_macro: number;
f1_weighted: number;
per_class: PerClassMetrics;
}
export interface ModelInfoResponse {
model_info: ModelInfo;
metrics: ModelMetrics;
label_config: {
name: string;
color: string;
}[];
}
export interface PredictRequest {
candles: {
time: number;
open: number;
high: number;
low: number;
close: number;
volume?: number;
}[];
}
export interface PredictResponse {
predictions: PerCandlePrediction[];
spans: PredictionSpan[];
model_info: {
model_name: string;
model_version: string;
};
}
export interface BatchPredictRequest {
pair: string;
timeframe: string;
start_time: number;
end_time: number;
batch_size?: number;
}
export interface PredictionState {
spans: PredictionSpan[];
perCandlePredictions: PerCandlePrediction[];
isLoading: boolean;
error: string | null;
modelInfo: ModelInfoResponse | null;
visible: boolean;
confidenceThreshold: number;
selectedLabels: Set<string>;
autoPredict: boolean;
cacheKey: string | null;
}
export type DisagreementType =
| 'missed_by_model' // Human annotation but no prediction
| 'missed_by_human' // Prediction but no human annotation
| 'label_mismatch'; // Both present but different labels
export interface Disagreement {
type: DisagreementType;
humanSpan?: {
id: number;
label: string;
start_time: number;
end_time: number;
};
predictionSpan?: PredictionSpan;
overlap_ratio?: number;
}
export interface PredictionSummary {
total_predictions: number;
total_human_annotations: number;
agreements: number;
disagreements: Disagreement[];
}