f96fdcb3b9
Lock health recording behind confirm-only state machines, reject public publishes for the health category, and ship the 14-day experiment Page Data templates with tests. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.4 KiB
JavaScript
115 lines
3.4 KiB
JavaScript
export const HEALTH_RULE_INTENTS = Object.freeze({
|
||
BLOOD_PRESSURE: 'blood_pressure',
|
||
WEIGHT: 'weight',
|
||
HEART_RATE: 'heart_rate',
|
||
SPO2: 'spo2',
|
||
TEMPERATURE: 'temperature',
|
||
SLEEP: 'sleep',
|
||
SYMPTOM: 'symptom',
|
||
ASSESS: 'assess',
|
||
ARCHIVE: 'archive',
|
||
});
|
||
|
||
const BP_PAIR = /(\d{2,3})\s*[//]\s*(\d{2,3})/;
|
||
|
||
export function matchHealthIdleRules(text) {
|
||
const raw = String(text ?? '').trim();
|
||
if (!raw) return { matched: false, confidence: 0 };
|
||
|
||
const bp = raw.match(BP_PAIR);
|
||
if (bp || /血压/.test(raw) && /\d{2,3}/.test(raw)) {
|
||
const systolic = bp ? Number(bp[1]) : null;
|
||
const diastolic = bp ? Number(bp[2]) : null;
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.BLOOD_PRESSURE,
|
||
confidence: bp ? 0.95 : 0.8,
|
||
extracted: { systolic, diastolic },
|
||
};
|
||
}
|
||
|
||
const weight = raw.match(/体重\s*(\d{2,3}(?:\.\d)?)/);
|
||
if (weight) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.WEIGHT,
|
||
confidence: 0.9,
|
||
extracted: { weight: Number(weight[1]) },
|
||
};
|
||
}
|
||
|
||
const hr = raw.match(/(?:心率|脉搏)\s*(\d{2,3})/);
|
||
if (hr) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.HEART_RATE,
|
||
confidence: 0.9,
|
||
extracted: { hr: Number(hr[1]) },
|
||
};
|
||
}
|
||
|
||
const spo2 = raw.match(/(?:血氧|SpO2|spo2)\s*(\d{2,3})/i);
|
||
if (spo2) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.SPO2,
|
||
confidence: 0.9,
|
||
extracted: { spo2: Number(spo2[1]) },
|
||
};
|
||
}
|
||
|
||
const temp = raw.match(/(?:体温|发烧)\s*(3\d(?:\.\d)?)/);
|
||
if (temp) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.TEMPERATURE,
|
||
confidence: 0.9,
|
||
extracted: { temperature: Number(temp[1]) },
|
||
};
|
||
}
|
||
|
||
if (/睡了|睡眠/.test(raw) && /\d/.test(raw)) {
|
||
return { matched: true, intent: HEALTH_RULE_INTENTS.SLEEP, confidence: 0.75, extracted: {} };
|
||
}
|
||
|
||
if (/头晕|胸闷|乏力|心慌|头痛|气短/.test(raw)) {
|
||
return { matched: true, intent: HEALTH_RULE_INTENTS.SYMPTOM, confidence: 0.8, extracted: {} };
|
||
}
|
||
|
||
if (/(?:最近怎么样|有没有异常|健康评估)|(?:身体|血压|健康).{0,12}(?:怎么样|帮我看看)|帮我看看.{0,12}(?:身体|血压|健康)/.test(raw)) {
|
||
return { matched: true, intent: HEALTH_RULE_INTENTS.ASSESS, confidence: 0.85, extracted: {} };
|
||
}
|
||
|
||
if (/我的档案|历史记录|报告/.test(raw)) {
|
||
return { matched: true, intent: HEALTH_RULE_INTENTS.ARCHIVE, confidence: 0.8, extracted: {} };
|
||
}
|
||
|
||
return { matched: false, confidence: 0 };
|
||
}
|
||
|
||
export function shouldForceActionChoice(ruleResult, llm = null) {
|
||
if (ruleResult?.matched && ruleResult.confidence >= 0.75) return false;
|
||
const confidence = Number(llm?.confidence ?? 0);
|
||
const gap = Number(llm?.top1 ?? 0) - Number(llm?.top2 ?? 0);
|
||
if (confidence < 0.75 || gap < 0.15) return true;
|
||
return false;
|
||
}
|
||
|
||
export function shouldRedirectOrdinaryChatToHealth(text) {
|
||
const hit = matchHealthIdleRules(text);
|
||
if (!hit.matched) return false;
|
||
return [
|
||
HEALTH_RULE_INTENTS.BLOOD_PRESSURE,
|
||
HEALTH_RULE_INTENTS.WEIGHT,
|
||
HEALTH_RULE_INTENTS.HEART_RATE,
|
||
HEALTH_RULE_INTENTS.SPO2,
|
||
HEALTH_RULE_INTENTS.TEMPERATURE,
|
||
HEALTH_RULE_INTENTS.SLEEP,
|
||
HEALTH_RULE_INTENTS.SYMPTOM,
|
||
HEALTH_RULE_INTENTS.ASSESS,
|
||
].includes(hit.intent);
|
||
}
|
||
|
||
export const HEALTH_REDIRECT_COPY =
|
||
'你想记录健康数据吗?健康数据需要在健康助手通道里录入,这样才能保证记录准确并纳入你的健康基线分析。';
|