{"record":{"id":"4466059e94ba8486","repo":"ZhuLinsen/daily_stock_analysis","slug":"error-446605","errorCode":null,"errorMessage":"配置版本冲突","messagePattern":"配置版本冲突","errorType":"validation","errorClass":"SystemConfigConflictError","httpStatus":409,"severity":"error","filePath":"apps/dsa-web/src/api/systemConfig.ts","lineNumber":329,"sourceCode":"      return toCamelCase<UpdateSystemConfigResponse>(response.data);\n    } catch (error: unknown) {\n      const parsed = getParsedApiError(error);\n      if (error && typeof error === 'object' && 'response' in error) {\n        const status = (error as { response?: { status?: number } }).response?.status;\n        const payloadData = (error as { response?: { data?: unknown } }).response?.data;\n\n        if (status === 400) {\n          const validationError = toCamelCase<SystemConfigValidationErrorResponse>(payloadData ?? {});\n          throw new SystemConfigValidationError(\n            parsed.message || validationError.message || '配置校验失败',\n            validationError.issues || [],\n            parsed,\n          );\n        }\n\n        if (status === 409) {\n          const conflict = toCamelCase<SystemConfigConflictResponse>(payloadData ?? {});\n          throw new SystemConfigConflictError(\n            parsed.message || conflict.message || '配置版本冲突',\n            conflict.currentConfigVersion,\n            parsed,\n          );\n        }\n      }\n\n      throw error;\n    }\n  },\n\n  /**\n   * 获取自选队列股票代码列表\n   */\n  getWatchlist: async (): Promise<string[]> => {\n    const response = await apiClient.get<Record<string, unknown>>('/api/v1/stocks/watchlist');\n    const data = toCamelCase<{ stockCodes: string[] }>(response.data);\n    return data.stockCodes || [];","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-web/src/api/systemConfig.ts#L311-L347","documentation":"Raised during full account replay (portfolio_service.py:883) when a trade event's side is neither 'buy' nor 'sell' (compared after lower().strip()). The account-level replay (cash balances, FIFO/avg cost) cannot classify the event, so the snapshot fails with validation_error.","triggerScenarios":"A trades row with side='LONG', side='B', side=NULL, or an unexpected enum reaching _replay_account. Same class of corruption as the quantity-replay variant at portfolio_service.py:763, but hit while building the full account snapshot including cash and cost basis.","commonSituations":"Rows written by external ETL or manual SQL that bypass add_trade's VALID_SIDES check; enum drift after migrating data from another portfolio tool; partially failed imports leaving rows with NULL side.","solutions":["Audit trades for the account: SELECT ... WHERE side IS NULL OR LOWER(TRIM(side)) NOT IN ('buy','sell') and repair","Map legacy values (B/S, long/short) to buy/sell in a migration script","Insert future trades only via add_trade","Add a DB CHECK constraint on side to stop drift at the storage layer"],"exampleFix":"# before\nraw_row.side = \"long\"\n# after\nsvc.add_trade(account_id=1, symbol=\"AAPL\", side=\"buy\", quantity=10, price=185.50, ...)","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":["Route all writes through add_trade's enum validation","Map legacy side vocabularies during migration","Add NOT NULL/CHECK constraints on the trades table"],"tags":["portfolio","validation","trade","side","replay","data-corruption"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}