Commit 5b72d147 by luoqi

fix(persona-ui): 说明写给客服不写给程序员 + 依据日期不折行 + 圈人标签给解释

## ① 依据行的日期折行了

定宽 68px 装不下 `2026-03-27`(11px 字号),每条依据的日期都断成两行。
改为按内容撑开 + nowrap;tabular-nums 保证各行等宽,不定宽也自然对齐。

## ② hover 说明太啰嗦,而且是写给程序员的

16 个标签的说明全部重写。原则:客服扫三秒能用,取值本身说得清就不再复述一遍。
典型对比(潜在治疗):

  改前 8 行 —— 副标题 + 3 条把 chip 上已有的词再列一遍 + 一句
    「口径与召回完全一致(就是召回在挖的机会),只是不受冷静期等时间限制」
  改后 3 行 —— 副标题「诊断或建议过、但还没做的项目」+ 取值 + 「已经在别处做掉的,这里看不到」

rules 现在允许为空(性别 / 治疗史 / 权益身份 / 治疗敏感 / 潜在治疗等 6 个都清空了),
note 只留会改变客服**做法**的那句。新增一条 CI 断言拦实现术语(fact / 字段 / K0 / data. …),
免得下次又写成实现说明。

## ③ 圈人面板的标签用户看不懂

「重要保持」vs「重要挽留」差在哪?「待激活」vs「沉睡客」几个月分界?这些名字是算法口径的
产物,客服只能凭感觉勾。给 PERSONA_TAG_FILTER_DIMS 的选项补 hint(64 项),内容是**判定条件**
的大白话,如「重要挽留 · 消费高,但很久没来了」「待激活 · 半年到一年半没来」。

展示方式**没用浮层**:这些选项是拿来横向比较的,浮层恰好盖住正要比的那几个(实测会遮住
2 个 chip)。改成面板底部一行定高说明,悬停即换,不遮挡、无 z-index 问题、扫读时一直在。

454 tests green · pac-web build 通过 · 三处均在本地真实数据上人工验过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent d7dc50eb
......@@ -41,15 +41,26 @@ describe('PERSONA_FEATURE_SPECS ↔ FeatureRegistry', () => {
});
describe('spec.display —— 详情页说明', () => {
test('⭐ 每个标签都要有客服版说明,且至少一条取值条目', () => {
test('⭐ 每个标签都要有一句副标题(rules 可为空 —— 取值自明就别复述)', () => {
for (const [key, spec] of Object.entries(PERSONA_FEATURE_SPECS)) {
expect(spec.display?.subtitle?.length ?? 0).toBeGreaterThan(0);
expect(spec.display?.rules?.length ?? 0).toBeGreaterThan(0);
for (const r of spec.display.rules) expect(r.body.length).toBeGreaterThan(0);
for (const r of spec.display.rules ?? []) expect(r.body.length).toBeGreaterThan(0);
expect(`${key}:${spec.display.subtitle}`).not.toContain('undefined');
}
});
test('⭐ 说明是给客服看的,不许出现实现术语', () => {
// 这些词一旦出现,说明写的是"程序怎么实现的"而不是"这个标签什么意思"
const JARGON = ['extractor', 'fact', 'jsonb', '字段', '接口', '口径与召回完全一致', 'K0', 'data.'];
const offenders: string[] = [];
for (const [key, spec] of Object.entries(PERSONA_FEATURE_SPECS)) {
const text = [spec.display.subtitle, spec.display.note ?? '',
...spec.display.rules.map((r) => `${r.label ?? ''}${r.body}`)].join(' ');
for (const w of JARGON) if (text.includes(w)) offenders.push(`${key}: ${w}`);
}
expect(offenders).toEqual([]);
});
test('每个标签都有中文展示名(前端 hover 标题取它)', async () => {
for (const key of await registryKeys()) expect(PERSONA_FEATURE_META[key]).toBeDefined();
});
......
......@@ -188,7 +188,9 @@ function FeatureCard({
const { badge, badgeTone, text: label } = factLabel(fact);
return (
<li key={fact.id} className="flex items-baseline gap-1.5 text-[11px] leading-relaxed">
<span className="w-[68px] flex-none tabular-nums text-slate-400">
{/* 日期不折行 —— 定宽 68px 装不下 `2026-03-27`(11px 字号)会断成两行;
改为按内容撑开 + nowrap,tabular-nums 保证各行等宽自然对齐 */}
<span className="flex-none whitespace-nowrap tabular-nums text-slate-400">
{(fact.occurredAt ?? fact.plannedFor ?? '').slice(0, 10) || '—'}
</span>
<span className={cn('flex-none rounded px-1 text-[10px]', badgeTone)}>{badge}</span>
......
......@@ -337,7 +337,7 @@ export function PatientPickerRail({
);
}
// ── 画像标签筛选 popover(4 维度分组多选)────────────────────
// ── 画像标签筛选 popover(维度分组多选)────────────────────
function PersonaTagFilter({
selected,
onToggle,
......@@ -347,6 +347,8 @@ function PersonaTagFilter({
onToggle: (kv: string) => void;
onClear: () => void;
}) {
/// 当前悬停选项的说明,渲染在面板底部固定行(见下方注释:为什么不用浮层)
const [hint, setHint] = useState<string | null>(null);
return (
<Popover>
<PopoverTrigger asChild>
......@@ -372,6 +374,9 @@ function PersonaTagFilter({
</button>
)}
</div>
{/* 悬停某个选项时在底部解释它。「重要保持」「待激活」这类名字是算法口径的产物,
光看名字猜不出边界(重要保持 vs 重要挽留差在哪?待激活 vs 沉睡客几个月分界?),
客服只能凭感觉勾。说明文案在 persona-tag-filters.ts 的 hint 字段。 */}
<div className="max-h-80 space-y-2 overflow-y-auto">
{PERSONA_TAG_FILTER_DIMS.map((dim) => (
<div key={dim.key}>
......@@ -387,6 +392,12 @@ function PersonaTagFilter({
key={kv}
type="button"
onClick={() => onToggle(kv)}
// 说明走面板底部的固定行,不用浮层:这些选项是用来**横向比较**的
// (重要保持 vs 重要挽留差在哪),浮层会盖住正要比的那几个。
onMouseEnter={() => o.hint && setHint(`${o.zh} · ${o.hint}`)}
onMouseLeave={() => setHint(null)}
onFocus={() => o.hint && setHint(`${o.zh} · ${o.hint}`)}
onBlur={() => setHint(null)}
className={cn(
'rounded-full border px-2 py-0.5 text-[11px] transition-colors',
on
......@@ -402,6 +413,14 @@ function PersonaTagFilter({
</div>
))}
</div>
{/* 定高:hover 进出不抖动 */}
<div className="mt-1 min-h-[30px] border-t border-slate-100 px-1 pt-1.5 text-[10.5px] leading-relaxed">
{hint ? (
<span className="text-slate-600">{hint}</span>
) : (
<span className="text-slate-300">悬停任一标签看它的含义</span>
)}
</div>
</PopoverContent>
</Popover>
);
......
......@@ -48,9 +48,13 @@ export const PERSONA_FEATURE_GROUP_ORDER: PersonaFeatureGroup[] = ['action', 'va
export interface PersonaFeatureDisplay {
/** 一句话副标题:这个标签在看什么 */
subtitle: string;
/** 取值条目:label = 标签值,body = 什么情况下会是它 */
/**
* 取值条目:label = 标签值,body = 什么情况下会是它。
* **取值本身已经说清楚了就留空**(如性别、治疗史、权益身份)—— 把 chip 上已有的词
* 在下面再列一遍是噪音,客服要的是"我没看懂的那部分"。
*/
rules: Array<{ label?: string; body: string }>;
/** 使用提醒 —— 只写会影响客服判断的(口径边界、已知局限),不写实现细节 */
/** 使用提醒 —— 只写会改变客服**做法**的(边界、已知局限);实现细节不写 */
note?: string;
/** 详情页分组 */
group: PersonaFeatureGroup;
......@@ -115,14 +119,13 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
' R≥4 F≥3 M<4→一般价值 / R=3 F≥3 M<4→一般保持 / R≥4 F=2 M<4→一般发展 / 其余(含 R≤2 任意)→低活跃',
].join('\n'),
display: {
subtitle: '按最近就诊 · 就诊频次 · 累计消费三个维度分层',
subtitle: '按最近来没来、来得勤不勤、花了多少分层',
rules: [
{ label: '重要价值', body: '近期来过、来得勤、花得多' },
{ label: '重要保持 / 发展 / 挽留', body: '消费高,但最近度或频次偏弱' },
{ label: '一般价值 / 保持 / 发展', body: '同样的象限,消费偏低' },
{ label: '低活跃', body: '很久没来,或三项都偏低' },
{ body: '「重要」= 消费排在前 40%;「一般」= 消费偏低' },
{ body: '价值 → 保持 → 发展 → 挽留:最近还在来 → 越来越久没来' },
{ label: '低活跃', body: '很久没来,或三项都低' },
],
note: '消费额取本院实付合计(已扣退费);高低是跟**同租户其他患者**比出来的分位,不是绝对金额档',
note: '消费高低是跟同院其他客户比出来的,不是绝对金额',
group: 'value',
order: 10,
},
......@@ -158,12 +161,11 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'26-30→中青年 / 31-45→中年 / 46-54→中老年 / ≥55→老年',
].join('\n'),
display: {
subtitle: '按建档生日算的当下周岁',
subtitle: '按建档生日算的当下年龄',
rules: [
{ body: '婴幼儿 0-2 / 学龄前 3-6 / 替牙期 7-11 / 青少年 12-17' },
{ body: '青年 18-25 / 中青年 26-30 / 中年 31-45 / 中老年 46-54 / 老年 ≥55' },
{ body: '婴幼儿 0-2 · 学龄前 3-6 · 替牙期 7-11 · 青少年 12-17' },
{ body: '青年 18-25 · 中青年 26-30 · 中年 31-45 · 中老年 46-54 · 老年 55+' },
],
note: '没填生日的不打此标签',
group: 'basic',
order: 10,
},
......@@ -183,11 +185,8 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
meaning: '客户生理性别,影响沟通话术与项目推荐',
algorithm: 'client_gender 直接映射:"M"/"男"→男性;"F"/"女"→女性;其他→未知',
display: {
subtitle: '影响话术称呼与项目推荐',
rules: [
{ label: '男性 / 女性', body: '按建档性别' },
{ label: '未知', body: '建档没填或填了无法识别的值' },
],
subtitle: '按建档性别',
rules: [], // 取值本身已自明,不再复述一遍
group: 'basic',
order: 20,
},
......@@ -207,12 +206,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
meaning: '客户首次到诊的获客来源,用于渠道价值分析。初诊来源一经判定不做二次改归类',
algorithm: 'host primary_category 经 assembler enum_mapping 归一到 PAC 立柱标准(走入→walk_in 等);二级 sub_category 原值透传。冲突解决(数仓侧):推荐人 > 活动码/渠道码 > 客户类型',
display: {
subtitle: '客户第一次到诊是怎么来的',
rules: [
{ body: '走入 / 口碑客户 / 集团销售渠道 / 集团营销渠道 / 地区营销渠道' },
{ body: '电商平台 / 自媒体网络 / 内部员工 / 其他' },
],
note: '初诊来源一经判定不再改归类 —— 后来又被谁推荐过不会覆盖它',
subtitle: '第一次到诊是怎么来的',
rules: [], // 取值本身已自明,不再复述一遍
note: '一经判定不再改,后来谁推荐过也不会覆盖',
group: 'basic',
order: 40,
},
......@@ -236,14 +232,11 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'⚠️ 依赖关系边覆盖,样本稀疏 + 非全量时覆盖偏低。',
].join('\n'),
display: {
subtitle: '按已建档的直系亲属关系反推',
subtitle: '按院内已建档的亲属关系推断',
rules: [
{ label: '多代之家', body: '有长辈(父母 / 祖辈)' },
{ label: '多口之家', body: '有子女' },
{ label: '两口之家', body: '有配偶' },
{ label: '单身家庭', body: '只有同辈 / 朋友这类非直系关系' },
{ body: '有长辈 → 多代之家;有子女 → 多口之家;有配偶 → 两口之家;其余 → 单身家庭' },
],
note: '只认已在院建档并关联上的亲属 —— 家人没在本院看过牙就推不出来,覆盖率偏低属正常',
note: '家人没在本院看过牙就推不出来',
group: 'basic',
order: 30,
},
......@@ -267,13 +260,12 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'⚠️ v1 用 DW 预聚合(逐个被推荐人"均有效转化"需其结算,跨患者+需被推荐人已摄入,留 v2)。',
].join('\n'),
display: {
subtitle: '推荐过他人**且**对方真的成交了',
subtitle: '推荐过 3 人以上,且带来了成交',
rules: [
{ label: '门槛', body: '推荐 ≥3 人,且这些人带来过实际成交金额' },
{ label: '家庭型', body: '满足门槛,且院内直系亲属关系 ≥3(带的是家里人)' },
{ label: '社交型', body: '满足门槛,但直系亲属关系不足 3(带的是家人以外的人)' },
{ label: '家庭型', body: '带的是家里人' },
{ label: '社交型', body: '带的是家人以外的人' },
],
note: '推荐人数与成交额取自数仓预聚合,不逐个核对被推荐人;成交额可能很小,谈奖励前先看具体数字',
note: '成交额可能很小,谈奖励前先看具体数字',
group: 'value',
order: 50,
},
......@@ -297,15 +289,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'储值会员 储值/预存(或有充值);儿牙会员 儿牙会员/乐牙卡/涂氟年卡;医保 医保。未命中→不打标签。',
].join('\n'),
display: {
subtitle: '历史结算里用过哪些卡券 / 保险',
rules: [
{ label: '高端保险直付', body: '用过 Bupa、中间带、万欣和、招商信诺等商业保险直付' },
{ label: '银行私行权益', body: '用过招行私行、私人银行、白金卡、贵宾卡等银行权益' },
{ label: '储值会员', body: '有储值 / 预存记录' },
{ label: '儿牙会员', body: '买过儿牙会员、乐牙卡、涂氟年卡' },
{ label: '医保客户', body: '用过医保结算' },
],
note: '可以同时命中多个。只说明**曾经**这样结算过,不代表当前仍在保 / 卡内还有余额',
subtitle: '历史结算里用过的卡券或保险',
rules: [], // 取值本身已自明,不再复述一遍
note: '只说明曾经这样结算过,不代表现在还有效',
group: 'value',
order: 40,
},
......@@ -330,15 +316,11 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'⚠️ 修正 spec 顺序 bug:原"沉睡>540"在"流失>730"前 → 流失永不触发;此处流失提前。',
].join('\n'),
display: {
subtitle: '客户走到了哪个阶段(看就诊时间 + 消费走势)',
subtitle: '客户走到了哪个阶段',
rules: [
{ label: '潜客', body: '还没到过诊,只约过或咨询过' },
{ label: '新客', body: '首诊半年内,且就诊不超过 3 次' },
{ label: '成长客', body: '还在来,且近一年消费明显高于以往年均' },
{ label: '成熟客', body: '还在来,消费平稳' },
{ label: '待激活', body: '半年到一年半没来' },
{ label: '沉睡客', body: '一年半到两年没来' },
{ label: '流失客', body: '两年以上没来' },
{ label: '潜客 / 新客', body: '还没来过 / 首诊半年内且不超过 3 次' },
{ label: '成长客 / 成熟客', body: '还在来,消费在涨 / 消费平稳' },
{ label: '待激活 / 沉睡 / 流失', body: '半年~1年半 / 1年半~2年 / 2年以上没来' },
],
group: 'value',
order: 20,
......@@ -362,14 +344,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'不标记 surgical(拔牙)/restorative(充填)/endodontic(根管)等基础治疗。仅 actual;全不命中→不打标签。',
].join('\n'),
display: {
subtitle: '做过哪些核心治疗',
rules: [
{ label: '种植史', body: '种过牙' },
{ label: '正畸史', body: '做过矫正' },
{ label: '修复史', body: '做过冠桥 / 贴面 / 嵌体' },
{ label: '牙周治疗史', body: '做过洁牙 / 刮治 / 牙周序列治疗' },
],
note: '只统计已实际执行的治疗;拔牙 / 补牙 / 根管等基础项目不计入',
subtitle: '做过哪些主要治疗',
rules: [], // 取值本身已自明,不再复述一遍
note: '拔牙、补牙、根管等基础项目不计入',
group: 'value',
order: 30,
},
......@@ -393,12 +370,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'⚠️ TZ 硬编北京(+8),多宿主应读 host 时区(follow-up)。',
].join('\n'),
display: {
subtitle: '习惯在什么时候来',
rules: [
{ label: '工作日 / 周末', body: '近两年预约里该类占比过半' },
{ label: '上午 / 下午 / 晚间', body: '同上,按预约时段统计' },
],
note: '看的是**预约时刻**而非实际到诊时刻;近两年预约少于 2 次不打标签',
subtitle: '习惯什么时候来',
rules: [], // 取值本身已自明,不再复述一遍
note: '看的是近两年的预约时间,不是实际到诊时间',
group: 'action',
order: 60,
},
......@@ -422,10 +396,8 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
].join('\n'),
display: {
subtitle: '历史上给过的最深一次折扣',
rules: [
{ body: '在原价 ≥¥500 的真实治疗里,取谈成过的最低折扣,并记下日期与项目' },
],
note: '免费洁牙 / 检查这类促销和全免单不计入,否则锚点会失去参考意义。锚点可能很久远,报价前先看日期',
rules: [], // 取值本身已自明,不再复述一遍
note: '免费洁牙等促销不计入。锚点可能很久远,报价前先看日期',
group: 'action',
order: 70,
},
......@@ -449,14 +421,14 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'不可等候 notes/tags/病历含 不可等候/时间敏感/赶时间/不能等。全不命中→不打标签。',
].join('\n'),
display: {
subtitle: '排班与触达要特别注意的事',
subtitle: '排班和联系时要注意的事',
rules: [
{ label: '屡次爽约', body: '近一年至少 3 次已定预约,履约率不足一半' },
{ label: '经常迟到', body: '近一年至少 3 次到店晚于预约 15 分钟以上' },
{ label: '免打扰', body: '已标记不接受主动联系' },
{ label: '不可等候', body: '病历或备注里提过赶时间 / 不能等' },
{ label: '屡次爽约', body: '近一年履约不到一半' },
{ label: '经常迟到', body: '多次晚到 15 分钟以上' },
{ label: '免打扰', body: '已标记不接受主动联系' },
{ label: '不可等候', body: '提过赶时间 / 不能等' },
],
note: '命中「免打扰」不要外呼',
note: '命中「免打扰」不要外呼',
group: 'action',
order: 30,
},
......@@ -480,13 +452,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'⚠️ 关键词按真实数据精炼排假阳:裸"紧张"=颏肌紧张(正畸)、裸"见血"=可见血凝块(临床)、裸"张口受限"=物理开口受限,均剔除。',
].join('\n'),
display: {
subtitle: '沟通时需要提前安抚的点',
rules: [
{ label: '看牙恐惧', body: '病历 / 备注提过牙科恐惧、害怕看牙' },
{ label: '晕针 / 晕血', body: '提过晕针、怕打针 / 晕血、见血不适' },
{ label: '密闭恐惧', body: '提过幽闭、长时间张口不适' },
],
note: '从病历自由文本里识别,只在明确写到时才打标 —— 没打标不等于患者不敏感',
subtitle: '沟通时要提前安抚的点',
rules: [], // 取值本身已自明,不再复述一遍
note: '从病历文本里识别,没标不等于患者不敏感',
group: 'action',
order: 50,
},
......@@ -511,12 +479,8 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
].join('\n'),
display: {
subtitle: '诊断或建议过、但还没做的项目',
rules: [
{ label: '种植 / 修复 / 拔牙', body: '缺牙待种、需要冠桥、残根残冠需拔' },
{ label: '正畸 / 早矫', body: '按年龄分成人正畸(13-40)与儿童早矫(3-12)' },
{ label: '根管 / 牙周 / 补牙', body: '牙髓、牙周、龋齿' },
],
note: '口径与召回完全一致(就是召回在挖的机会),只是不受冷静期等时间限制。已在别处做掉的看不到',
rules: [], // 取值本身已自明,不再复述一遍
note: '已经在别处做掉的,这里看不到',
group: 'action',
order: 10,
},
......@@ -540,13 +504,11 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
'无潜在治疗→不打标签。Step2 取最大(多路径 MAX,现仅 1 路径)。',
].join('\n'),
display: {
subtitle: '有待处理项时,多久没来了',
subtitle: '有待做的项目,而且多久没来了',
rules: [
{ label: '紧急', body: '有潜在待治项,且超过 90 天没来' },
{ label: '高', body: '有潜在待治项,30 到 90 天没来' },
{ label: '中', body: '有潜在待治项,30 天内来过或刚发现' },
{ body: '紧急 = 90 天以上 · 高 = 30~90 天 · 中 = 30 天内' },
],
note: '没有潜在待治项就不打此标签。目前只覆盖「诊断了没做」这条路径,复查提醒类尚未纳入',
note: '没有待做项目就不显示这个标签',
group: 'action',
order: 20,
},
......@@ -574,7 +536,7 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
rules: [
{ label: '种植禁忌', body: '未满 19 岁,颌骨尚未发育完全' },
],
note: '目前只做了年龄这一项,满 19 岁自动解除。全身疾病 / 用药 / 过敏类禁忌尚未覆盖,**不能**据此认为患者无禁忌',
note: '目前只做了年龄这一项 —— 没有标记**不等于**没有其他禁忌',
group: 'action',
order: 40,
},
......
......@@ -14,6 +14,14 @@ export interface PersonaTagOption {
/** data 里的结构化取值(英文 code) */
value: string;
zh: string;
/**
* 一句话说明 —— 筛选面板上 hover 该选项时显示。
*
* 「重要保持」「待激活」「重要发展」这类分群名是算法口径的产物,光看名字猜不出边界
* (重要保持 vs 重要挽留差在哪?待激活 vs 沉睡客几个月分界?),客服只能凭感觉勾。
* 这里用大白话给出**判定条件**,不解释算法。省略 = 名字本身够自明(性别之类)。
*/
hint?: string;
}
export interface PersonaTagFilterDim {
......@@ -33,14 +41,14 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '价值分群',
dataPath: 'segment',
options: [
{ value: 'important_value', zh: '重要价值' },
{ value: 'important_retain', zh: '重要保持' },
{ value: 'important_develop', zh: '重要发展' },
{ value: 'important_winback', zh: '重要挽留' },
{ value: 'general_value', zh: '一般价值' },
{ value: 'general_retain', zh: '一般保持' },
{ value: 'general_develop', zh: '一般发展' },
{ value: 'low_active', zh: '低活跃' },
{ value: 'important_value', zh: '重要价值', hint: '消费高、来得勤,最近还在来' },
{ value: 'important_retain', zh: '重要保持', hint: '消费高、来得勤,但有段时间没来了' },
{ value: 'important_develop', zh: '重要发展', hint: '消费高、最近来过,但来的次数少' },
{ value: 'important_winback', zh: '重要挽留', hint: '消费高,但很久没来了' },
{ value: 'general_value', zh: '一般价值', hint: '来得勤、最近还在来,消费一般' },
{ value: 'general_retain', zh: '一般保持', hint: '来得勤但有段时间没来,消费一般' },
{ value: 'general_develop', zh: '一般发展', hint: '最近来过但次数少,消费一般' },
{ value: 'low_active', zh: '低活跃', hint: '很久没来,或三项都偏低' },
],
},
{
......@@ -48,13 +56,13 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '生命周期',
dataPath: 'stage',
options: [
{ value: 'prospect', zh: '潜客' },
{ value: 'new', zh: '新客' },
{ value: 'growth', zh: '成长客' },
{ value: 'mature', zh: '成熟客' },
{ value: 'reactivate', zh: '待激活' },
{ value: 'dormant', zh: '沉睡客' },
{ value: 'churned', zh: '流失客' },
{ value: 'prospect', zh: '潜客', hint: '还没到过诊,只约过或咨询过' },
{ value: 'new', zh: '新客', hint: '首诊半年内,就诊不超过 3 次' },
{ value: 'growth', zh: '成长客', hint: '还在来,近一年消费明显上涨' },
{ value: 'mature', zh: '成熟客', hint: '还在来,消费平稳' },
{ value: 'reactivate', zh: '待激活', hint: '半年到一年半没来' },
{ value: 'dormant', zh: '沉睡客', hint: '一年半到两年没来' },
{ value: 'churned', zh: '流失客', hint: '两年以上没来' },
],
},
{
......@@ -62,9 +70,9 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '紧迫度',
dataPath: 'level',
options: [
{ value: 'urgent', zh: '紧急' },
{ value: 'high', zh: '高' },
{ value: 'mid', zh: '中' },
{ value: 'urgent', zh: '紧急', hint: '有待做的项目,且 90 天以上没来' },
{ value: 'high', zh: '高', hint: '有待做的项目,30 到 90 天没来' },
{ value: 'mid', zh: '中', hint: '有待做的项目,30 天内来过或刚发现' },
],
},
{
......@@ -72,15 +80,15 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '年龄段',
dataPath: 'bracket',
options: [
{ value: 'infant', zh: '婴幼儿' },
{ value: 'preschool', zh: '学龄前' },
{ value: 'mixed_dentition', zh: '替牙期' },
{ value: 'adolescent', zh: '青少年' },
{ value: 'youth', zh: '青年' },
{ value: 'young_adult', zh: '中青年' },
{ value: 'middle_aged', zh: '中年' },
{ value: 'pre_senior', zh: '中老年' },
{ value: 'senior', zh: '老年' },
{ value: 'infant', zh: '婴幼儿', hint: '0-2 岁' },
{ value: 'preschool', zh: '学龄前', hint: '3-6 岁' },
{ value: 'mixed_dentition', zh: '替牙期', hint: '7-11 岁,换牙阶段' },
{ value: 'adolescent', zh: '青少年', hint: '12-17 岁' },
{ value: 'youth', zh: '青年', hint: '18-25 岁' },
{ value: 'young_adult', zh: '中青年', hint: '26-30 岁' },
{ value: 'middle_aged', zh: '中年', hint: '31-45 岁' },
{ value: 'pre_senior', zh: '中老年', hint: '46-54 岁' },
{ value: 'senior', zh: '老年', hint: '55 岁以上' },
],
},
{
......@@ -97,10 +105,10 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '家庭构成',
dataPath: 'structure',
options: [
{ value: 'multigen', zh: '多代之家' },
{ value: 'nuclear', zh: '多口之家' },
{ value: 'couple', zh: '两口之家' },
{ value: 'single', zh: '单身家庭' },
{ value: 'multigen', zh: '多代之家', hint: '院内登记有长辈(父母 / 祖辈)' },
{ value: 'nuclear', zh: '多口之家', hint: '院内登记有子女' },
{ value: 'couple', zh: '两口之家', hint: '院内登记有配偶' },
{ value: 'single', zh: '单身家庭', hint: '只有旁系或朋友关系' },
],
},
{
......@@ -109,11 +117,11 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'high_insurance', zh: '高端保险直付' },
{ value: 'bank_vip', zh: '银行私行权益' },
{ value: 'stored_value', zh: '储值会员' },
{ value: 'pedo_member', zh: '儿牙会员' },
{ value: 'medical', zh: '医保客户' },
{ value: 'high_insurance', zh: '高端保险直付', hint: '用过 Bupa、万欣和等商业保险直付' },
{ value: 'bank_vip', zh: '银行私行权益', hint: '用过私人银行 / 白金卡等银行权益' },
{ value: 'stored_value', zh: '储值会员', hint: '有储值或预存记录' },
{ value: 'pedo_member', zh: '儿牙会员', hint: '买过儿牙会员卡 / 涂氟年卡' },
{ value: 'medical', zh: '医保客户', hint: '用过医保结算' },
],
},
{
......@@ -122,10 +130,10 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'implant_history', zh: '种植史' },
{ value: 'ortho_history', zh: '正畸史' },
{ value: 'prostho_history', zh: '修复史' },
{ value: 'perio_history', zh: '牙周治疗史' },
{ value: 'implant_history', zh: '种植史', hint: '种过牙' },
{ value: 'ortho_history', zh: '正畸史', hint: '做过矫正' },
{ value: 'prostho_history', zh: '修复史', hint: '做过冠桥 / 贴面 / 嵌体' },
{ value: 'perio_history', zh: '牙周治疗史', hint: '做过洁牙 / 刮治 / 牙周序列治疗' },
],
},
{
......@@ -134,10 +142,10 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'dental_fear', zh: '看牙恐惧' },
{ value: 'needle_faint', zh: '晕针' },
{ value: 'blood_faint', zh: '晕血' },
{ value: 'claustrophobia', zh: '密闭恐惧' },
{ value: 'dental_fear', zh: '看牙恐惧', hint: '病历里提过害怕看牙' },
{ value: 'needle_faint', zh: '晕针', hint: '病历里提过晕针 / 怕打针' },
{ value: 'blood_faint', zh: '晕血', hint: '病历里提过晕血' },
{ value: 'claustrophobia', zh: '密闭恐惧', hint: '病历里提过幽闭 / 长时间张口不适' },
],
},
{
......@@ -146,10 +154,10 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'frequent_no_show', zh: '屡次爽约' },
{ value: 'often_late', zh: '经常迟到' },
{ value: 'do_not_disturb', zh: '免打扰' },
{ value: 'cannot_wait', zh: '不可等候' },
{ value: 'frequent_no_show', zh: '屡次爽约', hint: '近一年至少 3 次预约,履约不到一半' },
{ value: 'often_late', zh: '经常迟到', hint: '近一年多次晚到 15 分钟以上' },
{ value: 'do_not_disturb', zh: '免打扰', hint: '已标记不接受主动联系,不要外呼' },
{ value: 'cannot_wait', zh: '不可等候', hint: '病历或备注提过赶时间 / 不能等' },
],
},
{
......@@ -158,11 +166,11 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'weekday', zh: '工作日' },
{ value: 'weekend', zh: '周末' },
{ value: 'morning', zh: '上午' },
{ value: 'afternoon', zh: '下午' },
{ value: 'evening', zh: '晚间' },
{ value: 'weekday', zh: '工作日', hint: '近两年预约多在工作日' },
{ value: 'weekend', zh: '周末', hint: '近两年预约多在周末' },
{ value: 'morning', zh: '上午', hint: '多约在 8-12 点' },
{ value: 'afternoon', zh: '下午', hint: '多约在 12-18 点' },
{ value: 'evening', zh: '晚间', hint: '多约在 18-21 点' },
],
},
{
......@@ -170,8 +178,8 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '转介绍达人',
dataPath: 'type',
options: [
{ value: 'family', zh: '家庭型' },
{ value: 'social', zh: '社交型' },
{ value: 'family', zh: '家庭型', hint: '推荐 3 人以上并带来成交,带的是家里人' },
{ value: 'social', zh: '社交型', hint: '推荐 3 人以上并带来成交,带的是家人以外的人' },
],
},
{
......@@ -179,7 +187,7 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
nameZh: '禁忌',
dataPath: 'types',
isArray: true,
options: [{ value: 'implant_age', zh: '种植年龄禁忌' }],
options: [{ value: 'implant_age', zh: '种植年龄禁忌', hint: '未满 19 岁,颌骨尚未发育完全' }],
},
{
key: 'potential_treatment',
......@@ -187,14 +195,14 @@ export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
dataPath: 'types',
isArray: true,
options: [
{ value: 'implant', zh: '种植' },
{ value: 'ortho', zh: '正畸' },
{ value: 'early_ortho', zh: '早矫' },
{ value: 'endo', zh: '根管' },
{ value: 'perio', zh: '牙周' },
{ value: 'filling', zh: '补牙' },
{ value: 'restoration', zh: '修复' },
{ value: 'extraction', zh: '拔牙' },
{ value: 'implant', zh: '种植', hint: '诊断缺牙,还没种' },
{ value: 'ortho', zh: '正畸', hint: '建议矫正,还没做(13-40 岁)' },
{ value: 'early_ortho', zh: '早矫', hint: '建议儿童早期矫治(3-12 岁)' },
{ value: 'endo', zh: '根管', hint: '诊断牙髓问题,还没做根管' },
{ value: 'perio', zh: '牙周', hint: '诊断牙周问题,还没治' },
{ value: 'filling', zh: '补牙', hint: '诊断龋齿,还没补' },
{ value: 'restoration', zh: '修复', hint: '需要冠桥 / 贴面,还没做' },
{ value: 'extraction', zh: '拔牙', hint: '残根残冠等需拔,还没拔' },
],
},
];
......
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