# 解析去运行时化：同步纯镜像 + 整理脚本化 + 矩阵自动重算

## 背景 / 决策（用户拍板，2026-08-30）

现行"同步即解析"模式系统性依赖上游链接命名（8 处解析点），上游改名会倒灌污染
已正确的标签/矩阵（族重算按当前名称重派生标签），缺关键字时静默默认兜底。

最终架构：

1. **同步 = 纯镜像**：SDS 给什么存什么，零解析、零归族
2. **解析/派生/归族 = 显式脚本（OrganizeService）**：跑才生效，可重跑可审查；
   触发方式 = CLI + 后台"整理"按钮（都是人工显式动作）
3. **价格矩阵 = 归族后自动计算**：成员变化/详情同步/标签调整自动触发重算；
   输入只有结构化数据（成员变体 + 标签 + 覆盖价），**不碰名称**；人工可改价（覆盖表保留）

## 变更清单

### A. sync.service 纯净化
- 删 `parseOriginName` 解析列写入（skuCode/logisticsLabel/craftLabel/warehouseLabel
  不再随同步写入；存量数据保留，缺列由整理脚本回填）
- 删同步时自动归族（同分类/同名已配置族自动挂接的整段逻辑）
- 保留：详情同步后 enqueue 族重算（重算纯净化后是无解析的聚合）

### B. family-recompute.service 纯净化
- `recomputeFamily` 尾部不再调用 `syncFamilyTags`（标签永不随重算变）
- `memberMatrixCombos` 删名称兜底：SDS 成员维度只认标签，无标签不进矩阵；
  CUSTOM 成员继续用显式 labels（管理员填写，非上游）
- 标签派生逻辑（deriveLinkTagNames 应用 + ensureDerivedTagMap + goods 镜像中的
  派生部分）迁出到 OrganizeService；`mirrorLinkTagsToGoods`（纯镜像聚合）保留

### C. 新增 OrganizeService（product-families/organize.service.ts）
`organize(): { labelsParsed, tagsDerived, familiesCreated, familiesRecomputed, unparsable }`
1. 回填缺失的四个解析列（幂等，只补 null）
2. 派生标签：非人工接管（tagsManual=false）SDS 链接按名称刷新自动标签，
   人工接管的不动；随后镜像到名下商品
3. `autoGroup` 建族（复用 ProductFamiliesService.autoGroup）
4. 全量族重算
`deriveTagsForOg(ogId)` 供 resetTags（恢复自动，显式人工动作）复用

### D. 入口
- CLI：`prisma/backfill-product-families.ts` 改薄壳调 OrganizeService；
  package.json 加 `organize` 命令（与 backfill 等价）
- 端点：`POST /product-families/organize`（JWT）
- admin：商品页工具栏「整理」按钮 → 调端点 → toast 结果

### E. 测试（TDD）
- organize.service.spec：派生/幂等/人工接管保护/镜像
- family-recompute.spec：无标签成员不进矩阵；重算不改标签
- sync.spec：同步不写解析列、不归族
- public / product-families / family-block spec：fixture 补标签（原靠重算时名称兜底）
- origin-goods resetTags：走 organize 派生

### F. 文档
product-center.md 架构段落重写（同步/整理/重算三层）、structs.md 端点、README 命令

## 明确不做
- 不删解析器/派生规则本身（脚本要用，规则不变）
- 不动人工接管/手动并族/覆盖价机制
- normalizeGoodName（管理员输入边界质检）保留，随本分支一并提交

## 风险
- 存量已正确标签不再被重派生覆盖（这是目标行为）；但同步改名后名称与标签可能
  不同步——由整理脚本显式刷新（人工决定何时跑）
- 新同步链接在整理前无标签无族（不进矩阵不进公开）——整理后齐全
