{"record":{"id":"a46545f1f1d49b7c","repo":"ZhuLinsen/daily_stock_analysis","slug":"error-a46545","errorCode":null,"errorMessage":"更新尚未下载完成，无法自动安装。","messagePattern":"更新尚未下载完成，无法自动安装。","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/dsa-desktop/main.js","lineNumber":1589,"sourceCode":"    cancelId: 0,\n    title: '发现新版本',\n    message: `检测到桌面端新版本 ${state.latestVersion}`,\n    detail: `当前版本 ${currentVersion}。新版本将跳转到 GitHub Releases 下载页，不会静默下载或自动安装。`,\n    noLink: true,\n  });\n\n  if (result.response === 1) {\n    await shell.openExternal(sanitizeReleaseUrl(state.releaseUrl));\n  }\n}\n\nasync function installDownloadedUpdate() {\n  const updater = getElectronAutoUpdater();\n  if (!updater) {\n    throw new Error('当前运行模式不支持自动安装更新。');\n  }\n  if (desktopUpdateState?.status !== UPDATE_STATUS.UPDATE_DOWNLOADED) {\n    throw new Error('更新尚未下载完成，无法自动安装。');\n  }\n\n  setDesktopUpdateState({\n    status: UPDATE_STATUS.INSTALLING,\n    updateMode: UPDATE_MODE.AUTO,\n    latestVersion: desktopUpdateState?.latestVersion || '',\n    releaseUrl: desktopUpdateState?.releaseUrl || RELEASES_PAGE_URL,\n    message: '正在重启并安装更新...',\n  });\n  let backupRoot = null;\n  try {\n    logLine('[update] stop backend and backup runtime data before install');\n    await stopBackend();\n    backupRoot = resolveUpdateBackupRoot();\n    cleanupUpdateBackupRoot();\n\n    for (let attempt = 1; attempt <= 3; attempt += 1) {\n      try {","sourceCodeStart":1571,"sourceCodeEnd":1607,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-desktop/main.js#L1571-L1607","documentation":"Raised by PortfolioService._validate_trade_identity (portfolio_service.py:666) when a trade's dedup_hash already exists for the account. dedup_hash is the content-based duplicate detector for trades that carry no explicit trade_uid; PortfolioConflictError (code=conflict) tells the caller the identical trade is already stored.","triggerScenarios":"add_trade(...) without trade_uid called twice with identical field content (same computed dedup_hash under the same account_id); re-running a CSV import after an interruption; resubmitting a form. The scope is per account, and only when dedup_hash is truthy.","commonSituations":"Import pipelines restarted mid-file replaying earlier rows; double-clicked form submission; at-least-once queues; a bug in the hash computation that collapses distinct trades (e.g. ignoring quantity) making unrelated trades collide.","solutions":["Catch PortfolioConflictError in importers and treat it as already-imported (skip), matching trade_uid semantics","Compute dedup_hash from all identifying fields (symbol, side, date, quantity, price, fee) so genuine duplicates collide but distinct trades never do","Make the import job resumable (track last processed row) so full re-runs are unnecessary","If two truly distinct trades hash the same, fix the hash inputs rather than disabling dedup"],"exampleFix":"# before\nsvc.add_trade(account_id=1, symbol=\"600519\", ...)  # no trade_uid\n# after\ntry:\n    svc.add_trade(account_id=1, symbol=\"600519\", ...)\nexcept PortfolioConflictError as exc:\n    if \"dedup_hash\" in str(exc):\n        continue  # already imported","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def dedup_inputs_complete(symbol: str, trade_date, side: str, quantity: float, price: float) -> bool:\n    \"\"\"All identifying fields present, so dedup_hash is well-defined.\"\"\"\n    return bool(symbol and trade_date and side in (\"buy\", \"sell\") and quantity > 0 and price > 0)","tryCatchPattern":"from src.services.portfolio_service import PortfolioConflictError\n\ntry:\n    svc.add_trade(account_id=a, ...)\nexcept PortfolioConflictError as exc:\n    if \"dedup_hash\" in str(exc):\n        continue  # identical trade already stored; idempotent success\n    raise","preventionTips":["Ensure dedup_hash covers all identifying fields so distinct trades never collide","Catch PortfolioConflictError in any importer and treat as already-imported","Make import jobs resumable so full re-runs do not resend rows"],"tags":["portfolio","conflict","idempotency","dedup","trade"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}