## 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: ""}` #### 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 ## 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-ID` header 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-ID` header 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-ID` header #### Scenario: Proxy training runs - **WHEN** an authenticated user calls GET /api/training/runs - **THEN** the route forwards to the FastAPI service with `X-User-ID` header 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-ID` header ### 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-ID` header 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_id` set 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