feat(product-commerce): 款式主键统一 style_id 并下线评价采集
采集端与 data-hub 统一用 style_id (= dim_style.id) 关联款式,style_name 降级为显示冗余。
款式主键:
- 14 张采集表加 style_id BIGINT + style_match_method + *_style_id_idx 索引
- 一律逻辑外键:不建 FOREIGN KEY,早期 10 个 *_style_id_fk 在 schema.sql 里幂等 DROP
- loader 下发 dim_style.id;db 层 attach_style_ids / resolve_style_ids_by_product 兜底
- 新增 backfill_style_id.py 三段回填(款名 / 日报按商品 ID / 广告表按 spu_id)
- 新增视图 v_style_id_coverage、v_style_unmatched_product、v_style_unmatched_source
- 实库补录 199 个商品 ID 到 67 款 spus;日报 style_id 覆盖 49.1% -> 79.0%,
tm 广告 99.4% / jd 92.4% / dy 83.2%,其余 11 张 100%
评价采集下线:
- product_reviews 归档为 product_reviews_archived_20261101
- 差评改由 shop.{tmall,jd,dy}_negative_reviews.daily 采集,消费方读 v_negative_reviews
- 删除 import_product_reviews.py / orchestrate_review_collection.py 及相关命令与绑定
- dy_product_scraping 默认流程摘除评论采集,保留 --reviews-only
天猫广告:
- 迁移到独立表 tm_product_ad_daily_metrics,不再写 product_daily_metrics.raw_data
同批附带:
- 光合上传支持天猫新店 new_bag(仅黑曜石系列)
- 京东商智双月日历排除 -diff-month 补位格
- 万相主体搜索词覆盖 STYLE_SEARCH_KEYWORDS
- ERP 部分编码失败时用已成功编码销量兜底,避免整款归零
This commit is contained in:
1 parent
622a609800
commit
6f1c193f4b
50 files changed
+3301
-1150
No files matched your search
+128
-11
@@ -43,7 +43,7 @@ uv run gyxx scripts run <command_id> --date 2026-08-01 --execute
|
||||
| 京东自营品牌单日回采 | `shop.jd_self_operated.collect_brand` | `--date` 自动成为起止日期 |
|
||||
| 商品历史补采 | `product.backfill.run` | `--date` 自动成为 `--from/--to` |
|
||||
| 商品评价补采 | `product.review.orchestrate` | `--date` 自动成为目标日期 |
|
||||
| 天猫旗舰店和箱包店视频上传 | `product.video_upload.run` | 业务日期用于运行审计;默认顺序扫描两店,由 PostgreSQL 附件级账本防重 |
|
||||
| 天猫旗舰店、箱包店与天猫新店视频上传 | `product.video_upload.run` | 业务日期用于运行审计;`--store all` 顺序扫描三家,由 PostgreSQL 附件级账本防重 |
|
||||
| 京东旗舰店视频上传 | `product.jd_video_upload.run` | 业务日期只用于运行审计;从固定凌云air记录锚点按飞书视图顺序扫描,日期可为空 |
|
||||
| 采购单更新 | `supply.workflow.run` | 自动选择 `mcp-run purchase-order-update` |
|
||||
|
||||
@@ -51,16 +51,16 @@ uv run gyxx scripts run <command_id> --date 2026-08-01 --execute
|
||||
|
||||
### 天猫视频上传工作流
|
||||
|
||||
天猫视频上传注册为幂等定时工作流 `product.video_upload`,按 `config/schedules.json` 以 2026-09-04 为起始日每 3 天 14:00(Asia/Shanghai)执行;前端仍可手动选择“光影行星旗舰店 / 光影行星鑫华达专卖店:龙虾仔”并立即运行,也可填写天猫话题完整名称或关键词。正式执行只处理所选店铺,话题搜索结果按包含关系选中。默认 dry-run 只生成运行记录并跳过浏览器节点;确认飞书待传记录、对应淘宝光合店铺的登录态和 PostgreSQL 后,再显式正式执行:
|
||||
天猫视频上传注册为幂等定时工作流 `product.video_upload`,按 `config/schedules.json` 以 2026-09-04 为起始日每 3 天 14:00(Asia/Shanghai)执行;前端仍可手动选择“光影行星旗舰店 / 光影行星鑫华达专卖店:龙虾仔 / 天猫新店(gyxx箱包旗舰店:龙虾仔)”并立即运行,也可填写天猫话题完整名称或关键词。正式执行只处理所选店铺,话题搜索结果按包含关系选中。默认 dry-run 只生成运行记录并跳过浏览器节点;确认飞书待传记录、对应淘宝光合店铺的登录态和 PostgreSQL 后,再显式正式执行:
|
||||
|
||||
```bash
|
||||
uv run gyxx run product.video_upload --date 2026-08-07
|
||||
uv run gyxx run product.video_upload --date 2026-08-07 --execute
|
||||
```
|
||||
|
||||
前端把店铺选择作为受限运行参数传给脚本,只允许 `flagship` 或 `luggage`。脚本先读取飞书并应用所选店铺路由,再写入/读取附件级账本;只有存在可领取的未上传附件时才启动有头 Chrome、登录对应账号并上传。旗舰店只选择“天猫上传”未勾选且有附件的光影行星男/女记录;箱包店只选择女性、全部款式命中箱包店白名单且有附件的记录。两店分别使用 `GUANGHE_USERNAME` / `GUANGHE_PASSWORD` 和 `GUANGHE_LUGGAGE_USERNAME` / `GUANGHE_LUGGAGE_PASSWORD`,箱包店当前账号为 `光影行星鑫华达专卖店:龙虾仔`,密码配置保持不变,并保留独立浏览器 Profile。发生登录或页面错误时保留窗口供人工定位,排查完成后手动关闭。
|
||||
前端把店铺选择作为受限运行参数传给脚本,只允许 `flagship`、`luggage` 或 `new_bag`。脚本先读取飞书并应用所选店铺路由,再写入/读取附件级账本;只有存在可领取的未上传附件时才启动有头 Chrome、登录对应账号并上传。旗舰店只选择“天猫上传”未勾选且有附件的光影行星男/女记录;箱包店只选择女性、全部款式命中箱包店白名单且有附件的记录;天猫新店不区分性别,只要求一条记录的**全部**「代拍产品」款式都属黑曜石系列(关键词 `黑曜石`,如黑曜石托特、黑曜石吐司、黑曜石电脑包、黑曜石饺子),组合款里掺了其它系列整条记录都不进新店,视频源与其它店铺为同一张飞书表。三店分别使用 `GUANGHE_USERNAME` / `GUANGHE_PASSWORD`、`GUANGHE_LUGGAGE_USERNAME` / `GUANGHE_LUGGAGE_PASSWORD` 和 `GUANGHE_NEW_BAG_USERNAME` / `GUANGHE_NEW_BAG_PASSWORD`,箱包店当前账号为 `光影行星鑫华达专卖店:龙虾仔`,天猫新店当前账号为 `gyxx箱包旗舰店:龙虾仔`,密码配置保持不变,各自保留独立浏览器 Profile(`-luggage` / `-new-bag`)。发生登录或页面错误时保留窗口供人工定位,排查完成后手动关闭。
|
||||
|
||||
每次正式扫描都会把视频附件写入 PostgreSQL 附件级发布账本,防重身份由视频 `file_token + 目标账号 + 飞书记录` 构成。账本状态为 `published` 的附件在下次执行时自动跳过,因此同一业务日期可以安全重跑并发现新增附件。旗舰店仍只在一条飞书记录的全部附件发布成功后回写“天猫上传”;箱包店不借用该勾选状态,是否已上传完全以自己的目标账号账本为准。
|
||||
每次正式扫描都会把视频附件写入 PostgreSQL 附件级发布账本,防重身份由视频 `file_token + 目标账号 + 飞书记录` 构成。账本状态为 `published` 的附件在下次执行时自动跳过,因此同一业务日期可以安全重跑并发现新增附件。旗舰店仍只在一条飞书记录的全部附件发布成功后回写“天猫上传”;箱包店和天猫新店不借用该勾选状态,是否已上传完全以自己的目标账号账本(`guanghe-luggage` / `guanghe-new-bag`)为准,同一视频可以分别发到多家店铺。
|
||||
|
||||
首次正式执行会读取 `config/tmall-video-publication-backfill.json`,将 2026-07-26 箱包店旧日志中可按 `record_id + file_token + 文件名` 再次核验的 25 个明确成功附件回填为 `published`。配置与当前飞书任一身份不一致都会停止,不会静默猜测。该回填不依赖部署机保留旧 `var/` 日志文件;证据路径与成功行号已固化在配置中供审计。
|
||||
|
||||
@@ -103,11 +103,17 @@ uv run gyxx scripts run product.jd_video_upload.run --date 2026-08-07 --arg=--re
|
||||
|
||||
唯一工作流 ID 是 `product.ecommerce_costs.daily`,每天 10:00 由项目内 Python 调度器运行,
|
||||
业务日期为前一天;天猫、京东、抖音三个下载分支并行启动,各自完成下载后再导入。天猫流程先进入万相台商品报表选择昨日,提交下载任务;任务在下载列表显示“生成成功”
|
||||
后下载 ZIP、做路径安全校验并解压 CSV,随后将商品级广告指标幂等写入
|
||||
`product_daily_metrics.raw_data.tm_ad_metrics`。商品款式先按 `dim_style.tm_spus` 的商品 ID 匹配,
|
||||
匹配不到再按商品名称做精确包含匹配,并在节点内保留 `style_name` 与
|
||||
`style_match_method`(`product_id`/`product_name`/`ambiguous`/`unmatched`)。它不会覆盖同一商品日期
|
||||
已有的访客、成交等经营日报字段;data-hub 产品生命进程从该节点读取广告成交金额和花费并计算投产比、电商费比。
|
||||
后下载 ZIP、做路径安全校验并解压 CSV,随后将商品级广告指标幂等写入独立表
|
||||
`tm_product_ad_daily_metrics`(每日每商品一行,主键 `(stat_date, product_id)`)。花费、直接/间接/归因
|
||||
成交金额和投产比走标量列,报表其余指标(展现、点击、点击率、转化率、预售成交等)原样存在 `metrics`
|
||||
JSONB。商品款式先按 `dim_style.tm_spus` 的商品 ID 匹配,匹配不到再按商品名称做精确包含匹配,结果
|
||||
写进 `style_name` 与 `style_match_method`(`product_id`/`product_name`/`ambiguous`/`unmatched`)。
|
||||
天猫广告数据**不再写入** `product_daily_metrics.raw_data.tm_ad_metrics`,投放与经营日报分表,经营数据
|
||||
重新同步不会覆盖投放数据;data-hub 产品生命进程从新表读取花费和广告归因成交金额并计算投产比、电商费比。
|
||||
|
||||
历史数据迁移(2026-11-01 一次性执行):`src/gyxx_flow/modules/product_commerce/scripts/
|
||||
migrate_tm_ad_metrics_to_table.py`,默认 dry-run,`--execute` 先备份旧节点到运行时 TMP_ROOT 再回填,
|
||||
校验行数与花费合计一致后才允许 `--clear-source` 清除 `raw_data` 里的旧节点。
|
||||
|
||||
正式服务必须注入 `WANXIANG_ACCOUNT`、`WANXIANG_PASSWORD`、`GYXX_DATA_ROOT` 和
|
||||
`GYXX_POSTGRES_DSN`。默认浏览器 Profile 是
|
||||
@@ -139,8 +145,9 @@ uv run gyxx scripts run product.import.tmall_ads --date 2026-08-17 --execute
|
||||
1. 检查 `gyxx schedule status`、服务日志和对应 `run_id`,确认失败发生在申请、下载、解压还是入库。
|
||||
2. 检查同一 Profile 是否仍能打开万相台商品报表;登录失效时在有头命令中人工完成验证。
|
||||
3. 检查日期目录中的 `manifest.json`、CSV 表头和 `日期/主体ID/主体名称`;ZIP 存在不代表数据库已导入。
|
||||
4. 检查 PostgreSQL 的 `product_daily_metrics` 是否存在 `platform='tm'`、目标日期和商品 ID,且
|
||||
`raw_data->'tm_ad_metrics'` 已更新;确认经营日报其他 JSON 节点仍保留。
|
||||
4. 检查 PostgreSQL 的 `tm_product_ad_daily_metrics` 是否存在目标日期与商品 ID 的行,`spend`、
|
||||
`attributed_deal_amount` 是否为目标值,`style_name` 是否已匹配;确认 `product_daily_metrics`
|
||||
同日期经营数据(访客、成交)仍完整。
|
||||
5. 只有在外部状态核对清楚后才按同一业务日期重跑,不能用换日期或重复申请掩盖下载结果不明。
|
||||
|
||||
## 调度操作
|
||||
@@ -385,6 +392,116 @@ ORDER BY style_name;
|
||||
必须把它拆成独立系列(单独的 `series_name` + 锚点标记)并把地址写在自己那行;只写地址
|
||||
而不拆系列的话,聚合会把它并入原系列,只在日志里留一条告警,不会真的生效。
|
||||
|
||||
## 款式主键统一(style_id = dim_style.id,2026-11-01)
|
||||
|
||||
采集端不再只用 `style_name` 关联款式。**唯一关联键是 `style_id`**(= `dim_style.id`,
|
||||
与 data-hub 的 22 张表同一套主键),`style_name` 降级为显示冗余。这样在 dim_style
|
||||
改款名时,历史指标表不会变成孤儿。
|
||||
|
||||
### 数据从哪里来
|
||||
|
||||
| 环节 | 机制 |
|
||||
|---|---|
|
||||
| loader 下发 | `merge_lifecycle_style_records()` 输出 `style_id` + `style_match_method`,经 `style_config_loader` 进入每条款式配置 |
|
||||
| db 层兜底 | `db.attach_style_ids()`(按款名)/ `db.resolve_style_ids_by_product()`(按平台商品 ID)在 upsert 内补齐 |
|
||||
| 历史回填 | `backfill_style_id.py` |
|
||||
|
||||
### 回填与验收
|
||||
|
||||
```powershell
|
||||
# 只报数,不写库
|
||||
.venv\Scripts\python.exe backfill_style_id.py --dry-run
|
||||
# 执行(幂等,只补 style_id IS NULL 的行)
|
||||
.venv\Scripts\python.exe backfill_style_id.py
|
||||
# 只补款名表,跳过按商品 ID 的两步
|
||||
.venv\Scripts\python.exe backfill_style_id.py --skip-product-table --skip-ad-tables
|
||||
```
|
||||
|
||||
回填分三段,缺一段覆盖率就上不去:
|
||||
|
||||
| 段 | 表 | 依据 |
|
||||
|---|---|---|
|
||||
| 按款名 | 13 张(`daily_style_metrics` / `weekly_style_metrics` / `erp_all_shop_style_daily_metrics` 等) | `t.style_name = dim_style.style_name` |
|
||||
| 按商品 ID | `product_daily_metrics` | `platform + product_id` 反查 `*_spus` |
|
||||
| 按商品 ID(二次) | 三张广告表 | 同上;**顺带把空的 `style_name` 补成 dim_style 的权威款名** |
|
||||
|
||||
广告表必须走第三段:平台的报表不给款式名,`jd_product_ad_daily_metrics` 有 27%、
|
||||
`dy_product_ad_daily_metrics` 有 40% 的行 `style_name` 为空,只按款名回填完全无效。
|
||||
补完后 data-hub 现有按款名的 join 立刻多认回一批行,不必等主键切换。
|
||||
|
||||
```sql
|
||||
-- 各表回填进度;除 product_daily_metrics 与三张广告表外都应 100%
|
||||
SELECT * FROM v_style_id_coverage ORDER BY tbl;
|
||||
-- 未建档的平台商品 ID,仅统计经营日报口径
|
||||
SELECT * FROM v_style_unmatched_product ORDER BY platform, rows DESC;
|
||||
-- 同上,但把三张广告表也算进来(纯投流商品只在这里出现)
|
||||
SELECT * FROM v_style_unmatched_source ORDER BY platform, rows DESC;
|
||||
```
|
||||
|
||||
`style_match_method` 取值:`dim_style`(按款名精确)、`product_id`(按平台商品 ID)、
|
||||
`product_name`(按商品名包含)、`unmatched`(确实没建档)。
|
||||
|
||||
### 补 spus 的正确做法
|
||||
|
||||
`product_daily_metrics.product_no` 存的是**款名**(不是 ERP 编码),天猫填充率 100%、
|
||||
京东 97.8%、抖音 0%。补 spus 时按这个优先级,命中率远高于猜商品标题:
|
||||
|
||||
1. `product_no` 精确等于 `dim_style.style_name`;
|
||||
2. `product_no` 与款名互为前后缀(长度差 ≤ 3,如「蓝鹊pro」→「蓝鹊」、「知更大号」→「知更」);
|
||||
3. 商品标题包含完整款名(抖音只能用这一条),并用「命中位置后的版本后缀」排除串款
|
||||
—— 例如标题「盖亚斜挎包**3**」不能归到「盖亚斜挎」;
|
||||
4. 编码与标题互相矛盾时**不要自动写**,列出来人工定(实测有天猫单品编码写「理岩」、
|
||||
标题写「阿波罗 air」,支付额 50 万,猜错代价极大)。
|
||||
|
||||
物料类(数据线、内胆包、补邮费、配件包、礼品袋、定制服务)不该进 `spus`,应留
|
||||
`unmatched`。`dim_style` 里本身建成款的物料(`配件包`、`飞机盒`、`电泵` 等)也不写。
|
||||
|
||||
### 必须知道的边界
|
||||
|
||||
- `product_daily_metrics` 表里**没有款名列**,只能拿 `platform + product_id` 去
|
||||
`tm_spus / jd_spus / dy_spus` 反查。未建档的商品 ID 回填不了,要靠建档页补 `spus`
|
||||
才能涨,不是脚本 bug。
|
||||
- 2026-11-01 补过一轮:`product_daily_metrics` 49.1% → **86.0%(jd)/ 77.3%(tm)/
|
||||
76.7%(dy)**,整体 79.0%;广告表 tm 99.4% / jd 92.4% / dy 83.2%。
|
||||
剩余 259 个商品 ID(dy 107 / jd 52 / tm 100)未建档,清单见 `v_style_unmatched_source`。
|
||||
- **不建物理外键**:14 张表的 `style_id` 都只做**逻辑外键**(与 data-hub 侧一致)。
|
||||
库里没有任何指向 `dim_style(id)` 的 FK 约束,完整性靠写入端(loader 下发 +
|
||||
`attach_style_ids` 兜底)、`backfill_style_id.py --dry-run`、`v_style_id_coverage`
|
||||
三道保证。理由:采集语义是「宁可留 NULL 待回填,也不要整批写入失败」,
|
||||
物理外键会在传错 id 时直接中断整天采集;此外拆系列/合并同款要改数据时,
|
||||
还得先删约束。早期 P4 建过的 10 个 `<表名>_style_id_fk` 已在 schema.sql 里
|
||||
幂等 `DROP CONSTRAINT IF EXISTS` 清掉,老库重跑 schema 即自动回到无 FK 状态。
|
||||
- 主键一律没动:`daily_style_metrics` 仍是 `(metric_date, style_name, platform)`。
|
||||
data-hub 有 6 处按 `style_name` join,改主键收益小、回归面大。
|
||||
|
||||
## 商品评价采集下线(product_reviews,2026-11-01)
|
||||
|
||||
`product_reviews` 已**停止写入并归档**为 `product_reviews_archived_20261101`。
|
||||
原因是 JD 侧 `style_name` 全靠 `sku_id` 反查,而映射表不存在,214 行全部为 NULL,
|
||||
两个消费方都是 `WHERE style_name = ?`,等于入库了读不到。
|
||||
|
||||
差评统一由三个独立工作流采集:`shop.tmall_negative_reviews.daily`、
|
||||
`shop.jd_negative_reviews.daily`、`shop.dy_negative_reviews.daily`。
|
||||
|
||||
### 消费方改读视图
|
||||
|
||||
```sql
|
||||
SELECT platform, comment_date, rating, content, style_id, style_name, style_match_method
|
||||
FROM v_negative_reviews; -- tm + jd + dy 三张差评表 UNION,款式按 ID 优先、商品名兜底
|
||||
```
|
||||
|
||||
改动点:`analyze_style.py:pull_reviews`、`content_marketing/daily_marketing_report.py:_review_context`。
|
||||
|
||||
### 口径变化(必须知道)
|
||||
|
||||
- 数据源**只有差评一档**,不再有好评/中评。风格分析的「评价等级分布」因此只剩差评。
|
||||
- 营销日报的 `sample_size` = 差评条数,`negative_rate` 恒为 1.0;门槛从「至少 5 条评价」
|
||||
改成「至少 1 条差评」(旧门槛在纯差评源上会导致永不产出)。
|
||||
- 覆盖面反而更全:天猫差评 97 条(旧表天猫为 0)、京东 32 条(旧表 8 月断更)。
|
||||
- 若产品侧确实需要好评样本,应把差评采集器扩成「按等级采集」,**不要恢复 product_reviews**。
|
||||
- `dy_product_scraping.py` 默认流程里的评论采集已摘除(只产出无人读取的本地 JSON);
|
||||
需要历史样本时用 `--reviews-only` 显式触发。
|
||||
|
||||
## 业务源码同步
|
||||
|
||||
外部业务源码目录只用于发现更新,GYXX Flow 运行时不导入或挂载这些目录。只读检查:
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
# 款式主键统一 + 评论采集下线 方案
|
||||
|
||||
> 状态:**已实施(2026-11-01)**,见文末「八、实施记录」
|
||||
> 涉及项目:`gyxx-flow`(采集端)、`data-hub`(消费端,共用同一个 PostgreSQL)
|
||||
> 编写日期:2026-11-01
|
||||
|
||||
## 一、三条结论
|
||||
|
||||
1. **`product_reviews` 可以下线**,但不能直接删——它有两个消费方(风格分析、营销日报)要改成读三张差评表。
|
||||
2. **`style_id` 就是 `dim_style.id`**,与 data-hub 现有 `style_id` 同一体系,不会产生第二套 ID。已实测:`erp_product.style_id` 的 135 个值 **135/135** 命中 `dim_style.id`,`own_brand_product.style_id` 31/34 命中(3 条为历史脏数据)。
|
||||
3. **改造入口只有一处**:`DIM_STYLE_RUNTIME_SQL` 早已 `SELECT s.id`,`merge_lifecycle_style_records()` 也已经拿到这个 id——只是被编码进 `source_record_id = "lifecycle_style:<id>"` 字符串里,没有作为独立字段下发给写入脚本。把它提成正式字段,全链路就都有了。
|
||||
|
||||
## 二、现状盘点
|
||||
|
||||
### 2.1 两套引用方式并存
|
||||
|
||||
| 侧 | 关联方式 | 表 |
|
||||
|---|---|---|
|
||||
| data-hub(产品生命、营销中台、客服、内容) | `style_id BIGINT` → `dim_style.id` | `erp_product`、`own_brand_product`、`erp_marketing_*`、`erp_cs_*`、`cmt_*`、`market_product_classification` 等 22 张 |
|
||||
| gyxx-flow(采集端) | `style_name TEXT` | 15 张,见 2.3 |
|
||||
|
||||
### 2.2 实测匹配率(2026-11-01)
|
||||
|
||||
| 表 | 总行数 | 按 style_name 命中 dim_style |
|
||||
|---|---|---|
|
||||
| `daily_style_metrics` | 17,352 | 17,352(100%) |
|
||||
| `erp_all_shop_style_daily_metrics` | 9,399 | 9,399(100%) |
|
||||
| `product_reviews` | 1,936 | 1,434(74%,全部来自抖音;京东 214 行 style_name 全 NULL) |
|
||||
| `product_daily_metrics` | 75,568 | **无款式列**,只能靠 `tm_spus/jd_spus/dy_spus @> to_jsonb(product_id)` 现算 |
|
||||
|
||||
`product_daily_metrics` 按商品 ID 反查的现状:
|
||||
|
||||
| 平台 | 行数 | 可匹配 | 去重商品 ID | 可匹配 ID |
|
||||
|---|---|---|---|---|
|
||||
| dy | 28,972 | 13,615(47%) | 253 | 83 |
|
||||
| jd | 17,044 | 9,394(55%) | 160 | 61 |
|
||||
| tm | 29,552 | 14,089(48%) | 281 | 92 |
|
||||
|
||||
一对多歧义 **0 条**。约一半行匹配不上,原因是 **458 个商品 ID 从未写进 dim_style 的 spus 数组**(dy 170 / jd 99 / tm 189),不是字段设计问题。
|
||||
|
||||
### 2.3 采集端 15 张只存 style_name 的表
|
||||
|
||||
`daily_style_metrics`、`weekly_style_metrics`、`erp_all_shop_style_daily_metrics`、`daily_persona_metrics`、`main_image_creatives`、`fact_style_analysis_reports`、`decline_events`、`fact_alert_history`、`jd_self_weekly_sales`、`persona_screenshots`、`product_reviews`、`jd_product_ad_daily_metrics`、`dy_product_ad_daily_metrics`、`workflow_dynamic_styles`、`product_daily_metrics`(无列,靠 ID 反查)。
|
||||
|
||||
## 三、目标模型
|
||||
|
||||
- **唯一关联键 = `style_id`(`dim_style.id`)**,所有采集表落库时必须带上。
|
||||
- `style_name` 保留为**显示冗余**,不再作为 join 键;等下游切完 join 后可以只在视图里出现。
|
||||
- 匹配不上的行(未建档商品、广告未匹配 SKU、差评)**允许 `style_id IS NULL`**,但必须同时写 `style_match_method`(`product_id` / `product_name` / `anchor` / `unmatched`),便于统计漏维护。
|
||||
- **不动现有主键**。`daily_style_metrics` 的 PK 仍是 `(metric_date, style_name, platform)`——data-hub 有 6 处 `metric.style_name = style.name` 的 join,改 PK 收益小、回归面大。加列、加索引、切 join 即可。
|
||||
|
||||
## 四、实施分期
|
||||
|
||||
### P0 观测层(只读,不动数据)
|
||||
|
||||
新增两个视图,先把问题量化出来,方案落地后靠它们验收:
|
||||
|
||||
```sql
|
||||
-- 未建档的平台商品 ID(推动在 dim_style 里补 spus)
|
||||
CREATE OR REPLACE VIEW v_style_unmatched_product AS
|
||||
SELECT m.platform, m.product_id,
|
||||
MAX(m.product_name) AS product_name,
|
||||
MIN(m.stat_date) AS first_date, MAX(m.stat_date) AS last_date,
|
||||
COUNT(*) AS rows
|
||||
FROM product_daily_metrics m
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM dim_style d
|
||||
WHERE (m.platform = 'tm' AND d.tm_spus @> to_jsonb(m.product_id))
|
||||
OR (m.platform = 'jd' AND d.jd_spus @> to_jsonb(m.product_id))
|
||||
OR (m.platform = 'dy' AND d.dy_spus @> to_jsonb(m.product_id)))
|
||||
GROUP BY m.platform, m.product_id;
|
||||
|
||||
-- 各表 style_id 回填进度
|
||||
CREATE OR REPLACE VIEW v_style_id_coverage AS
|
||||
SELECT 'daily_style_metrics' AS tbl, COUNT(*) AS rows,
|
||||
COUNT(style_id) AS with_style_id FROM daily_style_metrics
|
||||
UNION ALL SELECT 'erp_all_shop_style_daily_metrics', COUNT(*), COUNT(style_id)
|
||||
FROM erp_all_shop_style_daily_metrics
|
||||
UNION ALL SELECT 'product_daily_metrics', COUNT(*), COUNT(style_id)
|
||||
FROM product_daily_metrics;
|
||||
```
|
||||
|
||||
验收锚点:`v_style_id_coverage` 里前三张表 `with_style_id` 开始上涨到预期比例;`v_style_unmatched_product` 的条数随建档推进下降。
|
||||
|
||||
### P1 评论采集链路下线
|
||||
|
||||
**要删的东西**
|
||||
|
||||
| 位置 | 动作 |
|
||||
|---|---|
|
||||
| `orchestrate_daily_collection.py:437-452` | 移除 `import_product_reviews` 步骤 |
|
||||
| `import_product_reviews.py` | 删除 |
|
||||
| `orchestrate_review_collection.py` | 删除 |
|
||||
| `config/commands.json:516` `product.review.orchestrate` | 移除命令 |
|
||||
| `db/__init__.py` `upsert_product_reviews` | 删除 |
|
||||
| `db/schema.sql:813-870` `product_reviews` 建表段 | 移除(库里的表归档不删) |
|
||||
|
||||
**要补的东西**
|
||||
|
||||
1. 建统一差评视图,字段对齐两个消费方现在的用法(`platform` / `rating` / `content` / `style_id` / `comment_date`):
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE VIEW v_negative_reviews AS
|
||||
SELECT 'tm' AS platform, n.business_date AS comment_date, n.review_level AS rating,
|
||||
n.content, NULL::text AS order_id, NULL::text AS product_id,
|
||||
n.product_name, n.review_id, n.review_at, n.raw_review
|
||||
FROM tmall_negative_reviews n
|
||||
UNION ALL
|
||||
SELECT 'jd', n.business_date, n.review_level, n.content, n.order_id, NULL,
|
||||
n.product_name, n.review_id, n.review_at, n.raw_review
|
||||
FROM jd_negative_reviews n
|
||||
UNION ALL
|
||||
SELECT 'dy', n.business_date, n.rating_level, n.content, n.order_id, n.product_id,
|
||||
n.product_name, n.review_id, n.review_at, n.raw_review
|
||||
FROM dy_negative_reviews n;
|
||||
```
|
||||
|
||||
2. 改两个消费方:
|
||||
- `analyze_style.py:112 pull_reviews()` → 改查 `v_negative_reviews`(按 `style_id` 或 `product_id` 关联)
|
||||
- `content_marketing/daily_marketing_report.py:1020 _review_context()` → 同上
|
||||
|
||||
3. `product_reviews` 归档而不是删除:
|
||||
|
||||
```sql
|
||||
ALTER TABLE product_reviews RENAME TO product_reviews_archived_20261101;
|
||||
```
|
||||
|
||||
**必须明确的功能退化**:`product_reviews` 存的是好/中/差全量,差评三表只有差评。下线后:
|
||||
- 风格分析里的「评价等级分布」从三档变成只有差评一档(差评样本反而更完整:天猫 97 + 京东 32 + 抖音 14,而 `product_reviews` 里天猫是 0、京东 8 月就断了)。
|
||||
- 营销日报的「近 7 天差评归因」不受影响,反而多了天猫来源。
|
||||
- 若产品侧确实需要好评样本沉淀,需要把三个差评采集器扩成「按等级采集」,不要恢复 `product_reviews`。
|
||||
|
||||
另外 `dy_product_scraping.py` 主流程里的评论采集动作只产出本地 JSON,`product_reviews` 停写后就没有消费方,建议同步关掉(或保留但不再进 `product.daily`)。
|
||||
|
||||
### P2 loader 下发 style_id(1 处改动,收益最大)
|
||||
|
||||
`src/gyxx_flow/adapters/workflow_config.py:322 merge_lifecycle_style_records()`:
|
||||
|
||||
```python
|
||||
output["style_id"] = lifecycle.get("id") # dim_style.id
|
||||
output["style_match_method"] = "dim_style"
|
||||
```
|
||||
|
||||
同时 `source_record_id` 保留(控制台/账本还在用),只是不再承担传 ID 的职责。
|
||||
|
||||
影响面:所有通过 `runtime_workflow_configs()` 取款的脚本自动获得 `style_id`,包括日报聚合、周报、ERP 全店铺、人像、主图、声明式分析。这一步不改表、不改写入逻辑,可以先合入验证 loader 输出。
|
||||
|
||||
### P3 采集表加列 + 回填 + 写入端填充
|
||||
|
||||
**统一 DDL 模板**(以 `daily_style_metrics` 为例,其余同构):
|
||||
|
||||
```sql
|
||||
ALTER TABLE daily_style_metrics ADD COLUMN IF NOT EXISTS style_id BIGINT;
|
||||
UPDATE daily_style_metrics d SET style_id = s.id
|
||||
FROM dim_style s WHERE s.style_name = d.style_name AND d.style_id IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS daily_style_metrics_style_date_idx
|
||||
ON daily_style_metrics (style_id, metric_date DESC);
|
||||
```
|
||||
|
||||
按表处理(**必须匹配**的加 `NOT NULL`,**允许未匹配**的保持可空):
|
||||
|
||||
| 表 | 款式来源 | style_id | 写入端改动 |
|
||||
|---|---|---|---|
|
||||
| `daily_style_metrics` | loader 款式清单 | NOT NULL | `aggregate_daily_final.py` + `db.upsert_daily_metrics` |
|
||||
| `weekly_style_metrics` | 从 daily 聚合 | NOT NULL | `weekly_aggregate.py` |
|
||||
| `erp_all_shop_style_daily_metrics` | loader ERP 款 | NOT NULL | `backfill_erp_all_shop_daily.py` |
|
||||
| `daily_persona_metrics` | loader | NOT NULL | `collect_*_persona_to_bitable.py` |
|
||||
| `main_image_creatives` | loader | NOT NULL | `insert_main_image_records.py` / `insert_jd_main_image_records.py` |
|
||||
| `fact_style_analysis_reports` | loader | NOT NULL | `analyze_style.py` |
|
||||
| `decline_events` / `fact_alert_history` | daily 派生 | NOT NULL | `check_nine_day_decline.py` |
|
||||
| `jd_self_weekly_sales` | loader | NOT NULL | `jd_self_inventory_sales_collector.py` |
|
||||
| `persona_screenshots` | loader | NOT NULL | `taobao_dmp_item_crowd_insight_screenshots.py` |
|
||||
| `product_daily_metrics` | 平台商品 ID 反查 | **可空** | `import_product_daily.py` |
|
||||
| `jd_product_ad_daily_metrics` / `dy_product_ad_daily_metrics` | 名称/ID 反查 | **可空**(已有 `match_status`) | 两个 `import_*_ad_*.py` |
|
||||
| 差评三表 | 反查 | **可空** | `collectors/*_negative_reviews.py` 入库侧 |
|
||||
|
||||
**`product_daily_metrics` 的回填要处理系列聚合带来的歧义**(当前 0 条歧义,但要按锚点优先写死优先级):
|
||||
|
||||
```sql
|
||||
UPDATE product_daily_metrics m
|
||||
SET style_id = pick.style_id
|
||||
FROM LATERAL (
|
||||
SELECT d.id AS style_id
|
||||
FROM dim_style d
|
||||
WHERE (m.platform = 'tm' AND d.tm_spus @> to_jsonb(m.product_id))
|
||||
OR (m.platform = 'jd' AND d.jd_spus @> to_jsonb(m.product_id))
|
||||
OR (m.platform = 'dy' AND d.dy_spus @> to_jsonb(m.product_id))
|
||||
ORDER BY d.is_series_anchor DESC, LENGTH(d.style_name), d.id
|
||||
LIMIT 1
|
||||
) pick
|
||||
WHERE m.style_id IS NULL;
|
||||
```
|
||||
|
||||
(真实脚本里用 Python 侧 `_resolve_style_match()` 复用同一套优先级,SQL 只用于一次性回填。)
|
||||
|
||||
回填后预期的非空比例 = 2.2 节的实测值:**dy 47% / jd 55% / tm 48%**。这不是 bug——剩下的是 458 个未建档商品 ID,要靠 P5 的人补 spus 才能涨。
|
||||
|
||||
### P4 约束 + 下游切 join
|
||||
|
||||
1. ~~对「必须匹配」的表加外键~~ → **改为不建物理外键(2026-11-01 定案)**。
|
||||
|
||||
原始设想是给 10 张「必须匹配」的表加 `FOREIGN KEY (style_id) REFERENCES dim_style(id) ON UPDATE CASCADE`,
|
||||
实施后又按业主意见拆掉了。理由是采集链路的语义与硬约束冲突:
|
||||
|
||||
- 采集要求 **fail-soft**:一个商品 ID 没建档,正确行为是把该行 `style_id` 留 NULL 并标
|
||||
`unmatched`,继续写其它行。物理外键会让整批 INSERT/UPDATE 直接报错,等于「一个商品
|
||||
未建档 → 当天数据全丢」,比留 NULL 更糟。
|
||||
- 款式会动:拆系列、合并同款、重建 dim_style 行都要先卸约束再动数据,运维成本不小。
|
||||
- data-hub 侧 22 张表本来就用 `style_id` 关联、**不带物理外键**,两边保持一致才好维护。
|
||||
|
||||
改为**逻辑外键**,完整性由三处保证:
|
||||
|
||||
| 环节 | 手段 |
|
||||
|---|---|
|
||||
| 写入 | loader 下发 `style_id`(源自 dim_style 本身);缺失时 `attach_style_ids` / `resolve_style_ids_by_product` 批量兜底 |
|
||||
| 校验 | `backfill_style_id.py --dry-run` 预演;`v_style_id_coverage` 报每张表覆盖率 |
|
||||
| 观测 | `v_style_unmatched_product`(日报口径)/ `v_style_unmatched_source`(全源口径)列未建档清单 |
|
||||
|
||||
老库里早期建过的 10 个 `<表名>_style_id_fk` 由 `schema.sql` 里的 `DROP CONSTRAINT IF EXISTS`
|
||||
幂等清理;只保留 `*_style_id_idx` 索引(join 性能靠索引,不靠约束)。
|
||||
|
||||
2. data-hub 侧把 `metric.style_name = style.name` 换成 `metric.style_id = style.id`。涉及 `ProductLifecycleMapper.xml` 第 120、155 行(`daily_style_metrics`),以及 `JOIN dim_style dim ON dim.style_name = style.name`(第 15 行)。改完后 text join 变 bigint join,是纯性能收益;`style_name` 列仍在,改错了可以立即回切。
|
||||
|
||||
3. 加测试:
|
||||
- `tests/.../test_daily_style_metrics_style_id.py`:写入必须带 `style_id`,且等于 loader 输出的 id
|
||||
- `tests/.../test_style_id_coverage.py`:`v_style_id_coverage` 里「必须匹配」的表 `with_style_id == rows`
|
||||
- 现有 `test_import_tmall_product_ads.py` 的风格(断言 SQL 形状)继续用在新增字段上
|
||||
|
||||
### P5 数据治理(不是代码活,但决定 P3 的收益)
|
||||
|
||||
`v_style_unmatched_product` 出来的 458 个商品 ID 需要在 dim_style 建档页补 `tm_spus/jd_spus/dy_spus`。分批推进:先补最近 90 天有销量的,历史行允许永久 NULL。
|
||||
|
||||
## 五、风险与取舍
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
|---|---|---|
|
||||
| 款名在 dim_style 被改,历史表 `style_name` 变孤儿 | 现在 join 直接丢数据,这正是要 style_id 的原因 | P3 之后 join 走 id,款名改动不再影响 |
|
||||
| 加 `NOT NULL` 时某天有未建档款写入 | 写入失败,工作流中断 | 先在写入端做「未匹配记 NULL + 打 WARN」,P5 补齐后再收紧为 NOT NULL |
|
||||
| `product_reviews` 下线后没有好评样本 | 风格分析评价分布退化 | 明确接受;需要时扩差评采集器等级,不恢复旧表 |
|
||||
| 改 PK(有人会想顺手做) | data-hub 6 处 join + 飞书脚本全回归 | **本次明确不做**,PK 保持 style_name |
|
||||
| 差评表天猫侧没有 product_id | 只能靠商品名反查款式 | 采集器补 product_id(页面有商品链接可解析),或退化为名称匹配并标记 `unmatched` |
|
||||
|
||||
## 六、验收标准
|
||||
|
||||
1. `v_style_id_coverage`:`daily_style_metrics`、`erp_all_shop_style_daily_metrics`、`weekly_style_metrics` 的 `with_style_id = rows`(100%)。
|
||||
2. `product_daily_metrics` 的 `style_id` 非空比例不低于 2.2 节的实测基线(47%/55%/48%),且逐月上升。
|
||||
3. `product_reviews` 无新增行;两个消费方在 `v_negative_reviews` 上跑通,风格分析与营销日报输出结构不变。
|
||||
4. data-hub 产品生命页、营销中台预算页的款式指标与改造前同月数值一致(对账 2026-10 全月)。
|
||||
|
||||
## 七、建议执行顺序
|
||||
|
||||
P0 → P1(下线评论链路,独立可交付)→ P2(loader 一行改动,验证输出)→ P3(分表灰度,先做 `daily_style_metrics`)→ P4(约束 + 下游切换)→ P5(补建档)。
|
||||
|
||||
P1 与 P2/P3 没有依赖关系,可以先做 P1 拿到收益(少一条采集链路、少一张表),再从容做款式主键统一。
|
||||
|
||||
## 八、实施记录(2026-11-01)
|
||||
|
||||
### 已落地
|
||||
|
||||
| 阶段 | 成果 |
|
||||
|---|---|
|
||||
| P0 | `v_style_unmatched_product`、`v_style_id_coverage` 两个观测视图已建并验证 |
|
||||
| P1 | 删 `import_product_reviews.py` / `orchestrate_review_collection.py`;移除 `product.review.orchestrate` 命令与 runtime binding;`run_collect` 不再调评论入库;建 `v_negative_reviews`;两个消费方改读视图;`product_reviews` 归档为 `product_reviews_archived_20261101`(1936 行已备份到 `var/tmp/product_reviews_archive_20261101.json`) |
|
||||
| P2 | `merge_lifecycle_style_records` 输出 `style_id` + `style_match_method`;loader 透传;实库验证 **204/204 款全部拿到 style_id** |
|
||||
| P3 | 14 张采集表加 `style_id`(+ `style_match_method`)+ 索引;db 层 `attach_style_ids` / `resolve_style_ids_by_product` 统一兜底;`backfill_style_id.py` 回填历史 |
|
||||
| P4 | ~~10 张「必须匹配」表加 `style_id → dim_style(id)` 物理外键~~ → 已按要求**全部拆除**,改逻辑外键(库里 0 个 FK,只留 14 个 `style_id` 索引);新增 `tests/modules/product_commerce/test_style_id_identity.py`(28 条契约测试) |
|
||||
| P5 | 提供 `v_style_unmatched_product` 清单(dy 170 / jd 99 / tm 189),需业务侧补 spus |
|
||||
|
||||
### 回填结果(实库)
|
||||
|
||||
| 表 | 行数 | 有 style_id | 比例 |
|
||||
|---|---|---|---|
|
||||
| daily_style_metrics | 17,352 | 17,352 | 100% |
|
||||
| weekly_style_metrics | 532 | 532 | 100% |
|
||||
| erp_all_shop_style_daily_metrics | 9,399 | 9,399 | 100% |
|
||||
| daily_persona_metrics | 2,357 | 2,357 | 100% |
|
||||
| main_image_creatives | 9,094 | 9,094 | 100% |
|
||||
| fact_style_analysis_reports | 465 | 465 | 100% |
|
||||
| decline_events / fact_alert_history | 14 / 14 | 14 / 14 | 100% |
|
||||
| persona_screenshots | 25 | 25 | 100% |
|
||||
| jd_self_weekly_sales | 117 | 111 | 94.9%(6 款已不在 dim_style) |
|
||||
| tm_product_ad_daily_metrics | 11,727 | 11,108 | 94.7% |
|
||||
| jd_product_ad_daily_metrics | 33,808 | 24,707 | 73.1% |
|
||||
| dy_product_ad_daily_metrics | 18,202 | 10,875 | 59.7% |
|
||||
| product_daily_metrics | 75,568 | 37,098 | 49.1%(未建档商品 ID 所致) |
|
||||
|
||||
### 与方案不同之处
|
||||
|
||||
1. **天猫广告表已先行迁到独立表**:方案编写时天猫指标还写在
|
||||
`product_daily_metrics.raw_data.tm_ad_metrics`,实施期间该迁移已完成,因此改为给
|
||||
`tm_product_ad_daily_metrics` 加 `style_id`(并纳入 `v_style_id_coverage`),
|
||||
并未改动 `raw_data` 节点。
|
||||
2. **写入端没有逐个脚本改**:方案原计划在 13 个写入脚本里各自填充,实际收敛到 db 层
|
||||
的 `attach_style_ids` / `resolve_style_ids_by_product`(一次批量查询),再加 loader
|
||||
下发。少改 13 处、不会漏,代价是 upsert 多一次按款名/商品 ID 的查询。
|
||||
3. **`dy_product_scraping.py` 默认流程的评论采集直接摘除**(仍保留 `--reviews-only`
|
||||
显式入口),比方案建议的「保留但不再进 product.daily」更彻底。
|
||||
4. **物理外键最终没有保留**:P4 建过的 10 个 `<表名>_style_id_fk` 已全部 DROP,
|
||||
`schema.sql` 改为「幂等清理 + 只建索引」。`style_id` 与 `dim_style.id` 是纯逻辑
|
||||
外键关系,理由见 P4 段(fail-soft 采集语义、款式可重建、与 data-hub 对齐)。
|
||||
|
||||
### 仍待处理
|
||||
|
||||
- **data-hub 侧 join 未切**(`ProductLifecycleMapper.xml` 6 处仍是 `metric.style_name = style.name`)。
|
||||
现在两边都有 id,可以随时切;切换后是 bigint join 替代 text join。
|
||||
- P5 补建档:458 个商品 ID(dy 170 / jd 99 / tm 189)写进 `dim_style.spus`。
|
||||
- 既有红灯(与本次无关):`test_tm_brand_profiles` 的 backfill 用例、`shop_intelligence`
|
||||
的 12 个 jd 竞店用例,改动前即失败。
|
||||
|
||||
## 九、P5 补 spus 实施记录(2026-11-01)
|
||||
|
||||
### 依据的权威字段
|
||||
|
||||
补 spus 的匹配依据不是商品标题,而是 **`product_daily_metrics.product_no` —— 它存的就是款名**:
|
||||
|
||||
| 平台 | `product_no` 填充率 | 可用性 |
|
||||
|---|---:|---|
|
||||
| 天猫 | 100%(29552/29552) | 权威 |
|
||||
| 京东 | 97.8%(16664/17044) | 权威 |
|
||||
| 抖音 | 0%(0/28972) | 只能靠标题 |
|
||||
|
||||
这一条此前被忽略,导致「458 个未建档」被误判为只能猜标题。实际天猫/京东 136 个商品
|
||||
靠 `product_no` 就精确命中,另有 31 个靠前后缀近似命中(「蓝鹊pro」→「蓝鹊」、
|
||||
「知更大号」→「知更」、「星迹」→「星迹2」)。
|
||||
|
||||
注意 `product_no` **不是** ERP 编码:`dim_style.erp_style_codes` 存的是 `["10510"]`
|
||||
这种数字码,两者用 `product_no = erp_style_codes` 只能对上 2 个,别混淆。
|
||||
|
||||
### 匹配算法(四路,按优先级)
|
||||
|
||||
1. `product_no` 精确 = `style_name` → 直接写;
|
||||
2. `product_no` 与款名互为前后缀且长度差 ≤ 3,且候选唯一 → 直接写;
|
||||
3. 标题包含完整款名(抖音只能走这条)→ 用「命中位置后的版本后缀」防串款:
|
||||
标题「盖亚斜挎包**3**」、`dim_style` 有「盖亚斜挎3」时必须指后者而非「盖亚斜挎」;
|
||||
若该版本款没建档,判「版本款未建档」而不是硬归到基础款;
|
||||
4. 编码与标题互相矛盾 → 进人工清单,**不自动写**。
|
||||
|
||||
实测第 4 类里最贵的一条:天猫 `649545919334` 商品编码写「理岩」、标题写「阿波罗air」,
|
||||
支付额 **50.2 万**。猜错就是 50 万挂错款,只能人工定。
|
||||
|
||||
### 写入结果
|
||||
|
||||
| 项 | 数值 |
|
||||
|---|---:|
|
||||
| 写入商品 ID | 199 个 |
|
||||
| 涉及款式 | 67 个 |
|
||||
| 覆盖 `product_daily_metrics` 行 | 22,636 |
|
||||
| 未写(物料 / 冲突待定) | 87 个 |
|
||||
|
||||
合并策略:`tm_spus / jd_spus / dy_spus` 取**并集追加**,不覆盖已有值;写前备份到
|
||||
`var/tmp/dim_style_spus_backup_20261101.json`(204 行)。
|
||||
|
||||
### 覆盖率变化
|
||||
|
||||
| 表 | 补前 | 补后 |
|
||||
|---|---:|---:|
|
||||
| product_daily_metrics | 49.1% | **79.0%** |
|
||||
| tm_product_ad_daily_metrics | 94.7% | **99.4%** |
|
||||
| jd_product_ad_daily_metrics | 73.1% | **92.4%** |
|
||||
| dy_product_ad_daily_metrics | 59.7% | **83.2%** |
|
||||
|
||||
广告表的提升来自新增的第三段回填(按商品 ID + 补空 `style_name`):京东广告里
|
||||
32 个商品 ID 就占了 9,101 行空值,补完 `style_name` 空白从 9,095 降到 2,554,
|
||||
抖音从 7,327 降到 3,062。这意味着 **data-hub 侧不改任何 SQL,按款名的 join 已经多认回
|
||||
11,359 行**。
|
||||
|
||||
### 剩余 259 个未建档 ID 的构成
|
||||
|
||||
| 分类 | 商品数 | 支付额 | 处置 |
|
||||
|---|---:|---:|---|
|
||||
| 待建档-真款 | 128 | 6.9 万 | 需在 dim_style 新建款(星云系列链接、拾影单肩包、幻月、纪元、极地冰冠、阿瑞斯、盖亚S1、漫游者、光/暗系列等) |
|
||||
| 待建档-ozko 线 | 20 | 0.2 万 | ozko 子品牌款(逐云、微笑系列、甜酷系列、银鸥、独角兽、HB、cdbb、761X…) |
|
||||
| 白名单-物料配件 | 30 | 5.6 万 | 不该进 spus(内胆包、数据线、风扇、防丢器…) |
|
||||
| 白名单-服务/券 | 4 | 0 | 补邮费、兑换券、补贴 |
|
||||
| 冲突待定 | 10 | 62.9 万 | 编码与标题矛盾,需人工定 |
|
||||
|
||||
清单:`var/tmp/spus_todo.csv`(按分档 + 金额排序)。
|
||||
|
||||
### 新增的长期机制
|
||||
|
||||
- `backfill_style_id.py` 增加 `AD_PRODUCT_TABLES` 与 `_backfill_ad_tables_by_product`,
|
||||
广告表按 `spu_id / product_id` 二次回填,并只在 `style_name` 为空时补权威款名
|
||||
(`COALESCE(NULLIF(BTRIM(style_name), ''), %s)`,不覆盖平台给出的值)。
|
||||
- 新增视图 `v_style_unmatched_source`:`product_daily_metrics` + 三张广告表统一口径,
|
||||
补建档只看这一个视图就够(实测与原 `v_style_unmatched_product` 数值一致,
|
||||
说明没有纯投流商品只出现在广告表)。
|
||||
Reference in new issue
Block a user