Commit ff44c92b by luoqi

feat(persona): 治疗敏感特征(D.2.2,病历关键词精炼)

- treatment_sensitivity 多标签:看牙恐惧/晕针/晕血/密闭恐惧。扫 emr_record.content
  (past_history 既往史/illness_desc/pre_illness/disposal 等)+ profile.notes/tags。
-  关键词按全量数据排查精炼:看牙恐惧 恐惧/害怕看牙/牙科焦虑/看牙紧张(裸紧张=颏肌紧张);
  晕针;晕血/见血不适(裸见血=可见血凝块);幽闭/密闭/长时间张口不适(裸张口受限=物理受限)。
- 宿主未给 health_profile;EMR 既往史已承载(晕针病史等)。无迁移/无重摄。
- 本地 928:看牙恐惧 2(全量约 177 患者 0.13%,低覆盖高价值安全标记)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 5c7e097b
......@@ -13,6 +13,7 @@ import { TreatmentHistoryFeatureExtractor } from './treatment-history.feature';
import { TimePreferenceFeatureExtractor } from './time-preference.feature';
import { DiscountAnchorFeatureExtractor } from './discount-anchor.feature';
import { SpecialAttentionFeatureExtractor } from './special-attention.feature';
import { TreatmentSensitivityFeatureExtractor } from './treatment-sensitivity.feature';
/**
* FeatureRegistry — 收集所有 PersonaFeature 提取器。
......@@ -38,9 +39,10 @@ export class FeatureRegistry {
timePref: TimePreferenceFeatureExtractor,
discountAnchor: DiscountAnchorFeatureExtractor,
specialAttention: SpecialAttentionFeatureExtractor,
treatmentSensitivity: TreatmentSensitivityFeatureExtractor,
dnc: DoNotContactStatusFeatureExtractor,
entitlement: EntitlementStatusFeatureExtractor,
) {
this.extractors = [rfm, age, gender, acquisition, family, referral, lifecycle, treatmentHistory, timePref, discountAnchor, specialAttention, dnc, entitlement];
this.extractors = [rfm, age, gender, acquisition, family, referral, lifecycle, treatmentHistory, timePref, discountAnchor, specialAttention, treatmentSensitivity, dnc, entitlement];
}
}
import { Injectable } from '@nestjs/common';
import { PersonaFeatureKey, FactType } from '@pac/types';
import type {
ActiveFact,
FeatureExtractor,
FeatureExtractorContext,
PersonaFeatureDraft,
} from './feature.interface';
/**
* treatment_sensitivity 治疗敏感(D.2.2)— 规则层,snapshot · 多标签
*
* 标签:看牙恐惧 / 晕针 / 晕血 / 密闭恐惧。治疗中特殊敏感反应,影响医生匹配/舒缓/排班。
*
* 数据源:EMR 病历自由文本(emr_record.content:past_history 既往史 / illness_desc 现病史 /
* doctor_advice / disposal …)+ profile.notes/tags。宿主未给 health_profile,EMR 既往史已承载。
*
* ⚠️ 关键词按真实数据精炼(全量扫描验证,排假阳):
* 看牙恐惧:恐惧(EMR 里基本都是牙科恐惧)+ 害怕看牙/牙科焦虑/看牙紧张 等"牙科+情绪"组合;
* ❌ 不用裸"紧张"(实为"颏肌紧张"正畸术语)/裸"害怕""焦虑"(无牙科语境)。
* 晕针:晕针 / 害怕打针。
* 晕血:晕血 / 见血不适;❌ 不用裸"见血"(实为"可见血凝块/探之见血"临床所见)。
* 密闭恐惧:幽闭 / 密闭 / 长时间张口不适;❌ 不用裸"张口受限"(实为物理开口受限,智齿/感染)。
* 全不命中 → 不打标签。
*/
@Injectable()
export class TreatmentSensitivityFeatureExtractor implements FeatureExtractor {
readonly key = PersonaFeatureKey.TREATMENT_SENSITIVITY;
private static readonly RULES: Array<{ code: string; zh: string; kw: string[] }> = [
{
code: 'dental_fear',
zh: '看牙恐惧',
kw: ['恐惧', '害怕看牙', '看牙害怕', '害怕看牙医', '畏惧看牙', '牙科焦虑', '看牙焦虑', '看牙紧张', '牙科紧张'],
},
{ code: 'needle_faint', zh: '晕针', kw: ['晕针', '害怕打针', '怕打针'] },
{ code: 'blood_faint', zh: '晕血', kw: ['晕血', '见血不适', '见血恶心', '见血晕'] },
{ code: 'claustrophobia', zh: '密闭恐惧', kw: ['幽闭', '密闭', '长时间张口不适'] },
];
extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
const emrs = (ctx.factsByType.get(FactType.EMR_RECORD) ?? []) as ActiveFact[];
const parts: string[] = [];
for (const f of emrs) {
const c = (f.content ?? {}) as Record<string, unknown>;
parts.push(
String(c.illness_desc ?? ''),
String(c.pre_illness ?? ''),
String(c.past_history ?? ''),
String(c.general_condition ?? ''),
String(c.exam_findings ?? ''),
String(c.diagnosis_text ?? ''),
String(c.treatment_plan ?? ''),
String(c.doctor_advice ?? ''),
String(c.disposal ?? ''),
);
}
parts.push(ctx.profile?.notes ?? '', (ctx.profile?.tags ?? []).join(' '));
const hay = parts.join(' ');
if (!hay.trim()) return null;
const codes: string[] = [];
const labels: string[] = [];
for (const r of TreatmentSensitivityFeatureExtractor.RULES) {
if (r.kw.some((k) => hay.includes(k))) {
codes.push(r.code);
labels.push(r.zh);
}
}
if (codes.length === 0) return null;
return {
key: this.key,
description: labels.join(' / '),
score: null,
data: { types: codes, labels },
evidence: { factIds: [] },
};
}
}
......@@ -15,6 +15,7 @@ import { TreatmentHistoryFeatureExtractor } from './features/treatment-history.f
import { TimePreferenceFeatureExtractor } from './features/time-preference.feature';
import { DiscountAnchorFeatureExtractor } from './features/discount-anchor.feature';
import { SpecialAttentionFeatureExtractor } from './features/special-attention.feature';
import { TreatmentSensitivityFeatureExtractor } from './features/treatment-sensitivity.feature';
@Module({
controllers: [PersonaController],
......@@ -33,6 +34,7 @@ import { SpecialAttentionFeatureExtractor } from './features/special-attention.f
TimePreferenceFeatureExtractor,
DiscountAnchorFeatureExtractor,
SpecialAttentionFeatureExtractor,
TreatmentSensitivityFeatureExtractor,
DoNotContactStatusFeatureExtractor,
EntitlementStatusFeatureExtractor,
],
......
......@@ -392,6 +392,7 @@ export const PersonaFeatureKey = {
TIME_PREFERENCE: 'time_preference', // 时间偏好(工作日/周末/上午/下午/晚间;预约时刻统计)
DISCOUNT_ANCHOR: 'discount_anchor', // 折扣锚点(历史最低折扣率+日期;价格底线参考)
SPECIAL_ATTENTION: 'special_attention', // 特别关注(屡次爽约/经常迟到/免打扰/不可等候)
TREATMENT_SENSITIVITY: 'treatment_sensitivity', // 治疗敏感(看牙恐惧/晕针/晕血/密闭恐惧;病历关键词)
// v1 候选(规则路径,业务方反馈后逐步上)
ENTITLEMENT_STATUS: 'entitlement_status', // 权益身份(商保直付 / 医保 / 储值 / 私行;事实投影型,史+最近日期)
......
......@@ -49,6 +49,7 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }>
[PersonaFeatureKey.TIME_PREFERENCE]: { label: '时间偏好', tone: 'sky' },
[PersonaFeatureKey.DISCOUNT_ANCHOR]: { label: '折扣锚点', tone: 'rose' },
[PersonaFeatureKey.SPECIAL_ATTENTION]: { label: '特别关注', tone: 'rose' },
[PersonaFeatureKey.TREATMENT_SENSITIVITY]: { label: '治疗敏感', tone: 'rose' },
[PersonaFeatureKey.VALUE]: { label: '患者价值', tone: 'indigo' },
[PersonaFeatureKey.TREATMENT_CHAIN_STATUS]: { label: '治疗链状态', tone: 'amber' },
[PersonaFeatureKey.RECALL_RISK]: { label: '流失风险', tone: 'emerald' },
......
......@@ -269,4 +269,23 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
owner: 'pac-algo',
version: 1,
},
// ── D.2.2 治疗敏感(业务 CDP 口径;关键词精炼排假阳)──
treatment_sensitivity: {
key: 'treatment_sensitivity',
nameZh: '治疗敏感',
tier: 'rule',
timeSemantics: 'lifetime',
labelValues: ['看牙恐惧', '晕针', '晕血', '密闭恐惧'],
dataSource: 'EMR emr_record.content(past_history 既往史/illness_desc 等)+ profile.notes/tags。宿主未给 health_profile,EMR 既往史已承载',
dataFields: ['medical_record_tag(病历备注)', 'health_profile(健康档案)'],
meaning: '客户在治疗过程中的特殊敏感反应,直接影响医生匹配、舒缓策略与预约排班',
algorithm: [
'扫病历自由文本,命中即打标(允许并列):看牙恐惧 恐惧/害怕看牙/牙科焦虑/看牙紧张;',
'晕针 晕针/害怕打针;晕血 晕血/见血不适;密闭恐惧 幽闭/密闭/长时间张口不适。全不命中→不打标签。',
'⚠️ 关键词按真实数据精炼排假阳:裸"紧张"=颏肌紧张(正畸)、裸"见血"=可见血凝块(临床)、裸"张口受限"=物理开口受限,均剔除。',
].join('\n'),
owner: 'pac-algo',
version: 1,
},
};
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