candle-annotator/openspec/specs/backend-api/spec.md
Marko Djordjevic 925e7284e3 Archive code-review-fix change and sync specs to main
- Synced 14 capability delta specs to main specs
- Created 6 new main specs: api-authentication, error-boundary, input-validation, security-headers, shared-types
- Updated 8 existing specs with security, validation, and performance requirements
- Archived change to openspec/changes/archive/2026-02-20-code-review-fix/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 08:54:59 +01:00

13 KiB

ADDED Requirements

Requirement: Predict proxy endpoint

The system SHALL provide a POST /api/predict Next.js API route that proxies requests to the Python inference service at ${INFERENCE_API_URL}/predict. The route SHALL forward the request body (pair, timeframe, candles array) and return the Python service's response. If the inference service is unreachable, the route SHALL return HTTP 503 with { "error": "Inference service unavailable" }.

Scenario: Successful prediction proxy

  • WHEN POST /api/predict is called with valid candle data and the Python service is running
  • THEN the route forwards the request to the inference service and returns the prediction response with HTTP 200

Scenario: Inference service down

  • WHEN POST /api/predict is called but the Python inference service is unreachable
  • THEN the route returns HTTP 503 with { "error": "Inference service unavailable" }

Scenario: Inference service error

  • WHEN the Python inference service returns an error status (4xx or 5xx)
  • THEN the route forwards the error status and message to the client

Requirement: Batch predict proxy endpoint

The system SHALL provide a POST /api/predict/batch Next.js API route that proxies batch prediction requests to ${INFERENCE_API_URL}/predict/batch. The route SHALL forward pair, timeframe, start_date, and end_date.

Scenario: Successful batch prediction

  • WHEN POST /api/predict/batch is called with valid parameters
  • THEN the route forwards to the inference service and returns the batch prediction response

Scenario: Timeout on large batch

  • WHEN the batch prediction takes longer than INFERENCE_BATCH_TIMEOUT
  • THEN the route returns HTTP 504 with { "error": "Batch prediction timed out" }

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

Requirement: Model info proxy endpoint

The system SHALL provide a GET /api/model/info Next.js API route that proxies to ${INFERENCE_API_URL}/model/info. This endpoint returns model metadata and per-class metrics.

Scenario: Successful model info

  • WHEN GET /api/model/info is called and the inference service is running
  • THEN the route returns the model metadata JSON

Scenario: No model available

  • WHEN GET /api/model/info is called and the inference service returns 503
  • THEN the route returns HTTP 503 with { "error": "No model available" }

Requirement: Generic error responses

All Next.js API routes SHALL return generic error messages to clients for 500-level errors. The response body SHALL be { "error": "Internal server error" }. The full error details SHALL be logged server-side via console.error with request context.

Scenario: Internal error returns generic message

  • WHEN an API route handler throws an unexpected error
  • THEN the client receives HTTP 500 with { "error": "Internal server error" } and the full error is logged server-side

Scenario: No stack traces in response

  • WHEN a database query fails in any API route
  • THEN the response does NOT contain table names, file paths, connection strings, or stack traces

Requirement: Scoped bulk annotation delete

The DELETE /api/annotations endpoint SHALL require a chartId query parameter when all=true is specified. Unscoped delete-all (without chartId) SHALL be rejected with HTTP 400.

Scenario: Scoped bulk delete

  • WHEN DELETE /api/annotations?all=true&chartId=5 is called
  • THEN all annotations for chart 5 are deleted

Scenario: Unscoped bulk delete rejected

  • WHEN DELETE /api/annotations?all=true is called without chartId
  • THEN the route returns HTTP 400 with { "error": "chartId is required for bulk delete" }

Requirement: Transaction-wrapped chart cascade delete

The DELETE /api/charts/[id] route SHALL wrap all related deletions (annotations, span annotations, candles, chart) in a single database transaction using db.transaction().

Scenario: Cascade delete in transaction

  • WHEN DELETE /api/charts/5 is called
  • THEN span annotations, annotations, candles, and the chart record for chart 5 are all deleted within a single transaction

Scenario: Partial failure rolls back

  • WHEN the candles deletion fails mid-transaction
  • THEN all deletions are rolled back and the chart remains intact

Requirement: Span annotations included in chart cascade delete

The DELETE /api/charts/[id] route SHALL delete span annotations for the chart in addition to annotations and candles.

Scenario: Span annotations deleted with chart

  • WHEN DELETE /api/charts/5 is called
  • THEN all span_annotations rows with chart_id=5 are deleted before the chart record

Requirement: parseInt validation

All API routes that parse integer query parameters SHALL use parseInt(value, 10) with radix 10 and check for isNaN(). Invalid integer parameters SHALL return HTTP 400.

Scenario: Valid integer parameter

  • WHEN GET /api/candles?chartId=5 is called
  • THEN chartId is parsed as integer 5

Scenario: Invalid integer parameter

  • WHEN GET /api/candles?chartId=abc is called
  • THEN the route returns HTTP 400 with { "error": "Invalid chartId" }

Requirement: CSV injection protection on exports

All CSV export routes SHALL prefix cell values starting with =, +, -, or @ with a single quote (') to prevent spreadsheet formula injection.

Scenario: Dangerous cell value escaped

  • WHEN an annotation note contains =CMD("calc")
  • THEN the exported CSV cell contains '=CMD("calc")

Scenario: Normal values unchanged

  • WHEN an annotation note contains regular text
  • THEN the exported CSV cell contains regular text (no prefix)

Requirement: response.ok checks on all fetch calls

All fetch() calls in frontend components (page.tsx, CandleChart.tsx) SHALL check response.ok before calling response.json(). If !response.ok, the code SHALL throw an error or handle the failure explicitly.

Scenario: Successful response parsed

  • WHEN a fetch call returns HTTP 200
  • THEN response.json() is called and the data is used normally

Scenario: Error response handled

  • WHEN a fetch call returns HTTP 500
  • THEN the code detects !response.ok and shows an error message instead of attempting JSON parse