2baf29b3ae
Deliver encrypted health zone, observation API, baseline maturity pipeline, page-data bindings, and H5/WeChat channel integration for health P0. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
4.5 KiB
JavaScript
154 lines
4.5 KiB
JavaScript
import { extractBloodPressurePair } from './health-bp-parse.mjs';
|
||
import { looksLikeHealthReportPageRequest, looksLikeHealthQuickAssess } from './health-channel-aliases.mjs';
|
||
|
||
export { looksLikeHealthReportPageRequest, looksLikeHealthQuickAssess } from './health-channel-aliases.mjs';
|
||
|
||
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 bpPair = extractBloodPressurePair(raw);
|
||
if (bpPair?.systolic != null && bpPair?.diastolic != null) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.BLOOD_PRESSURE,
|
||
confidence: bpPair.source === 'chinese' ? 0.88 : 0.95,
|
||
extracted: { systolic: bpPair.systolic, diastolic: bpPair.diastolic },
|
||
};
|
||
}
|
||
|
||
const bp = raw.match(BP_PAIR);
|
||
if (bp) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.BLOOD_PRESSURE,
|
||
confidence: 0.95,
|
||
extracted: { systolic: Number(bp[1]), diastolic: Number(bp[2]) },
|
||
};
|
||
}
|
||
|
||
if (/血压/.test(raw) && /\d{2,3}/.test(raw)) {
|
||
const nums = [...raw.matchAll(/\d{2,3}/g)].map((m) => Number(m[0]));
|
||
if (nums.length >= 2) {
|
||
return {
|
||
matched: true,
|
||
intent: HEALTH_RULE_INTENTS.BLOOD_PRESSURE,
|
||
confidence: 0.8,
|
||
extracted: { systolic: nums[0], diastolic: nums[1] },
|
||
};
|
||
}
|
||
}
|
||
|
||
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 (looksLikeHealthReportPageRequest(raw) || looksLikeHealthQuickAssess(raw)) {
|
||
return { matched: true, intent: HEALTH_RULE_INTENTS.ASSESS, confidence: 0.85, extracted: {} };
|
||
}
|
||
|
||
if (/我的档案|历史记录/.test(raw) || (/报告/.test(raw) && !looksLikeHealthReportPageRequest(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 =
|
||
'你想记录健康数据吗?健康数据需要在健康助手通道里录入,这样才能保证记录准确并纳入你的健康基线分析。';
|
||
|
||
const HEALTH_ENTER_PATTERNS = [
|
||
/健康助手/,
|
||
/健康小助手/,
|
||
/进入健康/,
|
||
/健康通道/,
|
||
/录血压/,
|
||
];
|
||
|
||
export function isHealthEnterText(text) {
|
||
const raw = String(text ?? '').trim();
|
||
if (!raw) return false;
|
||
return HEALTH_ENTER_PATTERNS.some((pattern) => pattern.test(raw));
|
||
}
|