fix(frontend): update ModelInfoResponse types to match backend structure

- Update TypeScript types to match flat backend response structure
- Remove nested model_info and metrics objects
- Remove label_config, use labels array and per_class_metrics array
- Update all component references to use new structure
- Generate default colors for prediction labels in CandleChart
- Fix TypeScript type errors for nullable model_version
- Remove accuracy/F1 metrics display (not in new response)
This commit is contained in:
Marko Djordjevic 2026-02-15 21:39:38 +01:00
parent aa81d4f3d0
commit 5a7c901980
95 changed files with 326 additions and 63 deletions

2
next-env.d.ts vendored
View file

@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts"; import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -0,0 +1,8 @@
precision recall f1-score support
Bearish Engulfing 0.8065 1.0000 0.8929 25
Bullish Engulfing 1.0000 0.8125 0.8966 32
accuracy 0.8947 57
macro avg 0.9032 0.9062 0.8947 57
weighted avg 0.9151 0.8947 0.8949 57

View file

@ -0,0 +1,97 @@
data:
annotations_path: data/annotations/export.json
enriched_path: data/enriched/features.csv
labeled_path: data/labeled/dataset.csv
raw_path: data/raw/OHLCV.csv
stages:
annotation_ingestion:
context_padding: 20
enabled: true
label_encoding: window
merge_strategy: human_priority
min_confidence: 1
programmatic_labels:
enabled: true
talib_patterns:
- CDLENGULFING
- CDLHAMMER
- CDLINVERTEDHAMMER
- CDLSHOOTINGSTAR
- CDLDOJI
- CDLDOJISTAR
- CDLMORNINGSTAR
- CDLEVENINGSTAR
- CDLHARAMI
- CDLPIERCING
- CDLDARKCLOUDCOVER
- CDL3WHITESOLDIERS
- CDL3BLACKCROWS
window_size: 30
feature_engineering:
candle_features: true
custom_features: []
enabled: true
talib_indicators:
- name: RSI
params:
timeperiod: 14
- name: EMA
params:
timeperiod: 20
- name: EMA
params:
timeperiod: 50
- name: MACD
params:
fastperiod: 12
signalperiod: 9
slowperiod: 26
- name: BBANDS
params:
nbdevdn: 2
nbdevup: 2
timeperiod: 20
- name: ATR
params:
timeperiod: 14
- name: ADX
params:
timeperiod: 14
- name: CCI
params:
timeperiod: 14
- name: MFI
params:
timeperiod: 14
- name: STOCH
params:
fastk_period: 14
slowd_period: 3
slowk_period: 3
inference:
batch_size: 1000
enabled: true
local_model_path: models/best_model.pkl
mlflow_model_name: candlestick_pattern_v1
mlflow_model_stage: Production
model_source: local
use_training_config: true
training:
class_weights: balanced
enabled: true
hyperparameters:
max_depth: 15
min_samples_leaf: 2
min_samples_split: 5
n_estimators: 200
n_jobs: -1
random_state: 42
mlflow:
experiment_name: candlestick_patterns
log_artifacts: true
register_model: false
tracking_uri: http://localhost:5000
model_type: random_forest
split_method: temporal
test_split: 0.2
validation_split: 0.1

View file

@ -0,0 +1,14 @@
artifact_uri: file:///home/homoludens/projekti/bitcon/candle_annotator/services/ml/mlruns/358560345319124639/509970efc3e0494ba3c2fbd7dd24d438/artifacts
end_time: 1771187605319
entry_point_name: ''
experiment_id: '358560345319124639'
lifecycle_stage: active
run_id: 509970efc3e0494ba3c2fbd7dd24d438
run_name: painted-doe-188
source_name: ''
source_type: 4
source_version: ''
start_time: 1771187595858
status: 3
tags: []
user_id: homoludens

View file

@ -0,0 +1,2 @@
1771187597069 0.8947368421052632 0
1771187597069 0.8947368421052632 0

View file

@ -0,0 +1,2 @@
1771187597121 0.8928571428571429 0
1771187597121 0.8928571428571429 0

View file

@ -0,0 +1,2 @@
1771187597155 0.896551724137931 0
1771187597155 0.896551724137931 0

View file

@ -0,0 +1,2 @@
1771187597081 0.8947044334975369 0
1771187597081 0.8947044334975369 0

View file

@ -0,0 +1,2 @@
1771187597191 0.0 0
1771187597191 0.0 0

View file

@ -0,0 +1,2 @@
1771187597090 0.8949312937516204 0
1771187597090 0.8949312937516204 0

View file

@ -0,0 +1,2 @@
1771187597104 0.8064516129032258 0
1771187597104 0.8064516129032258 0

View file

@ -0,0 +1,2 @@
1771187597133 1.0 0
1771187597133 1.0 0

View file

@ -0,0 +1,2 @@
1771187597166 0.0 0
1771187597166 0.0 0

View file

@ -0,0 +1,2 @@
1771187597113 1.0 0
1771187597113 1.0 0

View file

@ -0,0 +1,2 @@
1771187597145 0.8125 0
1771187597145 0.8125 0

View file

@ -0,0 +1,2 @@
1771187597176 0.0 0
1771187597176 0.0 0

View file

@ -0,0 +1,2 @@
1771187596947 1.0 0
1771187596947 1.0 0

View file

@ -0,0 +1,2 @@
1771187596958 1.0 0
1771187596958 1.0 0

View file

@ -0,0 +1,2 @@
1771187596967 1.0 0
1771187596967 1.0 0

View file

@ -0,0 +1,6 @@
destination_id: m-cf12ffc008e047e1a37f58efe209422c
destination_type: MODEL_OUTPUT
source_id: m-cf12ffc008e047e1a37f58efe209422c
source_type: RUN_OUTPUT
step: 0
tags: {}

View file

@ -0,0 +1 @@
aa81d4f3d0dcb9c58799d7bf63fd8fe61006bdce

View file

@ -0,0 +1,23 @@
artifact_path: file:///home/homoludens/projekti/bitcon/candle_annotator/services/ml/mlruns/358560345319124639/models/m-cf12ffc008e047e1a37f58efe209422c/artifacts
flavors:
python_function:
env:
conda: conda.yaml
virtualenv: python_env.yaml
loader_module: mlflow.sklearn
model_path: model.pkl
predict_fn: predict
python_version: 3.13.5
sklearn:
code: null
pickled_model: model.pkl
serialization_format: cloudpickle
sklearn_version: 1.8.0
skops_trusted_types: null
mlflow_version: 3.9.0
model_id: m-cf12ffc008e047e1a37f58efe209422c
model_size_bytes: 793682
model_uuid: m-cf12ffc008e047e1a37f58efe209422c
prompts: null
run_id: 509970efc3e0494ba3c2fbd7dd24d438
utc_time_created: '2026-02-15 20:33:18.754593'

View file

@ -0,0 +1,15 @@
channels:
- conda-forge
dependencies:
- python=3.13.5
- pip
- pip:
- mlflow==3.9.0
- cloudpickle==3.1.2
- numpy==2.4.2
- pandas==2.3.3
- psutil==7.2.2
- pyarrow==22.0.0
- scikit-learn==1.8.0
- scipy==1.17.0
name: mlflow-env

View file

@ -0,0 +1,7 @@
python: 3.13.5
build_dependencies:
- pip
- setuptools==82.0.0
- wheel
dependencies:
- -r requirements.txt

View file

@ -0,0 +1,8 @@
mlflow==3.9.0
cloudpickle==3.1.2
numpy==2.4.2
pandas==2.3.3
psutil==7.2.2
pyarrow==22.0.0
scikit-learn==1.8.0
scipy==1.17.0

View file

@ -0,0 +1,10 @@
artifact_location: file:///home/homoludens/projekti/bitcon/candle_annotator/services/ml/mlruns/358560345319124639/models/m-cf12ffc008e047e1a37f58efe209422c/artifacts
creation_timestamp: 1771187598541
experiment_id: '358560345319124639'
last_updated_timestamp: 1771187605161
model_id: m-cf12ffc008e047e1a37f58efe209422c
model_type: ''
name: model
source_run_id: 509970efc3e0494ba3c2fbd7dd24d438
status: 2
status_message: null

View file

@ -0,0 +1 @@
1771187597069 0.8947368421052632 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597121 0.8928571428571429 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597155 0.896551724137931 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597081 0.8947044334975369 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597191 0.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597090 0.8949312937516204 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597104 0.8064516129032258 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597133 1.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597166 0.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597113 1.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597145 0.8125 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187597176 0.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187596947 1.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187596958 1.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
1771187596967 1.0 0 509970efc3e0494ba3c2fbd7dd24d438

View file

@ -0,0 +1 @@
aa81d4f3d0dcb9c58799d7bf63fd8fe61006bdce

View file

@ -0,0 +1 @@
custom_model_development

View file

@ -365,7 +365,7 @@ export default function Home() {
setPredictionState((prev) => ({ setPredictionState((prev) => ({
...prev, ...prev,
modelInfo: data, modelInfo: data,
selectedLabels: new Set(data.label_config.map((l) => l.name)), selectedLabels: new Set(data.labels),
error: null, error: null,
})); }));
return data; return data;
@ -382,9 +382,9 @@ export default function Home() {
}, []); }, []);
// Generate cache key from chart, timerange, and model version // Generate cache key from chart, timerange, and model version
const generateCacheKey = useCallback((chartId: number | null, modelVersion?: string) => { const generateCacheKey = useCallback((chartId: number | null, modelVersion?: string | null) => {
if (!chartId) return null; if (!chartId) return null;
const version = modelVersion || predictionState.modelInfo?.model_info.model_version || 'unknown'; const version = modelVersion || predictionState.modelInfo?.model_version || 'unknown';
return `${chartId}_${version}`; return `${chartId}_${version}`;
}, [predictionState.modelInfo]); }, [predictionState.modelInfo]);
@ -392,12 +392,12 @@ export default function Home() {
const fetchPredictions = useCallback(async (candles: any[]) => { const fetchPredictions = useCallback(async (candles: any[]) => {
if (!activeChartId || candles.length === 0) return; if (!activeChartId || candles.length === 0) return;
const cacheKey = generateCacheKey(activeChartId, predictionState.modelInfo?.model_info.model_version); const cacheKey = generateCacheKey(activeChartId, predictionState.modelInfo?.model_version);
// Check cache first // Check cache first
if (cacheKey && predictionCacheRef.current.has(cacheKey)) { if (cacheKey && predictionCacheRef.current.has(cacheKey)) {
const cached = predictionCacheRef.current.get(cacheKey)!; const cached = predictionCacheRef.current.get(cacheKey)!;
if (cached.modelVersion === predictionState.modelInfo?.model_info.model_version) { if (cached.modelVersion === predictionState.modelInfo?.model_version) {
setPredictionState((prev) => ({ setPredictionState((prev) => ({
...prev, ...prev,
spans: cached.spans, spans: cached.spans,
@ -562,7 +562,7 @@ export default function Home() {
// Clear prediction cache when model version changes // Clear prediction cache when model version changes
useEffect(() => { useEffect(() => {
if (predictionState.modelInfo) { if (predictionState.modelInfo) {
const currentVersion = predictionState.modelInfo.model_info.model_version; const currentVersion = predictionState.modelInfo.model_version;
// Clear cache entries with different model versions // Clear cache entries with different model versions
const newCache = new Map(); const newCache = new Map();
for (const [key, value] of predictionCacheRef.current.entries()) { for (const [key, value] of predictionCacheRef.current.entries()) {
@ -572,7 +572,7 @@ export default function Home() {
} }
predictionCacheRef.current = newCache; predictionCacheRef.current = newCache;
} }
}, [predictionState.modelInfo?.model_info.model_version]); }, [predictionState.modelInfo?.model_version]);
// Health polling - check model status every 30 seconds when offline // Health polling - check model status every 30 seconds when offline
useEffect(() => { useEffect(() => {

View file

@ -374,10 +374,21 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
} }
// Build a label-to-color map from modelInfo // Build a label-to-color map from modelInfo
// Generate colors for labels since backend no longer provides them
const labelColorMap: Record<string, string> = {}; const labelColorMap: Record<string, string> = {};
if (modelInfo?.label_config) { if (modelInfo?.labels) {
modelInfo.label_config.forEach((lc) => { const predefinedColors = [
labelColorMap[lc.name] = lc.color; '#3b82f6', // blue
'#ef4444', // red
'#10b981', // green
'#f59e0b', // amber
'#8b5cf6', // violet
'#ec4899', // pink
'#06b6d4', // cyan
'#f97316', // orange
];
modelInfo.labels.forEach((label, index) => {
labelColorMap[label] = predefinedColors[index % predefinedColors.length];
}); });
} }

View file

@ -9,7 +9,7 @@ interface PredictionPanelProps {
onFetchBatchPredictions: () => void; onFetchBatchPredictions: () => void;
onConfidenceChange: (threshold: number) => void; onConfidenceChange: (threshold: number) => void;
onToggleLabelSelection: (label: string) => void; onToggleLabelSelection: (label: string) => void;
predictionSummary?: PredictionSummary; predictionSummary: PredictionSummary | null;
isModelOnline: boolean; isModelOnline: boolean;
showOnlyDisagreements?: boolean; showOnlyDisagreements?: boolean;
onToggleShowOnlyDisagreements?: () => void; onToggleShowOnlyDisagreements?: () => void;
@ -77,23 +77,15 @@ export default function PredictionPanel({
<div className="mb-3 p-2 bg-muted/50 rounded text-xs"> <div className="mb-3 p-2 bg-muted/50 rounded text-xs">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Model:</span> <span className="text-muted-foreground">Model:</span>
<span className="font-mono text-foreground">{modelInfo.model_info.model_name}</span> <span className="font-mono text-foreground">{modelInfo.model_name}</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Version:</span> <span className="text-muted-foreground">Version:</span>
<span className="font-mono text-foreground">{modelInfo.model_info.model_version}</span> <span className="font-mono text-foreground">{modelInfo.model_version || 'N/A'}</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Type:</span> <span className="text-muted-foreground">Type:</span>
<span className="text-foreground">{modelInfo.model_info.model_type}</span> <span className="text-foreground">{modelInfo.model_type}</span>
</div>
<div className="flex justify-between mt-1 pt-1 border-t border-border">
<span className="text-muted-foreground">Accuracy:</span>
<span className="text-foreground">{(modelInfo.metrics.accuracy * 100).toFixed(1)}%</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">F1 (macro):</span>
<span className="text-foreground">{(modelInfo.metrics.f1_macro * 100).toFixed(1)}%</span>
</div> </div>
</div> </div>
)} )}
@ -144,26 +136,22 @@ export default function PredictionPanel({
<div className="mb-3"> <div className="mb-3">
<label className="text-xs text-muted-foreground mb-2 block">Filter by Label</label> <label className="text-xs text-muted-foreground mb-2 block">Filter by Label</label>
<div className="space-y-1 max-h-32 overflow-y-auto"> <div className="space-y-1 max-h-32 overflow-y-auto">
{modelInfo.label_config.map((labelConfig) => { {modelInfo.labels.map((label) => {
const metrics = modelInfo.metrics.per_class[labelConfig.name]; const metrics = modelInfo.per_class_metrics.find((m) => m.label === label);
const isSelected = selectedLabels.has(labelConfig.name); const isSelected = selectedLabels.has(label);
return ( return (
<label <label
key={labelConfig.name} key={label}
className="flex items-center gap-2 p-1 rounded hover:bg-muted/50 cursor-pointer" className="flex items-center gap-2 p-1 rounded hover:bg-muted/50 cursor-pointer"
> >
<input <input
type="checkbox" type="checkbox"
checked={isSelected} checked={isSelected}
onChange={() => onToggleLabelSelection(labelConfig.name)} onChange={() => onToggleLabelSelection(label)}
className="w-3 h-3" className="w-3 h-3"
/> />
<div <span className="text-xs text-foreground flex-1">{label}</span>
className="w-3 h-3 rounded"
style={{ backgroundColor: labelConfig.color }}
/>
<span className="text-xs text-foreground flex-1">{labelConfig.name}</span>
{metrics && ( {metrics && (
<span className="text-xs text-muted-foreground font-mono"> <span className="text-xs text-muted-foreground font-mono">
F1: {(metrics.f1_score * 100).toFixed(0)}% F1: {(metrics.f1_score * 100).toFixed(0)}%

View file

@ -16,40 +16,23 @@ export interface PerCandlePrediction {
confidence: number; 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 { export interface PerClassMetrics {
[label: string]: { label: string;
precision: number; precision: number;
recall: number; recall: number;
f1_score: number; f1_score: number;
support: number; support: number;
};
}
export interface ModelMetrics {
accuracy: number;
f1_macro: number;
f1_weighted: number;
per_class: PerClassMetrics;
} }
export interface ModelInfoResponse { export interface ModelInfoResponse {
model_info: ModelInfo; model_name: string;
metrics: ModelMetrics; model_version: string | null;
label_config: { model_type: string;
name: string; trained_at: string | null;
color: string; dataset_version: string | null;
}[]; feature_engineering_enabled: boolean;
labels: string[];
per_class_metrics: PerClassMetrics[];
} }
export interface PredictRequest { export interface PredictRequest {