20 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/detectis 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/detectis 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/availableis 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/startis 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/startis 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/startis 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/runsis 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/loadis 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/loadis 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-infois 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-infois 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/detectis 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/availableis 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/startis called - THEN the route forwards to the FastAPI service and returns the response
Scenario: Proxy training runs
- WHEN
GET /api/training/runsis 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/loadis 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=talibis 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=Engulfingis 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=5is called - THEN all annotations for chart 5 are deleted
Scenario: Unscoped bulk delete rejected
- WHEN
DELETE /api/annotations?all=trueis 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/5is 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/5is called - THEN all
span_annotationsrows withchart_id=5are 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=5is called - THEN chartId is parsed as integer 5
Scenario: Invalid integer parameter
- WHEN
GET /api/candles?chartId=abcis 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.okand shows an error message instead of attempting JSON parse
MODIFIED Requirements (user-accounts)
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 require authentication via getAuthUser(). The route SHALL forward the request body (pair, timeframe, candles array) and include the X-User-ID header with the authenticated user's UUID. If the inference service is unreachable, the route SHALL return HTTP 503 with { "error": "Inference service unavailable" }.
Scenario: Successful prediction proxy
- WHEN an authenticated user calls POST /api/predict with valid candle data and the Python service is running
- THEN the route forwards the request with
X-User-IDheader and returns the prediction response with HTTP 200
Scenario: Unauthenticated prediction request
- WHEN POST /api/predict is called without authentication
- THEN the route returns HTTP 401 with
{ "error": "Unauthorized" }
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 require authentication and include the X-User-ID header.
Scenario: Successful batch prediction
- WHEN an authenticated user calls POST /api/predict/batch with valid parameters
- THEN the route forwards to the inference service with
X-User-IDheader and returns the 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: 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. All training proxy routes SHALL require authentication and include the X-User-ID header.
Scenario: Proxy training start
- WHEN an authenticated user calls POST /api/training/start
- THEN the route forwards to the FastAPI service with
X-User-IDheader
Scenario: Proxy training runs
- WHEN an authenticated user calls GET /api/training/runs
- THEN the route forwards to the FastAPI service with
X-User-IDheader 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. The route SHALL require authentication and include the X-User-ID header.
Scenario: Proxy model load
- WHEN an authenticated user calls POST /api/model/load with a run_id
- THEN the route forwards to the FastAPI service with
X-User-IDheader
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. The route SHALL require authentication and include the X-User-ID header.
Scenario: Successful model info
- WHEN an authenticated user calls GET /api/model/info
- 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: Pattern detection proxy
The Next.js API SHALL provide a POST /api/patterns/detect route that proxies to the FastAPI /patterns/detect endpoint. The route SHALL require authentication and include the X-User-ID header.
Scenario: Proxy pattern detection
- WHEN an authenticated user calls POST /api/patterns/detect
- THEN the route forwards the request with
X-User-IDheader 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. The route SHALL require authentication.
Scenario: Proxy available patterns
- WHEN an authenticated user calls GET /api/patterns/available
- THEN the route forwards to the FastAPI service and returns the pattern list
Requirement: Bulk delete by source
The Next.js API DELETE /api/span-annotations endpoint SHALL require authentication and scope deletion by the authenticated user. When source is provided, all span annotations matching that source (and optionally label filter) for the current chart belonging to the authenticated user SHALL be deleted.
Scenario: Bulk delete TA-Lib annotations
- WHEN an authenticated user calls
DELETE /api/span-annotations?chartId=1&source=talib - THEN all span annotations with
source: "talib"for chart 1 belonging to that user are deleted
Scenario: Bulk delete by source and label
- WHEN an authenticated user calls
DELETE /api/span-annotations?chartId=1&source=talib&label=Engulfing - THEN only TA-Lib annotations containing "Engulfing" for chart 1 belonging to that user are deleted
ADDED Requirements (user-accounts)
Requirement: Auth guard on all data API routes
All existing data API routes (/api/upload, /api/candles, /api/annotations, /api/annotation-types, /api/charts, /api/span-annotations, /api/span-label-types, /api/export) SHALL call getAuthUser() at the top. If the user is not authenticated, the route SHALL return HTTP 401.
Scenario: Unauthenticated data API access
- WHEN any data API route is called without authentication
- THEN the route returns HTTP 401 with
{ "error": "Unauthorized" }
Scenario: Authenticated data API access
- WHEN any data API route is called with valid authentication
- THEN the route proceeds with queries scoped to the authenticated user's ID
Requirement: User-scoped queries in all data routes
All Drizzle queries in data API routes SHALL include a user_id filter matching the authenticated user. INSERT operations SHALL set user_id to the authenticated user's UUID.
Scenario: GET queries filtered by user
- WHEN an authenticated user requests data (charts, annotations, annotation types, etc.)
- THEN the query includes
.where(eq(table.user_id, user.id))or equivalent join condition
Scenario: INSERT operations set user_id
- WHEN an authenticated user creates new data (upload, create annotation, etc.)
- THEN the inserted row has
user_idset to the authenticated user's UUID
Scenario: Cross-user data isolation
- WHEN user A requests data
- THEN no data belonging to user B is returned