Commit 1171fb42 by luoqi

fix(persona): 核查项不进画像 + 风险区先隐藏 + 依据行不再露内部 ID

业务口径三处调整:

── ① check 档(待核查)不进画像 ──
「有糖尿病史但控制状态未知」对客服不构成可执行信息,摆出来只会让人把「问一句」
当成「不能治」,是净噪音。现在 diabetes / hypertension / osteoporosis / smoking
不再产任何画像输出(本地实测禁忌特征 1910 → 1840,少的 70 个正是只有核查项的患者)。

️ 信号本身**照常抽取并存在 fact 的 content.clinical_signals 里**,一条不丢。
这是「只讲事实」与「只说有用的话」的分工:事实层求全,画像层求准。

顺带改掉复评期的处理:超期不再降级为核查项(那会让绝对禁忌凭空消失),而是
**仍算禁忌**,只在依据行注明「记录较早,建议确认近况」—— 抗凝药可能已停,但漏挡
比多挡危险得多。副作用:正畸禁忌从 0 回到 13 人(之前被复评期降级掉了)。

── ② 关键事实的风险区先隐藏 ──
画像标签卡的摘要已会提到禁忌,同屏再来一条红块重复。禁忌照常在画像 chip +
详情抽屉露出。代码保留并加 SHOW_RISK_NOTES 开关 —— 这个位置将来要接投诉 /
退费纠纷 / 医疗争议(业务已提出设想,现无数据),届时改回 true 即可。

── ③ 依据行不再显示宿主内部 ID ──
接着上一个 commit 的 emr_record,把剩下三类一并修掉 —— 它们的 f.title 都是
`<中文> <externalId>`:
    接诊 16432552 → 洁牙 / 正畸复查(取 content.chief_complaint)
    挂号 <id>     → 科室 · X医生
    前台接待 <id> → 前台接待
生产实测 encounter_record 93.4%(2,004,383/2,146,887)带主诉,这条修复对绝大多数
依据行是实质改善,不是换个无用字符串。

测试 550 passed。
注:sync-lock-reap 那条在 37 suite 并发时偶发失败,单跑(改动前后均)通过 —— 是
它自身的时间竞态 flake,与本次改动无关,未处理。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent f85b7680
...@@ -32,9 +32,14 @@ import { earliestFuture, nextAgeBoundary } from './time-boundary'; ...@@ -32,9 +32,14 @@ import { earliestFuture, nextAgeBoundary } from './time-boundary';
* ── 三档严重度决定下游怎么用 ── * ── 三档严重度决定下游怎么用 ──
* absolute 绝对禁忌 → 打「X 禁忌」标签;将来接召回闸门挡的就是这一档 * absolute 绝对禁忌 → 打「X 禁忌」标签;将来接召回闸门挡的就是这一档
* relative 相对禁忌 → 打标签 + 话术警示,**不挡召回**(能不能做由医生按身体条件评估) * relative 相对禁忌 → 打标签 + 话术警示,**不挡召回**(能不能做由医生按身体条件评估)
* check 核查项 → 只提示「预约前确认 X」,不叫禁忌 * check 核查项 → **本特征不产出**(见下)
* 定量条件无数据的(糖尿病要 HbA1c、高血压要收缩压)一律落 check —— 控制达标的糖尿病 * 定量条件无数据的(糖尿病要 HbA1c、高血压要收缩压)一律落 check —— 控制达标的糖尿病
* 是可以种植的,硬判禁忌医学上就是错的。判断权还给医生,PAC 只把事实摆出来。 * 是可以种植的,硬判禁忌医学上就是错的。判断权还给医生。
*
* ⭐ check 档为什么不出现在画像里:业务口径 —— 「有糖尿病史但控制状态未知」对客服不构成
* 可执行信息,摆出来只会让人把「问一句」当成「不能治」,是净噪音。信号本身**照常抽取并
* 存在 fact 的 content.clinical_signals 里**(事实一条不丢,可查可回溯),只是画像层不说。
* 这就是「只讲事实」与「只说有用的话」的分工:事实层求全,画像层求准。
* *
* ── 时效 ── * ── 时效 ──
* 「禁忌≠终身禁忌」:妊娠 280 天、哺乳 180 天(产后半年解除)、放疗/抗骨吸收药终身。 * 「禁忌≠终身禁忌」:妊娠 280 天、哺乳 180 天(产后半年解除)、放疗/抗骨吸收药终身。
...@@ -53,7 +58,6 @@ export class ContraindicationFeatureExtractor implements FeatureExtractor { ...@@ -53,7 +58,6 @@ export class ContraindicationFeatureExtractor implements FeatureExtractor {
extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null { extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
const detail: ContraindicationDetail[] = []; const detail: ContraindicationDetail[] = [];
const checks: ContraindicationDetail[] = [];
const factIds = new Set<string>(); const factIds = new Set<string>();
// ── ① 年龄禁忌(不依赖任何 fact,证据是 patient.birthDate)── // ── ① 年龄禁忌(不依赖任何 fact,证据是 patient.birthDate)──
...@@ -73,28 +77,25 @@ export class ContraindicationFeatureExtractor implements FeatureExtractor { ...@@ -73,28 +77,25 @@ export class ContraindicationFeatureExtractor implements FeatureExtractor {
}); });
} }
// ── ② 病历信号裁决 ── // ── ② 病历信号裁决(check 档在 resolveSignals 里已滤掉,不进画像)──
for (const d of resolveSignals(collectSignals(ctx, factIds), ctx.now)) { detail.push(...resolveSignals(collectSignals(ctx, factIds), ctx.now));
(d.severity === 'check' ? checks : detail).push(d);
}
if (detail.length === 0 && checks.length === 0) return null; if (detail.length === 0) return null;
const domains = orderedDomains(detail); const domains = orderedDomains(detail);
const labels = domains.map((d) => CONTRAINDICATION_LABEL[d]); const labels = domains.map((d) => CONTRAINDICATION_LABEL[d]);
return { return {
key: this.key, key: this.key,
description: buildDescription(detail, checks, domains), description: `${labels.join(' / ')}(${[...new Set(detail.map((d) => d.label))].join('、')})`,
score: null, score: null,
data: { data: {
// 兼容既有消费方(persona-tag-filters 按 data.types 圈人,前端 compactPersonaValue 读 data.labels) // 兼容既有消费方:persona-tag-filters 按 data.domains 圈人(治疗域四类),
// 前端 compactPersonaValue 读 data.labels;data.types 留病因码供详情/排查用。
types: detail.map((d) => d.code), types: detail.map((d) => d.code),
labels, labels,
domains, domains,
detail, detail,
/// 核查项单列 —— 它**不是禁忌**,混进 labels 会让客服把「有糖尿病史」当成不能治
checks,
}, },
evidence: { factIds: [...factIds] }, evidence: { factIds: [...factIds] },
}; };
...@@ -125,7 +126,8 @@ interface ContraindicationDetail { ...@@ -125,7 +126,8 @@ interface ContraindicationDetail {
code: string; code: string;
label: string; label: string;
domains: TreatmentDomain[]; domains: TreatmentDomain[];
severity: 'absolute' | 'relative' | 'check'; /** 只会是 absolute / relative —— check 档在裁决时已滤掉,不进画像 */
severity: 'absolute' | 'relative';
reason: string; reason: string;
/// 医生原话片段 —— 详情页/话术引用的是这个,不是 PAC 的转述 /// 医生原话片段 —— 详情页/话术引用的是这个,不是 PAC 的转述
phrase: string | null; phrase: string | null;
...@@ -186,15 +188,20 @@ function resolveSignals(signals: SourcedSignal[], now: Date): ContraindicationDe ...@@ -186,15 +188,20 @@ function resolveSignals(signals: SourcedSignal[], now: Date): ContraindicationDe
const out: ContraindicationDetail[] = []; const out: ContraindicationDetail[] = [];
for (const s of byCode.values()) { for (const s of byCode.values()) {
if (s.negated !== false) continue; // 否定 / 不可判都不产禁忌 if (s.negated !== false) continue; // 否定 / 不可判都不产禁忌
// check 档(糖尿病/高血压/骨质疏松/吸烟史 —— 有病史但控制状态未知)不进画像:
// 对客服不构成可执行信息,摆出来只会让人把「问一句」当成「不能治」。
// 信号本身照常留在 fact 的 content.clinical_signals 里,事实一条不丢。
if (s.severity === 'check') continue;
if (s.validUntil && s.validUntil <= iso(now)) continue; // 已过期(2016 年那条哺乳期) if (s.validUntil && s.validUntil <= iso(now)) continue; // 已过期(2016 年那条哺乳期)
// 过了复评日 → 降级为核查项,而不是消失。「消失」会让客服以为患者从没有过这个病史。 // 过了复评日:**仍然算禁忌**(抗凝药可能已停,但漏挡比多挡危险得多),
// 只在依据行注明记录较早 —— 不降级、不消失,把"要不要确认"交给客服看日期判断。
const stale = s.reviewAfter != null && s.reviewAfter <= iso(now); const stale = s.reviewAfter != null && s.reviewAfter <= iso(now);
out.push({ out.push({
code: s.code, code: s.code,
label: s.label, label: s.label,
domains: s.contraindicates, domains: s.contraindicates,
severity: stale ? 'check' : s.severity, severity: s.severity,
reason: stale ? `${s.label}(记录已超复评期,需重新确认)` : s.label, reason: stale ? `${s.label}(记录较早,建议确认近况)` : s.label,
phrase: s.phrase, phrase: s.phrase,
source: { source: {
factId: s.factId, factId: s.factId,
...@@ -221,22 +228,3 @@ function orderedDomains(detail: ContraindicationDetail[]): TreatmentDomain[] { ...@@ -221,22 +228,3 @@ function orderedDomains(detail: ContraindicationDetail[]): TreatmentDomain[] {
return CONTRAINDICATION_DOMAIN_ORDER.filter((d) => hit.has(d)); return CONTRAINDICATION_DOMAIN_ORDER.filter((d) => hit.has(d));
} }
/**
* 自包含描述 —— 客服不点开也能看懂「禁什么、凭什么」。
* 例:`手术禁忌 / 种植禁忌(长期抗凝/抗血小板药物)· 待核查:糖尿病史(控制状态未知)`
*/
function buildDescription(
detail: ContraindicationDetail[],
checks: ContraindicationDetail[],
domains: TreatmentDomain[],
): string {
const parts: string[] = [];
if (domains.length > 0) {
const causes = [...new Set(detail.map((d) => d.label))].join('、');
parts.push(`${domains.map((d) => CONTRAINDICATION_LABEL[d]).join(' / ')}(${causes})`);
}
if (checks.length > 0) {
parts.push(`待核查:${[...new Set(checks.map((c) => c.label))].join('、')}`);
}
return parts.join(' · ');
}
...@@ -133,13 +133,18 @@ describe('③ 时效 —— 禁忌≠终身禁忌', () => { ...@@ -133,13 +133,18 @@ describe('③ 时效 —— 禁忌≠终身禁忌', () => {
}); });
}); });
describe('④ 超复评期 → 降级为核查项,而不是消失', () => { describe('④ 超复评期仍算禁忌,只在依据里注明记录较早', () => {
// 「消失」会让客服以为患者从没有过这个病史,那是丢事实 // 抗凝药可能已停,但**漏挡比多挡危险得多** —— 不降级、不消失,把"要不要确认"交给客服看日期判断
it('三年前的抗凝药记录(复评期 730 天)落到 checks,不再是禁忌', () => { it('三年前的抗凝药记录(复评期 730 天)仍是手术/种植禁忌', () => {
const d = data(withFacts([emr('长期口服泰嘉(抗凝药物)', '2023-01-01')])); const d = data(withFacts([emr('长期口服泰嘉(抗凝药物)', '2023-01-01')]));
expect(d?.types).not.toContain('anticoagulant'); expect(d?.types).toContain('anticoagulant');
expect(d?.checks.map((c) => c.code)).toContain('anticoagulant'); expect(d?.labels).toEqual(['手术禁忌', '种植禁忌']);
expect(String(d?.checks[0]!.reason)).toContain('需重新确认'); expect(String(d?.detail[0]!.reason)).toContain('记录较早');
});
it('复评期内的不加提示', () => {
const d = data(withFacts([emr('长期口服泰嘉(抗凝药物)', '2026-06-01')]));
expect(String(d?.detail[0]!.reason)).not.toContain('记录较早');
}); });
}); });
...@@ -165,22 +170,28 @@ describe('⑤ 多年病历冲突裁决', () => { ...@@ -165,22 +170,28 @@ describe('⑤ 多年病历冲突裁决', () => {
}); });
}); });
describe('⑥ 核查项不是禁忌 —— 这是对业务细则最大的一处偏离', () => { describe('⑥ check 档不进画像 —— 业务口径:对客服不构成可执行信息', () => {
it('糖尿病史落 checks 而非 labels(HbA1c 无数据,控制达标可种植)', () => { // 「有糖尿病史但控制状态未知」摆给客服看,只会让人把「问一句」当成「不能治」。
const d = data(withFacts([emr('糖尿病,长期服药史、其余体健', '2026-06-01')])); // 信号本身照常抽取并存在 fact 的 clinical_signals 里,事实一条不丢,只是画像层不说。
expect(d?.labels ?? []).toHaveLength(0); it('只有糖尿病史 → 完全不产禁忌特征', () => {
expect(d?.checks.map((c) => c.code)).toContain('diabetes'); expect(extractor.extract(withFacts([emr('糖尿病,长期服药史、其余体健', '2026-06-01')]))).toBeNull();
expect(extractor.extract(withFacts([emr('糖尿病,长期服药史、其余体健', '2026-06-01')]))?.description).toBe(
'待核查:糖尿病史(控制状态未知)',
);
}); });
it('禁忌与核查项并存时,description 两段分开表述', () => { it.each(['高血压病史现服药控制', '骨质疏松症', '患者自述有吸烟史'])('%s 也不产', (t) => {
expect(extractor.extract(withFacts([emr(t, '2026-06-01')]))).toBeNull();
});
it('真禁忌与 check 同句时,只讲禁忌那部分', () => {
const f = extractor.extract( const f = extractor.extract(
withFacts([emr('高血压病史现服药控制;曾在外院做个心脏支架手术', '2026-06-01')]), withFacts([emr('高血压病史现服药控制;曾在外院做个心脏支架手术', '2026-06-01')]),
); );
expect(f?.description).toContain('(心脏植入物/心血管手术史)'); expect(f?.description).toBe('手术禁忌 / 种植禁忌(心脏植入物/心血管手术史)');
expect(f?.description).toContain('待核查:高血压史(控制状态未知)'); expect(f?.description).not.toContain('高血压');
});
it('抽取层照常抽到 check 信号 —— 画像不说 ≠ 事实没存', () => {
const sigs = signalsOf('糖尿病,长期服药史、其余体健', '2026-06-01');
expect(sigs.find((s) => s.code === 'diabetes')?.negated).toBe(false);
}); });
}); });
......
...@@ -92,10 +92,23 @@ export function factLabel(f: AdaptedFact): FactLabel { ...@@ -92,10 +92,23 @@ export function factLabel(f: AdaptedFact): FactLabel {
}; };
return { badge: '预约', badgeTone: 'bg-sky-50 text-sky-700', text: ST_ZH[st] ?? st ?? '预约' }; return { badge: '预约', badgeTone: 'bg-sky-50 text-sky-700', text: ST_ZH[st] ?? st ?? '预约' };
} }
case 'encounter_record': // ⚠️ 下面三类的 f.title 都是 `<中文> <externalId>`(接诊 16432552 / 挂号 xxx / 前台接待 xxx)——
return { badge: '就诊', badgeTone: 'bg-teal-50 text-teal-700', text: f.title ?? '到诊' }; // 直接显示等于把宿主内部 ID 甩给客服。一律改取 content 里有语义的字段,取不到就只留中文词。
case 'visit_registration_record': case 'encounter_record': {
return { badge: '挂号', badgeTone: 'bg-teal-50 text-teal-700', text: f.title ?? '挂号' }; const tone = { badge: '就诊', badgeTone: 'bg-teal-50 text-teal-700' };
const cc = String(c.chief_complaint ?? '').trim() || String(f.summary ?? '').trim();
const doc = String(c.doctor_name ?? '').trim();
if (cc) return { ...tone, text: cc, hover: doc ? `${cc}(${doc}医生)` : cc };
return { ...tone, text: doc ? `${doc}医生接诊` : '到诊' };
}
case 'visit_registration_record': {
const doc = String(c.doctor_name ?? '').trim();
const dept = String(c.department ?? c.department_name ?? '').trim();
const text = [dept, doc ? `${doc}医生` : ''].filter(Boolean).join(' · ') || '挂号';
return { badge: '挂号', badgeTone: 'bg-teal-50 text-teal-700', text };
}
case 'visit_reception_record':
return { badge: '接待', badgeTone: 'bg-teal-50 text-teal-700', text: '前台接待' };
case 'emr_record': { case 'emr_record': {
const tone = { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600' }; const tone = { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600' };
// ⚠️ **不能用 f.summary** —— emr parser 写进去的是 `关联接诊:<encounter_id>`(内部 ID), // ⚠️ **不能用 f.summary** —— emr parser 写进去的是 `关联接诊:<encounter_id>`(内部 ID),
......
...@@ -1627,7 +1627,6 @@ interface ContraindicationEntry { ...@@ -1627,7 +1627,6 @@ interface ContraindicationEntry {
interface ContraindicationData { interface ContraindicationData {
labels?: string[]; labels?: string[];
detail?: ContraindicationEntry[]; detail?: ContraindicationEntry[];
checks?: ContraindicationEntry[];
} }
/** /**
...@@ -1646,44 +1645,42 @@ interface RiskNote { ...@@ -1646,44 +1645,42 @@ interface RiskNote {
muted?: boolean; muted?: boolean;
} }
/**
* 风险提示位开关 —— **当前关闭**。
*
* 关掉的理由(业务口径):画像标签卡的那句摘要已经会提到禁忌,关键事实卡再来一条
* 红块在同屏内重复,信息密度不划算。禁忌本身照常在画像标签 chip + 画像详情抽屉里露出。
*
* 保留代码而不是删掉:这个位置将来要接投诉 / 退费纠纷 / 医疗争议(业务已提出设想,
* 只是现在没数据)。届时把这里改回 true,再往 buildRiskNotes 里追加来源即可。
*/
const SHOW_RISK_NOTES = false;
/** 中文顿号连接,去重去空 */ /** 中文顿号连接,去重去空 */
function joinZh(xs: Array<string | null | undefined>): string { function joinZh(xs: Array<string | null | undefined>): string {
return [...new Set(xs.filter((x): x is string => !!x))].join('、'); return [...new Set(xs.filter((x): x is string => !!x))].join('、');
} }
function buildRiskNotes(persona: typeof mockPersona): RiskNote[] { function buildRiskNotes(persona: typeof mockPersona): RiskNote[] {
if (!SHOW_RISK_NOTES) return [];
const notes: RiskNote[] = []; const notes: RiskNote[] = [];
const d = (persona.features.find((f) => f.key === PersonaFeatureKey.CONTRAINDICATION)?.data ?? const d = (persona.features.find((f) => f.key === PersonaFeatureKey.CONTRAINDICATION)?.data ??
null) as ContraindicationData | null; null) as ContraindicationData | null;
if (d) { const bans = (d?.labels ?? []).filter(Boolean);
const bans = (d.labels ?? []).filter(Boolean);
if (bans.length > 0) { if (bans.length > 0) {
const detail = (d.detail ?? []).map((x) => const detail = (d?.detail ?? []).map((x) =>
x.source?.occurredAt ? `${x.label} · ${x.source.occurredAt}` : x.label, x.source?.occurredAt ? `${x.label} · ${x.source.occurredAt}` : x.label,
); );
notes.push({ notes.push({
title: bans.join(' / '), title: bans.join(' / '),
detail: joinZh(detail), detail: joinZh(detail),
// 全文原话留 hover —— 一行放不下「有放射治疗史,三周前进行颈淋巴结清扫(舌癌淋巴结转移)」 // 全文原话留 hover —— 一行放不下「有放射治疗史,三周前进行颈淋巴结清扫(舌癌淋巴结转移)」
tooltip: (d.detail ?? []) tooltip:
(d?.detail ?? [])
.map((x) => `${x.source?.occurredAt ? `${x.source.occurredAt} ` : ''}${x.phrase ?? x.reason ?? x.label}`) .map((x) => `${x.source?.occurredAt ? `${x.source.occurredAt} ` : ''}${x.phrase ?? x.reason ?? x.label}`)
.join('\n') || undefined, .join('\n') || undefined,
}); });
} }
const checks = d.checks ?? [];
if (checks.length > 0) {
notes.push({
title: `待核查:${joinZh(checks.map((c) => c.label))}`,
detail: joinZh(
checks.map((c) => (c.source?.occurredAt ? `记录于 ${c.source.occurredAt}` : null)),
),
tooltip: checks
.map((c) => `${c.source?.occurredAt ? `${c.source.occurredAt} ` : ''}${c.phrase ?? c.label}`)
.join('\n') || undefined,
muted: true,
});
}
}
// ⬆ 以后:投诉记录 / 退费纠纷 / 医疗争议 —— 各自 push 一条 RiskNote 即可(现无数据) // ⬆ 以后:投诉记录 / 退费纠纷 / 医疗争议 —— 各自 push 一条 RiskNote 即可(现无数据)
return notes; return notes;
} }
......
...@@ -559,9 +559,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = { ...@@ -559,9 +559,9 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
meaning: '客户因健康/年龄不适合或需谨慎特定治疗;预约/方案前必须核查,影响可行性与安全预警。允许并列', meaning: '客户因健康/年龄不适合或需谨慎特定治疗;预约/方案前必须核查,影响可行性与安全预警。允许并列',
algorithm: [ algorithm: [
'两层:摄入时按 data/_shared/dict/clinical-signals.yaml 把病历文本抽成结构化信号(带否定判定 + 到期日),本特征只做裁决。', '两层:摄入时按 data/_shared/dict/clinical-signals.yaml 把病历文本抽成结构化信号(带否定判定 + 到期日),本特征只做裁决。',
'严重度三档 —— absolute 绝对禁忌(打标签,将来接召回闸门挡的是这一档)/ relative 相对禁忌(打标签+话术警示,不挡召回,由医生按身体条件评估)/ check 核查项(只提示"预约前确认X",不叫禁忌)。', '严重度三档 —— absolute 绝对禁忌(打标签,将来接召回闸门挡的是这一档)/ relative 相对禁忌(打标签+话术警示,不挡召回,由医生按身体条件评估)/ check 核查项(**不进画像**,只留在 fact 层)。',
'时效:妊娠 280 天、哺乳 180 天(产后半年解除)、放疗/抗骨吸收药终身、抗凝药等设复评期(超期降级为核查项而非消失)。到期日在抽取时按源病历日期算好,nextBoundaryAt 到期当晚自动重算。', '时效:妊娠 280 天、哺乳 180 天(产后半年解除)、放疗/抗骨吸收药终身、抗凝药等设复评期(超期仍算禁忌,只在依据行注明"记录较早,建议确认近况")。到期日在抽取时按源病历日期算好,nextBoundaryAt 到期当晚由 02:00 stale-scan 自动重算。',
'⚠️ 定量条件无数据的一律落 check 不判禁忌:糖尿病要 HbA1c(全量仅 10 条)、高血压要收缩压(几乎无人记录)。控制达标的糖尿病可以种植,硬判禁忌医学上是错的。', '⚠️ 定量条件无数据的一律落 check 不判禁忌:糖尿病要 HbA1c(全量仅 10 条)、高血压要收缩压(几乎无人记录)。控制达标的糖尿病可以种植,硬判禁忌医学上是错的;而"有病史但状态未知"对客服不构成可执行信息,故 check 档不出现在画像里,只存在 fact 的 clinical_signals。',
'⚠️ 否定作用域按【句】不按分号 —— 「患者否认有高血压、冠心病;糖尿病、痛风」里分号后的糖尿病仍被否定。生产实测两种切法差 9.4 倍。', '⚠️ 否定作用域按【句】不按分号 —— 「患者否认有高血压、冠心病;糖尿病、痛风」里分号后的糖尿病仍被否定。生产实测两种切法差 9.4 倍。',
].join('\n'), ].join('\n'),
display: { display: {
...@@ -571,7 +571,6 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = { ...@@ -571,7 +571,6 @@ export const PERSONA_FEATURE_SPECS: Record<string, PersonaFeatureSpec> = {
{ label: '手术禁忌', body: '长期抗凝药、凝血功能障碍、抗骨吸收药、妊娠期 —— 拔牙/翻瓣/植骨前须医生评估' }, { label: '手术禁忌', body: '长期抗凝药、凝血功能障碍、抗骨吸收药、妊娠期 —— 拔牙/翻瓣/植骨前须医生评估' },
{ label: '麻醉禁忌', body: '局麻药物过敏(利多卡因/普鲁卡因等);哮喘为相对禁忌' }, { label: '麻醉禁忌', body: '局麻药物过敏(利多卡因/普鲁卡因等);哮喘为相对禁忌' },
{ label: '正畸禁忌', body: '诊断为重度牙周炎 —— 牙周控制后可矫治,属相对禁忌' }, { label: '正畸禁忌', body: '诊断为重度牙周炎 —— 牙周控制后可矫治,属相对禁忌' },
{ label: '待核查', body: '有糖尿病/高血压/骨质疏松/吸烟史但控制状态未知 —— 不是禁忌,预约前问一句' },
], ],
group: 'action', group: 'action',
order: 40, order: 40,
......
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