Files
gyxx-flow/docs/dynamic-workflow-config.md
T
wangyunlong 622a609800 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)
2026-10-30 11:49:31 +08:00

179 lines
14 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.
# 工作流动态配置
## 配置源
商品身份现在统一由产品生命进程维护,目标地址和平台画像等运行参数仍保存在云端
PostgreSQL 的动态配置表。生命进程负责款式名、品牌、ERP 款式编码、各平台商品编码
以及大/小 NFC 数量;控制台“配置中心 → 款式平台配置”继续负责目标地址、画像表和启停状态。
工作流运行时将两者合并,不再读取旧配置主表
`TtoCb1NuQaDy3NsZWTpc0GIvnph/tblKCjplVAFrRwMC`。
产品采集的统一身份门是:ERP 款式编码 + 至少一个平台商品编码。只有 ERP 款式编码的
研发新款会保留在生命进程中,但不会进入商品采集;如果指定平台没有自己的商品编码,
该平台也不会触发采集。内容汇总类工作流只需要款式名和对应目标,不因缺少商品编码而被
这道商品采集门拦截。
目标飞书表仍是工作流的业务输入或输出,例如销量表、主图表、人群画像表和合作达人表;
本次下线的是集中维护这些地址和商品 ID 的旧飞书索引表,不是业务目标表本身。
## 受影响工作流
| 工作流 | 动态读取内容 | 生效入口 |
| --- | --- | --- |
| `content.summary.monthly` | 款式与每周笔记分析/生命进程目标表 | `monthly_summary_all.py`(复用周汇总配置加载器) |
| `content.summary.weekly` | 款式与每周笔记分析/生命进程目标表 | `weekly_summary_all.py` |
| `content.metrics.daily` | 合作达人目标表 | `run_all.py`、`sync_metrics_to_cmt_notes.py` |
| `content.metrics.self_operated.daily` | 自营账号、款式映射与自营目标表 | `refresh_self_mapping.py`、`self_bilibili_scraper.py`、`chanmama_scraper.py`、`sync_metrics_to_cmt_notes.py` |
| `content.metrics.backfill` | 合作达人目标表 | `run_all.py` |
| `content.notes_master.daily` | 各款式合作达人及自营笔记表 | `sync_notes_master.py` |
| `product.persona.daily` | 三平台商品 ID 与人群画像目标表 | 三个平台画像采集脚本 |
| `product.daily` | 天猫/京东/抖音商品 ID、ERP 编码、销量目标表 | `orchestrate_daily_collection.py` 及平台采集脚本 |
| `product.style_analysis.interval` | 款式与平台单品分析目标表 | `analyze_style.py` |
| `product.main_image.weekly` | 京东 SPU、天猫款式和主图目标表 | 两个平台主图采集入口 |
| `product.sales_sheet.daily` | 款式与 ERP 编码 | `sync_monthly_sales_sheet.py` |
| `product.erp_all_shop_daily` | 全部款式与 ERP 编码 | `backfill_erp_all_shop_daily.py` |
共 12 个定时工作流依赖这套动态配置。维护脚本 `feishu_comment_batch.py` 也已改为读取同一项目数据库,
避免从非定时入口绕回旧主表。原先承担「动态配置 → 款式身份」投影的 `db/sync_dim_style.py` 与
`db/sync_sku_master.py` 均已废弃:运行时身份已直读 `dim_style`,这两个脚本跑一次等于把自己写回自己,
款式新增与改款统一走 data-hub「产品生命进程」建档页。
`product.alert.daily`、商品导入、万相台广告、视频发布和市场排行使用各自数据库或专用配置,
不依赖旧配置主表,因此不在本次切换范围内。
## 字段归属与数据模型
新模型不再把共享字段重复放在每个平台行:
| 层级 | 字段 | 使用目的 |
| --- | --- | --- |
| 产品生命进程 | 款式名、品牌、ERP 款式编码 | 统一业务身份;ERP 日采、月度销量表和款式主数据使用 |
| 产品生命进程 | `platform_product_ids` | JSONB 固定键 `tm`、`jd`、`jd_self`、`dy`,值为去重后的字符串编码数组 |
| 产品生命进程 | 大 NFC 数、小 NFC 数 | 产品规格记录,默认值为 1,允许填写 0 |
| 动态配置父表 | 销量表、主图表 | 商品日报和天猫/京东主图工作流的款式目标表 |
| 动态配置父表 | 合作达人表、自营合作达人表 | 内容采集、合作同步和笔记清单使用 |
| 动态配置父表 | 每周/每月笔记分析、平台单品分析 | 内容周月报和款式周期分析使用 |
| 动态配置子表 | 平台名、启用状态和画像目标 | 商品 ID 只保留兼容快照;运行时商品 ID 由生命进程覆盖,平台停用状态仍然生效 |
| 平台 | 本平台人群画像表 | 天猫、京东、抖音画像采集分别写入自己的目标表 |
数据库使用父子表:
- `dim_style`:产品生命进程主表,一个款式一行,保存 ERP 款式编码、四个平台商品编码、系列归属和销量飞书表;采集端运行时身份的唯一权威来源。
- `cmt_styles`:笔记分析用的款式挂载表(`cmt_notes_master.style_id` 引用其 id),已退出 product 采集身份,仅在 `dim_style` 不可用时作兜底。
- `workflow_dynamic_styles`:一个款式一行,保存共享飞书目标及内容工作流参数。
- `workflow_dynamic_style_platforms`:一个款式可有多个平台子行,保留平台画像表、启停状态和旧商品 ID 兼容快照。
- 旧 `workflow_dynamic_configs` 保留为原始迁移审计,不再参与运行时读取。
- `erp_codes`、`item_ids` 使用 PostgreSQL 数组,页面接受中英文逗号或换行并自动去重。
- `destinations` 使用通用 JSONB 目标列表,每项包含 `key`、`label`、`url`、`description`、`enabled`;六个旧目标字段仍同步保留,保证已有工作流兼容。后续新增分析逻辑只需增加一个稳定的 `key` 和对应飞书表地址。
- 款式带递增 `revision`;整页保存和删除必须提交 `If-Match`,避免并发覆盖。
- `dim_style` 的四个平台列固定为 `tm_spus`(天猫)、`jd_spus`(京东旗舰店)、`jd_self_skus`(京东自营)、`dy_spus`(抖音);空数组表示该平台尚未补齐。同系列只需主款填写,子款靠系列归属继承。
## 首次迁移
`config/workflow-dynamic-config-seed.json` 是 2026-08-19 从旧 Base 分页导出的只读迁移快照,
共 215 条源记录。项目第一次连接一个空数据库时会:
1. 幂等创建旧迁移审计表、款式父表和平台子表;
2. 在事务和 PostgreSQL advisory lock 内导入 215 条记录;
3. 写入迁移标记 `feishu-style-config-20260819-v1`;
4. 运行 v2 归一化迁移:按款式合并共享字段,按平台合并商品 ID;旧表中集中在天猫行的三平台画像地址分别迁入对应平台子行;
5. 写入迁移标记 `workflow-style-platform-config-20260819-v2`,此后不会重复迁移。
产品生命进程身份迁移随后执行 `product-lifecycle-style-identity-20260907-v1`:
1. 为 `cmt_styles` 补齐平台商品编码和大/小 NFC 字段;
2. 将已有 `dim_style` 的 ERP、天猫、京东、京东自营、抖音编码补入生命进程的空字段;
3. 对仍没有生命进程商品编码的旧动态配置做一次兼容提升;
4. 运行时以生命进程中的非空 JSONB 固定键为准,显式空数组会清除旧动态配置中的对应编码。
因此新增款式可先只填写 ERP 款式编码,等平台商品编码补齐后再进入对应采集;不会因为动态配置表
中残留旧编码而提前采集。
### 从旧飞书表重新校准生命进程
如果需要用现有“各平台款式 ID 收集表”补齐或校准生命进程身份,使用一次性同步脚本:
```powershell
uv run --env-file 'D:/product-collector-analyze-flow/.env' `
--project 'D:/gyxx-flow' `
python -m gyxx_flow.migration.sync_feishu_style_identity
uv run --env-file 'D:/product-collector-analyze-flow/.env' `
--project 'D:/gyxx-flow' `
python -m gyxx_flow.migration.sync_feishu_style_identity --apply
```
脚本按款式合并旧表的多平台行,只写入 `cmt_styles` 的品牌、ERP 编码和四个平台商品编码,
不会覆盖 NFC、图片、类目或市场标签;空源字段不会清空已有身份。拼多多等当前四键
模型之外的平台只保留在动态平台配置/审计中,不会伪装成天猫、京东或抖音编码。默认是预览,
必须显式传 `--apply` 才会写数据库。
> **`db/sync_dim_style.py` 已废弃并删除(2026-10-28)。**
> 采集端运行时身份改为直接读 `dim_style`,该脚本再跑就是把 `dim_style` 投影回它自己,
> 既补不了旧台账数据,也可能把空值写回。款式新增与改款统一走 data-hub「产品生命进程」
> 建档页,下一轮调度自动生效,不需要再手工同步。
服务器必须注入 `GYXX_POSTGRES_DSN`,或完整的 `PG_HOST/PG_PORT/PG_DB/PG_USER/PG_PASSWORD`。
没有数据库且没有数据库生成的运行缓存时,相关工作流会明确失败,不会静默回读旧飞书主表。
## 运行时路径
```text
产品生命进程 dim_style (商品身份:ERP 编码 / 四平台编码 / 系列归属 / 销量表)
+
workflow_dynamic_styles + workflow_dynamic_style_platforms (目标/画像/启停)
↓
统一运行时聚合(dim_style 优先,cmt_styles 仅在 dim_style 缺失时兜底)
├─ 完整身份门 → StyleConfigLoader → 商品经营工作流
└─ 目标兼容输出 → feishu_mapping → 内容营销工作流 → 各业务飞书目标表
```
系列共享规则:平台商品编码、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` 的目标表字段缓存仍保留,用来减少对各业务飞书表的字段查询。