迭代15: 委托归属候选纳入近期清仓持仓(R-017)

- 交易记录 tab 归属候选只回当前持仓 → 清仓后当日委托失去下拉锚点无法补关联
  (2026-09-07 大连热电实际发生: 盘中归属后被清为未关联,下拉已无做T候选)
- SqliteStore.getAttributionCandidates: 候选 = 该 code 当前持仓 + 近 7 天清仓的持仓
  (closed 标记 + closedAt,排序 当前持仓在前 → 清仓行 closed_at DESC)
- AttributionSelect: 下拉 value 改 holdingId 键控(同策略多轮持仓不撞值)、
  清仓选项「(已清仓 MM-DD)」后缀、已归属但候选缺失显示「当前归属」占位
- 回归: test-r017-candidates 12/12 + 存量回归全绿
This commit is contained in:
2026-09-07 18:15:20 +08:00
parent 51c70c48fb
commit 22ec732607
10 changed files with 278 additions and 17 deletions
@@ -0,0 +1,29 @@
# 技术实现方案:15-归属候选纳入清仓持仓
> 迭代编号:15 依据:PLAN-016 + R-017
## 1. 存储层(getAttributionCandidates 扩展,门面透传不变)
```sql
SELECT holding_id, strategy_id, shares, closed_at FROM strategy_holdings
WHERE code=? AND (closed_at IS NULL OR closed_at >= ?) -- ? = Date.now() - 7d
ORDER BY (closed_at IS NULL) DESC, shares DESC, closed_at DESC
```
- 返回行新增 closed: boolean / closedAt: number|null(消费方:api strategies.js 同方法直接受益——注意 attribution-candidates 端点在 trades.js,调 DataStore.getTradeAttributionCandidates 透传,零改动);
- 窗口常量 7 * 86400000(与 R-016 默认范围一致)。
## 2. UITradeRecordsTab.jsx AttributionSelect 重构)
- 锚点键:currentKey = order.holdingId != null ? String(order.holdingId) : ''holdingId 是 R-009 定稿的归属锚点;strategyId 在同策略多轮持仓时会撞值);
- handleSelect:按 String(holdingId) 找候选 → setAttribution(orderId, code, cand.strategyId, cand.holdingId)__unassigned__ → 双 null
- 占位选项:已归属但候选缺失(清仓超 7 天)→ 「当前归属({strategyId})」,value=currentKey,防 select 悬空显示第一项误导;
- 清仓候选文本后缀「(已清仓 MM-DD)」(fmtClosed 本地 helper);select maxWidth 120→150。
## 3. 数据修复记录(一次性,非功能)
2026-09-07 大连热电卖出委托 645001623 经 live set-attribution 通道回填 strategy_id=manual-t / holding_id=15curl 实测 ok:trueorders/by-holding 回读确认)。当日归属曾被清为未关联的原因:候选下拉无做T项时老师误触「未关联」入口——本迭代修复后此路径消除。
## 4. 验证链
test-r017-candidates.mjs 12 断言 + typecheck + build + 存量回归(r016 19 / position-sync 35 / r013 21 / quote-sync 27)。