{"record":{"id":"608764d6c3a92488","repo":"ZhuLinsen/daily_stock_analysis","slug":"stockcode","errorCode":null,"errorMessage":"股票 ${stockCode} 正在分析中","messagePattern":"股票 (.+?) 正在分析中","errorType":"exception","errorClass":"DuplicateTaskError","httpStatus":409,"severity":"error","filePath":"apps/dsa-web/src/api/analysis.ts","lineNumber":94,"sourceCode":"\n    const response = await apiClient.post<Record<string, unknown>>(\n      '/api/v1/analysis/analyze',\n      requestData,\n      {\n        // Allow 202 accepted responses in addition to standard success codes.\n        validateStatus: (status) => status === 200 || status === 202 || status === 409,\n      }\n    );\n\n    // Handle duplicate submission compatibility.\n    if (response.status === 409) {\n      const errorData = toCamelCase<{\n        error: string;\n        message: string;\n        stockCode: string;\n        existingTaskId: string;\n      }>(response.data);\n      throw new DuplicateTaskError(errorData.stockCode, errorData.existingTaskId, errorData.message);\n    }\n\n    return toCamelCase<AnalyzeAsyncResponse>(response.data);\n  },\n\n  /**\n   * Trigger market review in background mode.\n   */\n  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,","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-web/src/api/analysis.ts#L76-L112","documentation":"Raised during quantity replay (portfolio_service.py:757) when a trade event has quantity <= 0 (null coerces to 0.0). The replay's ledger semantics require positive quantities, so a corrupt or zero-quantity trade row aborts the whole position computation with validation_error.","triggerScenarios":"A trades row with quantity = 0, negative, or NULL reaching the replay; test rows inserted directly into the DB; importer writing 0 when the source field is missing; float('-inf')/NaN coerced through float() to a non-positive value.","commonSituations":"Direct database seeding in dev/test that skips service validation; CSV import with a blank quantity column parsed as 0; fee-only rows mistakenly imported as trades with zero quantity; refactors of the write path that forgot the positivity check.","solutions":["Locate the offending trade: query trades for the symbol where quantity IS NULL or <= 0 and fix or delete the row","Re-insert valid trades through add_trade, which validates quantity at write time","Fix the importer to fail on blank quantity instead of defaulting to 0","Add a DB-level CHECK (quantity > 0) or a periodic audit query"],"exampleFix":"# before\nsvc.add_trade(account_id=1, symbol=\"AAPL\", side=\"buy\", quantity=0, price=100.0, ...)\n# after\nsvc.add_trade(account_id=1, symbol=\"AAPL\", side=\"buy\", quantity=10, price=100.0, ...)","handlingStrategy":"validation","validationCode":"def trade_qty_ok(qty):\n    return qty is not None and qty > 0","typeGuard":"from numbers import Real\n\ndef is_positive_quantity(value: Real | None) -> bool:\n    return value is not None and float(value) > 0.0","tryCatchPattern":"try:\n    svc.get_snapshot(account_id=a)\nexcept ValueError as exc:\n    if \"Invalid trade quantity\" in str(exc):\n        repair_or_delete_bad_trade_rows(a); svc.get_snapshot(account_id=a)\n    else:\n        raise","preventionTips":["Never insert trade rows directly into the DB","Reject blank quantity in importers instead of defaulting to 0","Periodically audit trades for quantity <= 0 or NULL"],"tags":["portfolio","validation","trade","replay","data-corruption"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}