# 商品数据清洗(Goods Data Cleaning) 本目录存放商品数据清洗相关的全部脚本与规则文件。清洗目标:把右侧 SDS 原产品库 (`origin_goods`)按规则批量配置为左侧官网商品(`goods`),替代后台手动逐个点击。 ## 转换规则调查(2026-08-27,基于生产环境只读数据) > 数据来源:`https://official.inkreach.cc/api` 生产接口(JWT 只读拉取): > `/origin-goods?page=1..3&pageSize=200`(共 552 条)、`/goods?page=1..2&pageSize=200`(共 240 条)、 > `/tags`、`/categories`、`/countries`。未做任何写操作。 > 原始 JSON 快照存于 `runs/survey-2026-08-27/`(已加入 .gitignore,不入库)。 ### 1. 右侧原产品(origin_goods)命名结构 右侧只有**一个字符串** `goodName`,整体模式: ``` 国家(物流备注)品名-SKU-工艺位置[-仓库名] ↑ ↑ ↑ ↑ ↑ | | | | └─ 可选第4段:美西洛杉矶一仓 / 美中亚特兰大仓 等 | | | └─ 单面印花 / 双面印花 / 直喷双面 / 直喷单面 / 不打印 ... | | └─ SKU 编码(字母+数字,如 DG001 / C1717 / KRHM003) | └─ 包邮 / 不包邮 / DHL包邮*运费结算订单时支付 / 不包邮光板 等变体 └─ 美国/德国/韩国...(全角括号为主,少量半角混用) ``` 统计(552 条): - **541 条**匹配 `国家(备注)其余` 模式;**11 条异常**: - 4 条自定义商品(`Goods Origin ...`,source=CUSTOM) - 2 条缺国家前缀:`(不包邮)230g水洗炒雪花T恤-双面印花` / `(不包邮)180g纯棉T恤成人款-...` - 1 条用空格代替括号:`法国 180g纯棉T恤-FRTM001-双面印花` - 2 条半角左括号混用:`美国(不包邮)女士高弹中长款瑜伽运动裤-JSD004-...` - 2 条同上变体 - 「备注)之后」按 `-` 分段:**387 条 = 3 段**(品名-SKU-工艺)、**154 条 = 4 段**(品名-SKU-工艺-仓库) - 物流备注出现的变体形态(示例):`包邮`、`不包邮`、`DHL包邮`、`DHL包邮*运费结算订单时支付`、`包邮*运费订单结算时支付`、`不包邮光板` - 国家前缀出现过的值:美国、德国、英国、意大利、西班牙、西班牙直发、韩国、日本、加拿大、墨西哥、巴西、波兰、澳大利亚 ### 2. 左侧已配置商品(goods)现状 —— 人工配置的实际结果 240 条 goods 记录,覆盖 **209 个**不同 origin_good_id(**343 个 origin 尚未配置**)。 **没有系统级拆分逻辑**:前端 [GoodsView.vue](../apps/admin/src/views/goods/GoodsView.vue) 提交的是完整原名, 后端 [goods.service.ts](../apps/api/src/goods/goods.service.ts) create/batchCreate 也原样入库。 因此左侧名称与右端的差异(113/240 条被改过)全部是**人工在编辑弹窗里改的**,且存在多种风格并存。 改名风格分布(113 条改名的归类): | 风格 | 数量 | 示例 | |------|------|------| | A. 全拆:去前缀去括号,品名+空格+SKU,丢弃工艺段 | 27 | `韩国(包邮)200g纯棉T恤-KRTM001-单面印花` → `200g纯棉T恤 KRTM001` | | B. 半改:仅把长备注缩成「X包邮」,其余原样保留 | 86 | `德国(DHL包邮*运费结算订单时支付)230g水洗T恤-DETM002-单面印花` → `德国(DHL包邮)230g水洗T恤-DETM002-单面印花` | | R0. 保持原名不动 | 127 | 多为后期配置(2026-06 后期 ~ 07),未加工 | 标签体系(生产库现有 3 组 7 个标签)与左侧使用情况: | 分组 | 标签(id) | 左侧使用次数 | |------|----------|--------------| | 物流渠道 | 包邮(30) / 不包邮(31) | 87 / 153 ← 基本每个商品都挂了物流标签 | | 印刷位置 | 双面印(35) / 单面印(34) | 14 / 14 ← 只有少数挂了 | | 印刷工艺 | 烫画(32) / 直喷(33) / 不打印(42) | 11 / 3 / 1 | 其他字段:goodPriority 几乎全部为 5;positionId 全部为空; 国家字段与名称里的国家前缀一致率 238/240(仅 `西班牙直发(...)` 两条归入了「西班牙」)。 ### 3. 从字符串解析出的隐含规则(草稿,待确认) 一条原产品字符串可以机械解析出以下信息(541/552 可完全机器解析): | 解析项 | 来源 | 对应目标字段 | |--------|------|--------------| | 国家 | 第一个 `(` 之前文本(需处理:空格分隔、半角括号、「西班牙直发→西班牙」映射) | `countryId` | | 物流渠道 | 括号内含「包邮」与否 → 包邮 / 不包邮 | tag: 物流渠道 | | 工艺/印刷位置 | 最后一段的 关键词:单面印花→单面印、双面印花→双面印、直喷→直喷、烫画→烫画、光板/不打印→不打印 | tags: 印刷位置 + 印刷工艺 | | 展示名 | 目标形态(用户口径):`品名 SKU`(最后一个 `-` 连接改为空格连接,丢弃工艺/仓库段) | `goodName` | ### 4. 待确认问题(规则定稿前必须回答) 1. 展示名采用哪种?A 全拆(`200g纯棉T恤 KRTM001`,推荐—— majority 且官网展示干净)还是 B 保留国家括号? 2. 同一基础商品的「单面印花」「双面印花」是拆成两个 goods(现状),还是合并为一个 good 并用标签区分? (注意 A 全拆后两者 goodName 完全相同,如 ITTM001 已出现重名两条) 3. 历史 R0 的 127 条保持原名的记录是否要统一重命名? 4. 未配置的 343 个 origin 是本次要全部自动配置的目标吗?(其中可能有不应上架的商品) 5. 仓库名后缀(第 4 段)只做清洗忽略即可,还是需要保留到某个字段? 6. 国家中文名 → countries 表的映射以 `countryName` 精确匹配为准?缺失国家(如法国 FRTM001 对应的国家若不存在)如何处理? --- ## 执行流水线 ``` [生产库] │ ① 备份(pg_dump 全量,安全网) ▼ ② apps/api/scripts/export-data.mjs 导出 JSON(现有脚本) ▼ ③ sync-transform.mjs 转换脚本 + sync-rules.config.mjs 规则文件(本目录,待创建) 读取导出 JSON → 按规则加工 → 输出待导入 JSON + 变更摘要报告(人工检查点) ▼ ④ apps/api/scripts/import-data.mjs 导入生产库(现有脚本) ▼ ⑤ 验证:行数对比 / 抽查商品配置 / 官网公开 API 抽查 ``` 原则:三步式独立执行,每步可检查、可回滚;规则只改 `sync-rules.config.mjs`,不改代码; 导入前必须有全量备份;转换脚本为纯函数式(JSON in → JSON out),不直接连接数据库。 ## 目录内容规划 | 文件 | 状态 | 说明 | |------|------|------| | `sync-rules.config.mjs` | 待创建 | 同步规则文件(品类映射 / 国家分配 / 标签分配 / 优先级策略),规则未定,先占位 | | `sync-transform.mjs` | 待创建 | 数据转换脚本 | | `runs/` | 待创建 | 每次执行的导出、转换产物与变更摘要报告(按日期归档);已含 `survey-2026-08-27/` 调查快照(gitignore) | 详细实施计划见 [plans/feature/goods-data-cleaning-feature.md](../plans/feature/goods-data-cleaning-feature.md)。