import { inferBloodPressureContext } from './health-observation-validate.mjs'; import { HEALTH_DRAFT_TTL_MS, createDraftKey } from './health-observation-draft-store.mjs'; function extractionToCommitBody(extracted = {}, { observedAt = Date.now(), source = 'photo', sourceRef = null } = {}) { const metricSet = extracted.metricSet ?? extracted.metric_set; if (metricSet === 'blood_pressure') { return { confirmed: true, metricSet: 'blood_pressure', systolic: extracted.values?.systolic ?? extracted.systolic, diastolic: extracted.values?.diastolic ?? extracted.diastolic, context: extracted.context ?? inferBloodPressureContext(observedAt), observedAt, source, sourceRef, qualityFlag: extracted.lowConfidence ? 'suspect' : 'ok', rawPayload: extracted.raw ?? extracted, }; } if (metricSet === 'symptom') { return { confirmed: true, metricSet: 'symptom', symptomCode: extracted.symptomCode ?? extracted.code, severity: extracted.severity, observedAt, source, sourceRef, }; } const valueNum = extracted.values?.valueNum ?? extracted.values?.hr ?? extracted.values?.weight ?? extracted.values?.spo2 ?? extracted.values?.temperature ?? extracted.valueNum; return { confirmed: true, metricSet, valueNum, observedAt, source, sourceRef, context: 'any', qualityFlag: extracted.lowConfidence ? 'suspect' : 'ok', rawPayload: extracted.raw ?? extracted, }; } export function createHealthObservationDraftService({ draftStore, observationService } = {}) { if (!draftStore || !observationService) { throw new Error('createHealthObservationDraftService requires draftStore and observationService'); } return { draftStore, observationService, async saveExtractionDraft(userId, { extraction, channel = 'h5', sourceRef = null, now = Date.now(), } = {}) { if (!extraction?.ok) { const error = Object.assign(new Error('抽取结果无效,无法创建草稿'), { code: 'invalid_extraction' }); throw error; } if (sourceRef) { const existing = await draftStore.findPendingBySourceRef(userId, sourceRef); if (existing) return existing; } const draftKey = createDraftKey({ userId, sourceRef: sourceRef ?? `manual-${now}`, metricSet: extraction.metricSet, now, }); return draftStore.insert(userId, { draftKey, metricSet: extraction.metricSet, sourceRef, channel, expiresAt: now + HEALTH_DRAFT_TTL_MS, extracted: { metricSet: extraction.metricSet, values: extraction.values, lowConfidence: extraction.lowConfidence ?? false, raw: extraction.raw ?? null, sourceRef, }, validation: { requireConfirm: true, ok: true, }, }); }, async getDraft(userId, draftKey) { const draft = await draftStore.findByKey(userId, draftKey); if (!draft) return null; if (draft.expiresAt <= Date.now()) return null; return draft; }, async commitDraft(userId, draftKey, { source = 'photo' } = {}) { const draft = await this.getDraft(userId, draftKey); if (!draft) { const error = Object.assign(new Error('草稿不存在或已过期'), { code: 'draft_not_found' }); throw error; } const body = extractionToCommitBody(draft.extracted, { observedAt: Date.now(), source, sourceRef: draft.sourceRef, }); const result = await observationService.commit(userId, body); await draftStore.update(userId, draftKey, { status: 'committed', committedAt: Date.now(), }); return { draft, ...result }; }, async discardDraft(userId, draftKey) { const draft = await draftStore.findByKey(userId, draftKey); if (!draft) return null; return draftStore.update(userId, draftKey, { status: 'discarded' }); }, }; }