Commit 4f5df5c8 by luoqi

feat(persona): 禁忌标签特征(D.2.4 v1,仅种植年龄禁忌)

- contraindication 多标签结构(种植/正畸/麻醉/手术禁忌)。v1 仅实现【种植年龄禁忌:年龄≤18,
  骨骼发育未完全】,validUntil=满19岁自动解除(到期重算即不打)。
- ️ 其余禁忌不上规则版(会医疗事故):既往史'过敏'97%是'否认过敏'(否定泛滥)、HbA1c全量仅10条
  (量化条件无数据)、需控制状态/急性期判断 → 留 Layer C(LLM 抽取)。data 结构已留多标签位。
- 本地 928:种植禁忌 167,validUntil 正确,交叉验证 age_bracket≤18 一致。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent ff44c92b
import { Injectable } from '@nestjs/common';
import { PersonaFeatureKey } from '@pac/types';
import type {
FeatureExtractor,
FeatureExtractorContext,
PersonaFeatureDraft,
} from './feature.interface';
/**
* contraindication 禁忌标签(D.2.4)— 规则层,snapshot · 多标签
*
* 标签:种植禁忌 / 正畸禁忌 / 麻醉禁忌 / 手术禁忌(治疗可行性 + 安全预警)。
*
* ⚠️ v1 只实现【种植年龄禁忌:年龄≤18(骨骼发育未完全)】—— 这是唯一可用确定性规则可靠判定的。
* 其余禁忌(糖尿病/高血压/过敏/抗凝/妊娠/放疗…)依赖既往史自由文本,而:
* ① 否定问题:含"过敏"的既往史 97% 是"否认过敏"——naive 关键词会把无过敏标成麻醉禁忌(医疗事故);
* ② 量化条件无数据:HbA1c(全量仅10条)/收缩压 无法判"控制不佳",分不清能否种植;
* ③ 需控制状态/急性期判断 + 时效窗口。
* → 这些是 Layer C(LLM 抽取:读既往史、处理否定、判控制/急性)的活,留 W5+。本特征 data 结构
* 已留多标签位,Layer C 就绪后并入(同一 key,补 types)。
*
* 时效:年龄禁忌随年龄自动解除(满 19 岁后重算即不打);validUntil 标注到期日(满 19 岁)。
*/
@Injectable()
export class ContraindicationFeatureExtractor implements FeatureExtractor {
readonly key = PersonaFeatureKey.CONTRAINDICATION;
private static readonly IMPLANT_MIN_AGE = 19; // ≤18 禁忌 → 满 19 解除
private static ageYears(birth: Date, now: Date): number {
let age = now.getFullYear() - birth.getFullYear();
const m = now.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age--;
return age;
}
extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
const birth = ctx.patient.birthDate;
if (!birth) return null;
const age = ContraindicationFeatureExtractor.ageYears(birth, ctx.now);
if (age < 0 || age > 120) return null;
const types: Array<{ code: string; zh: string; reason: string; validUntil: string | null }> = [];
// 种植年龄禁忌:≤18 岁
if (age <= 18) {
const lift = new Date(birth);
lift.setFullYear(lift.getFullYear() + ContraindicationFeatureExtractor.IMPLANT_MIN_AGE);
types.push({
code: 'implant_age',
zh: '种植禁忌',
reason: `年龄${age}岁(骨骼发育未完全)`,
validUntil: lift.toISOString().slice(0, 10),
});
}
// 其余禁忌(糖尿病/过敏/抗凝/妊娠…)留 Layer C —— 见类注释
if (types.length === 0) return null;
return {
key: this.key,
description: types.map((t) => `${t.zh}(${t.reason})`).join(' / '),
score: null,
data: { types: types.map((t) => t.code), labels: types.map((t) => t.zh), detail: types },
evidence: { factIds: [] },
};
}
}
......@@ -14,6 +14,7 @@ import { TimePreferenceFeatureExtractor } from './time-preference.feature';
import { DiscountAnchorFeatureExtractor } from './discount-anchor.feature';
import { SpecialAttentionFeatureExtractor } from './special-attention.feature';
import { TreatmentSensitivityFeatureExtractor } from './treatment-sensitivity.feature';
import { ContraindicationFeatureExtractor } from './contraindication.feature';
/**
* FeatureRegistry — 收集所有 PersonaFeature 提取器。
......@@ -40,9 +41,10 @@ export class FeatureRegistry {
discountAnchor: DiscountAnchorFeatureExtractor,
specialAttention: SpecialAttentionFeatureExtractor,
treatmentSensitivity: TreatmentSensitivityFeatureExtractor,
contraindication: ContraindicationFeatureExtractor,
dnc: DoNotContactStatusFeatureExtractor,
entitlement: EntitlementStatusFeatureExtractor,
) {
this.extractors = [rfm, age, gender, acquisition, family, referral, lifecycle, treatmentHistory, timePref, discountAnchor, specialAttention, treatmentSensitivity, dnc, entitlement];
this.extractors = [rfm, age, gender, acquisition, family, referral, lifecycle, treatmentHistory, timePref, discountAnchor, specialAttention, treatmentSensitivity, contraindication, dnc, entitlement];
}
}
......@@ -16,6 +16,7 @@ import { TimePreferenceFeatureExtractor } from './features/time-preference.featu
import { DiscountAnchorFeatureExtractor } from './features/discount-anchor.feature';
import { SpecialAttentionFeatureExtractor } from './features/special-attention.feature';
import { TreatmentSensitivityFeatureExtractor } from './features/treatment-sensitivity.feature';
import { ContraindicationFeatureExtractor } from './features/contraindication.feature';
@Module({
controllers: [PersonaController],
......@@ -35,6 +36,7 @@ import { TreatmentSensitivityFeatureExtractor } from './features/treatment-sensi
DiscountAnchorFeatureExtractor,
SpecialAttentionFeatureExtractor,
TreatmentSensitivityFeatureExtractor,
ContraindicationFeatureExtractor,
DoNotContactStatusFeatureExtractor,
EntitlementStatusFeatureExtractor,
],
......
......@@ -393,6 +393,7 @@ export const PersonaFeatureKey = {
DISCOUNT_ANCHOR: 'discount_anchor', // 折扣锚点(历史最低折扣率+日期;价格底线参考)
SPECIAL_ATTENTION: 'special_attention', // 特别关注(屡次爽约/经常迟到/免打扰/不可等候)
TREATMENT_SENSITIVITY: 'treatment_sensitivity', // 治疗敏感(看牙恐惧/晕针/晕血/密闭恐惧;病历关键词)
CONTRAINDICATION: 'contraindication', // 禁忌标签(v1 仅种植年龄≤18;余 Layer C)
// v1 候选(规则路径,业务方反馈后逐步上)
ENTITLEMENT_STATUS: 'entitlement_status', // 权益身份(商保直付 / 医保 / 储值 / 私行;事实投影型,史+最近日期)
......
......@@ -50,6 +50,7 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }>
[PersonaFeatureKey.DISCOUNT_ANCHOR]: { label: '折扣锚点', tone: 'rose' },
[PersonaFeatureKey.SPECIAL_ATTENTION]: { label: '特别关注', tone: 'rose' },
[PersonaFeatureKey.TREATMENT_SENSITIVITY]: { label: '治疗敏感', tone: 'rose' },
[PersonaFeatureKey.CONTRAINDICATION]: { label: '禁忌标签', tone: 'rose' },
[PersonaFeatureKey.VALUE]: { label: '患者价值', tone: 'indigo' },
[PersonaFeatureKey.TREATMENT_CHAIN_STATUS]: { label: '治疗链状态', tone: 'amber' },
[PersonaFeatureKey.RECALL_RISK]: { label: '流失风险', tone: 'emerald' },
......
......@@ -288,4 +288,23 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
owner: 'pac-algo',
version: 1,
},
// ── D.2.4 禁忌标签(v1 仅种植年龄;余 Layer C)──
contraindication: {
key: 'contraindication',
nameZh: '禁忌标签',
tier: 'rule', // v1 规则;完整版需 LLM(Layer C)
timeSemantics: 'snapshot', // 年龄禁忌随年龄自动解除(validUntil=满19岁)
labelValues: ['种植禁忌', '正畸禁忌', '麻醉禁忌', '手术禁忌'],
dataSource: '现:patient.birthDate(年龄禁忌);完整版:EMR 既往史/诊断(需 LLM 抽取,Layer C)。宿主未给 health_profile',
dataFields: ['health_profile', 'medical_record_tag(既往史+诊断)', 'emr_history'],
meaning: '客户因健康/年龄不适合或需谨慎特定治疗;预约/方案前必须核查,影响可行性与安全预警。允许并列',
algorithm: [
'v1 仅实现【种植年龄禁忌:年龄≤18,骨骼发育未完全】(validUntil=满19岁自动解除)。',
'⚠️ 其余禁忌(糖尿病控制不佳/过敏/抗凝/妊娠/放疗…)规则做不了:① 既往史否定泛滥(含"过敏"97%是"否认过敏",naive 匹配=医疗事故);',
'② 量化条件无数据(HbA1c 全量仅10条/血压自由文本);③ 需控制状态/急性期判断+时效窗口 → 留 Layer C(LLM 抽取)。',
].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