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>
58 lines
2.0 KiB
JavaScript
58 lines
2.0 KiB
JavaScript
import { validateBloodPressurePair, validateMetricValue } from './health-observation-validate.mjs';
|
|
|
|
export const EXTRACTION_CONFIDENCE_MIN = 0.9;
|
|
|
|
const METRIC_SET_FIELDS = Object.freeze({
|
|
blood_pressure: ['systolic', 'diastolic'],
|
|
heart_rate: ['hr'],
|
|
weight: ['weight'],
|
|
spo2: ['spo2'],
|
|
temperature: ['temperature'],
|
|
});
|
|
|
|
export function validateExtractionResult(extracted = {}, { metricSetHint = null } = {}) {
|
|
const metricSet = extracted.metric_set || metricSetHint;
|
|
if (!metricSet || !METRIC_SET_FIELDS[metricSet]) {
|
|
return { ok: false, error: 'unknown_metric_set', unreadable: true };
|
|
}
|
|
const confidence = extracted.confidence && typeof extracted.confidence === 'object'
|
|
? extracted.confidence
|
|
: {};
|
|
const lowConfidence = [];
|
|
const values = {};
|
|
|
|
if (metricSet === 'blood_pressure') {
|
|
const pair = validateBloodPressurePair(extracted.systolic, extracted.diastolic);
|
|
if (!pair.ok) {
|
|
return { ok: false, error: pair.error, unreadable: pair.error === 'out_of_range', field: pair.field };
|
|
}
|
|
values.systolic = pair.systolic;
|
|
values.diastolic = pair.diastolic;
|
|
if (extracted.pulse != null) {
|
|
const pulse = validateMetricValue('hr', extracted.pulse);
|
|
if (!pulse.ok) return { ok: false, error: pulse.error, field: 'pulse' };
|
|
values.pulse = pulse.value;
|
|
}
|
|
} else {
|
|
const field = METRIC_SET_FIELDS[metricSet][0];
|
|
const metricType = metricSet === 'heart_rate' ? 'hr' : metricSet === 'weight' ? 'weight' : metricSet;
|
|
const checked = validateMetricValue(metricType, extracted[field] ?? extracted.value);
|
|
if (!checked.ok) return { ok: false, error: checked.error, unreadable: checked.error === 'out_of_range' };
|
|
values[field] = checked.value;
|
|
}
|
|
|
|
for (const field of Object.keys(values)) {
|
|
const score = Number(confidence[field] ?? extracted.confidence?.[field] ?? 1);
|
|
if (score < EXTRACTION_CONFIDENCE_MIN) lowConfidence.push(field);
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
metricSet,
|
|
values,
|
|
lowConfidence,
|
|
requireConfirm: true,
|
|
unreadable: false,
|
|
};
|
|
}
|