版本:v1 草案 | 2026-08-17 | 作者:backend-dev | 状态:待主会话评审(评审通过后才出表设计迁移与代码,本文只出设计)
上游输入:《推荐子系统 PRD》v1.1(docs/product/xijian-recommend-prd.md,产品定义)|《推荐子系统业务册》v1.1(docs/business/xijian-recommend-biz.md,SOP 评分卡/分档/一票否决)| 基线 v2.1 §2/§5.1/§5.2/§5.3/§6.13/§7.1/§7.4/§12A(唯一口径源)| docs/dev/influencer-schema-design.md(特征的表来源)| docs/dev/jixing-api-notes.md(集星字段与四坑)| docs/dev/msgbridge-contract.md §2.1/§2.5(批次归因)| docs/xijian-architecture.md §4/§5/§9(架构约束)| 代码实况 server/app/domains/influencers/grade.py、domains/jixing/enrich.py、app/scheduler.py、domains/config_center/cache.py
纪律声明:本文不定义任何口径数字。凡判定阈值/权重/比例一律指向 cfg 键(§8)并注基线出处;SOP 未给到子项分值的地方,本文只出打分框架,分值标「初值待运营校准,走 cfg」。
评分引擎(五维映射/硬门槛短路/证据 schema/沉底与探索位,P1-6.4)| 估价引擎(双锚/距离分段/输出结构/冷启动,P1-6.5)| 特征流水线(快照库与标签库 schema 草案/刷新策略/指纹去重,P1-6.1~6.3)| C/C 飞轮(权重版本/校准批次/度量/切换回滚——三期主体,P1 只建表与报表)| 归因链路(wcid→邀约→报名→履约,P1-6 + P1-11.2 接口点)| 运行形态(进程内计算论证/调度清单/性能实测)。
pending_ai),一期不给分、不猜分(PRD §01「不输出伪判断」)。| 已落地 | 推荐域怎么用 |
|---|---|
influencers/grade.py::evaluate/recompute |
分级结果(excellent/low_recommend)与 recommend_penalty_reason(P0 已写)是排序输入,不是评分维度 |
grade_evidence 结构(含 cfg_snapshot) |
评分证据 JSON 同构扩展(§2.4):证据带判定时读到的 cfg 值,供事后追溯 |
influencer.recommend_index / _at |
P0 建好的占位列(恒 NULL),本子系统是其唯一写入方(清单生成后回写归一化分,值变才写) |
jixing/enrich.py(fingerprint 值变才写) |
批量特征刷新直接复用,不重写集星链路;METRIC_FIELDS 指纹手法照搬到特征表 |
douyin_account_metric.quotation_min/max_fen、douyin_account.resident_city_code |
分别是估价行情锚与距离分段/「异地无法拍摄」门槛的现成输入(集星直供) |
influencer_binding.wcid / batch_id |
归因链路现成落点(契约 §2.1),不新增接口 |
app/scheduler.py::@scheduled、config_center/cache.py::cfg |
job 用装饰器登记不改装配点;阈值唯一入口,占位 "待确认" 原样返回字符串绝不转 0(短路先例见 grade.py) |
候选池(scope.outreach_scope 过滤旁类)
→ ① 轨道门槛 track_gate(粉丝/等级/招募价,按轨)→ ② 一票否决 hard_gates(命中即 blocked,不再算分)
→ ③ 五维打分(逐维出 score/status/证据)→ ④ 汇总 raw/coverage/normalized/tier
→ ⑤ 强制规则(配合度<6 或黑名单 → 不作主选)→ ⑥ 排序(blocked 出列 → sunk 沉底 → 分数降序)
→ ⑦ 探索位插入(cfg 比例,可复现随机)→ ⑧ 逐人挂建议价(§3)
→ ⑨ 落 recommend_batch / recommend_item + 特征快照(§4)
满分列 = SOP 100 分表(基线 §5.3:定位 20 / 内容 30 / 数据健康 30 / 商业匹配 10 / 配合度 10),落 cfg
recommend.weights.<track>三套模板(PRD §4.2 三轨独立)。子项分值 SOP 未给,全部走recommend.subscore.<dim>的分段规则表,初值待运营校准。
| 维度 | 满分 | 子项特征 | 来源(表列 / API 字段) | 计算式 | 一期状态 |
|---|---|---|---|---|---|
| 数据健康度 | 30 | 中位数播放 | 播放波动 | 断更 | 视频序列(P1-6.3 中台)|降级源 douyin_account_metric.video_avg_vv(集星 talent_video_avg_vv) |
median(vv_30d) 落分段表 | MAD(vv)/median(vv) 越低越稳 | now − last_publish_at + 近 90 天最大间隔 |
✅ 自动(降级需标注,见下) |
| 涨粉趋势 | douyin_account.fans_count 的自累积时间序列(§4.2) |
Δfans_30d;无历史点 → insufficient_data |
⚠️ 冷启动 3 个月内不可算 | ||
| 退款率 | 刷量信号 | 中台 v_aftersale_detail(P1-6.2)| 互动均值 video_avg_like/comment/share_cnt ÷ video_avg_vv |
达人维度退款额/成交额 | 互动率离群 → 扣分 + 触发 gate 标记 | ⏳ 待中台 | ✅ 自动(粗判) | ||
| 内容质量与稳定性 | 30 | 可复制结构/出镜稳定/制作质量 | 豆包视频模型(抽样:爆款/普通/低播放各 3,PRD §4.1) | AI 初判分 | ⛔ pending_ai(二期) |
| 定位清晰度 | 20 | 主页与内容一致性、一句话定位 | 豆包(二期)|一期代理:categories 集中度 + tags 一致性 |
代理分默认不计入(cfg 开关默认关) | ⛔ pending_ai(二期) |
| 用户与商业匹配 | 10 | 品类匹配 | 地域 | 粉丝画像(二期) | categories/primary_category vs 任务品类 | resident_city_code vs 项目地 code | 集星 fans_tag_list |
命中主/次/不命中 三档 | 同城/省内/跨省 三档(同 §3.4 距离口径) | ✅ 自动(画像项 ⏳ 二期) |
| 达人配合度 | 10 | 催发响应时长 | 交稿准时率 | 人工 1–10 分 | 消息台触达→回复间隔 | 商单域 SLA(基线 §4)| fulfillment_label.cooperation_score(业务册 §06 待业务确认) |
分段 | 达标次数/总次数 | 直接映射 | ⚠️ 首次合作 → pending_human,明示「待人工」;已履约项待 P0-5/P1-7 |
降级源的坑(必须标注在证据里):SOP 要的是中位数播放,集星只给稿均(均值)播放。均值被爆款拉高,与 SOP 的抗噪意图相反——一期用降级源时,证据项必须写 source.degraded=true, note="集星稿均,非中位数",且该子项的分段表用独立 cfg 子键,序列到位后切换不影响历史快照可比性(快照存的是当时的 feature_version)。
gate 注册表 recommend/scoring/gates.py,每条 gate 返回 pass | blocked | manual | unknown 四态。unknown 绝不当 pass(数据源未接入不等于没问题,PRD §03 舆情源仍「评估中」)。
| # | gate code | 业务册 §03 措辞 | PRD §4.1 措辞 | 一期实现 | 判定输入 |
|---|---|---|---|---|---|
| 1 | category_conflict |
定位冲突 | 品类冲突(自动 pass) | 自动 blocked | categories ∩ 任务品类 = ∅ |
| 2 | unstable_appearance |
不稳定出镜 | 无出镜(自动 pass) | unknown → 转人工核 |
二期视频理解 |
| 3 | suspected_fake_traffic |
疑似刷量 | 刷量疑似(自动标记人工核) | 半自动 → manual |
互动率离群信号(§2.2);二期加评论区人机判别 |
| 4 | violation_risk |
违规风险 | 违规舆情(源接入后自动) | unknown(舆情源评估中) |
— |
| 5 | capacity_missing |
承接条件缺失 | 意愿与承接条件(人工核对项) | manual |
— |
| 6 | ⚠️冲突项 | 拒绝复盘 | 云剪短片号 | 不实现,标待确认 | 上游两份材料第 6 条不一致(见 §10 G1) |
| 7 | offsite_infeasible |
—(PRD §03 数据基建行提及「异地无法拍摄自动判」) | 同左 | cfg 开关默认关 | 距离 > recommend.offsite_gate_max_km,待运营给值 |
fans_count ≥ 20w、品效 item_level ≥ 6、团购 item_level ≥ 4 + 招募价约束 → cfg recommend.track_gates(基线 §5.1 有出处)。不过轨道门槛者不进本轨清单(不是 blocked,是不同轨的事)。blocked → 立即返回,不计算任何维度(省算力,且避免「85 分但不能选」的误导展示);manual/unknown 不阻断打分,但在清单上以「待人工核对项」清单展示(基线 §6.13 判据 1「硬门槛不过者单列并注原因」)。grade_evidence 先例)一分一证据,落 recommend_item.evidence(JSON)。schema 草案:
{ "schema":"recommend.evidence.v1", "track":"effect", // brand|effect|group
"weight_version_id":12, // champion 权重版本(可回放)
"feature_version":"feat.v1", // 特征口径版本(降级源切换时递增)
"gates":[ {"code":"category_conflict","result":"pass","evidence":{...}},
{"code":"violation_risk","result":"unknown","note":"舆情源未接入,不视为通过"} ],
"dimensions":[
{"code":"data_health","max":30,"score":24.5,"status":"scored","items":[
{"code":"median_vv","value":12000,"score":8.0,"max":10,
"rule":"cfg:recommend.subscore.data_health.median_vv_tiers",
"source":{"type":"metric","field":"douyin_account_metric.video_avg_vv",
"fetched_at":"2026-08-17T03:00:00","degraded":true,"note":"集星稿均,非中位数"}} ]},
{"code":"content_quality","max":30,"score":null,"status":"pending_ai"},
{"code":"cooperation","max":10,"score":null,"status":"pending_human","note":"首次合作,SOP 明示待人工"} ],
"raw_score":41.5, "coverage":0.6, "normalized_score":69.2,
"tier":null, "tier_reason":"coverage 低于 recommend.tier_min_coverage,不给档位",
"cfg_snapshot":{"recommend.tier_thresholds":{...},"recommend.tier_min_coverage":0.7} }
汇总口径:raw = Σ 已评维度得分;coverage = Σ 已评维度满分 / 100;normalized = raw / coverage。
分档裁决建议:分档基于 normalized,但 coverage < cfg("recommend.tier_min_coverage") 时不给档位,只标「证据不足」——不能拿 60 分可评满分里的 45 分冒充 75 档。该阈值初值待运营校准。分档线 85/75/65 有出处(基线 §5.3),走 recommend.tier_thresholds。
influencer.grade == "low_recommend" 或 recommend_penalty_reason 非空(P0 已写入,基线 §2 CPM>80)→ sunk=true,只改排序不改分数(分数要可解释、可回测;改分会污染飞轮的相关性统计)。< cfg("recommend.cooperation_min_score") 或 influencer.blacklisted → not_primary=true,清单上明示「总分再高也不作主选」,同样只影响标注与排序。探索候选池 = 过硬门槛 且 coverage < cfg("recommend.explore_coverage_max") 且 未履约
e_min/e_max = round(N * cfg("recommend.explore_slot_ratio_min|max") / 100) # 强制下限/上限
e = clamp(e_min, 0, min(e_max, len(候选池))) # 候选不足可少,但必须回报实际比例
step = max(1, N // (e + 1)) # 插入位 [step, 2*step, ...],均匀分散不堆尾部
候选池内排序 = 以 md5(f"{batch_id}:{task_id}") 为种子的可复现打散(回放一致)
响应必带 explore_ratio_actual;候选不足导致低于下限时给出显式 note,不静默降配额(基线 §6.13 判据 2)。
建议上限 suggest_max = min(market_high, income_cap) # 取低
建议下限 suggest_min = min(max(base_fee, market_low), suggest_max)
income_cap = P30(预测产出) / (roi_target + 1) # roi_target 走 cfg
+1 不是魔法数字:基线 §7.4 定义 ROI = (GMV − 费用) / 费用,要求 ROI ≥ r ⟺ 费用 ≤ GMV/(r+1)。PRD §05 的例子(GMV 3,000、ROI≥2 → 上限 1,000)正是 3000/(2+1)。roi_target 取 cfg("settlement.explore_roi_min")(实探)/cloud_edit_roi_min/live_roi_min,按任务类型选,不另立推荐域自己的达标线。
| 源 | 数据来源 | 取值 |
|---|---|---|
| 自身历史成交价(权重最高,私有真实数据) | 新库商单实际成交价 + influencer.last_deal_price_fen;历史部分跨库只读 lynoxi.task_order.actual_earnings(架构 §5.2 三分法) |
中位数(low=P25, high=P75) |
| 同层同类分布 | fulfillment_label 按 item_level + primary_category + track 聚合 |
P50 / P70 |
| 达人自报价(偏高,权重最低,只作上界参考) | douyin_account_metric.quotation_min_fen/max_fen(集星 quotations) |
原样 |
| 车马费基础价 | 距离分段金额 × 层级系数(全走 cfg,见 §3.4) |
单值 |
合成:market_x = Σ wᵢ·xᵢ,权重走 recommend.market_anchor_weights(初值待校准)。样本量不足即降权归零并重新归一(n < cfg("recommend.market_anchor_min_samples")),证据记 dropped_sources——不足样本的源静默参与是"用噪声当行情"。
cfg("recommend.income_anchor_window_days")(建议初值 90,PRD §08 滚动窗口刹车);样本条件:视频满 cfg("data.ramp_protection_days") 天(基线 §8 爬坡保护)、剔除鸽约(fulfillment_label.is_ghosted)。statistics.quantiles(samples, n=100, method="inclusive")[29](stdlib,无需 numpy;本机实测 quantiles([1..10], n=10, method="inclusive")[2] == 3.7)。P 值走 cfg("recommend.income_anchor_percentile")(已有键,默认 30)。fallback_level 进证据):① 达人自身样本(n ≥ recommend.income_anchor_min_samples)→ ② 同层级(item_level)+ 同品类群体 → ③ 同轨全体 → ④ 收益锚缺席:只出行情锚 + 显式标注,绝不用默认值假装。resident_city_code(集星直供,6 位行政区划码)→ 项目地城市 code(项目域字段;未接入前由请求参数传入)。距离方案三选一:① 城市质心 + Haversine 直线距离(静态 JSON,约 300 城)→ ✅ 一期采用,零外部依赖且精度对"几十公里粒度的分段"足够;② 城市码同城/省内/跨省三档——质心表缺城时的兜底分支;③ 地图 API 驾车距离——❌ 需外部 key 且增外呼,YAGNI。
cfg("recommend.travel_fee_distance_tiers") 已在种子占位为 [](基线 §12A「待运营给现行标准」)。为空时车马费项短路为 0 + 日志告警 + 证据标 base_fee_unavailable(与 grade.py 白名单空集短路同构),不猜数字。层级系数 cfg("recommend.travel_fee_level_coefficients") 同为待运营。{ "schema":"recommend.price.v1", "suggest_min_fen":90000, "suggest_max_fen":110000,
"binding_anchor":"income", // 哪把尺子卡住了上限
"market":{"high_fen":150000,"low_fen":80000,"sources":[...],"dropped_sources":["peer_dist"]},
"income":{"p30_gmv_fen":300000,"roi_target":2,"cap_fen":100000,"sample_n":7,"fallback_level":1},
"base_fee_fen":0, "base_fee_status":"unavailable", "confidence":"medium", // 由样本量与回退级派生
"quote_fen":150000, "quote_flag":"over_cap", "target_price_fen":110000, // 「压价至 ¥X 或换人」
"cfg_snapshot":{...} }
冷启动策略(PRD §10 一期「数据就绪即做」):
| 情形 | 输出 |
|---|---|
| 无自身成交价,有同层同类样本 | 行情锚走群体分布,confidence=medium |
| 无任何成交样本,有集星 quotations | 只出「行情参考区间」= quotations ∩ 车马费基础价,confidence=low,明示「无历史成交样本」 |
| 收益锚样本不足且行情锚也不可用 | suggest_* 返回 null + reason,清单显示「暂无建议价」——不编造 |
base_fee > income_cap(车马费本身超收益上限) |
区间退化为单点 + 标 infeasible,提示「需加预算或换人」 |
命名与类型沿用达人域约定(influencer-schema-design.md §0):无 xijian_ 前缀、金额列名带 _fen、比率 DECIMAL(10,4) 标度 0–1、多值 JSON、不建物理 FK。
① influencer_feature_current(当前特征,与账号 1:1,值变才写)
account_id BIGINT UNIQUE / window_days SMALLINT(默认 30,与 metric 表同口径)/ median_vv INT / vv_mad_ratio DECIMAL(10,4) / publish_cnt INT / max_gap_days INT / last_publish_at DATETIME / fans_count INT / fans_growth INT NULL(NULL=历史点不足)/ refund_rate、interaction_rate、fake_signal_score DECIMAL(10,4) NULL / item_density INT NULL(接单密度,PRD §03)/ source_map JSON(逐特征来源与degraded标记,证据要用)/ computed_at / fingerprint CHAR(32) / created_at / updated_at
② influencer_feature_snapshot(推荐时刻全量快照,append-only)
id / batch_id / influencer_id / account_id / track / features JSON / feature_version / source_map JSON / created_at,唯一约束 uq(batch_id, account_id)。
为什么两张表:current 是"最新值"(打分快取,高频刷新、值变才写);snapshot 是"当时值"(回测地基,PRD §07「模型可换,特征快照库是不动产」)。合成一张会逼出"要么丢历史,要么每次刷新都写一行"的两难。快照不可变、不去重、不清理——它是资产不是缓存。
③ fulfillment_label(履约标签库,护城河本体)
id / order_id UNIQUE / influencer_id / account_id / task_id / track / deal_price_fen / gmv_fen / exposure / roi DECIMAL(10,4) / cpm_fen / refund_rate / is_ghosted / on_time_delivery / response_minutes / cooperation_score TINYINT(1–10 人工) / settled_at / label_version / fingerprint / created_at / updated_at
为什么物化而不是查商单视图:① 回测要冻结口径(商单表会随业务演进改列,标签要按
label_version稳定);② 跨库聚合(历史单在 lynoxi)成本高;③ 对外产品化时它就是精算数据集(PRD §01A)。写入方 = 商单/结算域的「已结算」事件(P0-5/P1-9),推荐域只读消费。
④ recommend_batch / recommend_item(清单与决策留痕)
recommend_batch:id / task_id / track / requirement JSON(反选需求表单 SOP §2 四问)/ weight_version_id / candidate_count / explore_ratio_actual / created_by / created_atrecommend_item:id / batch_id / influencer_id / account_id / rank / is_explore / gate_result / raw_score / coverage / normalized_score / tier / not_primary / sunk / evidence JSON / price_suggestion JSON / decision(adopted|skipped NULL)/ decision_reason / decided_by / decided_at,唯一约束 uq(batch_id, account_id)(基线 §6.13 判据 4 的落点)| 来源 | 通道 | 复用什么 | 频率与受控集合 |
|---|---|---|---|
| 集星档案/指标 | dls.lynoxi.com(架构 §5.4) |
直接复用 jixing/enrich.py(fingerprint 值变才写、四坑已平),只加批量编排 |
job recommend.feature_refresh,只刷活跃实体:layer ∈ (contacted, fulfilled) 或近 recommend.feature_active_window_days 天被推荐过的账号——永不全量扫(轮询三原则) |
| 视频指标序列 | 中台统一 API 包(P1-6.3,待用户侧统发) | app/central.py client |
接口未就绪 → 特征置 None + source_map.status="unavailable",绝不填 0;打分侧走 §2.2 降级源 |
| 退款率/带货归因 | 中台 v_aftersale_detail / v_talent_sales_detail(P1-6.2) |
同上 | 同上 |
| 履约数据回流 | 商单「已结算」事件(P0-5 状态机) | 事件驱动,非轮询 | upsert fulfillment_label + 标记该账号特征失效 |
| 涨粉趋势的自造历史 | 每轮刷新把 fans_count 追加进 influencer_feature_snapshot(快照天然是时间序列) |
— | 冷启动期该子项 insufficient_data,不假装有趋势 |
feature_current:整轮特征值排序 JSON → md5 → 与库中比对,相同则整行不写(零 UPDATE、零 binlog)。手法与 enrich.py::_fingerprint 一致,指纹只覆盖本模块负责的列。feature_snapshot:不做值去重(快照必须每批都写),靠 uq(batch_id, account_id) 防重复生成。fulfillment_label:按 order_id upsert + 逐字段值变才写。influencer.recommend_index 回写值变才写,recommend_index_at 语义 = 变更时间(与 grade_at/layer_at 同构,不是"上次计算时间")。recommend_weight_version:id / track / role(champion|challenger|archived)/ version_no / weights JSON / subscore_rules JSON / derived_from_id / source(seed|challenger|manual)/ promoted_at / retired_at / note / created_by / created_at
每轨同时最多一个 champion + 一个 challenger(MySQL 无部分唯一索引 → 应用层保证 + ix(track, role))。清单必记版本(recommend_batch.weight_version_id)让任何历史清单可回放。cfg 里的 recommend.weights.<track> 只是种子默认值;上线后权威值在版本表,cfg 仅在无版本行时兜底(避免两份真相)。
recommend_calibration_batch:id / track / batch_no / window_start / window_end / champion_version_id / challenger_version_id / metrics JSON / sample_size / verdict(challenger_win|champion_win|inconclusive)/ applied_action(promote|rollback|none)/ created_at
cfg("recommend.calibration_batch_k") 现为占位 "待确认"(str)。语义定义:连续 K 个 verdict=challenger_win 的批次才晋升。读到非数字 → 自动晋升链路整体短路(仍跑影子对比与度量,只是不晋升)+ 显式告警,与 grade.py 白名单短路同构。cfg("recommend.calibration_rollback_line") 同为占位:质量指标跌破该线自动回退上一版;未定案前同样短路(只告警不动作)。| 指标 | 计算式 | 数据来源 |
|---|---|---|
| TopN 采纳率 | count(rank ≤ N and decision='adopted') / N |
recommend_item |
| 采纳者达标率 | 采纳者商单达标数 / 采纳者商单数 | fulfillment_label + 结款达标 cfg(基线 §7.4) |
| 人工直选对照组 | 该场次商单中不在任何 recommend_item.decision='adopted' 集合内的达人 |
天然对照(PRD §06) |
| 按建议价成交达标率 | suggest_min ≤ 实际成交价 ≤ suggest_max(用快照价,非当前重算价)者的达标率 vs 整体 |
recommend_item.price_suggestion + 商单 |
| 建议价偏差 | abs(建议价中值 − 实际成交价) 分布的收敛趋势 |
同上 |
| 探索位命中率 | is_explore=true 且最终达标者占比 |
跟踪即可,不设目标 |
显著性护栏:任一指标样本 < cfg("recommend.metric_min_samples") → verdict=inconclusive,不下结论、不触发晋升/回滚(小样本噪声当信号是飞轮最容易犯的错)。季节作控制变量:按 window 同期对比,不跨季节直接比(PRD §08 分布漂移刹车)。
challenger 生成: ① 逐维 clamp 到 champion ± cfg("recommend.weight_change_limit_pct")(已有键,20)
② 归一化到总分 100 ③ 归一后二次校验,仍越界 → 拒绝该 challenger 并记原因
晋升: 连续 K 批 challenger_win 且 cfg("recommend.auto_promote_enabled")=1 → promote(旧版 archived)
回滚: 质量指标跌破 rollback_line → 自动回退上一版
告警: 连续回滚 ≥ cfg("recommend.rollback_alert_streak") → 冻结自动晋升 + 告警(人只在异常时介入)
审计: 每次 promote/rollback 一条 audit(action=recommend.weights.promote/rollback),走既有 audit()
步骤 3 的坑值得单独测:先 clamp 再归一是常见做法,但归一会把被压制维度的份额转嫁给其他维度,可能反而突破 ±20% 限速——必须二次校验,否则"限速"形同虚设。 shadow-first 同构:
recommend.auto_promote_enabled默认 0(只影子跑批、只出报表),与 msgbridge 三态门同一纪律——自动改权重是"重大影响结果的决策"(基线 §9.1 第 2 条)。
recommend_batch + recommend_item ─▶ 人工圈定(decision=adopted/skipped+可选原因,基线 §6.13 判据 4)
─▶ ②A 定向邀约(P1-11.2,人工确认后发起;契约 §2.1 的 send-miniprogram 返回 wcid,
server 存 wcid ↔ (batch_id, influencer_id),不新增任何接口)
─▶ 达人点卡片 ─▶ wcid 回流(架构 §6.2)─▶ influencer_binding.wcid/batch_id(P0 已建列)
─▶ 报名意向 ─▶ 商单(P0-5)─▶ 履约 ─▶ fulfillment_label(§4.1③)
| 漏斗段 | 数据来源 | 现状 |
|---|---|---|
| 推荐 → 采纳 | recommend_item.decision |
本子系统建 |
| 采纳 → 邀约送达 | msgbridge 审计事件 msgbridge.invite.*(记 scene/batch_id/influencer_id/wcid,契约 §2.1) |
契约已定 |
| 邀约 → 报名 → 履约达标 | influencer_binding.wcid/batch_id → 商单状态机 → fulfillment_label |
P0 列已建;P0-5 / P1-9 |
与 P1-11.2 的接口点(必须对齐,否则漏斗断在这一段):邀约批次需要能反查推荐批次。建议 ①邀约批次表加列 source_recommend_batch_id,或 ②统一批次号命名 rec:<recommend_batch_id>(msgbridge 的 note 已用 xijian:<batch_no> 作幂等标记,天然可承载)。推荐方案 ①(显式列,可索引),②作为消息台侧的可读兜底。此项需在 P1-11.2 开工前确认。
量级实测(本机 Python 3.12,纯 Python 五维 × 每维 5 子项分段查表 + 证据对象构造):N=1,000 → 9.9 ms;N=6,660(当前达人量级,上游口径)→ 76.4 ms;N=20,000(达人广场采集 19,299 行量级,基线 §8 实测)→ 264.6 ms(单人 10–13 μs)。
结论:打分不是瓶颈,取数才是(一次批量拉 1e4 行特征表约数百 ms)。单次清单生成 p95 < 2s 完全可达,无需异步化、无需独立算分服务。
@scheduled 注册表模式,不改 app/scheduler.py)| job | 触发 | 频率 cfg | 受控集合 | 无活跃实体时 |
|---|---|---|---|---|
recommend.feature_refresh |
interval | recommend.feature_refresh_minutes |
活跃账号(§4.2),分批 recommend.feature_batch_size |
零出站 |
recommend.label_backfill |
interval | 同上(低频档) | 按 settled_at 水位游标补齐标签 |
零写入 |
recommend.calibration |
cron | recommend.calibration_cron |
上一窗口内有结算的批次 | 直接返回 |
recommend.challenger_shadow |
cron | 同上(校准后串行) | 有 challenger 的轨 | 直接返回 |
调度纪律(APScheduler 进程内的两个真坑):① 全部 job 显式 max_instances=1 + coalesce=True——特征刷新慢于间隔时会堆积并发,重复打集星接口触发反爬;② 单进程部署(systemd + uvicorn 单 worker)是当前形态,若将来多 worker 必须只在一个进程起调度器,否则 job 重复执行。写进 job 注册处的注释。
recommend.* 7 键(config_center/seed.py 实测):explore_slot_ratio_min(10)、explore_slot_ratio_max(20)、income_anchor_percentile(30)、weight_change_limit_pct(20)、travel_fee_distance_tiers([] 占位)、calibration_batch_k(待确认)、calibration_rollback_line(待确认)data.ramp_protection_days(取数窗口)|settlement.explore_roi_min/cloud_edit_roi_min/live_roi_min(收益锚 roi_target 与达标度量)|settlement.explore_cpm_max(达标度量)|grade.cpm_low_recommend_threshold(沉底,经 P0 分级结果间接消费)|grade.travel_category_whitelist(品类匹配)|ark.daily_request_limit(二期 AI 成本闸)| # | 键(类型) | 默认/状态 | 出处 |
|---|---|---|---|
| 1–3 | recommend.weights.brand/.effect/.group(json) |
SOP 五维满分(20/30/30/10/10)为初值,三轨差异待运营校准 | 基线 §5.3 / PRD §4.2 |
| 4–6 | recommend.subscore.data_health / .business_match / .cooperation(json) |
子项分段规则表,SOP 未给子项分值 → 初值待运营校准 | SOP 缺口(§10 G3) |
| 7 | recommend.tier_thresholds(json) |
{primary:85,backup:75,caution:65} |
基线 §5.3 |
| 8 | recommend.tier_min_coverage(float) |
待校准:低于此覆盖率不给档位 | 本文 §2.4 裁决 |
| 9 | recommend.cooperation_min_score(int) |
6(<6 不作主选) | 基线 §5.3 强制规则 |
| 10 | recommend.hard_gates(json) |
六条 gate 的启用与模式;第 6 条待确认(§10 G1) | 业务册 §03 / PRD §4.1 |
| 11 | recommend.track_gates(json) |
品宣 fans≥200000 / 品效 item_level≥6 / 团购 item_level≥4 + 招募价约束 | 基线 §5.1 / PRD §4.2 |
| 12 | recommend.positioning_proxy_enabled(int) |
0(一期不用代理分装懂) | PRD §01 边界 |
| 13 | recommend.explore_coverage_max(float) |
待校准:探索候选的「数据不足」界线 | PRD §4.3 |
| 14–15 | recommend.market_anchor_weights(json)/ .market_anchor_min_samples(int) |
待校准:四源权重与最小样本 | PRD §05 / 本文 §3.2 |
| 16–17 | recommend.income_anchor_window_days / .income_anchor_min_samples(int) |
待校准(窗口建议初值 90) | PRD §08 / 本文 §3.3 |
| 18 | recommend.travel_fee_level_coefficients(json) |
待运营(与距离分段配套) | 基线 §5.3 / §12A |
| 19 | recommend.price_band_floor_ratio(float) |
待校准:两锚均缺下限时的兜底比例 | 本文 §3.1 |
| 20–21 | recommend.distance_source(str,city_centroid)/ .offsite_gate_max_km(int) |
前者工程取值;后者待运营(gate 7 默认关) | 本文 §3.4 / PRD §03 |
| 22–24 | recommend.feature_refresh_minutes / .feature_active_window_days / .feature_batch_size(int) |
工程取值(受控集合与批量,铁律 3 轮询三原则) | 本文 §7.2 |
| 25 | recommend.auto_promote_enabled(int) |
0(shadow-first 同构,自动改权重属重大决策) | 基线 §9.1 |
| 26–29 | recommend.calibration_window_days(int)/ .calibration_cron(str)/ .metric_min_samples(int)/ .rollback_alert_streak(int) |
窗口与显著性下限待校准;cron 与告警连击数为工程取值 | PRD §06 / §08 |
计数:新增 29 键(编号 1–29)。其中 14 键无可用初值(4/5/6/8/13/14/15/16/17/18/19/21/26/28)→ 一律以
value="待确认"(str)占位入种子,依赖它的判定短路 + 告警,不编默认值(沿用recommend.calibration_batch_k与grade.travel_category_whitelist的先例);#10 键本身有默认值,仅其第 6 条 gate 待确认;#1–3 以 SOP 五维满分为初值(有基线出处),三轨差异待校准。
| 本文章节 | 子任务 | 建议顺序与理由 |
|---|---|---|
| §4.1 表 schema 草案 | P1-6.1 特征快照库 + 标签库设计→评审→迁移 | ① 最先。四张表(feature_current/feature_snapshot/fulfillment_label/recommend_batch+item)+ 两张飞轮表(§5.1/5.2)一次评审、一个迁移 |
| §4.2 集星/中台/回流 | P1-6.2 数据前置聚合(属地/接单密度/报价/退款率) | ② 复用 enrich 链路,工作量最小、解锁最多下游 |
| §2.2 降级源与序列 | P1-6.3 视频指标序列接入 | ③ 可并行但不阻塞:中台 API 未到就走降级源,特征位先留好 |
| §2 全节 | P1-6.4 推荐引擎一期 | ④ 依赖 6.1~6.3;内部顺序:gates → dimensions → 汇总分档 → 排序沉底 → 探索位 |
| §3 全节 | P1-6.5 估价引擎 | ⑤ 与 6.4 可并行(只依赖 6.2 的报价/属地);先行情锚(数据现成)后收益锚(依赖标签库) |
| §2.4 / §3.5 输出结构 | P1-6.6 console 清单页 | ⑥ 前端依赖本文两个 JSON schema 冻结后开工 |
| §2.5 / §3.1 / §4.3 | P1-6.7 契约测试 | ⑦ 必测:分档边界(85/75/65 各 ±1)| 探索位比例可配(改 cfg 即变)| 双锚取低(构造 market<income 与 income<market 两例)| 快照落库断言 | cfg 占位短路("待确认" 不转 0)| 佣金不入现金 |
| 基线 §6.13 | P1-6.8 验收 | ⑧ 五条判据逐条实证 |
| §5 全节 | 三期(P2 排期) | 飞轮自动化。P1 期只落版本表 + 快照 + 度量报表(人工看),auto_promote_enabled=0 |
模块布局(单模块 ≤200 行有效代码,机检):
app/domains/recommend/
├── router.py / schemas.py(清单生成、决策留痕、单人评分卡)| service.py(批次编排:候选→打分→估价→落库)
├── scoring/{engine,gates,dimensions,evidence}.py | pricing/{engine,market_anchor,income_anchor,distance}.py
├── features/{build,refresh,snapshot}.py | flywheel/{attribution,challenger,promote}.py
└── data/city_centroid.json(静态城市质心,§3.4)
G1/G2 已裁决(主会话拉 SOP 原文核对,2026-08-17):G1——硬门槛六条以 SOP 原文为准(5=拒绝复盘、6=承接条件缺失),"云剪短片号 pass"属 §4 三分钟初筛规则,作为独立的预筛 gate 类别注册(不占硬门槛六条);PRD 该处归类错误待修。G2——评分表就是五项,"六维"是 §5 数据观察维度(数据健康度 30 分的子观察项),权重模板按五项落地,基线 §5.3 已同步修正。(不自补,报告上游)
| # | 问题 | 影响 | 建议处置 |
|---|---|---|---|
| G1 | 一票否决第 6 条两份材料不一致:业务册 §03 写「拒绝复盘」,PRD §4.1 写「云剪短片号」,其余五条可一一对应 | 六条 gate 中有一条无从实现 | 报 product-owner 回查 SOP 原文;未定案前该 gate 不注册,清单标注「硬门槛 5/6 项已判」 |
| G2 | 「六维」实为五维:基线 §5.3 与 PRD §4.1 均称「六维评分表」,但列出的是 5 项(20+30+30+10+10=100) | 命名与实现不符,易误导 | 要么 SOP 有第六维未转录,要么是措辞误差;权重模板设计成可扩展 map,加维只改 cfg 不改代码 |
| G3 | 子项分值分配 SOP 未给(如数据健康 30 分里中位数播放占几分) | 一期分数的绝对值不可信 | 框架先落,分值走 cfg 占位;由运营用真实案例校准(建议:拿 10 个已知好/坏达人反推) |
| G4 | 车马费距离分段口径未给(travel_fee_distance_tiers 仍是 [])+ 层级系数未给 |
行情锚缺车马费项 | 基线 §12A/PRD §10 已列为运营待办;未给前短路为 0 并告警 |
| G5 | calibration_batch_k / calibration_rollback_line 占位「待确认」 |
飞轮无法自动晋升/回滚 | 三期开工前定案;一期只跑度量报表 |
| G6 | 配合度记录机制待业务确认(业务册 §06 两件小事:跳过原因、1–10 分) | 配合度 10 分维度长期 pending_human |
已在业务册请确认清单内,跟踪其答复 |
| G7 | 邀约批次 ↔ 推荐批次的连接键未定(§6) | 漏斗在"采纳→邀约"段断裂 | P1-11.2 开工前拍板(建议加显式列) |
| G8 | 「6,660 达人」量级为上游口径,本文未实证(基线 §8 实测值是达人广场 19,299 行采集数据) | 仅影响性能估算表述 | 性能结论在 1e3–2e4 全区间成立,不受影响 |
docs/product/xijian-recommend-prd.md v1.1 §01–§10 | docs/business/xijian-recommend-biz.md v1.1 §01/§03/§04/§05/§06 | docs/business/xijian-business-baseline.md v2.1 §2/§5.1/§5.2/§5.3/§6.13/§7.1/§7.4/§8/§9.1/§12Adocs/dev/influencer-schema-design.md §0/§2.2/§2.3/§4/§7 | docs/dev/jixing-api-notes.md §1/§2/四坑 | docs/dev/msgbridge-contract.md §2.1/§2.5 | docs/xijian-architecture.md §4.1/§5.2/§5.4/§6.2/§7/§9 | docs/dev/execution-plan.md P1-6server/app/domains/influencers/grade.py(证据+cfg_snapshot+短路先例)、domains/jixing/enrich.py(fingerprint 值变才写)、app/scheduler.py(@scheduled 注册表)、domains/config_center/{cache,seed}.py(cfg 语义与 7 个 recommend 种子键)、app/models/douyin_account.py(metric 字段)uv run python 跑纯 Python 打分基准,N=1000/6660/20000 → 9.9/76.4/264.6 ms),分位数 API 验证 statistics.quantiles([1..10], n=10, method="inclusive")[2] == 3.7| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-08-17 | v1 草案 | 首版:双引擎算法设计(五维映射/硬门槛短路/证据 schema/探索位算法/双锚估价/距离方案)+ 特征流水线四表 schema 草案 + C/C 飞轮两表与限速回滚 + 归因链路 + 运行形态实测 + 29 个新 cfg 键 + 8 项上游缺口。未出迁移、未写代码,待主会话评审 |