Files
inkreach-official-website/data-cleaning/README.md
T

203 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 商品数据清洗(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 几乎全部为 5positionId 全部为空;
国家字段与名称里的国家前缀一致率 238/240(仅 `西班牙直发(...)` 两条归入了「西班牙」)。
### 3. 人工操作流程(脚本要模拟的完整动作)
后台右侧树有「仅未配置」筛选按钮,人工实际是**两步操作**:
**第一步:配置(右→左创建)**
1. 右侧找到未配置的原产品,点击「配置」
2. 弹窗显示:原产品名(**纯文本,不可编辑**,[L1716](../apps/admin/src/views/goods/GoodsView.vue#L1716))、预览图片(可改但默认回填)
3. 人工选择三项:**国家、分类、标签**(名称在此步不能改)
4. 确认提交 → `POST /goods`,goodName 直接传原产品完整原名([L403](../apps/admin/src/views/goods/GoodsView.vue#L403)
**第二步:编辑改名(左侧已有记录上改)**
1. 左侧找到刚配置的商品,点击「编辑」
2. 编辑弹窗有 `el-input` 绑定 goodName[L1821](../apps/admin/src/views/goods/GoodsView.vue#L1821)),这里才能改名
3. 保存 → `PATCH /goods/:id`[L690](../apps/admin/src/views/goods/GoodsView.vue#L690)
因此脚本的**实际操作序列**是:先 `POST /goods`(用原名创建)→ 再 `PATCH /goods/:id`(改名)
弹窗字段与 API 参数对应:
| 步骤 | 弹窗字段 | API 参数 | 说明 |
|------|----------|----------|------|
| 配置 | 国家 | `countryId` | 必填 |
| 配置 | 分类 | `categoryId` | 必填 |
| 配置 | 标签 | `tagIds[]` | 写入 good_tags 中间表 |
| 配置 | 预览图片 | `goodImage` | 默认回填 origin_good.goodImage |
| 配置 | 名称 | `goodName` | **不可编辑**,自动传原名 |
| 编辑 | 名称 | `goodName` | **这一步才能改**,调 PATCH |
| 编辑 | 其余字段 | 同上 | 也可在编辑时调整 |
### 4. 定稿转换规则
> 以下规则已确认,脚本按此执行。
#### 4.1 整体策略
- **方案:API 驱动**(不导出/导入 DB,避免 BigInt 自增序列和外键约束问题)
- 脚本调用 `POST /goods` 逐条配置,走现有业务逻辑(校验、事务、标签关联全部由后端处理)
- 幂等:已存在的 (originGoodId, countryId) 组合跳过
- scope:仅处理**未配置**的原产品(configuredCount === 0),已配置的不动
#### 4.2 名称解析与改名规则
原产品名格式:`国家(物流备注)品名-SKU-工艺位置[-仓库名]`
**改名规则**:保留到 `-` 分隔的第 2 段,用空格连接,丢弃后续段(工艺、仓库)。
```
输入: 美国(包邮)180g纯棉T恤成人款-DG001-单面印花
解析: 品名="180g纯棉T恤成人款" SKU="DG001" 工艺="单面印花"(丢弃)
输出: goodName = "180g纯棉T恤成人款 DG001"
```
- 仓库名后缀(第 4 段,如「美西洛杉矶一仓」)直接丢弃
- 异常名称(缺国家前缀、半角括号等)单独输出到报告,不自动处理
#### 4.3 国家
- 从名称第一个 `` 之前提取国家文本
- 通过 `GET /countries` 拿到 countries 表,按 `countryName` 精确匹配 → `countryId`
- 特殊映射:「西班牙直发」→ 匹配「西班牙」
- 匹配不到的 → 输出到报告,不自动处理
#### 4.4 分类(品类)
- 右侧树已经按 categories 树分组(通过 `origin_goods.sds_category_id``categories.sds_category_id` 桥接)
- 同一原产品的分类就是它在右侧树中所挂的分类节点,直接取该节点的 `categoryId`
- 未分类的(sdsCategoryId 无匹配)→ 输出到报告
#### 4.5 标签(3 组,从名称解析)
标签体系(3 组 7 个):
| 分组 | 标签 | id | 解析规则 |
|------|------|----|----------|
| 物流渠道 | 包邮 / 不包邮 | 30 / 31 | 括号内含「包邮」→ 包邮(30),否则 → 不包邮(31) |
| 印刷位置 | 单面印 / 双面印 | 34 / 35 | 工艺段含「单面」→ 单面印(34),含「双面」→ 双面印(35) |
| 印刷工艺 | 烫画 / 直喷 / 不打印 | 32 / 33 / 42 | 工艺段含「直喷」→ 直喷(33);含「不打印」或「光板」→ 不打印(42);其余默认 → 烫画(32) |
工艺段 = `-` 分段的最后一段(去仓库段后),如 `单面印花``直喷双面``不打印``烫画`
解析示例:
```
美国(不包邮)180g纯棉T恤成人款-DG001-单面印花
→ 物流: 不包邮(31), 印刷位置: 单面印(34), 印刷工艺: 烫画(32)
美国(不包邮)207G重磅纯棉T恤-C1717-直喷双面
→ 物流: 不包邮(31), 印刷位置: 双面印(35), 印刷工艺: 直喷(33)
美国(不包邮光板)180g纯棉T恤成人款-DG001-不打印
→ 物流: 不包邮(31), 印刷位置: 跳过(工艺段=不打印无法判断单双面), 印刷工艺: 不打印(42)
```
#### 4.6 其他字段
- `goodImage`:直接用 origin_good.goodImage,不改
- `goodPriority`:默认 5(与现有 236/240 条一致)
- `positionId`:不填(与现有 240/240 条一致)
---
## 执行流水线(API 驱动方案)
```
① 拉取只读数据
调 GET /origin-goods/tree(含 configuredCount)、/tags、/categories、/countries
存到 runs/<date>/snapshot/
② 本地解析脚本 sync-plan.mjs
读取快照 → 筛选未配置 → 逐条解析名称 → 生成待创建指令列表
输出: runs/<date>/plan.json(每条包含 originGoodId/countryId/categoryId/goodName/tagIds/goodImage
+ runs/<date>/plan-report.md(人工检查点:共 N 条待创建、X 条异常需人工确认)
③ 人工审查 plan.json 和 plan-report.md
④ 执行脚本 sync-apply.mjs
读取 plan.json → 逐条 POST /goods(带 JWT,自动刷新 token
每条打印 +/skip/error;失败自动重试 3 次;生成 apply-report.md
⑤ 验证:调 /origin-goods/tree 确认未配置数归零,调 /goods 抽查新记录
```
原则:
- 不导出/导入数据库,全部通过 API 操作
- ② 和 ④ 分离:先生成计划供人工审查,确认后再执行
- 每步产物存 runs/(已 gitignore
- token 30 分钟过期,脚本内自动用 /auth/login 刷新
## 目录内容规划
| 文件 | 状态 | 说明 |
|------|------|------|
| `sync-rules.config.mjs` | 待创建 | 国家映射/异常处理等配置项(解析规则已定稿在上文) |
| `sync-plan.mjs` | 待创建 | 拉取 + 解析 + 生成 plan.json |
| `sync-apply.mjs` | 待创建 | 读取 plan.json + 逐条 POST /goods |
| `runs/` | 已创建 | 调查快照 survey-2026-08-27/ 已存在;后续每次运行产物按日期归档 |
详细实施计划见 [plans/feature/goods-data-cleaning-feature.md](../plans/feature/goods-data-cleaning-feature.md)。