{"record":{"id":"c5df192ecc9ab89b","repo":"ZhuLinsen/daily_stock_analysis","slug":"error-c5df19","errorCode":null,"errorMessage":"大盘复盘正在执行中，请稍后再试","messagePattern":"大盘复盘正在执行中，请稍后再试","errorType":"exception","errorClass":"Error","httpStatus":409,"severity":"error","filePath":"apps/dsa-web/src/api/analysis.ts","lineNumber":121,"sourceCode":"  triggerMarketReview: async (data: MarketReviewRequest = {}): Promise<MarketReviewAccepted> => {\n    const response = await apiClient.post<Record<string, unknown>>(\n      '/api/v1/analysis/market-review',\n      {\n        send_notification: data.sendNotification ?? true,\n        report_language: data.reportLanguage,\n        ...(data.regions !== undefined && { region: serializeMarketReviewRegions(data.regions) }),\n      },\n      {\n        validateStatus: (status) => status === 202 || status === 409,\n      }\n    );\n\n    if (response.status === 409) {\n      const detail = response.data?.detail;\n      const message = detail && typeof detail === 'object' && 'message' in detail\n        ? String((detail as { message?: unknown }).message || '')\n        : String(response.data?.message || '');\n      throw new Error(message || '大盘复盘正在执行中，请稍后再试');\n    }\n\n    return toCamelCase<MarketReviewAccepted>(response.data);\n  },\n\n  /**\n   * Get async task status.\n   * @param taskId Task ID\n   */\n  getStatus: async (taskId: string): Promise<TaskStatus> => {\n    const response = await apiClient.get<Record<string, unknown>>(\n      `/api/v1/analysis/status/${taskId}`\n    );\n\n    const data = toCamelCase<TaskStatus>(response.data);\n\n    // Ensure nested result payloads are converted recursively.\n    if (data.result) {","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-web/src/api/analysis.ts#L103-L139","documentation":"Raised during quantity replay (portfolio_service.py:763) when a trade row's side is neither 'buy' nor 'sell' after strip+lowercase. The replay cannot apply the event to the running position, so it fails with validation_error instead of guessing.","triggerScenarios":"A trades row with side='B', side='Bought', side=NULL/empty (None or '' fails both the buy and sell branches), or any non-enum value reaching _calculate_available_quantity; typically rows written outside add_trade's validated path.","commonSituations":"Broker exports using B/S codes loaded by a script that writes straight to the repository; NULL side from a partially failed import; a schema change where side became optional and older rows have empty strings.","solutions":["Query trades for the symbol/account where side NOT IN ('buy','sell') or side IS NULL and repair the rows","Map the source vocabulary (B->buy, S->sell) in the importer before writing","Re-write rows through add_trade which enforces VALID_SIDES at write time","Add a NOT NULL + CHECK constraint or audit query to catch drift early"],"exampleFix":"# before\nrepo_inserted_row.side = \"B\"\n# after\nsvc.add_trade(account_id=1, symbol=\"AAPL\", side=\"buy\", quantity=10, price=100.0, ...)","handlingStrategy":"validation","validationCode":"VALID_SIDES = {\"buy\", \"sell\"}\n\ndef stored_side_ok(side):\n    return bool(side) and side.strip().lower() in VALID_SIDES","typeGuard":"from typing import Literal, TypeGuard\n\nStoredSide = Literal[\"buy\", \"sell\"]\n\ndef is_stored_side(value: str | None) -> TypeGuard[StoredSide]:\n    return bool(value) and value.strip().lower() in {\"buy\", \"sell\"}","tryCatchPattern":"try:\n    svc.get_snapshot(account_id=a)\nexcept ValueError as exc:\n    if \"Unsupported trade side\" in str(exc):\n        normalize_sides_in_db(a); svc.get_snapshot(account_id=a)\n    else:\n        raise","preventionTips":["Write trades only through add_trade","Map B/S vocabulary in importers","Add a DB CHECK constraint on side"],"tags":["portfolio","validation","trade","side","replay","data-corruption"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}