XIJIAN DOCS

达人域模型设计

7 张新表逐字段设计/三层判定/导入映射 · ← 返回文档中心

目录
0. 设计前提(定案,不重议)1. 表清单总览(7 张,全部新建)2. 逐表设计3. 三层判定规则(基线 §2 表的落地)4. 分级与推荐指数5. 索引与唯一约束6. 导入映射与地雷规避7. 开放问题与裁决建议8. 给 P0-2.4 的接口预告(域接口草签名)附:信息来源变更记录

达人三层库新模型设计(P0-2.3)

版本:v1 草案 | 2026-08-17 | 作者:backend-dev | 状态:待主会话评审(评审通过后才出 Alembic 迁移,本文只出设计)

上游输入:口径基线 v2.1 §0/§2/§5/§8(唯一口径源)| docs/dev/schema-notes.md(P0-2.2 旧库摸底:8 张达人域旧表逐字段、21 条迁移地雷、8 个开放问题)| docs/dev/jixing-api-notes.md(集星 API 字段与三坑)| docs/dev/msgbridge-contract.md §2.5/§3(binding_mirror 形状)| docs/dev-charter.md v1.3 §4(三分法 + 独立新库)| server/app/models/(现有模型风格)

下游:P0-2.4(influencers 域实现)、P0-2.6(集星富化)、P0-2.7(console 列表页)、P0-3.1(建联匹配/wcid 回流)、P0-5.1(商单模型,回写履约事实)、P1-6(推荐子系统)


0. 设计前提(定案,不重议)

  1. 全新库新口径:所有表建在 xijian / xijian_test(charter §4),旧 lynoxi 表只是一次性导入源,导入后不再依赖;跨库只读仅用于历史成交价/旧订单等(charter §4 三分法「历史旧表」)。
  2. 唯一识别键 = 抖音号 + 手机号(基线 §0);企微加好友只能搜手机号(基线 §0/§6.11)。
  3. jixing_* 100+ 历史快照字段不搬:集星走 API 实时拉(dls.lynoxi.com),本设计只保留「档案级消费字段 + 一份最新指标快照」;P1-6 的特征快照库是另一张表,不在本设计范围。
  4. quality_douyin_account 弃用不迁:新「优质」= 系统按 cfg 阈值自动判定(基线 §2),不设人工优质池。人工重点名单概念留讨论位(§7 S5),本期不建表
  5. 三层 + 旁类是状态判定,不是分表(基线 §2):一套判定字段 + 一个推导函数,落一个 layer 列。
  6. 判定数字零硬编码:全部走 cfg()app/domains/config_center/cache.py),表里只存判定结果 + 判定时间,绝不存阈值。
  7. 写库前判值是否真变(工程铁律 3):指标同步用 fingerprint 比对,层级/分级值未变不写。

命名与类型约定(全表统一)

约定 取值 理由
表名前缀 无前缀(influencer / douyin_account …) charter §4「新表名不再需要 xijian_ 前缀」;xijian_config/xijian_audit_log 是前定案时期的历史命名,不追改。代价与缓解:与 lynoxi 同名 → 纪律「跨库只读一律写全限定名 lynoxi.xxx 且走独立只读 engine」,禁止裸表名跨库
主键 id BIGINT AUTO_INCREMENT 与既有模型一致
枚举 VARCHAR(16/24) + 应用层 StrEnum,全小写下划线;不用 MySQL ENUM 规避地雷 1(旧库大写下划线/小写连字符两套);改枚举值不需要 DDL
金额 BIGINT列名带 _fen 后缀(分) 规避地雷 19 + 基线 §8 单位坑(同表混用分/元差 100 倍),单位写进列名不靠注释
比率 DECIMAL(10,4),标度统一 0–1 规避地雷 8(0-1 与 0-100 混用)
多值 JSON 数组(MySQL JSON / SQLite TEXT),不用逗号分隔 规避地雷 9(旧库 tags JSON、accountTypes 逗号分隔两种解析函数)
时间 DATETIMEcreated_at/updated_at 全表必备(server_default now / onupdate)
软删 deleted_at DATETIME NULL(NULL=未删),走标准 ORM 逻辑删除 规避地雷 13(旧库手写局部 UPDATE 生成无 SET 的坏 SQL)
导入锚 每张有旧库映射源的表带 legacy_id BIGINT NULL UNIQUE 导入脚本按此 upsert → 天然可重跑 + 可对账
外键 不建物理 FK,只建索引 + 应用层保证 跨域回写(商单/结算回写履约事实)与一次性导入的写入顺序自由;软删是主删除路径,CASCADE 无意义。取舍:孤儿行靠导入对账与定期巡检兜底

1. 表清单总览(7 张,全部新建)

# 新表 职责一句话 旧库映射源 口径出处
1 influencer 达人主档(人维度):身份/联系/入驻/分级/推荐指数/黑名单当前态 lynoxi.influencer 基线 §0/§2
2 douyin_account 抖音账号(三层 layer 的唯一落点)+ 档案级集星字段 lynoxi.douyin_account排除全部 jixing_* 快照列 基线 §2/§8、jixing §1
3 douyin_account_metric 账号近 30 天指标最新快照(1:1,高频刷新与档案分离) 不迁旧值;集星 API 重新拉 基线 §8、jixing §1/§2
4 influencer_binding 企微绑定镜像(收编 msgbridge 契约的 binding_mirror,字段超集) 旧库无;来源=消息台 wcid 回流 + 对账接口 契约 §2.5/§3.1
5 influencer_payment_info 收款信息(对公/对私)+ 灵工签约手机号一致校验落点 lynoxi.influencer_payment_info 基线 §7.6
6 influencer_service_region 达人接单地区(1:N) lynoxi.influencer_region 旧表沿用;基线未展开
7 influencer_blacklist 人工拉黑记录(可逆,保留恢复用信用分) lynoxi.blacklist_douyin_account 基线 §2「保留人工口子」

明确不建的表(各有理由,避免评审时误以为遗漏):

不建 理由
优质达人池表 基线 §2 的「优质」是系统自动分级,旧 quality_douyin_account 语义冲突弃用(schema-notes §5#6)。人工重点名单见 §7 S5
达人标签字典表 标签值走 JSON 列 + 选项集合走 cfg(§4 新增键,值待业务确认);建字典表属过度设计
省市区字典表(旧 region 存文本 + 可选行政区划 code(集星给 resident_city_code)已够;级联选择器是前端数据,不落库(§7 S8)
团长表(旧 manager 基线 §0/§6.3「团长只开秒杀、不入库」;团长线在 P1-11 指挥台过渡(§7 S4)
入园/入住缓存(旧 influencer_visitor_info 虽挂达人维度,但语义属商单履约链路 → 归 P0-5.1 商单模型设计
分级历史表 分级变更走既有 xijian_audit_log(action=influencer.grade.change),不新建历史表(YAGNI)
达人档案快照/特征表 P1-6 推荐子系统的特征快照库,本设计不预建

2. 逐表设计

列格式:字段 / 类型 / 口径出处 / 导入映射(旧表.旧字段 → 转换规则=旧库无此概念,新增)

2.1 influencer(达人主档,人维度)

字段 类型 口径出处 导入映射
id BIGINT PK 新生成
legacy_id BIGINT NULL UNIQUE influencer.id(导入幂等锚)
name VARCHAR(64) NULL 旧表沿用 influencer.name
display_name VARCHAR(64) NULL 旧表沿用 influencer.display_name
phone VARCHAR(20) NULL UNIQUE 基线 §0 唯一识别键 / §6.11 企微只能搜手机号 influencer.phone → 去空格/去 +86;空串归一为 NULL(空串会撞唯一键
wechat_id VARCHAR(64) NULL 旧表沿用 influencer.wechat_id(注:企微搜不到微信号,仅存档)
wx_open_id VARCHAR(64) NULL UNIQUE 小程序身份 influencer.open_id
wx_union_id VARCHAR(64) NULL 小程序身份 influencer.union_id
gender TINYINT NOT NULL DEFAULT 0 0未知/1男/2女 influencer.gender
birth_year SMALLINT NULL influencer.age 反推:age <= 0 一律 NULL(地雷 10:抖音拿不到年龄返回 -1)
onboarding_status VARCHAR(16) NOT NULL DEFAULT 'none' 旧四态重命名:none/pending/approved/rejected influencer.onboarding_status 0→none 1→pending 2→approved 3→rejected
onboarding_at DATETIME NULL 无对应,NULL
reject_reason VARCHAR(200) NULL 旧表沿用 influencer.reject_reason
contactable_at DATETIME NULL 基线 §2「已建联」事实字段 派生:有 wx_open_idcreated_at;企微绑定另行回填
contact_channel VARCHAR(16) NULL wecom/miniapp/both 派生(见 §3)
grade VARCHAR(16) NOT NULL DEFAULT 'ungraded' 基线 §2:excellent(优质)/low_recommend(低推荐)/normal(中间地带)/ungraded(未履约) 旧库无此口径 → 导入后按 cfg 重算
grade_at DATETIME NULL 分级值变更时间(非每次计算时间,见 §4)
grade_evidence JSON NULL 基线 §2/§7.4 {roi, cpm_fen, primary_category, sample_order_ids, window_days, cfg_snapshot}——只存证据不存阈值cfg_snapshot 记本次判定读到的阈值值供追溯
roi_recent DECIMAL(10,4) NULL 基线 §7.4 ROI=(成交GMV−达人费用)/达人费用 不迁旧值,按公式重算(地雷 15:0701 公式实测是错的)
cpm_recent_fen BIGINT NULL 基线 §7.4 CPM=达人费用/曝光×1000 同上,重算;单位分
settled_order_count INT NOT NULL DEFAULT 0 基线 §2「已履约」 由旧 task_order.status=11 计数导入;上线后实时查询回写,不做多路径加减(地雷 3:旧计数字段会漂移)
last_settled_at DATETIME NULL 同上 同上
last_deal_price_fen BIGINT NULL 基线 §5.3 估价行情锚 task_order.actual_earnings(最近一笔已结算)→ 元转分
recommend_index DECIMAL(6,2) NULL 基线 §2/§5.3 推荐指数 P1-6 占位,P0 期恒 NULL
recommend_index_at DATETIME NULL 同上 同上
recommend_penalty_reason VARCHAR(64) NULL 基线 §2「CPM>80 降推荐指数、沉底展示并标注原因」 分级计算时写,如 cpm_over_low_recommend_threshold
blacklisted BOOL NOT NULL DEFAULT 0 基线 §2「保留人工口子」 blacklist_douyin_account 反查其账号所属达人置位;明细见表 7
credit_score INT NULL 口径缺口(§7 S7) influencer.credit_score 原样带入;新规则未定前不设默认值、不参与任何判定
admin_remark VARCHAR(500) NULL 旧表沿用 influencer.admin_remark
deleted_at / created_at / updated_at DATETIME deleted=1 → deleted_at=导入时间

不迁的旧字段influencer.id_card(移到表 5,涉税场景才用,R6 权限收敛)、influencer.inviter_id(旧 invite_record 已判死表,邀请关系无消费场景)、influencer.age(改存 birth_year)。

2.2 douyin_account(抖音账号 / 三层落点)

字段 类型 口径出处 导入映射
id BIGINT PK 新生成
legacy_id BIGINT NULL UNIQUE douyin_account.id
douyin_id VARCHAR(64) NOT NULL UNIQUE 基线 §0 唯一识别键 douyin_account.douyin_id → 去空格/小写化;空值行进 reject 清单不导入
douyin_uid VARCHAR(32) NULL UNIQUE jixing §1:集星 API 的调用参数(数字 UID,非抖音号 douyin_account.uid
influencer_id BIGINT NULL 基线 §2:池层/旁类允许为 NULL douyin_account.influencer_id → 映射到新 id;旧值指向不存在达人 → 置 NULL 并记对账
nickname / avatar_url / profile_url VARCHAR(128/512/512) 基线 §8 采集字段(昵称/主页链接) 同名字段;profile_url ← 旧库 douyin_account.homepage_urlP0-2.8b 实证修正:本行原写"旧库无"是笔误,homepage_url 就是源,达人主页链接反选场景要用)
gender TINYINT NOT NULL DEFAULT 0 集星 gender
fans_count / local_fans_count INT NULL 基线 §5.1 品宣硬门槛 ≥20 万;jixing fans_count/local_fans_count fans_countlocal_fans_count 旧库无 → 集星补
item_level / live_level / content_level / overall_level TINYINT NULL 基线 §5.1(品效 LV6+/团购 LV4+);jixing 等级四件套 video_carrying_power_levelitem_levellive_carrying_power_levellive_level-1 归一为 NULL(旧库 -1 渲染为「非团购达人」,不是等级值)
account_types JSON NOT NULL DEFAULT [] 基线 §1 品宣/品效/团购 account_types 逗号分隔 → JSON 数组(地雷 9)
categories JSON NOT NULL DEFAULT [] jixing cooperation_detail.item_great_product_category_list specialty_categories(JSON 串)解析;集星覆盖刷新
primary_category VARCHAR(64) NULL 基线 §2「品类为酒旅(景点票券/游玩项目居首位)」 categories[0];酒旅判定见 §4(类目集合走 cfg,值待确认)
tags JSON NOT NULL DEFAULT [] 基线 §8「达人标签入驻自填、客户按标签一筛即出」 tags(JSON 串)解析
city_name / resident_city_name / resident_city_code VARCHAR(64/64/16) NULL jixing §1:估价距离项 + 硬门槛「异地无法拍摄」 regioncity_nameresident_cityresident_city_namecode 由集星补
credit_score INT NULL jixing credit_score 集星
status VARCHAR(16) NOT NULL DEFAULT 'active' active/invalid(旧三态无审核流程,实为二值) status 0/1→active、2→invalid
is_primary BOOL NOT NULL DEFAULT 0 旧表沿用(一人多号时的主账号) is_primary
is_leader_reported BOOL NOT NULL DEFAULT 0 基线 §2 旁类「团长报来的临时抖音号」 旧库无 → 全部 0;团长线录入时置 1
layer VARCHAR(16) NOT NULL DEFAULT 'pool' 基线 §2:pool/contacted/fulfilled/side_partner 导入后统一按 §3 规则计算
layer_at DATETIME NULL 层级值变更时间
first_settled_order_at DATETIME NULL 基线 §2「至少一个商单走到已结算」 由旧 task_order.status=11 AND douyin_account_id=? 取最早时间
source VARCHAR(24) NOT NULL 入库来源:pool_crawl/jixing/miniapp/wecom_bind/manual/leader_report 旧库无 → 一律 legacy_import,再按有无 influencer 细分
source_batch VARCHAR(64) NULL 采集批次/邀约批次追溯
jixing_synced_at DATETIME NULL jixing §1
remark VARCHAR(500) NULL remark(同名字段直迁,管理员备注;P0-2.8b 实证修正:本行原空白导入映射是笔误)
deleted_at / created_at / updated_at DATETIME 同 2.1

不迁:全部 jixing_*(约 100 列,走 API)、work_count(地雷 7:字段已不存在)、旧 GMV 类字段(基线 §8 单位存疑,只放行已验证的 → 一律从集星重拉,见 2.3)。

2.3 douyin_account_metric(近 30 天指标最新快照,与账号 1:1)

为什么单独一张表:档案字段稳定、指标字段日更;分表后同步任务只写这一张,主表不被高频 UPDATE 污染(旧仓 binlog 日增 5G 的教训,铁律 3)。

字段 类型 口径出处 说明
id BIGINT PK
account_id BIGINT NOT NULL UNIQUE 1:1;不建物理 FK
window_days SMALLINT NOT NULL DEFAULT 30 jixing §2 default_item_date_n=30 口径窗口显式化
gmv_fen BIGINT NULL jixing talent_item_gmv 单位=分(实测样本 value=44.21万digit_value=44214338,44214338 分 = 44.21 万元;取 digit_value 不取 value,jixing 坑 1)
video_avg_gmv_fen BIGINT NULL jixing talent_video_avg_gmv 基线 §5.2 评分卡「稿均销量」
video_avg_vv INT NULL jixing talent_video_avg_vv 基线 §5.2「近 30 天稿均播放量」
video_gpm_fen BIGINT NULL jixing talent_video_gpm 稿均千次曝光 GMV
video_avg_finish_rate DECIMAL(10,4) NULL jixing talent_video_avg_finish_rate 标度 0–1(地雷 8)
video_avg_anchor_click_rate DECIMAL(10,4) NULL jixing talent_video_avg_anchor_click_rate 同上
item_cnt INT NULL jixing talent_item_cnt 接单密度参考
video_avg_like_cnt / _comment_cnt / _share_cnt INT NULL jixing 互动均值
verify_amount_fen BIGINT NULL 基线 §8 采集口径「30 天核销额」 集星未提供 → 来源为来客/中台;未验证前保持 NULL,不猜
quotation_min_fen / quotation_max_fen BIGINT NULL jixing quotations 基线 §5.3 估价行情锚直接数据源
source VARCHAR(16) NOT NULL jixing/laike/manual 逐来源可追溯
fetched_at DATETIME NOT NULL 取数时间(爬坡保护判定用,基线 §8 满 7 天)
fingerprint CHAR(32) NOT NULL 铁律 3 本轮全部指标值的 hash;与库中相同则整行不写(零 UPDATE、零 binlog)
updated_at DATETIME

2.4 influencer_binding(企微绑定镜像,收编契约 binding_mirror

契约 §2.5 要求的字段 (influencer_id, guid, peer_user_id, conv_id, source, synced_at) 全部包含,本表是其超集实现(表名差异见 §7 S6)。

字段 类型 口径出处 说明
id BIGINT PK
influencer_id BIGINT NOT NULL 契约 §2.5 反查索引(消息台缺口 2 的犀见侧自解)
guid VARCHAR(64) NOT NULL 契约 §2.5 我方企微账号
peer_user_id VARCHAR(64) NOT NULL 契约 §2.5 企微外部联系人 userId
conv_id VARCHAR(64) NULL 契约 §2.5 会话 id(GET /api/conv-of 结果缓存)
wcid VARCHAR(128) NULL 契约 §2.1 批次归因键 达人点卡片后回流;(batch_id, influencer_id) 归因靠它
batch_id VARCHAR(64) NULL 契约 §2.1/基线 §6.9 判据 2 邀约批次追溯
source VARCHAR(16) NOT NULL 契约 §3.1/§3.2 wcid / manual / reconcile(manual 优先级最高,不被自动覆盖)
active BOOL NOT NULL DEFAULT 1 解绑=置 0,不物理删(留痕)
bound_at DATETIME NULL 绑定发生时间
synced_at DATETIME NOT NULL 契约 §4.3 水位 对账轮询游标
created_at / updated_at DATETIME

唯一约束 uq(guid, peer_user_id):一个企微身份在一个我方账号下只绑一个达人(与消息台 peer_binding 同语义)。一个达人可有多条(多个我方账号加过他)。

2.5 influencer_payment_info(收款信息,1:N)

字段 类型 口径出处 导入映射
id / legacy_id BIGINT influencer_payment_info.id
influencer_id BIGINT NOT NULL 映射新 id
account_type VARCHAR(16) NOT NULL 基线 §7.6 PERSONALpersonalCORPORATEcorporate(地雷 1:统一小写)
account_holder VARCHAR(64) NULL account_name
bank_name / bank_branch / bank_card_no VARCHAR(128/128/32) NULL 同名
id_card VARCHAR(32) NULL 基线 §7.6 灵工签约/个税按身份证 influencer.id_card 迁入此处(R6 权限收敛:达人档案主表不再存)
payee_phone VARCHAR(20) NULL 基线 §7.6「表中手机号必须与签约手机号一致」 旧库无独立字段 → 导入取 influencer.phone并标 phone_source='inherited' 供后续人工核对
company_name / tax_no VARCHAR(128/32) NULL 基线 §7.6 对公通道 同名;corporate 时必填(应用层校验)
is_default BOOL NOT NULL DEFAULT 0 一人多条时的默认收款方式
verified_at / verify_note DATETIME / VARCHAR(200) NULL 与灵工签约记录比对结果(结算域 P1-9 写)
deleted_at / created_at / updated_at DATETIME

权限:本表读取挂新权限码 influencer.payment.read;列表接口一律掩码(卡号/身份证只回后 4 位)。依据基线 §9.3 R6「底价/达人库字段权限收敛」。

2.6 influencer_service_region(接单地区,1:N)

字段 类型 说明
id / legacy_id BIGINT influencer_region.id
influencer_id BIGINT NOT NULL
province / city / district VARCHAR(32) NULL 旧库是纯文本,原样迁
region_code VARCHAR(16) NULL 行政区划码(集星 resident_city_code 可补),为将来级联/距离计算预留
created_at DATETIME

唯一约束 uq(influencer_id, province, city, district) 防重复录入。

2.7 influencer_blacklist(人工拉黑记录,可逆)

字段 类型 口径出处 导入映射
id / legacy_id BIGINT blacklist_douyin_account.id
influencer_id BIGINT NULL 由旧表账号反查所属达人
douyin_account_id BIGINT NULL douyin_account_id 映射新 id(旁类账号无达人时只填这个
reason VARCHAR(200) NOT NULL 基线 §2「恶劣个案人工手动拉黑」 reasonP0-2.8b 实证修正:本行原写源字段 remark 是笔误,旧表 blacklist_douyin_account 该列真实名是 reason,源码见 BlacklistDouyinAccount.java);空则填 legacy_import
previous_credit_score INT NULL schema-notes §2.1「取消拉黑时恢复信用分的可逆设计要保留」 previous_credit_score
created_by / created_at VARCHAR(64) / DATETIME created_by
revoked_at / revoked_by / revoke_reason DATETIME / VARCHAR(64) / VARCHAR(200) NULL 取消拉黑=写这三列,不物理删

约束:应用层校验 influencer_iddouyin_account_id 至少一个非空(两列都可空,DB 级 CHECK 可选加 ck_influencer_blacklist_target)。生效中的黑名单 = revoked_at IS NULLinfluencer.blacklisted 是它的冗余当前态,值真变才写


3. 三层判定规则(基线 §2 表的落地)

3.1 判定落点:layer 唯一挂在 douyin_account

理由:池层(有信息、未建联)的实体只有账号没有人influencer_id IS NULL),层级若挂人维度则池层无处安放。人维度不存 layer,避免两份真相;需要「人属于哪层」时由服务层函数实时算(见 3.3)。

3.2 推导规则(纯函数,输入全是事实字段)

def resolve_layer(acc, inf) -> str:
    # 优先级自上而下,命中即返回
    if acc.is_leader_reported and acc.influencer_id is None:
        return "side_partner"      # 旁类:已合作未建联(基线 §2 旁类行)
    if acc.first_settled_order_at is not None:
        return "fulfilled"         # 已履约:至少一个商单走到已结算
    if inf is not None and is_contactable(inf):
        return "contacted"         # 已建联:企微或小程序可触达
    return "pool"                  # 达人广场池:有信息、未建联

def is_contactable(inf) -> bool:
    return bool(inf.wx_open_id) or EXISTS(influencer_binding
                                          WHERE influencer_id=inf.id AND active=1)
判定事实(基线 §2) 落库字段
达人广场(池) pool 有信息、未建联 兜底分支
已建联 contacted 加上企微 小程序完成绑定 influencer.wx_open_id / influencer_binding.active=1
已履约 fulfilled 至少一个商单走到已结算 douyin_account.first_settled_order_at(商单域 P0-5 回写)
旁类 已合作未建联 side_partner 团长报来的临时抖音号、无联系方式 is_leader_reported=1 AND influencer_id IS NULL

关键说明: - 旁类不会与 fulfilled 打架:旁类走团长线、不产生犀见商单,first_settled_order_at 天然为 NULL;且一旦补齐联系方式并建了人(influencer_id 非空),判定自动升级,不需要人工改标。 - 有手机号 ≠ 已建联:手机号只是可发好友申请的前提(基线 §6.11),未加上企微仍是 pool——这正是「每周新增 500 名进企微」KPI 的目标池:layer='pool' AND influencer.phone IS NOT NULL。 - 无手机号也可能已建联:企微不给外部联系人手机号(契约 §2.5 实测),wcid 回流绑定的达人可能永远没有 phone。所以 phone 必须可空,见 §5。

3.3 人维度层级(不落库)

def layer_of_influencer(inf) -> str:
    accs = 其名下未软删账号
    if accs: return max(acc.layer, key=层级序)        # 序:fulfilled > contacted > side_partner > pool
    return "contacted" if is_contactable(inf) else "pool"   # 孤儿达人(有人无号,旧库存在)

3.4 重算触发时机(写前判值真变)

触发 来源 动作
绑定写入/失效 P0-3.1 wcid 回流、契约 §4.3 对账轮询 重算该达人名下全部账号
小程序绑定 P0-3.1 同上
商单进入已结算 P0-5 状态机事件 回写 first_settled_order_at(只在原值为 NULL 或更早时写)+ 重算该账号
采集/导入新账号 P0-2.6 新行计算一次
团长线录入 P1-11 is_leader_reported 后重算

纪律layer 值未变则整行不 UPDATE(连 layer_at 也不动);layer_at 语义 = 层级变更时间,不是「上次计算时间」。计算过程走结构化日志,不落库。

3.5 旁类隔离(不占建联流程)

统一由一个查询 helper 收口,不散落在各处 where 里:

# app/domains/influencers/scope.py
def outreach_scope(q):      # 建联/邀约/加好友/催发类名单一律先过这个过滤器
    return q.where(DouyinAccount.layer != "side_partner",
                   DouyinAccount.deleted_at.is_(None))

覆盖场景:②A 定向邀约名单(基线 §6.9)、批量好友申请(§6.11)、一键催发(§6.10)、推荐清单候选(§5.3)。旁类账号仅出现在「数据归档/对账」类视图,并在 console 三层 tab 中独立成一个 tab(不混入已建联)。


4. 分级与推荐指数

4.1 分级判定(基线 §2 + §7.4,全部阈值走 cfg)

roi  = (成交GMV_fen - 达人费用_fen) / 达人费用_fen          # 基线 §7.4,绝不用旧库存的 roi 列(地雷 15)
cpm_fen = 达人费用_fen / 曝光数 * 1000                      # 基线 §7.4

if cpm > cfg("grade.cpm_low_recommend_threshold"):   grade = "low_recommend"
     recommend_penalty_reason = "cpm_over_low_recommend_threshold"     # 降推荐指数、沉底,不拉黑
elif roi > cfg("grade.roi_excellent_min") \
     and cpm < cfg("grade.cpm_excellent_max") \
     and primary_category ∈ cfg("grade.travel_category_whitelist"):    grade = "excellent"
else: grade = "normal"

4.2 计算触发时机

触发 说明
商单进入已结算 该达人重算
视频/订单指标日更(数据域) 只对已履约达人重算,且仅当参与判定的输入值变化
cfg 阈值被改 配置写接口触发缓存失效 → 由夜间批任务(APScheduler)全量重算;不在写配置的请求路径里现算全量
人工触发 console 详情页「重算分级」按钮(P0-2.7 可选)

判值真变grade 未变 → 不写 grade/grade_at;但 grade_evidence 内的 roi/cpm 变化属正常刷新,允许更新(它是证据不是判定结果)——若怕高频写,evidence 也用 fingerprint 比对(与 2.3 同一手法),建议采纳。

4.3 推荐指数(P1-6 占位)

本期只建字段不算值:recommend_indexrecommend_index_atrecommend_penalty_reason。P0 期 recommend_index 恒 NULL,console 显示「—」。recommend_penalty_reason 在 P0 就写(分级顺带产出),供 P1-6 沉底排序直接消费。

4.4 本设计需要的 cfg 键

状态 出处
grade.roi_excellent_min(10) ✅ 已在种子 基线 §2/§12A
grade.cpm_excellent_max(20) ✅ 已在种子 基线 §2/§12A
grade.cpm_low_recommend_threshold(80) ✅ 已在种子 基线 §2/§12A
data.ramp_protection_days(7) ✅ 已在种子 基线 §8/§12A
grade.travel_category_whitelist ⚠️ 新增,值待业务确认 基线 §2 只给了「酒旅(景点票券/游玩项目居首位)」的描述,未给可判定的类目集合;集星类目名如「酒店宾馆/景点票券」。不自补口径——建议以 value_type=jsonvalue="待确认" 占位入种子(沿用 recommend.calibration_batch_k 的占位先例),确认前 excellent 判定短路为 false 并在日志显式告警
influencer.tag_options ⚠️ 新增,值待业务确认 基线 §8 只说「标签入驻时自填」,未定选项集合;旧库 2026-07 改版有六项(亲子/情侣/攻略/国风/颜值/外国)但那是旧口径,不能直接当基线

两个新键都不进本次迁移的判定路径(占位即可),确认后由配置中心补种子迁移。


5. 索引与唯一约束

5.1 唯一键落地(基线 §0「抖音号 + 手机号」)

组合键在关系模型里自然分解为两个可空唯一键,靠 influencer_id 关联:

约束 说明
uq_douyin_account_douyin_id douyin_account 抖音号全局唯一、NOT NULL(池/旁类最少也有抖音号)
uq_douyin_account_douyin_uid douyin_account 集星 UID 唯一、可空(采集时未必有;MySQL 唯一索引允许多个 NULL)
uq_influencer_phone influencer 手机号唯一、可空
uq_influencer_wx_open_id influencer 小程序 openid 唯一、可空
uq_influencer_binding_peer influencer_binding (guid, peer_user_id)
uq_*_legacy_id 各有映射源的表 导入幂等

中间态一览(都是合法状态,不能用 NOT NULL 堵)

抖音号 手机号 典型来源 层级
只有抖音号 ✗(连 influencer 都没有) 达人广场采集 / 团长报送 pool / side_partner
有号有人无手机号 企微 wcid 回流绑定(企微不给外部联系人手机号) contacted
有人有手机号无抖音号 旧库孤儿达人 / 先加好友后补号 人维度算 contacted,不出现在账号三层列表
齐全 小程序入驻 contacted / fulfilled

应用层校验(DB 无法表达):influencer 至少要有 phone/wx_open_id/有效 binding 三者之一才允许创建;否则拒绝(否则会造出永远匹配不上的幽灵档案)。

匹配与冲突(P0-2.4/P0-3.1 共用): 1. 先按手机号查 influencer,再按抖音号查 douyin_account; 2. 两边都命中且 account.influencer_id != influencer.id冲突:返回 envelope code=409(HTTP 200,先例 app/domains/system/roles_service.py:20;P0-2.8 所写「唯一键冲突 409」即此 code,非 HTTP 状态码),并给出双方 id 供人工合并; 3. 只命中一边 → 建联即补齐另一边(不新建重复档案); 4. 都没命中 → 新建(P0-3.1 的「未命中建档进已建联」)。

软删与唯一键:软删行仍占用唯一号(deleted_at 不参与唯一索引,MySQL 无部分索引)。重新入库 = 复活原行(deleted_at=NULL),导入脚本按 legacy_id/douyin_id upsert 天然满足。

5.2 索引清单

索引 用途
douyin_account ix(layer, grade_proxy)→实际为 ix(layer)ix(influencer_id)ix(status)ix(fans_count)ix(updated_at) 三层 tab 筛选、按人反查、列表排序、增量同步
douyin_account ix(primary_category) 品类筛选(客户「亲子/酒旅类达人一筛即出」,基线 §8)
influencer ix(grade)ix(recommend_index)ix(blacklisted)ix(updated_at) 分级筛选、推荐排序(P1-6)、黑名单排除
douyin_account_metric uq(account_id)ix(fetched_at) 1:1、爬坡保护取数
influencer_binding uq(guid, peer_user_id)ix(influencer_id)ix(wcid)ix(synced_at) 反查(契约缺口 2)、归因、对账游标
influencer_payment_info ix(influencer_id)ix(id_card) 结算域按身份证匹配灵工签约(基线 §7.6)
influencer_service_region uq(influencer_id, province, city, district) 防重
influencer_blacklist ix(influencer_id)ix(douyin_account_id)ix(revoked_at) 生效中黑名单查询

模糊搜索(昵称/姓名 kw):本量级(万级)用 LIKE 'kw%' 前缀索引即可,不引全文索引(YAGNI);LIKE '%kw%' 走全表在万级可接受,超 10 万行再议。


6. 导入映射与地雷规避

6.1 导入步骤(一次性、可重跑、有对账)

  1. 前置核对(解 schema-notes §5#1/#2):连 lynoxi_testinformation_schema.columns 导出达人域 8 表真实列清单,与 schema-notes 的源码推导逐列比对,差异先记录再导入;同时确认「达人广场 19,299 行采集数据是否就在 douyin_account」。
  2. 只读跨库:独立只读 engine + 只读账号,SQL 一律全限定名 lynoxi.xxx;分页按主键游标WHERE id > ? ORDER BY id LIMIT n),禁 OFFSET 深分页(schema-notes §4-20)。
  3. 顺序influencerdouyin_account(补 influencer 映射)→ payment_info/region/blacklist → 计算 layer → 重算 ROI/CPM → 计算 grade
  4. 幂等:全部按 legacy_id upsert;逐字段比对,值未变不写(铁律 3)。
  5. 对账产出(脚本必打印,qa 验收看这个):各表源行数/成功/跳过/reject 数、字段级空值率、influencer_id 悬空数、抖音号缺失数、重复手机号数、layer 分布、grade 分布。
  6. reject 清单落文件不静默丢:空抖音号、脏 UTF-8、重复唯一键、age=-1 等异常各成一节。
  7. 集星富化独立于导入(P0-2.6):导入只搬旧库有的、已验证的;指标一律事后从集星 API 拉。

6.2 地雷逐条对应(schema-notes §4 全 21 条)

# 地雷 本设计的规避措施
1 枚举大小写/命名不一致 全域枚举小写下划线 + 应用层 StrEnum;导入时统一映射(PERSONALpersonal
2 单条结算接口是 bug 商单/结算域,本域不适用(留 P0-5.1)
3 计数字段会漂移 settled_order_count 不做多路径加减,只由「已结算」事件按实时查询回写;导入时按旧订单实查而非搬旧计数
4 时间格式两种 落库统一 DATETIME;序列化统一由 pydantic 输出 ISO(envelope 层统一,不各写各的)
5 task_order 无 projectId 商单/项目域,本域不适用
6 任务状态 2 是派生态 商单域不适用;本域同类纪律:layer/grade存下来的判定结果,派生视图(人维度层级)不落库
7 达人「作品数」是死字段 新模型无 work_count,指标一律在 douyin_account_metric
8 百分比标度两套 比率列统一 DECIMAL(10,4)、标度 0–1,列名不带 _pct
9 标签两种格式 tags/categories/account_types 全 JSON 数组,导入时把逗号分隔串解析归一
10 年龄可能是 -1 不存 age,存 birth_yearage<=0 → NULL
11 脏 UTF-8 字节 导入用 errors="replace" 解码 + 逐行 try/except,脏行进 reject 清单并记录原始 bytes 长度
12 脱敏须保持跨表映射一致 导入脚本用统一 idmap/usermap(同一 legacy 值映射同一新值),身份证/卡号脱敏在测试库时按值映射不按行 id
13 软删曾生成坏 SQL deleted_at + 标准 ORM 逻辑删除,禁止手写局部 UPDATE
14 /admin/feishu/** 端点不能动 本设计零写旧库,旧 api 的 8 个定时任务(含「优质达人自动入池」写 quality_douyin_account)继续跑不受影响
15 0701 ROI 公式错(算成费用 MAX) ROI/CPM 一律按基线 §7.4 重算,旧 work_review_record.roi/cpm 两列整列丢弃不迁
16 review_type 注释过时 商单域不适用(本域不引用该表)
17 SUBMIT_FAIL 第 6 态 结算域不适用
18 task_order ↔ feishu_execution_record 无外键 结算域不适用
19 时长单位 ms/s 混用 全域单位写进列名_fen/_ms/_sec/_days
20 上线前必补索引 见 §5.2;跨库只读旧表的查询(导入期)走主键游标不依赖旧索引
21 灵工系数 1.06 vs 1.062 不一致 本设计不落任何系数(结算域的事);payment_info 只存账户信息

6.3 ROI/CPM 重算的数据来源(导入期)

来源 单位处理
达人费用 lynoxi.task_order.actual_earnings(已结算单);为空则 expected_earnings 不采用(schema-notes §2.2:expectedEarnings 会被列表页动态重算覆盖,库值不可信) 元 → 分
成交 GMV lynoxi.work_review_record.gmv 单位存疑 → 导入前抽样人工核对,核对不通过则该达人 ROI 置 NULL 并记 reject,不猜
曝光 lynoxi.work_review_record.play_count 整数
时间窗 视频发布满 cfg("data.ramp_protection_days") 基线 §8

旧库已结算订单仅 13 条(schema-notes §2.2 实测)——历史 ROI/CPM 样本极稀,导入后绝大多数达人 grade='ungraded'预期结果,不是 bug;qa 验收按此口径核对。


7. 开放问题与裁决建议

主会话评审结论(2026-08-16):设计通过,S1–S11 全部按建议倾向裁决采纳。其中 S2/S5 已获用户定案(2026-08-16 二批确认):S2 白名单=酒店宾馆/景点票券/游玩项目(可配,excellent 短路解除);S5 建 influencer_watchlist(用户:可以做且有必要,命名禁用「优质」);S7(credit_score 新口径)仍待业务;S6 契约加注已批准执行;S11 权限码扩充已批准。放行后半段:出 Alembic 迁移与模型代码。 状态原图例:待裁决=需主会话拍板 | 待业务=口径缺口需产品/业务确认 | 已自决=本设计已定

# 问题 建议倾向 状态
S1 标签挂账号还是(schema-notes §2.1 提出:旧库达人级完全无标签字段) 账号douyin_account.tags)。理由:标签是内容属性(品类/风格),一人多号风格可不同;采集与集星数据天然账号维度。筛「亲子类达人」= 账号标签 join 到人 待裁决
S2 「酒旅品类」的可判定类目集合(基线 §2 只给描述,未给枚举) 新增 cfg 键 grade.travel_category_whitelist值待业务给;确认前 excellent 判定短路 false + 日志告警。不编默认值 待业务
S3 分级/推荐指数落维度(本设计如此)还是账号维度 :业务动作(「下轮优先复用」「沉底」)针对人;②A 邀约候选来自已建联达人库(必有 influencer);旁类账号不分级 待裁决
S4 团长主档是否建表(旧 manager 表,与 influencer 同构) 本期不建。基线 §0/§6.3「团长只开秒杀、不入库」;团长线走 P1-11 指挥台过渡,其报来的抖音号进旁类 待裁决
S5 人工「重点关注名单」是否需要(schema-notes §5#6;旧 quality_douyin_account 弃用后的空位) 本期不建表,留讨论位。若业务确需,用独立命名 influencer_watchlist禁止复用「优质」一词,该词已被基线 §2 的自动分级占用) 待业务
S6 绑定镜像表名:influencer_binding(本设计)vs 契约 §2.5 字面的 binding_mirror influencer_binding(达人域命名统一、字段是契约超集),并在 msgbridge 契约 §2.5 加一行注明实现表名。改契约文档需主会话批准,故列此 待裁决
S7 credit_score 的新口径(初值/加减规则/与黑名单恢复的关系) 基线无口径 → 本期只原样导入旧值、可空、不参与任何判定;新规则定案前不写默认值 待业务
S8 省市区字典表是否需要(旧 region 表) 不建:存文本 + region_code 预留;级联选择器用前端静态数据 已自决(可推翻)
S9 达人广场 19,299 行采集数据的真实落表(schema-notes §5#2) 导入前连 lynoxi_test 实查确认(§6.1 步骤 1);若存在另一张未发现的采集表,映射源需补 待裁决(导入前必须闭环)
S10 msgbridge_touch_log.influencer_idString(64),新主键是 BIGINT 不改已上线表:服务层写 str(id),不建 FK。理由:改列型要动已迁移表,收益仅为类型美观 已自决(可推翻)
S11 敏感字段权限(身份证/银行卡,基线 §9.3 R6) 新增权限码 influencer.payment.read;列表默认掩码,明文需权限 + 审计打点 待裁决(涉及 RBAC 权限码清单扩充)

8. 给 P0-2.4 的接口预告(域接口草签名)

模块布局(单模块 ≤200 行,路由/服务/模型分层):

app/domains/influencers/
├── router.py        # 达人档案 CRUD 路由
├── accounts_router.py  # 抖音账号路由
├── service.py       # 档案读写(唯一键匹配/冲突 409)
├── layer.py         # 三层判定纯函数(§3.2)+ 重算入口
├── grade.py         # 分级计算(cfg 取阈值,§4.1)
├── scope.py         # outreach_scope 等共享查询过滤器(§3.5)
└── schemas.py       # pydantic 模型即文档

REST 草签名(全部走 envelope,业务错误 HTTP 200 + success:false):

方法 路径 说明
GET /api/admin/influencers 分页列表:kw(姓名/手机/昵称)/grade/blacklisted/layer(按人维度聚合)
GET /api/admin/influencers/{id} 档案详情(含账号列表、绑定、地区;付款信息掩码)
POST /api/admin/influencers 建档;唯一键冲突 → code 409
PUT /api/admin/influencers/{id} 编辑(值真变才写 + 审计)
DELETE /api/admin/influencers/{id} 软删
GET /api/admin/influencers/search 搜索选择器专用(轻返回:id/姓名/手机/主账号),解 core-modules「账号绑定靠手填数字 id」的老问题
POST /api/admin/influencers/{id}/blacklist 拉黑(记 previous_credit_score)
DELETE /api/admin/influencers/{id}/blacklist 取消拉黑(恢复信用分,可逆)
POST /api/admin/influencers/{id}/recompute-grade 手动重算分级(返回新旧值与证据)
GET /api/admin/douyin-accounts 分页列表:layer tab / kw / category / level_min / has_phone
GET /api/admin/douyin-accounts/{id} 账号详情(含最新指标快照 + 取数时间)
POST/PUT /api/admin/douyin-accounts[/{id}] 建/改(influencer_id 经搜索选择器传入)
POST /api/admin/douyin-accounts/{id}/bind 绑定/换绑达人(触发 layer 重算)
GET /api/msgbridge/influencer 契约 §3.4 供给侧:?douyin_id=|phone=|influencer_id= → 档案(基线 §6.8 判据)

域内函数签名(供 P0-3.1/P0-5 调用,不走 HTTP):

layer.resolve(account, influencer) -> str                  # §3.2 纯函数
layer.recompute_for_influencer(session, influencer_id) -> int   # 返回真正变更的行数
layer.mark_settled(session, account_id, settled_at) -> bool     # 商单域回写履约事实,值真变才写
grade.evaluate(session, influencer_id) -> GradeResult      # cfg 取阈值,返回 grade+evidence
match.find_by_unique_key(session, phone=None, douyin_id=None) -> MatchResult  # 命中/冲突/未命中

附:信息来源

变更记录

日期 版本 变更
2026-08-17 v1 草案 首版:7 张新表逐字段设计 + 三层/分级判定规则 + 唯一键中间态 + 21 条地雷对应 + 11 项开放问题 + P0-2.4 接口草签名。未出迁移、未写代码,待主会话评审
2026-08-16 v1.1 P0-2.8b 导入脚本实证修正 3 处笔误(主会话裁决):§2.2 douyin_account.profile_url 源改为旧库 homepage_url(原写"旧库无"有误);§2.2 remark 补上同名字段导入映射(原空白);§2.7 influencer_blacklist.reason 源字段改为 reason(原误写 remark,旧表无此列)