{"record":{"id":"b8f267c98f31fa37","repo":"ZhuLinsen/daily_stock_analysis","slug":"error","errorCode":null,"errorMessage":"当前运行模式不支持自动安装更新。","messagePattern":"当前运行模式不支持自动安装更新。","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/dsa-desktop/main.js","lineNumber":1586,"sourceCode":"    type: 'info',\n    buttons: ['稍后', '前往下载'],\n    defaultId: 1,\n    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();","sourceCodeStart":1568,"sourceCodeEnd":1604,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-desktop/main.js#L1568-L1604","documentation":"Raised by PortfolioService._validate_trade_identity (portfolio_service.py:664) when recording a trade whose client-supplied trade_uid already exists for the same account. It surfaces as PortfolioConflictError (code=conflict) and is the idempotency guard against double-submitting the same external trade reference.","triggerScenarios":"add_trade(..., trade_uid='abc123') called twice with the same account_id and trade_uid; a retrying importer (network retry, user double-click) re-sending the same broker record; two threads importing the same statement concurrently. The lookup runs per account, so the same uid under a different account_id is allowed.","commonSituations":"Broker-statement importers that re-run after a partial failure and resend already-persisted rows; UI double-submit because the button is not disabled; at-least-once message queues delivering the same trade event twice; changing uid generation to a non-deterministic scheme and then reverting.","solutions":["Treat the conflict as success in importers: catch PortfolioConflictError and skip/mark the row as already-imported (idempotent upsert semantics)","Ensure trade_uid is stable and deterministic per external trade (e.g. broker reference or hash of broker+date+symbol+qty+price)","Disable submit buttons / dedupe in-flight requests in the UI","If the trade genuinely should be recorded twice (rare), assign a distinct trade_uid"],"exampleFix":"# before\nsvc.add_trade(account_id=1, symbol=\"AAPL\", ..., trade_uid=\"broker-42\")\n# after\ntry:\n    svc.add_trade(account_id=1, symbol=\"AAPL\", ..., trade_uid=\"broker-42\")\nexcept PortfolioConflictError:\n    logger.info(\"trade broker-42 already imported, skipping\")","handlingStrategy":"try-catch","validationCode":"def already_has_trade(svc, account_id, trade_uid):\n    # cheap pre-check when no concurrent writers exist\n    existing = svc.query_trades(account_id=account_id, page=1, page_size=1)\n    # authoritative check is the service conflict; prefer try/except below\n    return False","typeGuard":"def has_stable_trade_uid(trade_uid: str | None) -> bool:\n    \"\"\"True when the uid is safe to use for idempotency.\"\"\"\n    return bool(trade_uid and trade_uid.strip())","tryCatchPattern":"from src.services.portfolio_service import PortfolioConflictError\n\ntry:\n    svc.add_trade(account_id=a, trade_uid=uid, ...)\nexcept PortfolioConflictError:\n    logger.info(\"trade %s already recorded, skipping\", uid)  # idempotent success","preventionTips":["Derive trade_uid deterministically from the broker reference","Treat code=conflict as success in importers (skip and continue)","Disable double-submit in UIs; dedupe in-flight requests","Keep uids unique per account but stable across retries"],"tags":["portfolio","conflict","idempotency","trade"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}