Commit 5c7e097b by luoqi

docs(algorithm): 重写画像层 v2 为实现版(13 标签 + 架构 + 算法审查发现)

旧版停在早期设计(RFM几何八象限/简化lifecycle/圈人群),跟最终实现对不上。
重写为:画像层定位 + persona schema + 4 数据范式 + 时间语义 + Feature Registry(指向
persona-feature-specs.ts 单一源)+ 13 标签总览表 + 算法审查发现/口径决策(RFM图口径/
lifecycle顺序bug/discount金额非比率/TZ/no_show不在active等)+ follow-up + 部署清单。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 90ca7f57
# 画像层设计 v2 — 时间语义 / Feature Registry / RFM 八象限 / 圈人群
# 画像层 v2(实现版)
> **版本**:W7 · 2026-06 · 把画像从"被动给召回打分 + 话术摘要标签"升级成"有治理、有时间语义、可圈人群运营"的特征体系。
> **关联**:[`db-design-v2.md`](../db-design-v2.md)(personas/persona_features 表)· [`pac-algorithms-overview.md`](./pac-algorithms-overview.md)(6 因子打分)· `priority-scorer.ts`(消费方)· `canonical-codes.ts`(字典单一源)
> **版本**:W7 · 2026-06 · 反映**已实现的 13 个画像标签** + 架构 + 算法审查发现。
> **单一真理源**:每个标签的 标签值/数据源/释义/算法/时间语义 在代码注册表
> [`packages/types/src/persona-feature-specs.ts`](../../packages/types/src/persona-feature-specs.ts)(`PERSONA_FEATURE_SPECS`);
> extractor 在 `apps/pac-service/src/modules/persona/features/*.feature.ts`。本文是**人读总览 + 决策记录**,不复制每字段。
> **关联**:[`db-design-v2.md`](../db-design-v2.md)(personas/persona_features 表)· [`pac-algorithms-overview.md`](./pac-algorithms-overview.md)。
>
> **一句话**:画像 = 对全历史的「压缩当前态」,每个特征各自声明时间语义;RFM 八象限统一现有的 value+risk;特征带结构化 value 支持按标签圈人群批量召回
> **一句话**:画像 = 对全历史的「压缩当前态」,每标签声明自己的时间语义;13 标签按业务 CDP 标签卡口径实现,口径沉淀在代码注册表;过程中审出并修了多处上游/spec 问题
---
## 〇、为什么重构(现状诊断)
现状画像很「轻」:`persona_feature = key + description + score + evidence + version`,4 个起步特征(value / treatment_chain_status / recall_risk / do_not_contact_status),只被两处消费——召回 6 因子打分(valueBonus/likelihoodBonus)+ 话术输入摘要。
| 相对大厂成熟方案缺的 | 本设计补法 |
|---|---|
| 特征没声明**时间语义**(当前态/窗口/全历史) | §一 时间语义模型,每特征在 Registry 声明 |
| 没有**特征注册表/元数据**(口径散在代码) | §二 Feature Registry(OneModel 单一收口) |
| 几乎没有**统计/模型层**特征 | §四 RFM 八象限 + §五 lifecycle_stage |
| 特征只有散文,**圈不了人群** | §三 schema 加 typed `value` JSONB |
| 用途只「被动打分」 | §六 圈人群 → 批量召回 campaign |
| 质量无监控 | §七 覆盖率/时效/漂移 |
**保留不动**:`key+description+evidence+version+eventWatermark` 这套骨架——等于 Feature Store 的 point-in-time + 血缘 + 版本的轻量版,是对的。**重做的是肉**:特征集、时间语义、治理、用途。
---
## 一、时间语义模型(画像的「时效性」怎么解释)
**画像不是单一时态。三种时间语义同时存在,每个特征各自声明。**
| 时间语义 | 含义 | 牙科例子 | 计算 |
## 一、画像在三层模型的位置
事实层(`patient_facts`)→ **画像层(`personas`/`persona_features`)** → 召回层(`followup_plans`)。
画像是**对全历史 fact 的压缩**(facts=raw context,persona=hidden state),回答「现在这个人是什么样」。历史明细留 fact 层,**画像不存明细**;要看历史时点画像 → 读 `personas` 版本流(`version + computedAt + eventWatermark` = point-in-time)。
## 二、persona schema(怎么存)
`persona_features` 每行 = 一个标签:
- `key` — 标签 key(注册表闭集)
- `description` — 自然语言(给 LLM / 详情页展示)
- `data` (JSONB) — **结构化值**(机器消费:打分 / 圈人群 / 筛选)。这是"是什么"。
- `evidence` — 证据(factIds/ruleIds…)。这是"为什么"。
- `score`**已弃用语义**(分值是消费场景的事,不在特征层;消费方从 `data` 自算)。
`personas`:`version` 版本流 + `eventWatermark`(算到哪个事件,判 stale)。
## 三、4 种数据范式(标签从哪来)
| 范式 | 说明 | 标签 | 部署成本 |
|---|---|---|---|
| `snapshot` 当前态(慢变维) | 属性的「现在值」,覆盖 | 勿扰、归属诊所、在治状态 | 取最新 |
| `window` 窗口聚合 | 限定时间窗内 | 近 24 月就诊次数、近 90 天消费 | 窗口内 agg,窗口长度是参数 |
| `lifetime` 全历史累计 | 跨全部历史 | 累计净消费(LTV)、首诊时间 | 全量 agg |
| `trend` 趋势变化 | 两窗口对比 | 就诊频次变稀(流失前兆) | 窗口相减 |
**两条铁律**:
1. **画像存「压缩特征」,不存历史明细。** 画像回答「**现在**这人是什么样」,是从全历史压缩出来的。全量明细留在 `patient_facts`(事实层)。"看全部历史"是事实层的活,不是画像的活。(对应设计哲学:facts=raw context,persona=压缩 hidden state)
2. **时点回放靠版本流。** 画像本身是当前态;要看「上个月怎么判的」→ 读历史 `personas` 版本(`version + computedAt + eventWatermark` = point-in-time correctness,不穿越未来)。
> **RFM 正是三种时态的融合**:R=当前态(距上次多久)/ F=窗口(近 N 月频次)/ M=全历史(累计消费)。一个模型同时表达「最近、频次、终身价值」。
---
## 二、Feature Registry(特征注册表 — 单一收口)
`canonical-codes.ts` 同级的单一真理源:`packages/types/src/persona-features.ts`(或 `apps/pac-service/.../persona/feature-registry.ts`)。每个 feature_key 登记元数据:
```ts
export const PersonaFeatureRegistry = {
value: {
nameZh: '患者价值', tier: 'statistical',
timeSemantics: 'lifetime', window: null,
valueShape: '{ tier:0..4, monetaryCents, mScore:1..5 }',
dependsOn: ['payment_record', 'refund_record'],
refresh: 'event', owner: 'pac-algo', version: 2,
coverageTarget: 0.95,
},
lifecycle_stage: {
nameZh: '生命周期阶段', tier: 'statistical',
timeSemantics: 'window+trend', window: '24m',
valueShape: "{ stage:'new'|'active'|'silent'|'churned'|'reactivated', recencyDays, ... }",
dependsOn: ['encounter_record', 'treatment_record', 'visit_registration_record'],
refresh: 'event', owner: 'pac-algo', version: 1, coverageTarget: 0.98,
},
rfm: {
nameZh: 'RFM 八象限', tier: 'statistical', timeSemantics: 'mixed',
valueShape: '{ rScore, fScore, mScore, segment, recencyDays, freqCount, monetaryCents }',
dependsOn: ['encounter_record','treatment_record','visit_registration_record','payment_record','refund_record'],
refresh: 'event', owner: 'pac-algo', version: 1, coverageTarget: 0.95,
},
recall_risk: { nameZh:'流失/触达风险', tier:'rule', timeSemantics:'derived', dependsOn:['rfm'], ... },
do_not_contact_status: { nameZh:'勿扰', tier:'rule', timeSemantics:'snapshot', ... },
// LLM 层(W7+):communication_preference / treatment_intent / family_social_relation ...
} as const;
```
**CI 防漂移**:producer 实际产出的 feature key ⊆ Registry;每个 producer 必须声明它产哪个 key(类似 FactWriter 的收口纪律)。
**tier 4 类 producer**(都统一写 persona_features):`rule`(规则)/ `statistical`(SQL 聚合,本轮重点)/ `model`(ML,后续)/ `llm`(自由文本抽,W7+)。
---
## 三、表设计(在现有表上增强)
```prisma
model PersonaFeature {
id String @id @default(uuid()) @db.Uuid
personaId String @map("persona_id") @db.Uuid
key String // Registry 闭集
description String @db.Text // 自然语言(给 LLM/展示)— 已有
score Float? // 排序单值 — 已有
value Json? // ⭐ 新增:结构化 typed 值(给 SQL 圈人)
evidence Json // {factIds,ruleIds,...} — 已有
createdAt DateTime @default(now())
persona Persona @relation(...)
@@unique([personaId, key])
}
```
**`value` 的 shape 由 Registry.valueShape 约定**(application 层 zod 校验,跟 fact.content 同思路)。例:
- `value` 特征:`{ tier: 3, monetaryCents: 4477000, mScore: 5 }`
- `rfm` 特征:`{ rScore:2, fScore:1, mScore:5, segment:'important_retain', recencyDays:540, freqCount:2, monetaryCents:4477000 }`
- `lifecycle_stage`:`{ stage:'churned', recencyDays:540 }`
**圈人群查询索引**(13 万规模够用):
```sql
-- 当前 persona 指针:personas(patient_id) WHERE superseded_at IS NULL
CREATE UNIQUE INDEX persona_current ON personas(platform_id, tenant_id, patient_id) WHERE superseded_at IS NULL;
-- 特征值检索
CREATE INDEX pf_key ON persona_features(key);
CREATE INDEX pf_value_gin ON persona_features USING GIN (value);
```
圈人群 = `persona_features f JOIN personas p ON f.persona_id=p.id AND p.superseded_at IS NULL WHERE f.key='rfm' AND f.value->>'segment'='important_retain'`
**移除 `treatment_chain_status`**:它是 episode 级状态、不是人级属性(一人多链有损)。
- 停止 producer;降级为**详情页 fact 派生视图**(从 treatment_record 直接算 5 阶段,不进画像)。
- 人级有用信号(是否有未闭环治疗在进行)并入 `lifecycle_stage``inTreatment` 子字段。
- 历史行不删(版本流保真),只是新版本不再产该 key。
---
## 四、RFM 八象限(第一个统计层特征)
### R / F / M 口径(牙科)
| | 口径 | 时间语义 | 来源 | 默认阈值(Registry 可调) |
|---|---|---|---|---|
| **R 最近** | 距最后一次临床到诊天数 | snapshot | max(occurred_at) of encounter/treatment/visit_registration | high=≤270d(还在召回节律内)/ low=>270d |
| **F 频次** | 近 24 月去重就诊天数 | window-24m | encounter/visit 按天去重 count | high=≥4 次 / low=<4 次 |
| **M 金额** | 累计净消费(收−退) | lifetime | payment − refund(现有 LTV 口径) | 按租户分位:high=≥ 中位 / low=< 中位 |
> 阈值口径放 Registry,默认值上线后用真实分布(扫描器)校准。R 用临床固定阈(牙科洁牙节律 ~6mo,270d≈ 逾期一轮);M 用**租户内分位**(不同诊所绝对值不可比)。
### 八象限(高/低三维 → 2³)+ 召回语义映射
| segment(key) | R | F | M | 中文 | 召回策略 |
| **① 已摄入直接算** | 读现有 fact / 主档 | rfm / lifecycle_stage / treatment_history / time_preference / age_bracket / gender / do_not_contact | 仅 `--force` 重算 |
| **② 副表立柱新摄** | fact_client_out 字段 → `patient_profiles` 新列(enum_mapping 归一,多宿主标准) | acquisition_channel / referral_champion | migration + 重摄 |
| **③ fact.content 新字段** | 结算字段 → `payment_record.content`(JSONB,无迁移) | entitlement_status(卡券名)/ discount_anchor(折扣额) | 重摄 |
| **④ 衍生表 / ctx 加载** | 复用 PAC 已有派生数据,注入 persona ctx | family_structure(PatientRelation)/ special_attention(appointmentsAll) | 仅 `--force` |
## 四、时间语义模型
每标签在注册表声明 `timeSemantics`:`snapshot`(当前态,如年龄/性别)/ `window`(近 N 期,如时间偏好近2年)/ `lifetime`(全史,如治疗史/折扣锚点)/ `trend`/`mixed`(如 RFM:R 当前态、F/M 全史)。
**TZ**:PAC 全 UTC 存储(DW 北京,摄入时转换);算"星期/时段"等本地概念时 `AT TIME ZONE 'Asia/Shanghai'` / +8(试点硬编北京,多宿主应读 host TZ — follow-up)。
## 五、Feature Registry + recompute
- **Registry** = `PERSONA_FEATURE_SPECS`(代码常量,git/PR review),每标签一张"标签卡"(标签值/数据源/数据字段/释义/算法/时间语义/owner)。OneModel 单一收口。
- **extractor** 实现在 `features/*.feature.ts`,由 `FeatureRegistry` 收集,`PersonaService.recompute` 遍历。
- **重算**:`pnpm recompute-persona -- --host=jvs-dw --force`。⚠️ `--force` 必需 —— 算法/特征变更但数据没变时,水位幂等闸会全 noop;`--force` 跳过闸强制重算(部署也用)。
## 六、13 标签总览(本地 928 样本)
| 标签 | 类 | 标签值 | 数据源 | 时间语义 | 覆盖 |
|---|---|---|---|---|---|
| `important_value` | 高 | 高 | 高 | 重要价值 | 在来,**别过度打扰**(已活跃) |
| `important_retain` | 低 | 高 | 高 | 重要保持 | 高价值+曾高频、最近变稀 → **高优召回** |
| `important_develop` | 高 | 低 | 高 | 重要发展 | 高价值低频 → 提频次/复查邀约 |
| `important_winback` | 低 | 低 | 高 | 重要挽留 | 高价值流失 → **最高优召回**(挽回 ROI 最高) |
| `general_value` | 高 | 高 | 低 | 一般价值 | 维护 |
| `general_retain` | 低 | 高 | 低 | 一般保持 | 一般召回 |
| `general_develop` | 高 | 低 | 低 | 一般发展 | 培育 |
| `general_winback` | 低 | 低 | 低 | 一般挽留 | 低优/批量 SMS |
> 关键洞察:**RFM 统一了现有零散的 value+risk**——M→`value`(钱),R+F→`recall_risk`/`lifecycle`(活跃)。八象限直接给「该不该召、多优先」一个可解释的人群标签。
### 落地产出
`rfm` producer(SQL 聚合,确定性、便宜)写 3 个特征:
- `rfm`:完整 `{rScore,fScore,mScore,segment,recencyDays,freqCount,monetaryCents}`
- `value` ← M 派生(`{tier,monetaryCents,mScore}`,**保持 valueBonus 兼容**)
- `lifecycle_stage` ← R+F 派生(§五)
---
## 五、lifecycle_stage(生命周期分层 — R+F 派生)
状态机(就诊节律):
| stage | 判据(默认,可调) | 召回/话术含义 |
|---|---|---|
| `new` 新客 | 首诊 ≤180d 且 总就诊 ≤2 | 建立关系语气 |
| `active` 活跃 | R 高(近 ~270d 有到诊) | 正常维护 |
| `silent` 沉默 | 逾期 1 轮(270d–540d 无到诊) | **召回黄金窗** |
| `churned` 流失 | >540d 无到诊 | 挽回/低优 |
| `reactivated` 回流 | 近期到诊 但 此前长间断 | 强化粘性,别再当流失 |
`value.inTreatment`(并入):有未闭环 treatment_record 在进行 → true(替代旧 treatment_chain_status 的人级语义)。
---
## 六、圈人群 → 批量召回(主动运营)
把画像从「被动打分」升级到「主动圈人」。复用 `plan_reasons.source='campaign'` + `campaignId`(已预留)。
```prisma
model Campaign { // 新增轻量表
id String @id @default(uuid()) @db.Uuid
platformId String; tenantId String
name String // "高价值流失挽回 2026Q3"
segmentQuery Json // {feature:'rfm', filters:{segment:'important_winback'}}
status String // draft|running|done
createdBy String; createdAt DateTime @default(now())
}
```
**流程**:定义 segment 查询 → 预览命中人数 → 批量建 plan(`plan_reasons.source='campaign', campaignId, scenario='campaign_recall'`,priorityScore 用 RFM 段位)→ 进客服池。受同样的合规闸 + 单 active plan 约束 + ⑤b/⑤f 抑制。
**与算法召回的关系**:算法召回 = 临床信号驱动(诊断未治);campaign = 运营/价值驱动(按人群圈)。两者都进同一 plan 的 reasons[],去重由 patient 级单 plan 保证。
---
## 七、质量监控
复用 `persona_recompute_logs` + 一个看板查询:
- **覆盖率**:每个 key 有值的 active 患者占比 vs Registry.coverageTarget。
- **时效**:`max(now - personas.computedAt)`(最旧水位)+ stale 比例。
- **LLM fallback rate**:llm-tier 特征降级占比。
- **分布漂移**:RFM segment / value tier 的周分布对比(PSI),异常即上游/口径漂移信号。
---
## 八、消费方改造
- `priority-scorer.ts`:`valueBonus` 改读 `value.tier`(口径不变,来源换成 RFM-M);`likelihoodBonus` 改读 `lifecycle_stage`/`recall_risk`(RFM-R/F 派生)。八象限可直接给 base bonus 表。
- 话术 ScriptContext:薄画像标签换成 `lifecycle_stage` + `rfm.segment`(定语气),`value.tier`(称呼/价值)。
- 详情页:treatment chain 改 episode 视图;新增 RFM 段位 + 生命周期标签展示。
---
## 九、实施顺序(分 PR,范围=完整八象限+圈人群)
1. **PR1 schema + Registry**:persona_features 加 `value JSONB` + 索引(migration);建 Feature Registry + CI 收口;停产 treatment_chain_status。
2. **PR2 RFM producer**:statistical 层 SQL → `rfm`/`value`/`lifecycle_stage` 三特征落 persona;本地全量校准阈值(扫描器看分布)。
3. **PR3 消费方**:priority-scorer 读新特征(保 valueBonus 兼容);话术/详情页标签。
4. **PR4 圈人群**:Campaign 表 + segment 预览 + 批量建 plan;前端圈人界面(可后置)。
5. **PR5 质量看板**:覆盖率/时效/漂移。
每步本地全量验证(13 万)+ 服务器灰度,口径变更同步本文档。
---
## 附:口径决策记录(2026-06)
- 画像特征加结构化 `value` JSONB(支持 SQL 圈人群)。
- treatment_chain_status 移出画像 → episode 视图;人级信号并入 lifecycle。
- RFM 做完整八象限 + 圈人群 campaign 一起。
- M=全历史累计净消费(牙科大额低频,价值看终身);R 临床固定阈;F/M 阈值用租户分位,上线后扫描器校准。
| `rfm` 价值分群 | B | 8 分群(重要价值..低活跃) | 付款/就诊 fact | mixed | 928 |
| `lifecycle_stage` 生命周期 | B | 潜客/新客/成长/成熟/待激活/沉睡/流失 | 付款/就诊/预约 fact(自算) | window+trend | 928 |
| `treatment_history` 治疗史 | C | 种植/正畸/修复/牙周(多标签) | treatment_record.category | lifetime | 773 |
| `time_preference` 时间偏好 | D | 工作日/周末/上午/下午/晚间(多) | appointment.planned_for(北京) | window(2y) | 601 |
| `discount_anchor` 折扣锚点 | D | 最低折扣率+日期/项目 | payment.content(应收/折扣额) | lifetime | 338 |
| `special_attention` 特别关注 | D | 屡次爽约/经常迟到/免打扰/不可等候(多) | appointmentsAll + profile + emr | window(1y) | 88 |
| `acquisition_channel` 获客渠道 | A | 走入/口碑/集团营销/地区/电商/自媒体… | fact_client_out.primary_category(副表立柱) | snapshot | 928 |
| `entitlement_status` 权益身份 | B | 高端保险/银行私行/储值/儿牙/医保(多) | payment 卡券名关键词 + 充值 | lifetime | 274 |
| `age_bracket` 年龄段 | A | 婴幼儿..老年(9 档) | patient.birthDate | snapshot | 928 |
| `gender` 性别 | A | 男/女/未知 | patient.gender | snapshot | 928 |
| `family_structure` 家庭构成 | A | 单身/两口/多口/多代 | PatientRelation 直系 | snapshot | 85 |
| `referral_champion` 转介绍达人 | B | 家庭型/社交型 | recommend_num/amount(副表)+ 关系 | snapshot | 26 |
| `do_not_contact_status` 勿扰 | — | 可触达/勿扰 | profile + 投诉 | snapshot | 928 |
> 多标签(允许并列):treatment_history / time_preference / entitlement_status / special_attention。
> 稀疏(依赖数据完整性,全量更准):family_structure / referral_champion(关系边稀疏)、special_attention 的免打扰/不可等候(本地无数据)。
## 七、算法审查发现 + 口径决策(这轮核心 —— 不是照搬 spec)
1. **RFM**:采业务图 B.1.1 口径(M 按**租户分位** p20/40/60/80,非绝对¥;8 段**决策树**非几何八象限)。补全 spec 缺口:`F<3` 也归 develop(近期单次新客不误落"低活跃")。RFM 与生命周期解耦(RFM 管价值,lifecycle 管阶段)。
2. **lifecycle 顺序 bug**:spec 原顺序「沉睡(末诊>540)」在「流失(末诊>730)」前 → 流失永不触发(被沉睡遮蔽)。**修:流失提前**。另:源表无 `net_receipts_total/total_visit_times` → PAC 自算(同 RFM)。
3. **discount_anchor**:DW 无 `original_amount`;`discount_*_rate` **实为折扣金额非比率**(应收3−dept0.45=实收2.55)→ 折扣率=1−Σ折扣/应收。修 spec naive 点:① 免费洁牙/检查促销(0折)霸占锚点 → 只看原价≥¥500+ratio>0;② 保险方付款非诊所折扣 → 排除 `discount_insurance_rate`
4. **time_preference**:`occurred_at` 钟点被搞乱(摄入 TZ/解析),`planned_for`+8 才是干净营业钟形 → 用 planned_for。
5. **special_attention**:① `no_show`/`cancelled` 预约 `patient_facts.status≠active/fulfilled` → 不在 persona ctx → 新增 `ctx.appointmentsAll`(全状态单独加载);② `arrived_at`(in_time)摄入 TZ bug 一致早 8h → +8h 补偿。
6. **referral_champion**:逐个被推荐人「均有效转化」需跨患者 + 全量 → v1 用 DW 预聚合 `recommend_num/recommend_amount`(数仓已替我们算)。
7. **acquisition_channel**:DW 实际一级渠道比图多(走入最大、集团销售渠道、瑞尔员工);按数仓实际值,enum_mapping 归一 PAC 立柱标准。
8. **score 弃用**:特征"马上量化"是伪命题,分值是消费场景的事 → 特征只产 `data`,场景自算分。
9. **treatment_chain_status 移除**:它是 episode 级状态非人级属性 → 移出画像,降级为详情页 episode 视图(人级在治信号并入 lifecycle)。
## 八、follow-up / 未实现
- **appointment 时间 TZ 根治**:`occurred_at` 乱 + `arrived_at` 早 8h(+8h 是 band-aid)→ 修 `in_time` 摄入时区。
- **recommend_num 增量游标**:fact_client_out 无 updated_date,游标=`last_visit_time` → 推荐人不来诊则 recommend_num 更新滞后;改 `greatest(last_visit_time, recommend_last_visit_time)`
- **转介绍 v2**:逐人有效转化(跨患者图谱 + 全量)。
- **discount_anchor 口径**:近免费(>90%off)comp/全保 是否算锚点(现 ≥¥500+ratio>0,未加 floor);¥500 阈值可调。
- **TZ 多宿主**:硬编北京 → 读 host 时区。
- **圈人群 campaign**:未实现;`data` 带 segment + GIN 可查,schema 已 ready(`plan_reasons.campaignId` 预留)。
- **LLM 层标签**:沟通偏好/治疗意向/家庭社交关系等(从自由文本抽,走 AiCall),预留。
- **清理**:`score` 列后续 migration 删;`persona-design-v2.md`(本文)随标签增改维护。
## 九、部署清单(累积)
- **2 migration**:`patient_profiles` 加 acquisition_channel/sub + referral_count/amount_cents。
- **需重摄**(`sync --full`):acquisition/referral(主档)+ entitlement(payment 卡券名)+ discount(payment 折扣额)。
- **重算**:`recompute-persona:prod --force``recompute-plans:prod`
- 其余范式①④标签仅需 `--force` 重算。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment