feat(product-commerce): 系列聚合去重与采集开关两层语义
配置来源收敛 - 系列平台编码 tm/jd/dy/jd_self 与 9 类飞书地址改为整系列并集去重后挂锚点, 子款编码保留在 series.platform_ids 备查;多表冲突保留最高优先级并告警 - 写表脚本(当日/天猫主图/京东主图)去掉本地 JSON 回退,地址缺失或读取失败 直接 fail closed,避免库与表地址不一致被静默掩盖 - 测试注入 records_provider 时禁用配置缓存读写,防止假数据写入生产快照 采集开关(dim_style.collect_enabled) - 锚点为系列总开关:false 则整系列不采 - 锚点为 true 时,子款自身 false 仅把自己的 ERP 编码排除出聚合,其余子款照常 - 排除结果记录在 series.erp_excluded 并在运行时提示 聚水潭全店铺日报防错 - 列布局 fail closed、行 td 数校验、满页判定 + 总页数兜底、值域交叉校验 - 新增单日总量闸:超近 14 天中位数 8 倍拒绝落库 其他 - 新增电商费用归集配置 ad_cost_rollup.json 与积分兑换周报采集 - 店铺情报、供应链到货同步、通知路由与相关配置/文档同步更新 - 忽略本地 dsh workbench 脚手架(含 node_modules)
This commit is contained in:
1 parent
2f31d5872f
commit
622a609800
66 files changed
+4156
-469
No files matched your search
@@ -128,9 +128,51 @@ workflow_dynamic_styles + workflow_dynamic_style_platforms (目标/画像/启停
|
||||
└─ 目标兼容输出 → feishu_mapping → 内容营销工作流 → 各业务飞书目标表
|
||||
```
|
||||
|
||||
系列共享规则:ERP 款式编码逐款必填;平台商品编码与销量飞书表整系列只需主款填写,
|
||||
子款通过系列归属继承销量表,但**不复制平台编码**——同一个商品 ID 被多款重复采集会把
|
||||
访客/加购重复入账。
|
||||
系列共享规则:平台商品编码、ERP 编码与 9 类飞书地址都以「系列」为一个采集单元,
|
||||
子款留空即可;子款误填也不会丢——loader 会把整条系列聚合并去重后挂到锚点。
|
||||
|
||||
- **平台编码聚合去重到锚点**:整条系列各平台的商品 ID 先并集去重,再挂到锚点,由锚点
|
||||
一个采集单元去抓。子款的原始编码保留在 `series.platform_ids` 供诊断,采集口判定让出
|
||||
(「缺 tm商品编码(同系列「X」已在采)」),同一个商品 ID 不会被采两次、访客与加购
|
||||
不会重复入账。锚点不在采集范围内时子款仍可回退自采。
|
||||
- **ERP 编码先聚合再采集**:正常建档形态是锚点填一条款式编码 + 各平台商品编码,子款只填
|
||||
自己的款式编码;也可能出现锚点 `erp_style_codes` 为空、编码全散落在子款上的形态。
|
||||
loader 一律把整条系列的 ERP 编码聚合成并集挂到锚点(`series.erp_owner`),锚点作为唯一
|
||||
ERP 采集单元去聚水潭取数,子款让出并记录「ERP 已由同系列 X 归集采集」,但仍保留自己的
|
||||
原始编码(`series.own_erp_codes`)供按款查询与月度销量表使用。
|
||||
**例外(2026-10-30)**:子款自身 `collect_enabled = FALSE` 时,它的 ERP 编码不并入聚合
|
||||
(`series.erp_excluded` 可见),锚点不代采;锚点自身 `FALSE` 仍是整系列停采。
|
||||
平台商品编码不受子款开关影响,仍按整系列聚合。
|
||||
- **飞书地址聚合去重下发**:`workflow_config._propagate_series_targets()` 与
|
||||
`_apply_series_sharing()` 两处都按「锚点优先、其余按款名」收集整系列非空地址,去重后
|
||||
下发到每一行。一个表位只能落一张表,同字段出现多个不同地址时保留优先级最高的一条并打
|
||||
`[WARN] 系列「X」<字段> 有 N 个不同表`。子款想用独立一套表,必须拆成独立系列。
|
||||
- **月度销量表按款明细**:`product.sales_sheet.daily` 的报表口径**不用**锚点上的聚合并集,
|
||||
而是每个款式只查自己建档时填的编码。采集端的并集是给聚水潭当唯一取数单元用的,报表
|
||||
照搬会让同系列每个成员行各查一遍并集、销量重复计入。锚点自身没编码时该行按 0 写入
|
||||
(`--allow-missing-erp-as-zero`),同系列销量落在各子款行上,合计与采集总量一致。
|
||||
|
||||
采集门只校验身份:ERP 日采要求 ERP 款式编码,平台日采再叠加对应平台商品编码。
|
||||
**销量飞书表地址不再是采集前置条件**——缺表地址的款式照常采集并落库,只是写表环节按
|
||||
地址缺失跳过(`get_bitable_style_map` 不生成该款目标),不影响数据入库与后续分析。
|
||||
主图采集同理:`get_main_image_styles` 只要求身份齐全,**主图飞书表地址不参与采集门**,
|
||||
缺地址的款照常抓图并写 `main_image_creatives`,`get_bitable_main_image_map` 只返回有地址
|
||||
的款,写表脚本对没有地址的款打 `[NO-TABLE]` 跳过飞书、仍入库。
|
||||
|
||||
**写表脚本对目标地址 fail closed(2026-10-30)**:`insert_bitable_records.py`、
|
||||
`scripts/insert_main_image_records.py`、`scripts/insert_jd_main_image_records.py` 读取
|
||||
`get_bitable_style_map()` / `get_bitable_main_image_map()` 失败或返回空时,直接打
|
||||
`[FAIL] …拒绝回退本地 JSON` 并退出码 1,**不再回退** `bitable_style_map.json` /
|
||||
`bitable_main_image_map.json`。库与本地文件不一致时,回退会让数据静默写到错误的表,
|
||||
比直接失败更难发现。`styles_input.json` 同理由 `collect_erp_yesterday_metrics.load_erp_styles`
|
||||
与 `export_bitable_records.load_style_meta` 兜住:身份源不可用以 `lifecycle_unavailable` 失败。
|
||||
|
||||
**旧表只保留「行集合 + 启停」职责**:`workflow_dynamic_styles` / `_platforms` 仍决定有哪些
|
||||
款式、`enabled` 启停、`note`、`style_content`、`destinations`,但 ERP 编码、平台商品编码与
|
||||
9 类地址全部由 `dim_style` 覆盖(`merge_lifecycle_style_records` 中 `managed` 由
|
||||
`jsonb_build_object` 恒生成四个键而恒为真)。旧表里**不在 dim_style 的残留行**(孤儿款)
|
||||
地址会被清空、不再采集,属于建档改名/合并后的历史垃圾,应直接删除
|
||||
(2026-10-30 已清理 `拓界mode`、`逐星evo`,备份 `var/tmp/workflow_dynamic_orphans_20261030.json`)。
|
||||
|
||||
`StyleConfigLoader` 只在数据库短暂不可用时使用最近一次数据库成功读取后生成的本地缓存;
|
||||
`feishu_mapping` 的目标表字段缓存仍保留,用来减少对各业务飞书表的字段查询。
|
||||
@@ -286,6 +286,105 @@ uv run gyxx effects reconcile <workflow_id> \
|
||||
|
||||
浏览器状态异常时,只备份并处理目标脚本的 `state/browser/<module>/<script>`,不要清理其他脚本状态。调度状态异常时先停止唯一调度进程并备份 `state/scheduler`,不要同时启动第二实例。
|
||||
|
||||
## 商品日报采集开关(product.daily)
|
||||
|
||||
采集开关是 `dim_style.collect_enabled`(2026-10-29 起),**两层语义**(2026-10-30 起):
|
||||
|
||||
1. **锚点是系列总开关**:以该系列 `is_series_anchor = TRUE` 那一行为准;系列没有锚点时按款名
|
||||
排序第一个。锚点 `FALSE` → **整条系列一款都不采**,同系列其余款式跟随锚点。
|
||||
2. **锚点 `TRUE` 时,子款开关只决定自己的 ERP 编码是否参与聚合**:子款 `TRUE` 的 ERP 款式编码
|
||||
并入锚点一起去聚水潭取数;子款 `FALSE` 的编码不并入(既不由它自己采,也不让锚点代采)。
|
||||
被排除的子款列在 `series.erp_excluded`,运行时打
|
||||
`[INFO] 系列「X」子款 Y 已关闭采集,ERP 编码不纳入「Z」聚合`。平台商品编码仍按整系列聚合,
|
||||
不受子款开关影响。
|
||||
|
||||
旧的独立表 `product_daily_collect_scope` 已迁移并删除,备份在
|
||||
`var/tmp/collect_scope_before_drop_20261029.json`。
|
||||
|
||||
默认值是 `TRUE`(采集),因此产品生命进程在 `dim_style` 新增的款式**无需任何操作**就会进入日报;
|
||||
配件、包装、耗材、停售款才需要显式关掉。
|
||||
|
||||
关掉整条系列(改锚点那一行即可,子款自动跟随):
|
||||
|
||||
```sql
|
||||
-- 单系列
|
||||
UPDATE dim_style SET collect_enabled = FALSE WHERE style_name = '黑曜石托特包2';
|
||||
|
||||
-- 批量:把若干系列的所有行一起关(无锚点的系列也覆盖到)
|
||||
UPDATE dim_style SET collect_enabled = FALSE
|
||||
WHERE COALESCE(NULLIF(btrim(series_name), ''), style_name)
|
||||
IN ('飞机盒', '香片', '雨衣', '鞋袋');
|
||||
```
|
||||
|
||||
恢复采集把 `FALSE` 改成 `TRUE`。改完无需重启控制台或调度器:`StyleConfigLoader.load()`
|
||||
每次运行都直接读库,下一轮 `product.daily`(每天 08:40,业务日 T-1)即生效。
|
||||
|
||||
两点注意:
|
||||
|
||||
- **开关落在锚点行上才控制整条系列。** ERP 编码与三平台商品编码都由
|
||||
`_apply_series_sharing()` 按整系列聚合并去重后挂到锚点(子款误填的编码也会并入,但
|
||||
`collect_enabled = FALSE` 的子款 ERP 编码除外),锚点是系列唯一的采集单元;子款自己的
|
||||
编码保留在 `series.own_erp_codes` 与 `series.platform_ids` 备查。
|
||||
- 关掉只影响**商品日报**(tm/jd/dy 平台段 + ERP 段)。天猫/京东主图周采集、聚水潭全店铺日报、
|
||||
三平台电商费用**不受此开关影响**。
|
||||
|
||||
放行不等于一定会采:采集门仍要求「ERP 款式编码 + 各平台商品编码」齐备;飞书目标表地址
|
||||
(下一节的 9 类 `bitable_*` 列)只决定能否写飞书,缺地址的款照常采集入库。缺口写在
|
||||
`var/logs/product_commerce/<date>_skipped_styles.json` 的 `skipped.reason`。
|
||||
|
||||
改款式名时要同步改 `series_name`/锚点标记,否则旧开关会变成孤儿行、新名字按默认 TRUE 采集。
|
||||
|
||||
## 飞书表地址维护(dim_style)
|
||||
|
||||
2026-10-29 起,9 类飞书多维表格地址**全部维护在 `dim_style`**,而且只写在系列锚点行上。
|
||||
`workflow_dynamic_styles` / `workflow_dynamic_style_platforms` 里的同名字段已废弃,
|
||||
代码不再读取;旧值备份在 `var/tmp/bitable_urls_before_move_20261029.json`。
|
||||
|
||||
| 用途 | `dim_style` 列 | 原动态配置字段 |
|
||||
|---|---|---|
|
||||
| 销量表 | `bitable_weekly_url` | `sales_bitable_url` |
|
||||
| 主图表 | `bitable_main_image_url` | `main_image_bitable_url` |
|
||||
| 合作达人表 | `bitable_creator_url` | `creator_bitable_url` |
|
||||
| 自营合作达人表 | `bitable_self_creator_url` | `self_creator_bitable_url` |
|
||||
| 每周/每月笔记分析 | `bitable_weekly_note_url` | `weekly_note_analysis_url` |
|
||||
| 平台单品分析 | `bitable_style_analysis_url` | `style_analysis_bitable_url` |
|
||||
| 人群画像(天猫) | `bitable_persona_tm_url` | `tm_persona_bitable_url` |
|
||||
| 人群画像(京东) | `bitable_persona_jd_url` | `jd_persona_bitable_url` |
|
||||
| 人群画像(抖音) | `bitable_persona_dy_url` | `dy_persona_bitable_url` |
|
||||
|
||||
子款留空即可:两处代码会把整条系列的地址**聚合去重**后统一下发——
|
||||
`workflow_config._propagate_series_targets()` 在合并建档数据时按「锚点优先、其余按款名」
|
||||
收集整系列的非空地址,去重后写回每一行;`style_config_loader._apply_series_sharing()`
|
||||
在聚合配置时再做一次同样的兜底。因此子款误填的地址不会丢、也不会让同系列出现两套表。
|
||||
一个表位只能落一张表:同一字段填了多个不同地址时,保留优先级最高的那条,并在日志里打
|
||||
`[WARN] 系列「X」<字段> 有 N 个不同表`,提示到锚点手动裁决。
|
||||
|
||||
```sql
|
||||
-- 改某个系列的主图表(只改锚点一行,子款自动跟随)
|
||||
UPDATE dim_style
|
||||
SET bitable_main_image_url = 'https://xxx.feishu.cn/base/<base>?table=<tbl>'
|
||||
WHERE style_name = '逐星'; -- 必须是该系列的锚点行
|
||||
|
||||
-- 查看哪些锚点还缺地址
|
||||
SELECT style_name, series_name,
|
||||
bitable_weekly_url, bitable_main_image_url, bitable_creator_url
|
||||
FROM dim_style
|
||||
WHERE is_series_anchor AND COALESCE(bitable_weekly_url, '') = ''
|
||||
ORDER BY style_name;
|
||||
```
|
||||
|
||||
改完不需要重启:`StyleConfigLoader.load()` 每次运行都读库,下一轮工作流即生效。
|
||||
若某类地址为空,对应环节会跳过写表但照常采集入库(例如主图缺表地址打 `[NO-TABLE]`)。
|
||||
|
||||
**地址读取是 fail-closed 的**(2026-10-30):写表脚本拿不到 `dim_style` 地址时会打
|
||||
`[FAIL] … 拒绝回退本地 JSON` 并以退出码 1 结束,**不会**改用 `bitable_style_map.json` /
|
||||
`bitable_main_image_map.json` 里的旧地址。所以看到这类 `[FAIL]` 先去查库里的地址,
|
||||
不要去改本地 JSON(这两个文件已无读取方)。
|
||||
|
||||
新增系列时,地址写在锚点行。若某款业务上确实是独立一套表(历史上 `逐星GT` 这种情形),
|
||||
必须把它拆成独立系列(单独的 `series_name` + 锚点标记)并把地址写在自己那行;只写地址
|
||||
而不拆系列的话,聚合会把它并入原系列,只在日志里留一条告警,不会真的生效。
|
||||
|
||||
## 业务源码同步
|
||||
|
||||
外部业务源码目录只用于发现更新,GYXX Flow 运行时不导入或挂载这些目录。只读检查:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 工作流总览(未隐藏)
|
||||
|
||||
> 生成日期:2026-10-08。数据来源:`config/workflows.json`、`config/schedules.json`、控制台显示名(`var/state/console/workflow-display-names.json`);数据库表提取自各入口脚本及共享 DB 辅助模块的内联 SQL。
|
||||
> 范围:控制台中未被隐藏的工作流共 **30 个**(全部 39 个中剔除浏览器端隐藏的 10 个,其中 `content.relogin.weekly` 已不在当前配置中)。
|
||||
> 范围:控制台中未被隐藏的工作流共 **31 个**(全部 40 个中剔除浏览器端隐藏的 10 个,其中 `content.relogin.weekly` 已不在当前配置中)。
|
||||
> 定时时间均为 **Asia/Shanghai**,由常驻调度器 `gyxx schedule run` 按 `config/schedules.json` 触发;标注「未启用」的仅支持控制台手动运行。
|
||||
> 启动脚本为仓库根目录相对路径,由工作流引擎相对 `src/gyxx_flow/modules/<模块>/` 解析执行;`×N` 表示同一脚本被 N 个步骤以不同参数复用。
|
||||
>
|
||||
@@ -36,13 +36,14 @@
|
||||
| 天猫差评采集 | `shop.tmall_negative_reviews.daily` | 固定筛选负面评价并按评价时间范围采集后入库 | 每月 25 日 03:50(未启用,仅手动) | `src/gyxx_flow/modules/shop_intelligence/collectors/tmall_negative_reviews.py` | `tmall_negative_reviews`(写) |
|
||||
| 抖音待改价申诉 | `shop.douyin_price_appeal` | 每个时点重新扫描可申诉并发起价格申诉;已申诉商品不重复出现 | 每日每小时整点(00:00–23:00) | `src/gyxx_flow/modules/shop_intelligence/collectors/dy_store_competitor_store_scraping.py` | 无(浏览器采集 + 本地 JSON) |
|
||||
|
||||
## 商品电商(product_commerce,12 个)
|
||||
## 商品电商(product_commerce,13 个)
|
||||
|
||||
| 工作流名称 | ID | 功能 | 定时执行 | 启动脚本 | 关联数据库表 |
|
||||
|---|---|---|---|---|---|
|
||||
| 商品人群画像周采集 | `product.persona.daily` | 天猫、抖音、京东人群画像并行采集并写入对应飞书多维表 | 每周二 12:00 | `src/gyxx_flow/modules/product_commerce/run_daily_persona.py` | `daily_persona_metrics`(写) |
|
||||
| 商品经营日报采集 | `product.daily` | 按业务日期(运行日 −1)采集 ERP 与三平台数据,幂等写 PostgreSQL,汇总分析后导出并写飞书日报 | 每日 08:40 | `src/gyxx_flow/modules/product_commerce/orchestrate_daily_collection.py` | 写:`product_reviews`、`product_daily_metrics`、`daily_style_metrics`、`fact_style_analysis_reports`、`decline_events`、`fact_alert_history`<br>读:`weekly_style_metrics`、`daily_persona_metrics`、`main_image_creatives`、`cmt_styles`、`cmt_notes_master`、`cmt_comments`、`cmt_creators`(及上述写表) |
|
||||
| 私域库存预警 | `product.online_stock_alert.daily` | 查询线上渠道全部商品,库存低于 3 时汇总 SKU 与库存发飞书预警 | 每日 09:30 | `src/gyxx_flow/modules/product_commerce/online_stock_alert.py` | 无(企业数据交互 HTTP API + 飞书) |
|
||||
| 私域商品兑换排行 | `product.points_exchange.weekly` | 按自然周查询私域积分兑换明细,按商品汇总 `quantity` 兑换数量并从高到低排名,以飞书卡片发送周报 | 每周一 09:45(统计上一个完整自然周) | `src/gyxx_flow/modules/product_commerce/points_exchange_weekly.py` | 无(企业数据交互 HTTP API + 飞书) |
|
||||
| 三平台电商费用采集 | `product.ecommerce_costs.daily` | 并行采集天猫万相台、京东、抖音千川业务日前一天的费用,各平台先下载再导入,单平台失败不阻断其他 | 每日 10:00 | `…/product_commerce/collect_tmall_wanxiang_ads.py`<br>`…/product_commerce/import_tmall_product_ads.py`<br>`…/product_commerce/collect_jd_ad_costs.py`<br>`…/product_commerce/import_jd_ad_costs.py`<br>`…/product_commerce/collect_douyin_qianchuan_ads.py`<br>`…/product_commerce/import_douyin_qianchuan_ads.py` | `product_daily_metrics`(写,天猫原始费用并入 raw_data JSONB)<br>`jd_product_ad_daily_metrics`、`dy_product_ad_daily_metrics`(建+写)<br>`dim_style`(读) |
|
||||
| 京东与天猫主图周采集 | `product.main_image.weekly` | 京东与天猫万相台主图并行采集,各自完成 PG 入库和飞书插入,互不影响 | 每周日 08:30 | `…/product_commerce/run_weekly_jd_main_image.py`<br>`…/product_commerce/run_weekly_main_image.py` | `main_image_creatives`(写) |
|
||||
| 飞书商品月度销量表填写 | `product.sales_sheet.daily` | 抓取并校验全部 ERP 报表后填写飞书月度销量表,同名月份页幂等覆盖,无 ERP 编码产品按 0 写入 | 每月 1 日 10:00 | `src/gyxx_flow/modules/product_commerce/sync_monthly_sales_sheet.py` | 无(聚水潭浏览器采集 + 飞书写入) |
|
||||
|
||||
Reference in new issue
Block a user