feat: add FastAPI model/load endpoint and all Next.js proxy routes (tasks 2-4)

This commit is contained in:
Marko Djordjevic 2026-02-17 18:47:04 +01:00
parent b8e649e333
commit 2a02669222
29 changed files with 1110 additions and 780 deletions

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-17

View file

@ -0,0 +1,87 @@
## Context
The Candle Annotator has a fully implemented ML pipeline (feature engineering, annotation ingestion, training, inference) in a separate FastAPI service (`services/ml/`, port 8001), and a standalone TA-Lib pattern detection script (`generate_talib_annotations.py`). The Next.js frontend already has a `PredictionPanel` that can run predictions and display results, but TA-Lib pattern detection and model training are CLI-only operations.
The sidebar layout is a fixed 240px column with vertically stacked sections: ChartSelector → FileUpload → Toolbox → SpanAnnotationList → PredictionPanel → Export. All state lives in the root `Home` component and flows down as props.
## Goals / Non-Goals
**Goals:**
- Let users run TA-Lib CDL pattern detection from the sidebar and see results as span annotations on the chart
- Let users trigger ML training runs from the UI with model type and basic parameter selection
- Let users switch between trained models for prediction
- Keep the sidebar usable — panels must be collapsible since we're adding significant new UI surface
**Non-Goals:**
- Advanced hyperparameter tuning UI (grid search, cross-validation config)
- Real-time training progress (websockets) — polling is sufficient for now
- MLflow dashboard embedding — just link out to it
- Custom TA-Lib indicator configuration (RSI, EMA, etc.) — only CDL pattern functions
- Multi-chart training (training always uses the exported annotation dataset)
## Decisions
### 1. TA-Lib pattern detection runs server-side via new FastAPI endpoint
**Decision:** Add a `POST /patterns/detect` endpoint to the FastAPI service that accepts candle data and a list of pattern names, runs TA-Lib CDL functions, and returns span annotations.
**Rationale:** The `generate_talib_annotations.py` script already has the detection logic. Extracting it into a FastAPI endpoint reuses that code and keeps TA-Lib (C library) on the Python side only. The frontend sends candles the same way it does for `/predict`.
**Alternatives considered:**
- Running TA-Lib in a Next.js API route via child process — adds complexity, fragile
- Pre-computing all patterns on upload — wasteful, users want to select specific patterns
### 2. Detected patterns become span annotations with `source: "talib"`
**Decision:** When patterns are detected, save them directly as span annotations in the Next.js database via `POST /api/span-annotations` with `source: "talib"`. This allows bulk delete by source and makes them immediately visible on the chart.
**Rationale:** The `span_annotations` table already has a `source` field supporting arbitrary strings. Using `"talib"` as a new source value enables filtering and bulk operations without schema changes.
**Alternatives considered:**
- Separate "patterns" table — unnecessary duplication, patterns are just a special kind of span annotation
- Client-side only (no persistence) — lose patterns on page reload
### 3. Training triggered via new FastAPI endpoint, status via polling
**Decision:** Add `POST /training/start` (triggers training in a background thread) and `GET /training/runs` (returns training history from `training_runs` table). The frontend polls `/training/runs` every 5s while a run is active.
**Rationale:** Training takes minutes, not seconds. A background thread with DB status tracking is the simplest approach. The `training_runs` table already tracks status (`running`/`completed`). Polling is simpler than websockets and sufficient for an operation that runs infrequently.
**Alternatives considered:**
- Synchronous training endpoint — would time out (training takes minutes)
- Celery/Redis task queue — over-engineered for single-user tool
- WebSocket progress — complex, not worth it for rare operations
### 4. Model selection via training run list
**Decision:** The model selector shows completed training runs from the `training_runs` table. Selecting a model sends its `run_id` to a new `POST /model/load` endpoint that loads the model (from MLflow or local path). The prediction panel then reflects the newly loaded model.
**Rationale:** Training runs are already tracked with metadata (model type, metrics, timestamps). Each run produces a model artifact. Loading by run_id is unambiguous.
**Alternatives considered:**
- Scanning filesystem for `.pkl` files — fragile, no metadata
- MLflow model registry stages (staging/production) — too formal for this use case
### 5. Collapsible sidebar sections with accordion pattern
**Decision:** Wrap TA-Lib panel, Training panel, and Prediction panel in collapsible `<details>`/accordion sections. Only one ML-related panel open at a time is recommended but not enforced.
**Rationale:** Adding two new panels to an already full sidebar requires space management. Collapsible sections are the simplest approach, consistent with the existing compact sidebar design.
### 6. Next.js API routes as proxies (existing pattern)
**Decision:** All new FastAPI endpoints are proxied through Next.js API routes (`/api/patterns/*`, `/api/training/*`, `/api/model/*`), following the existing pattern used by `/api/predict` and `/api/model/info`.
**Rationale:** Keeps the frontend hitting a single origin, avoids CORS issues, and maintains consistency with the established architecture.
## Risks / Trade-offs
- **Training blocks the FastAPI process** — Training runs in a background thread sharing the same process. A long training run could affect prediction latency. → Mitigation: Training is rare and the tool is single-user. If it becomes a problem, move to subprocess.
- **Model hot-swap during prediction** — Loading a new model while a prediction request is in-flight could cause errors. → Mitigation: Use a lock around model swap; prediction requests wait briefly.
- **TA-Lib pattern detection on large datasets** — Running 50 patterns on 10k+ candles could be slow. → Mitigation: Let users select specific patterns rather than "run all". Show loading state.
- **Training data preparation** — Training requires an exported/labeled dataset CSV. The UI must make clear that training uses the annotation export, not the raw chart data. → Mitigation: Show which dataset file will be used, with candle/annotation counts.
## Open Questions
- Should we add a "run full pipeline" button (feature engineering → annotation ingestion → training) or keep these as separate steps?
- Should pattern detection results be auto-saved or require explicit "save to chart" action?

View file

@ -0,0 +1,31 @@
## Why
TA-Lib pattern recognition and ML model training/inference capabilities are fully implemented in the Python backend but require terminal commands to use. Users cannot select TA-Lib patterns, trigger training, or switch between trained models from the UI — making these powerful features inaccessible during normal annotation workflow.
## What Changes
- Add a **TA-Lib pattern panel** in the sidebar where users can select from the 50 implemented CDL pattern functions, run them on the current chart, and see results as span annotations
- Add ability to **bulk delete** TA-Lib-generated annotations (by source) or selectively keep them for ML training
- Add a **training panel** where users can select a model type (RandomForest, XGBoost), configure basic parameters, and trigger training from the UI
- Add a **model selector** to the existing prediction panel so users can switch between trained models and apply them to the current chart
- Add new API endpoints to support TA-Lib pattern detection and training triggers from the frontend
- Expose training run history and status in the UI
## Capabilities
### New Capabilities
- `talib-pattern-ui`: UI panel for selecting and running TA-Lib CDL pattern recognition functions on the current chart, viewing results as span annotations, and managing (keeping/deleting) detected patterns
- `training-ui`: UI panel for selecting model type, configuring parameters, triggering training runs, and viewing training history/status
- `model-selector`: UI for listing available trained models, switching the active model, and applying predictions to the current chart
### Modified Capabilities
- `prediction-ui`: Add model selection dropdown to existing prediction panel, integrate with model-selector for switching active model
- `backend-api`: New endpoints for TA-Lib pattern detection, training triggers, model listing, and training status
## Impact
- **Frontend**: New sidebar panels (TA-Lib patterns, training), modifications to PredictionPanel component
- **Backend API (Next.js)**: New proxy routes for TA-Lib and training endpoints
- **ML Service (FastAPI)**: New endpoints for pattern detection, training trigger, model listing
- **Database**: May need training_runs table exposure via API (already exists in PostgreSQL)
- **Dependencies**: No new dependencies — all TA-Lib and ML libraries already installed

View file

@ -0,0 +1,122 @@
## ADDED Requirements
### Requirement: Pattern detection endpoint
The FastAPI service SHALL provide a `POST /patterns/detect` endpoint that accepts candle data and a list of CDL pattern names. The endpoint SHALL run the specified TA-Lib CDL functions on the candle data and return detected patterns as span annotation objects. Each returned annotation SHALL include start_time, end_time, label, confidence, and source ("talib").
#### Scenario: Detect specific patterns
- **WHEN** `POST /patterns/detect` is called with `{candles: [...], patterns: ["CDLENGULFING", "CDLHAMMER"]}`
- **THEN** the endpoint runs only Engulfing and Hammer detection and returns matching span annotations
#### Scenario: Detect all patterns
- **WHEN** `POST /patterns/detect` is called with `{candles: [...], patterns: []}` (empty list)
- **THEN** the endpoint runs all available CDL pattern functions
#### Scenario: No patterns found
- **WHEN** detection runs but no patterns match
- **THEN** the endpoint returns `{annotations: [], metadata: {count: 0}}`
#### Scenario: Invalid pattern name
- **WHEN** a pattern name is not a valid TA-Lib CDL function
- **THEN** the endpoint returns HTTP 400 with the invalid pattern name in the error message
### Requirement: Available patterns endpoint
The FastAPI service SHALL provide a `GET /patterns/available` endpoint that returns the list of all supported CDL pattern names with their friendly display names.
#### Scenario: List available patterns
- **WHEN** `GET /patterns/available` is called
- **THEN** the endpoint returns a list of `{function_name, display_name}` for all supported CDL patterns
### Requirement: Training start endpoint
The FastAPI service SHALL provide a `POST /training/start` endpoint that triggers a training run in a background thread. The endpoint SHALL accept `{model_type}` and return immediately with a run_id and status "running". Only one training run SHALL be allowed at a time.
#### Scenario: Start training
- **WHEN** `POST /training/start` is called with `{model_type: "random_forest"}`
- **THEN** the endpoint returns `{run_id, status: "running"}` and training begins in the background
#### Scenario: Training already in progress
- **WHEN** `POST /training/start` is called while a training run is active
- **THEN** the endpoint returns HTTP 409 with `{error: "Training already in progress", run_id: "<active_run_id>"}`
#### Scenario: Invalid model type
- **WHEN** `POST /training/start` is called with an unsupported model type
- **THEN** the endpoint returns HTTP 400 with `{error: "Unsupported model type. Available: random_forest, xgboost"}`
### Requirement: Training runs endpoint
The FastAPI service SHALL provide a `GET /training/runs` endpoint that returns training run history from the database. Each entry SHALL include run_id, model_type, status, created_at, completed_at, and metrics_summary. Results SHALL be sorted by created_at descending.
#### Scenario: List training runs
- **WHEN** `GET /training/runs` is called
- **THEN** the endpoint returns training run records sorted by date descending
#### Scenario: No training runs
- **WHEN** no training runs exist in the database
- **THEN** the endpoint returns `{runs: []}`
### Requirement: Model load endpoint
The FastAPI service SHALL provide a `POST /model/load` endpoint that loads a model by run_id. The endpoint SHALL look up the training run, find the model artifact (MLflow or local), and replace the currently loaded model. The endpoint SHALL return the new model's info.
#### Scenario: Load model by run_id
- **WHEN** `POST /model/load` is called with `{run_id: "abc123"}`
- **THEN** the endpoint loads the model associated with that run, updates the active model, and returns model info
#### Scenario: Run not found
- **WHEN** `POST /model/load` is called with a non-existent run_id
- **THEN** the endpoint returns HTTP 404 with `{error: "Training run not found"}`
#### Scenario: Model artifact missing
- **WHEN** the training run exists but the model file is missing
- **THEN** the endpoint returns HTTP 500 with `{error: "Model artifact not found for run"}`
### Requirement: Dataset info endpoint
The FastAPI service SHALL provide a `GET /training/dataset-info` endpoint that returns information about the training dataset: file path, existence status, file size, and last modified date.
#### Scenario: Dataset exists
- **WHEN** `GET /training/dataset-info` is called and the labeled dataset file exists
- **THEN** the endpoint returns `{path, exists: true, size_bytes, last_modified, row_count}`
#### Scenario: Dataset missing
- **WHEN** `GET /training/dataset-info` is called and the labeled dataset file does not exist
- **THEN** the endpoint returns `{path, exists: false}`
### Requirement: Pattern detection proxy
The Next.js API SHALL provide a `POST /api/patterns/detect` route that proxies to the FastAPI `/patterns/detect` endpoint.
#### Scenario: Proxy pattern detection
- **WHEN** `POST /api/patterns/detect` is called
- **THEN** the route forwards the request to the FastAPI service and returns the response
### Requirement: Available patterns proxy
The Next.js API SHALL provide a `GET /api/patterns/available` route that proxies to the FastAPI `/patterns/available` endpoint.
#### Scenario: Proxy available patterns
- **WHEN** `GET /api/patterns/available` is called
- **THEN** the route forwards to the FastAPI service and returns the pattern list
### Requirement: Training proxy endpoints
The Next.js API SHALL provide proxy routes for training operations: `POST /api/training/start`, `GET /api/training/runs`, and `GET /api/training/dataset-info`.
#### Scenario: Proxy training start
- **WHEN** `POST /api/training/start` is called
- **THEN** the route forwards to the FastAPI service and returns the response
#### Scenario: Proxy training runs
- **WHEN** `GET /api/training/runs` is called
- **THEN** the route forwards to the FastAPI service and returns the run list
### Requirement: Model load proxy
The Next.js API SHALL provide a `POST /api/model/load` route that proxies to the FastAPI `/model/load` endpoint.
#### Scenario: Proxy model load
- **WHEN** `POST /api/model/load` is called with a run_id
- **THEN** the route forwards to the FastAPI service and returns the response
### Requirement: Bulk delete by source
The Next.js API `DELETE /api/span-annotations` endpoint SHALL support a `source` query parameter for bulk deletion. When `source` is provided, all span annotations matching that source (and optionally `label` filter) for the current chart SHALL be deleted.
#### Scenario: Bulk delete TA-Lib annotations
- **WHEN** `DELETE /api/span-annotations?chartId=1&source=talib` is called
- **THEN** all span annotations with `source: "talib"` for chart 1 are deleted
#### Scenario: Bulk delete by source and label
- **WHEN** `DELETE /api/span-annotations?chartId=1&source=talib&label=Engulfing` is called
- **THEN** only TA-Lib annotations containing "Engulfing" in the label for chart 1 are deleted

View file

@ -0,0 +1,38 @@
## ADDED Requirements
### Requirement: Model selector dropdown
The system SHALL display a model selector dropdown in the prediction panel area. The dropdown SHALL list all completed training runs from the backend, showing model type, date, and key metric (F1 macro) for each entry. The currently loaded model SHALL be indicated with a checkmark or "active" badge.
#### Scenario: Display available models
- **WHEN** the user opens the model selector dropdown
- **THEN** completed training runs are listed with model type, training date, and F1 score
#### Scenario: No models available
- **WHEN** no completed training runs exist
- **THEN** the dropdown shows "No trained models available"
#### Scenario: Current model indicated
- **WHEN** a model is currently loaded
- **THEN** the corresponding entry in the dropdown shows an "active" indicator
### Requirement: Model switching
The system SHALL load a different model when the user selects a training run from the model selector. The system SHALL send the run_id to `POST /api/model/load` and update the prediction panel to reflect the newly loaded model's info. Existing cached predictions SHALL be cleared on model switch.
#### Scenario: Switch model
- **WHEN** the user selects a different model from the dropdown
- **THEN** the system sends `POST /api/model/load` with the run_id, shows a loading indicator, and upon success updates the model info display and clears prediction cache
#### Scenario: Model load failure
- **WHEN** model loading fails (e.g., model artifact not found)
- **THEN** the system shows an error message and keeps the previously loaded model active
#### Scenario: Prediction cache cleared
- **WHEN** a new model is successfully loaded
- **THEN** all cached predictions are invalidated and the chart clears any displayed predictions
### Requirement: Model info refresh on switch
The system SHALL refresh the model info display (name, version, type, per-class metrics) after a successful model switch. The prediction panel SHALL reflect the new model's capabilities and labels.
#### Scenario: Model info updates after switch
- **WHEN** a new model is loaded successfully
- **THEN** the prediction panel refreshes to show the new model's name, type, version, and per-class metrics

View file

@ -0,0 +1,31 @@
## MODIFIED Requirements
### Requirement: Prediction controls panel
The system SHALL display a prediction controls panel in the sidebar with: master on/off toggle, model selector dropdown (listing available trained models), model info (name, version, type, training date), action buttons ("Run on Visible", "Predict All"), auto-predict toggle, confidence threshold slider, label checkboxes with per-class precision/recall metrics, prediction count, agreement count, and a "Show only disagreements" filter.
#### Scenario: Display model info
- **WHEN** the prediction panel loads and the inference API is available
- **THEN** the panel fetches /api/model/info and displays model name, version, type, and training date
#### Scenario: Inference API unavailable
- **WHEN** the prediction panel loads and /api/model/info returns an error
- **THEN** the panel shows "Model server offline — predictions unavailable" and all controls are disabled
#### Scenario: Per-class metrics display
- **WHEN** model info includes per-class metrics
- **THEN** each label checkbox shows precision and recall values (e.g., "bull_flag (P:0.89 R:0.76)")
#### Scenario: Model selector integrated
- **WHEN** the prediction panel renders with trained models available
- **THEN** a model selector dropdown appears above the action buttons, allowing the user to switch the active model
### Requirement: Prediction cache invalidation on model change
The system SHALL cache predictions in memory keyed by `${pair}_${timeframe}_${startTime}_${endTime}_${modelVersion}`. When the user scrolls to a range with cached predictions, the system SHALL use the cache instead of re-fetching. Cache SHALL be invalidated when the model version changes OR when the user switches models via the model selector.
#### Scenario: Cache hit
- **WHEN** user scrolls back to a previously predicted range with the same model version
- **THEN** the system renders cached predictions without making an API call
#### Scenario: Cache invalidation on model switch
- **WHEN** the user switches to a different model via the model selector
- **THEN** all cached predictions are cleared and the chart removes displayed predictions

View file

@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: TA-Lib pattern selection panel
The system SHALL display a collapsible "TA-Lib Patterns" panel in the sidebar. The panel SHALL list all available CDL pattern functions grouped by category (single-candle, multi-candle). Each pattern SHALL have a checkbox for selection. The panel SHALL include a "Select All" / "Deselect All" toggle and a "Detect Patterns" action button.
#### Scenario: Display available patterns
- **WHEN** the user expands the TA-Lib Patterns panel
- **THEN** all available CDL pattern names are listed with checkboxes, grouped by category, all unchecked by default
#### Scenario: Select specific patterns
- **WHEN** the user checks "Engulfing" and "Hammer" checkboxes
- **THEN** those two patterns are selected and the "Detect Patterns" button shows the count "(2 selected)"
#### Scenario: Select all patterns
- **WHEN** the user clicks "Select All"
- **THEN** all pattern checkboxes become checked
### Requirement: Pattern detection execution
The system SHALL send selected patterns and current chart candles to the backend for detection when the user clicks "Detect Patterns". The system SHALL display a loading state during detection and show results as span annotations on the chart upon completion.
#### Scenario: Run pattern detection
- **WHEN** the user has selected patterns and clicks "Detect Patterns"
- **THEN** the system sends current chart candles and selected pattern names to `POST /api/patterns/detect`, shows a loading spinner, and upon response saves returned annotations to the database via `POST /api/span-annotations`
#### Scenario: No patterns selected
- **WHEN** the user clicks "Detect Patterns" with no patterns selected
- **THEN** the button is disabled and nothing happens
#### Scenario: No chart loaded
- **WHEN** the user clicks "Detect Patterns" with no chart loaded
- **THEN** the system shows an error message "Load a chart first"
### Requirement: Pattern results as span annotations
The system SHALL save detected patterns as span annotations with `source: "talib"`. Each annotation SHALL include the pattern label (e.g., "Bullish Engulfing"), confidence from TA-Lib, start_time, and end_time. The annotations SHALL appear immediately on the chart after detection.
#### Scenario: Pattern saved as span annotation
- **WHEN** TA-Lib detects a "Bullish Engulfing" pattern at candles T10-T12
- **THEN** a span annotation is created with `label: "Bullish Engulfing"`, `source: "talib"`, `start_time: T10`, `end_time: T12`, and it renders on the chart
#### Scenario: Multiple patterns detected
- **WHEN** detection finds 5 Engulfing and 3 Hammer patterns
- **THEN** 8 span annotations are created, each with appropriate label and timestamps
### Requirement: TA-Lib annotation management
The system SHALL allow users to bulk delete all TA-Lib-generated annotations or delete them by pattern type. The panel SHALL show a count of detected patterns and a "Clear All TA-Lib" button. Individual TA-Lib annotations SHALL also be deletable from the SpanAnnotationList.
#### Scenario: Bulk delete all TA-Lib annotations
- **WHEN** the user clicks "Clear All TA-Lib"
- **THEN** all span annotations with `source: "talib"` for the current chart are deleted
#### Scenario: Delete by pattern type
- **WHEN** the user clicks the delete icon next to "Engulfing" in the results summary
- **THEN** all span annotations with `source: "talib"` and label containing "Engulfing" for the current chart are deleted
#### Scenario: Detection results summary
- **WHEN** pattern detection completes with results
- **THEN** the panel shows a summary: total patterns found, grouped by pattern name with counts (e.g., "Engulfing: 5, Hammer: 3")

View file

@ -0,0 +1,60 @@
## ADDED Requirements
### Requirement: Training panel
The system SHALL display a collapsible "Training" panel in the sidebar. The panel SHALL contain model type selection, a "Start Training" button, and training run history. The panel SHALL be usable independently of the prediction panel.
#### Scenario: Display training panel
- **WHEN** the user expands the Training panel
- **THEN** the panel shows model type selector, training action button, and recent training history
### Requirement: Model type selection
The system SHALL provide a dropdown to select the ML model type for training. Available options SHALL be "Random Forest" and "XGBoost". The selection SHALL default to "Random Forest".
#### Scenario: Select model type
- **WHEN** the user opens the model type dropdown
- **THEN** "Random Forest" and "XGBoost" are listed as options
#### Scenario: Default selection
- **WHEN** the training panel loads
- **THEN** "Random Forest" is pre-selected
### Requirement: Training execution
The system SHALL trigger a training run via `POST /api/training/start` when the user clicks "Start Training". The request SHALL include the selected model type. The system SHALL show a progress indicator and poll for status updates every 5 seconds while training is active. The "Start Training" button SHALL be disabled while a training run is in progress.
#### Scenario: Start training
- **WHEN** the user selects "XGBoost" and clicks "Start Training"
- **THEN** the system sends `POST /api/training/start` with `{model_type: "xgboost"}`, disables the button, and shows a "Training in progress..." indicator
#### Scenario: Training completes
- **WHEN** a training run status changes from "running" to "completed"
- **THEN** the system shows a success message with key metrics (accuracy, F1 score), re-enables the button, and adds the run to training history
#### Scenario: Training fails
- **WHEN** a training run status changes to "failed"
- **THEN** the system shows an error message with the failure reason and re-enables the button
#### Scenario: Prevent concurrent training
- **WHEN** a training run is already in progress and the user tries to start another
- **THEN** the "Start Training" button remains disabled
### Requirement: Training run history
The system SHALL display a list of recent training runs fetched from `GET /api/training/runs`. Each entry SHALL show: model type, status, date, and key metrics (accuracy, F1 macro). The list SHALL be sorted by date descending (most recent first). The list SHALL show the 5 most recent runs.
#### Scenario: Display training history
- **WHEN** the training panel loads
- **THEN** the system fetches training runs and displays them with model type, status badge, date, and metrics
#### Scenario: No training runs
- **WHEN** no training runs exist
- **THEN** the panel shows "No training runs yet"
### Requirement: Dataset info display
The system SHALL display information about the training dataset before training starts. This SHALL include the dataset file path and whether it exists. The user SHALL be informed that training uses the exported annotation dataset.
#### Scenario: Dataset available
- **WHEN** the training panel loads and the labeled dataset exists
- **THEN** the panel shows the dataset path and a ready indicator
#### Scenario: Dataset missing
- **WHEN** the training panel loads and no labeled dataset exists
- **THEN** the panel shows a warning "No training dataset found. Export annotations first." and the "Start Training" button is disabled

View file

@ -7,25 +7,25 @@
## 2. FastAPI Training Endpoints
- [ ] 2.1 Add `POST /training/start` endpoint that launches training in a background thread, returns `{run_id, status: "running"}`, and rejects concurrent runs with HTTP 409
- [ ] 2.2 Add `GET /training/runs` endpoint returning training run history from the `training_runs` table, sorted by date descending
- [ ] 2.3 Add `GET /training/dataset-info` endpoint returning labeled dataset file path, existence, size, and row count
- [ ] 2.4 Add background training thread management: track active run, update DB status on completion/failure
- [x] 2.1 Add `POST /training/start` endpoint that launches training in a background thread, returns `{run_id, status: "running"}`, and rejects concurrent runs with HTTP 409
- [x] 2.2 Add `GET /training/runs` endpoint returning training run history from the `training_runs` table, sorted by date descending
- [x] 2.3 Add `GET /training/dataset-info` endpoint returning labeled dataset file path, existence, size, and row count
- [x] 2.4 Add background training thread management: track active run, update DB status on completion/failure
## 3. FastAPI Model Loading Endpoint
- [ ] 3.1 Add `POST /model/load` endpoint accepting `{run_id}`, looking up the training run, loading the model artifact, and replacing the active model in `AppState`
- [ ] 3.2 Add thread-safe model swap with locking to prevent conflicts with in-flight prediction requests
- [x] 3.1 Add `POST /model/load` endpoint accepting `{run_id}`, looking up the training run, loading the model artifact, and replacing the active model in `AppState`
- [x] 3.2 Add thread-safe model swap with locking to prevent conflicts with in-flight prediction requests
## 4. Next.js Proxy Routes
- [ ] 4.1 Add `GET /api/patterns/available` proxy route
- [ ] 4.2 Add `POST /api/patterns/detect` proxy route
- [ ] 4.3 Add `POST /api/training/start` proxy route
- [ ] 4.4 Add `GET /api/training/runs` proxy route
- [ ] 4.5 Add `GET /api/training/dataset-info` proxy route
- [ ] 4.6 Add `POST /api/model/load` proxy route
- [ ] 4.7 Extend `DELETE /api/span-annotations` to support `source` and `label` query parameters for bulk deletion
- [x] 4.1 Add `GET /api/patterns/available` proxy route
- [x] 4.2 Add `POST /api/patterns/detect` proxy route
- [x] 4.3 Add `POST /api/training/start` proxy route
- [x] 4.4 Add `GET /api/training/runs` proxy route
- [x] 4.5 Add `GET /api/training/dataset-info` proxy route
- [x] 4.6 Add `POST /api/model/load` proxy route
- [x] 4.7 Extend `DELETE /api/span-annotations` to support `source` and `label` query parameters for bulk deletion
## 5. TA-Lib Pattern UI Panel