${escapeHtml(journey.location ?? journey.room ?? "\u9662\u5185\u884C\u7A0B\u6301\u7EED\u66F4\u65B0")}
-
${visibleStages.map((node) => `
- ${node.label} `).join("")}
/* Generated from patient-service-desk-v3.js for direct file:// opening. */ (() => { // docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs function createInitialState({ activePatientId = "patient-zhang-san", demoNow = "2026-07-03T09:00:00+08:00" } = {}) { return { activeTab: "home", activePatientId, demoNow, appliedProjectionFixtureIds: [], activeAgentTaskProjection: null, patientWorkItems: [], journeyProjections: [], reportSourceProjections: {}, reportFeedback: [], reportFilters: { family: "ALL", status: "ALL" }, resultSnapshotProjections: {}, commandProjections: {}, messages: [], consents: {}, consentScopeBindings: {}, overlays: { commandId: null, consentId: null, consentScope: null, reportId: null, reportView: null, reportUnavailableReason: null, reportExpanded: false, reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null, appointmentId: null, journeyView: null, scenarioPanelOpen: false } }; } var deepFreeze = (value) => { if (value && typeof value === "object" && !Object.isFrozen(value)) { Object.freeze(value); Object.values(value).forEach(deepFreeze); } return value; }; var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value); var reportIdPattern = /^[^\s\u0000-\u001F\u007F]{1,128}$/; var reportHashPattern = /^[^\s\u0000-\u001F\u007F]{1,256}$/; var reportSourceStatuses = /* @__PURE__ */ new Set(["FINAL", "PRELIMINARY"]); var reportFamilies = /* @__PURE__ */ new Set(["LAB", "EXAM"]); var reportTypes = /* @__PURE__ */ new Set(["BLOOD_ROUTINE", "IMAGING"]); var reportVerificationStatuses = /* @__PURE__ */ new Set(["VERIFIED", "NEED_REVIEW"]); var bloodRoutineReportFixture = deepFreeze({ reportId: "report-blood-20260702", family: "LAB", reportType: "BLOOD_ROUTINE", title: "\u8840\u5E38\u89C4", hospitalName: "\u7A7A\u6D77\u533B\u9662", departmentName: "\u68C0\u9A8C\u79D1", issuedAt: "2026-07-03 09:30", sourceStatus: "FINAL", interpretationStatus: "NOT_REQUESTED", verificationStatus: "VERIFIED", sourceVersion: 1, sourceHash: "sha256:blood-v1", readStatus: "UNREAD", metrics: [ { code: "WBC", name: "\u767D\u7EC6\u80DE\u8BA1\u6570", value: 11.2, unit: "10^9/L", referenceRange: "3.5\u20139.5", sourceFlag: "HIGH" } ] }); var registrationDemoText = "\u6211\u60F3\u6302\u660E\u5929\u795E\u7ECF\u5185\u79D1\u674E\u660E\u4E3B\u4EFB\u7684\u53F7"; var registrationResultRef = "result-slot-001"; var registrationResultVersion = 1; var registrationItems = deepFreeze([ { candidateId: "candidate-slot-0930", visitDate: "2026-07-03", timePeriod: "\u4E0A\u5348", visitTime: "09:30", departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", remainingSlots: 3, fee: "30\u5143" }, { candidateId: "candidate-slot-1030", visitDate: "2026-07-03", timePeriod: "\u4E0A\u5348", visitTime: "10:30", departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", remainingSlots: 1, fee: "30\u5143" }, { candidateId: "candidate-slot-1430", visitDate: "2026-07-03", timePeriod: "\u4E0B\u5348", visitTime: "14:30", departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", remainingSlots: 5, fee: "30\u5143" } ]); var registrationResultText = [ `\u5DF2\u4E3A\u4F60\u627E\u5230 ${registrationItems[0].visitDate} ${registrationItems[0].departmentName}${registrationItems[0].doctorName}${registrationItems[0].doctorTitle}\u7684 ${registrationItems.length} \u4E2A\u53F7\u6E90\uFF1A`, ...registrationItems.map((item) => `${item.visitDate} ${item.timePeriod} ${item.visitTime}\uFF0C${item.departmentName}\uFF0C${item.doctorName}${item.doctorTitle}\uFF0C\u4F59\u53F7 ${item.remainingSlots}\uFF0C\u6302\u53F7\u8D39 ${item.fee}\uFF1B`), "\u8BF7\u9009\u62E9\u4E00\u4E2A\u53F7\u6E90\u3002" ].join(""); var nextMessageId = (messages, role) => { const usedIds = new Set( messages.filter((message) => message && typeof message === "object").map(({ id }) => id) ); let sequence = messages.length + 1; while (usedIds.has(`message-${role}-${sequence}`)) { sequence += 1; } return `message-${role}-${sequence}`; }; var simulateRegistrationQueryBackendFixture = (interaction) => { if (interaction?.text !== registrationDemoText) { return { type: "NO_RESULT", text: interaction?.text ? "\u6682\u65F6\u6CA1\u6709\u627E\u5230\u5339\u914D\u7ED3\u679C\u3002\u4F60\u53EF\u4EE5\u6362\u4E2A\u8BF4\u6CD5\uFF0C\u8BD5\u8BD5\u667A\u80FD\u5BFC\u8BCA\uFF0C\u6216\u8F6C\u4EBA\u5DE5\u670D\u52A1\u3002" : "\u8BF7\u8F93\u5165\u60F3\u529E\u7406\u7684\u5C31\u533B\u670D\u52A1\u3002\u4F60\u4E5F\u53EF\u4EE5\u8BD5\u8BD5\u667A\u80FD\u5BFC\u8BCA\u6216\u4EBA\u5DE5\u670D\u52A1\u3002" }; } return { type: "REGISTRATION_CANDIDATES", task: { taskId: "agent-task-registration-001", type: "REGISTRATION", stage: "CANDIDATE_SELECTION", constraints: { departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", visitDate: "2026-07-03" }, missingFields: [], activeResultRef: registrationResultRef }, result: { resultRef: registrationResultRef, resultVersion: registrationResultVersion, expiresAt: "2026-07-03T10:00:00+08:00", text: registrationResultText, items: registrationItems.map((item) => ({ ...item })) } }; }; var applyRegistrationQueryResponseFixture = (state, interaction, fixture) => { const messages = Array.isArray(state.messages) ? state.messages : []; const userMessage = interaction.text ? [{ id: nextMessageId(messages, "user"), role: "user", text: interaction.text }] : []; const assistantMessage = { id: nextMessageId([...messages, ...userMessage], "assistant"), role: "assistant", text: fixture.text ?? fixture.result?.text, ...fixture.result?.resultRef ? { resultRef: fixture.result.resultRef } : {} }; if (fixture.type !== "REGISTRATION_CANDIDATES") { return { ...state, activeTab: "assistant", messages: [...messages, ...userMessage, assistantMessage] }; } return { ...state, activeTab: "assistant", activeAgentTaskProjection: fixture.task, resultSnapshotProjections: { ...state.resultSnapshotProjections ?? {}, [fixture.result.resultRef]: fixture.result }, messages: [...messages, ...userMessage, assistantMessage] }; }; function submitInteraction(state, text) { if (!state || typeof state !== "object" || Array.isArray(state)) { return state; } const normalizedText = typeof text === "string" ? text.trim() : ""; const interaction = { interactionType: "USER_MESSAGE", text: normalizedText }; const responseFixture = simulateRegistrationQueryBackendFixture( interaction ); return applyRegistrationQueryResponseFixture( state, interaction, responseFixture ); } function buildCandidateSelectionInteraction(state, candidateSelection, now) { const task = state?.activeAgentTaskProjection; const resultRef = task?.activeResultRef; const result = typeof resultRef === "string" ? state?.resultSnapshotProjections?.[resultRef] : null; const normalizedSelection = typeof candidateSelection === "string" ? candidateSelection.trim() : candidateSelection; const candidateId = normalizedSelection === "\u9009\u7B2C\u4E00\u4E2A" ? result?.items?.[0]?.candidateId : normalizedSelection; const expiresAt = Date.parse(result?.expiresAt); const evaluatedAt = Date.parse(now ?? state?.demoNow); const isExpired = !Number.isFinite(expiresAt) || !Number.isFinite(evaluatedAt) || expiresAt <= evaluatedAt; const hasCandidate = Array.isArray(result?.items) && result.items.some( (item) => item?.candidateId === candidateId ); if (task?.type !== "REGISTRATION" || task.stage !== "CANDIDATE_SELECTION" || result?.resultRef !== resultRef || !Number.isInteger(result?.resultVersion) || result.resultVersion <= 0 || isExpired || !hasCandidate) { return null; } return { interactionType: "SELECT_CANDIDATE", resultRef, resultVersion: result.resultVersion, candidateId }; } function applyCandidateSelectionResponseFixture(state, fixture) { if (!state || typeof state !== "object" || !fixture?.id) { return state; } const appliedIds = Array.isArray(state.appliedProjectionFixtureIds) ? state.appliedProjectionFixtureIds : []; if (appliedIds.includes(fixture.id)) { return state; } const task = state?.activeAgentTaskProjection; const result = task?.activeResultRef ? state?.resultSnapshotProjections?.[task.activeResultRef] : null; const isActiveSelection = task?.type === "REGISTRATION" && task.stage === "CANDIDATE_SELECTION" && result?.resultRef === task.activeResultRef; if (!isActiveSelection) { return state; } const canonicalErrors = /* @__PURE__ */ new Set([ "RESULT_VERSION_CONFLICT", "RESULT_EXPIRED", "SLOT_UNAVAILABLE" ]); const validation = fixture.validation ?? {}; const derivedError = validation.versionMatched === false ? "RESULT_VERSION_CONFLICT" : validation.notExpired === false ? "RESULT_EXPIRED" : validation.realtimeAvailable === false || validation.l1LockSucceeded === false ? "SLOT_UNAVAILABLE" : null; const baseState = { ...state, appliedProjectionFixtureIds: [...appliedIds, fixture.id] }; const evaluatedAt = Date.parse(state.demoNow); const snapshotExpiresAt = Date.parse(result.expiresAt); const snapshotNotExpired = Number.isFinite(evaluatedAt) && Number.isFinite(snapshotExpiresAt) && snapshotExpiresAt > evaluatedAt; const lockExpiresAt = Date.parse(fixture.lock?.expiresAt); const lockIdValid = typeof fixture.lock?.lockId === "string" && fixture.lock.lockId.trim().length > 0; const lockExpiryValid = Number.isFinite(evaluatedAt) && Number.isFinite(lockExpiresAt) && lockExpiresAt > evaluatedAt; const candidateExists = Array.isArray(result?.items) && result.items.some( (item) => item?.candidateId === fixture.candidateId ); const validationPassed = [ "tenantMatched", "patientMatched", "sessionMatched", "versionMatched", "notExpired", "realtimeAvailable", "l1LockSucceeded" ].every((key) => validation[key] === true); const successIsValid = fixture.outcome === "SUCCESS" && fixture.resultRef === result.resultRef && fixture.resultVersion === result.resultVersion && candidateExists && validationPassed && snapshotNotExpired && lockIdValid && lockExpiryValid; if (!successIsValid) { const errorCode = canonicalErrors.has(fixture.errorCode) ? fixture.errorCode : derivedError ?? (!snapshotNotExpired ? "RESULT_EXPIRED" : !lockIdValid && fixture.outcome === "SUCCESS" ? "SLOT_UNAVAILABLE" : !lockExpiryValid && fixture.outcome === "SUCCESS" ? "RESULT_EXPIRED" : "UNKNOWN_SELECTION_ERROR"); const unavailableAdvice = errorCode === "SLOT_UNAVAILABLE" ? " \u5F53\u524D\u53F7\u6E90\u5DF2\u65E0\u53F7\uFF0C\u53EF\u67E5\u770B\u5176\u4ED6\u65F6\u6BB5\u6216\u533B\u751F\u3002" : ""; const messages2 = Array.isArray(state.messages) ? state.messages : []; return { ...baseState, activeAgentTaskProjection: { ...task, stage: "REQUERY_REQUIRED", activeResultRef: null }, messages: [ ...messages2, { id: nextMessageId(messages2, "assistant"), role: "assistant", text: `\u7ED3\u679C\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u3002${unavailableAdvice}` } ] }; } const messages = Array.isArray(state.messages) ? state.messages : []; return { ...baseState, activeAgentTaskProjection: { ...task, stage: "AWAITING_REGISTRATION_CONFIRMATION", selectedCandidateId: fixture.candidateId, lock: { ...fixture.lock } }, messages: [ ...messages, { id: nextMessageId(messages, "assistant"), role: "assistant", text: "\u9501\u53F7\u6210\u529F\uFF0C\u8BF7\u5728\u9501\u5B9A\u65F6\u95F4\u5185\u5B8C\u6210\u4E8C\u6B21\u786E\u8BA4\u540E\u6302\u53F7\u3002" } ] }; } var commandStatuses = /* @__PURE__ */ new Set([ "PREPARED", "AWAITING_CONFIRMATION", "EXECUTING", "SUCCEEDED", "FAILED", "EXPIRED", "UNKNOWN", "REJECTED" ]); var commandTransitions = { L1: { PREPARED: /* @__PURE__ */ new Set(["EXECUTING"]), EXECUTING: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED", "UNKNOWN"]), UNKNOWN: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED"]) }, L2: { PREPARED: /* @__PURE__ */ new Set(["AWAITING_CONFIRMATION"]), AWAITING_CONFIRMATION: /* @__PURE__ */ new Set([ "EXECUTING", "REJECTED", "EXPIRED" ]), EXECUTING: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED", "UNKNOWN"]), UNKNOWN: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED"]) } }; var initialCommandStatuses = { L1: /* @__PURE__ */ new Set(["PREPARED", "UNKNOWN"]), L2: /* @__PURE__ */ new Set(["PREPARED", "AWAITING_CONFIRMATION", "UNKNOWN"]) }; var commandFixtureError = (message) => { throw new Error(`Command projection fixture invalid: ${message}`); }; var legacyAppointmentResult = { type: "APPOINTMENT_CREATED", eventId: "appointment-created-001", appointmentId: "appointment-001", patientId: "patient-zhang-san", summary: { visitDate: "2026-07-03", visitTime: "09:30", departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", fee: "30\u5143" } }; var applyAppointmentCreatedProjection = (state, fixture) => { const result = fixture.result ?? (fixture.resultFixtureId === "appointment-created-001" ? legacyAppointmentResult : null); if (!result || result.type !== "APPOINTMENT_CREATED") { return state; } const eventId = result.eventId ?? fixture.resultFixtureId; if (typeof eventId !== "string" || !eventId || typeof result.appointmentId !== "string" || !result.appointmentId) { commandFixtureError("appointment result identifiers are required"); } if (state.appliedProjectionFixtureIds.includes(eventId)) { return state; } const summary = result.summary ?? {}; const doctor = `${summary.doctorName ?? ""}${summary.doctorTitle ?? ""}`; return { ...state, appliedProjectionFixtureIds: [ ...state.appliedProjectionFixtureIds, eventId ], patientWorkItems: [ ...state.patientWorkItems, { id: `work-item-${result.appointmentId}`, appointmentId: result.appointmentId, patientId: result.patientId, type: "APPOINTMENT", status: "UPCOMING", stage: "WAITING_ARRIVAL", priority: 80, title: `${summary.departmentName ?? "\u95E8\u8BCA"}\u9884\u7EA6\u6210\u529F`, description: `${summary.visitDate ?? ""} ${summary.visitTime ?? ""} \xB7 ${doctor} \xB7 ${summary.fee ?? ""}`, fee: summary.fee, primaryAction: null, secondaryAction: null, expiresAt: null } ], journeyProjections: [ ...state.journeyProjections, { id: `journey-${result.appointmentId}`, stage: "WAITING_ARRIVAL", title: "\u5F85\u5230\u9662", appointmentId: result.appointmentId, patientId: result.patientId } ] }; }; function applyCommandProjectionFixture(state, fixture) { if (!state || typeof state !== "object" || Array.isArray(state)) { commandFixtureError("state is required"); } if (!fixture || typeof fixture !== "object" || Array.isArray(fixture)) { commandFixtureError("fixture is required"); } const { id, commandId, status } = fixture; if (typeof id !== "string" || !id.trim()) { commandFixtureError("id is required"); } if (typeof commandId !== "string" || !commandId.trim()) { commandFixtureError("commandId is required"); } if (status === "COMPLETED") { commandFixtureError("COMPLETED is not a Command status"); } if (!commandStatuses.has(status)) { commandFixtureError(`unknown status ${String(status)}`); } const appliedIds = Array.isArray(state.appliedProjectionFixtureIds) ? state.appliedProjectionFixtureIds : []; if (appliedIds.includes(id)) { return state; } const current = state.commandProjections?.[commandId]; const riskLevel = fixture.riskLevel ?? current?.riskLevel ?? "L2"; if (riskLevel !== "L1" && riskLevel !== "L2") { commandFixtureError(`unknown riskLevel ${String(riskLevel)}`); } if (current && current.riskLevel !== riskLevel) { commandFixtureError("riskLevel cannot change"); } const version = fixture.version ?? current?.version; if (version !== void 0 && (!Number.isInteger(version) || version <= 0)) { commandFixtureError("version must be a positive integer"); } if (!current && !initialCommandStatuses[riskLevel].has(status)) { commandFixtureError( `illegal initial ${riskLevel} status ${status}` ); } if (current) { const isFinalBusinessSnapshot = riskLevel === "L2" && current.status === "AWAITING_CONFIRMATION" && status === "SUCCEEDED" && fixture.resultFixtureId === "appointment-created-001" && fixture.result === void 0 && fixture.version === void 0; const transitionAllowed = commandTransitions[riskLevel][current.status]?.has(status); if (!isFinalBusinessSnapshot && (!Number.isInteger(fixture.version) || !Number.isInteger(current.version) || fixture.version <= current.version)) { commandFixtureError("version must explicitly increase"); } if (!transitionAllowed && !isFinalBusinessSnapshot) { commandFixtureError( `illegal ${riskLevel} transition ${current.status} -> ${status}` ); } } const projection = { ...current, commandId, status, riskLevel, ...version === void 0 ? {} : { version }, ...fixture.details === void 0 ? {} : { details: structuredClone(fixture.details) }, ...fixture.resultFixtureId === void 0 ? {} : { resultFixtureId: fixture.resultFixtureId }, ...fixture.result === void 0 ? {} : { result: structuredClone(fixture.result) } }; let nextState = { ...state, appliedProjectionFixtureIds: [...appliedIds, id], commandProjections: { ...state.commandProjections ?? {}, [commandId]: projection }, overlays: { ...state.overlays, commandId } }; if (status === "SUCCEEDED" && (fixture.result?.type === "APPOINTMENT_CREATED" || fixture.resultFixtureId === "appointment-created-001")) { nextState = applyAppointmentCreatedProjection(nextState, fixture); } return nextState; } function buildCommandConfirmationRequest(state, commandId, { channel, idempotencyKey } = {}) { const command = state?.commandProjections?.[commandId]; if (!command) { throw new Error("Command \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u786E\u8BA4"); } if (command.status === "UNKNOWN") { throw new Error("\u7ED3\u679C\u786E\u8BA4\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u64CD\u4F5C"); } if (command.status === "EXPIRED") { throw new Error("Command \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u53F7\u6E90"); } if (command.status !== "AWAITING_CONFIRMATION") { throw new Error(`\u5F53\u524D\u72B6\u6001 ${command.status} \u4E0D\u5141\u8BB8\u91CD\u590D\u786E\u8BA4`); } if (!Number.isInteger(command.version) || command.version <= 0) { throw new Error("Command version \u65E0\u6548"); } if (channel !== "CARD") { throw new Error("\u786E\u8BA4\u6E20\u9053\u4E0D\u53D7\u652F\u6301"); } if (typeof idempotencyKey !== "string" || !idempotencyKey.trim()) { throw new Error("\u5E42\u7B49\u952E\u4E0D\u80FD\u4E3A\u7A7A"); } return { path: `/api/v2/commands/${encodeURIComponent(commandId)}/confirm`, method: "POST", commandId, body: { commandVersion: command.version, confirmation: { channel, explicit: true } }, headers: { "X-Emoon-Idempotency-Key": idempotencyKey } }; } function buildCommandRejectionRequest(state, commandId, { reason, idempotencyKey } = {}) { const command = state?.commandProjections?.[commandId]; if (!command) { throw new Error("Command \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u62D2\u7EDD"); } if (command.status !== "AWAITING_CONFIRMATION") { throw new Error(`\u5F53\u524D\u72B6\u6001 ${command.status} \u4E0D\u5141\u8BB8\u62D2\u7EDD`); } if (!Number.isInteger(command.version) || command.version <= 0) { throw new Error("Command version \u65E0\u6548"); } if (typeof idempotencyKey !== "string" || !idempotencyKey.trim()) { throw new Error("\u5E42\u7B49\u952E\u4E0D\u80FD\u4E3A\u7A7A"); } return { path: `/api/v2/commands/${encodeURIComponent(commandId)}/reject`, method: "POST", commandId, body: { commandVersion: command.version, ...typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : {} }, headers: { "X-Emoon-Idempotency-Key": idempotencyKey } }; } var statusRank = { ACTION_REQUIRED: 0, IN_PROGRESS: 1, UPCOMING: 2, COMPLETED: 3 }; var reportInterpretationProjectionFixture = deepFreeze({ resultRef: "result-report-interpretation-001", resultVersion: 1, interpretation: { headline: "3 \u9879\u6307\u6807\u8F7B\u5EA6\u5F02\u5E38", summary: "\u8FD9\u4EFD\u62A5\u544A\u6709 3 \u9879\u6307\u6807\u8D85\u51FA\u53C2\u8003\u8303\u56F4\uFF0C\u53D8\u5316\u5E45\u5EA6\u8F83\u5C0F\uFF0C\u9700\u8981\u7ED3\u5408\u8FD1\u671F\u75C7\u72B6\u548C\u75C5\u53F2\u7531\u533B\u751F\u7EFC\u5408\u5224\u65AD\u3002", safety: "\u672C\u89E3\u91CA\u4EC5\u7528\u4E8E\u5E2E\u52A9\u7406\u89E3\u62A5\u544A\u6307\u6807\uFF0C\u4E0D\u7528\u4E8E\u5224\u65AD\u75BE\u75C5\u6216\u66FF\u4EE3\u533B\u751F\u610F\u89C1\u3002\u5982\u6709\u660E\u663E\u4E0D\u9002\uFF0C\u8BF7\u53CA\u65F6\u8054\u7CFB\u533B\u52A1\u4EBA\u5458\u3002", highlights: [ { name: "\u767D\u7EC6\u80DE\u8BA1\u6570", value: "11.2", flag: "\u504F\u9AD8" }, { name: "\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B", value: "78%", flag: "\u504F\u9AD8" }, { name: "\u6DCB\u5DF4\u7EC6\u80DE\u6BD4\u4F8B", value: "18%", flag: "\u504F\u4F4E" } ], explanations: [ { indicator: "\u767D\u7EC6\u80DE 11.2 \xD710\u2079/L", explanation: "\u8F7B\u5EA6\u9AD8\u4E8E\u62A5\u544A\u6240\u793A\u53C2\u8003\u8303\u56F4\uFF0C\u5355\u9879\u53D8\u5316\u4E0D\u80FD\u72EC\u7ACB\u8BF4\u660E\u5177\u4F53\u539F\u56E0\u3002", commonFactors: "\u8FD1\u671F\u611F\u67D3\u3001\u7761\u7720\u4E0D\u8DB3\u3001\u8FD0\u52A8\u6216\u60C5\u7EEA\u7D27\u5F20\u7B49\u3002" }, { indicator: "\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B 78%", explanation: "\u6BD4\u4F8B\u8F7B\u5EA6\u5347\u9AD8\uFF0C\u9700\u7ED3\u5408\u767D\u7EC6\u80DE\u603B\u6570\u548C\u5F53\u524D\u75C7\u72B6\u5224\u65AD\u3002", commonFactors: "\u7EC6\u83CC\u611F\u67D3\u3001\u6025\u6027\u708E\u75C7\u6216\u751F\u7406\u5E94\u6FC0\u7B49\u3002" }, { indicator: "\u6DCB\u5DF4\u7EC6\u80DE\u6BD4\u4F8B 18%", explanation: "\u6BD4\u4F8B\u8F7B\u5EA6\u504F\u4F4E\uFF0C\u53EF\u80FD\u4E0E\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B\u5347\u9AD8\u540C\u65F6\u51FA\u73B0\u3002", commonFactors: "\u611F\u67D3\u6062\u590D\u671F\u3001\u538B\u529B\u6216\u4E2A\u4F53\u77ED\u671F\u6CE2\u52A8\u7B49\u3002" } ], canDo: [ "\u67E5\u770B\u5E76\u4FDD\u5B58\u533B\u9662\u539F\u59CB\u62A5\u544A\u3002", "\u7ED3\u5408\u8FD1\u671F\u75C7\u72B6\u548C\u65E2\u5F80\u60C5\u51B5\uFF0C\u5411\u5F00\u5355\u533B\u751F\u54A8\u8BE2\u3002", "\u5982\u6709\u660E\u663E\u4E0D\u9002\uFF0C\u53CA\u65F6\u8054\u7CFB\u533B\u52A1\u4EBA\u5458\u3002" ], evidence: [ "\u533B\u9662\u539F\u59CB\u62A5\u544A\u4E2D\u7684\u6307\u6807\u7ED3\u679C\u3001\u5355\u4F4D\u3001\u53C2\u8003\u8303\u56F4\u548C\u5F02\u5E38\u6807\u8BB0\u3002", "\u533B\u9662\u5DF2\u5BA1\u6838\u7684\u7EC8\u7248\u62A5\u544A\u3002" ] } }); var reportConsentId = (reportId) => `consent-ai-${reportId}`; var encodeReportId = (reportId) => String(reportId).replace( /[^A-Za-z0-9._-]/g, (character) => `~${character.charCodeAt(0).toString(16).padStart(4, "0")}` ); var reportResultSnapshotId = (reportId) => `result-report-interpretation-${encodeReportId(reportId)}`; var reportFeedbackCategories = /* @__PURE__ */ new Set([ "HELPFUL", "METRIC_INACCURATE", "HARD_TO_UNDERSTAND", "DIFFERS_FROM_DOCTOR", "OTHER" ]); var readReportIssuedFixture = (fixture) => { if (fixture.payload !== void 0 && !isRecord(fixture.payload)) { return null; } const payload = fixture.payload ?? {}; const isExamination = payload.family === "EXAM"; const report = { ...bloodRoutineReportFixture, ...payload, interpretationStatus: "NOT_REQUESTED", metrics: Array.isArray(payload.metrics) ? payload.metrics : isExamination ? [] : bloodRoutineReportFixture.metrics }; if (typeof report.reportId !== "string" || !reportIdPattern.test(report.reportId) || !Number.isInteger(report.sourceVersion) || report.sourceVersion <= 0 || typeof report.sourceHash !== "string" || !reportHashPattern.test(report.sourceHash) || !reportSourceStatuses.has(report.sourceStatus) || !reportFamilies.has(report.family) || !reportTypes.has(report.reportType) || !reportVerificationStatuses.has(report.verificationStatus) || report.family === "LAB" && report.reportType !== "BLOOD_ROUTINE" || report.family === "EXAM" && report.reportType !== "IMAGING") { return null; } return deepFreeze(structuredClone(report)); }; var hasValidReportConsent = (state, reportId) => { if (typeof reportId !== "string" || reportId.length === 0 || !isRecord(state?.consents) || !isRecord(state?.consentScopeBindings)) { return false; } const consentId = reportConsentId(reportId); const consent = Object.hasOwn(state.consents, consentId) ? state.consents[consentId] : null; const binding = Object.hasOwn(state.consentScopeBindings, consentId) ? state.consentScopeBindings[consentId] : null; const expiresAt = Date.parse(consent?.expiresAt); const evaluatedAt = Date.parse(state?.demoNow); return consent?.id === consentId && consent.status === "GRANTED" && Number.isFinite(expiresAt) && Number.isFinite(evaluatedAt) && expiresAt > evaluatedAt && binding?.patientId === state?.activePatientId && binding?.reportId === reportId; }; var selectValidReportResult = (state, reportId) => { if (typeof reportId !== "string" || reportId.length === 0) { return null; } const resultRef = reportResultSnapshotId(reportId); const reportSources = state?.reportSourceProjections; const results = state?.resultSnapshotProjections; const report = isRecord(reportSources) && Object.hasOwn(reportSources, reportId) ? reportSources[reportId] : null; const result = isRecord(results) && Object.hasOwn(results, resultRef) ? results[resultRef] : null; const hasScopedTask = Array.isArray(state?.patientWorkItems) && state.patientWorkItems.some((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId); return isRecord(report) && report.reportId === reportId && report.interpretationStatus === "READY" && hasScopedTask && hasValidReportConsent(state, reportId) && evaluateReportInterpretationGate(state, reportId).allowed && isRecord(result) && result.resultRef === resultRef && result.reportId === reportId && result.sourceVersion === report.sourceVersion && result.sourceHash === report.sourceHash && Number.isInteger(result.resultVersion) && result.resultVersion > 0 && result.valid === true && isRecord(result.interpretation) ? result : null; }; function selectHomeTasks(state) { const patientWorkItems = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.filter( (item) => item !== null && typeof item === "object" && !Array.isArray(item) ) : []; const priorityOf = ({ priority }) => Number.isFinite(priority) ? priority : 0; const rankOf = ({ status }) => typeof status === "string" && Object.hasOwn(statusRank, status) ? statusRank[status] : 4; return patientWorkItems.sort((left, right) => { const statusDelta = rankOf(left) - rankOf(right); return statusDelta || priorityOf(right) - priorityOf(left); }); } function selectReports(state, filters = {}) { const family = filters?.family ?? "ALL"; const status = filters?.status ?? "ALL"; const projections = state?.reportSourceProjections; if (!projections || typeof projections !== "object" || Array.isArray(projections)) { return []; } return Object.values(projections).filter((report) => report && typeof report === "object" && !Array.isArray(report)).filter((report) => family === "ALL" || report.family === family).filter((report) => status === "ALL" || report.readStatus === status).sort((left, right) => { const issuedAtDelta = String(right.issuedAt ?? "").localeCompare(String(left.issuedAt ?? "")); return issuedAtDelta || String(left.reportId ?? "").localeCompare(String(right.reportId ?? "")); }); } var upsertProjection = (items, nextItem) => { const source = Array.isArray(items) ? items : []; const index = source.findIndex((item) => item?.id === nextItem.id); if (index < 0) { return [...source, nextItem]; } return source.map((item, itemIndex) => itemIndex === index ? { ...item, ...nextItem } : item); }; var journeyTitleByStage = { WAITING_ARRIVAL: "\u5F85\u5230\u9662", CHECKIN_AVAILABLE: "\u53EF\u4EE5\u7B7E\u5230", QUEUE_WAITING: "\u5019\u8BCA\u4E2D", VISIT_IN_PROGRESS: "\u5C31\u8BCA\u4E2D", POST_VISIT: "\u8BCA\u540E\u670D\u52A1" }; var updateJourneyProjection = (journeys, appointmentId, stage, details = {}) => upsertProjection(journeys, { id: `journey-${appointmentId}`, appointmentId, stage, title: journeyTitleByStage[stage], ...details }); var appendJourneyMessage = (messages, fixture, text) => { if (!text) { return Array.isArray(messages) ? messages : []; } return [ ...Array.isArray(messages) ? messages : [], { id: `message-${fixture.id}`, type: "JOURNEY", unread: true, title: text, createdAt: fixture.occurredAt } ]; }; var readReportInvalidationFixture = (fixture) => { const payload = isRecord(fixture?.payload) ? fixture.payload : null; const reportId = payload?.reportId; const sourceVersion = payload?.sourceVersion; const sourceHash = payload?.sourceHash; if (typeof reportId !== "string" || reportId.trim().length === 0 || !/^[A-Za-z0-9._-]+$/.test(reportId) || !Number.isInteger(sourceVersion) || sourceVersion <= 0 || typeof sourceHash !== "string" || sourceHash.trim().length === 0 || !/^[A-Za-z0-9._:-]+$/.test(sourceHash)) { return null; } return { reportId, sourceVersion, sourceHash: sourceHash.trim() }; }; var applyReportInvalidationProjection = (state, nextState, fixture) => { const { reportId, sourceVersion, sourceHash } = readReportInvalidationFixture(fixture); const report = typeof reportId === "string" && isRecord(state.reportSourceProjections) && Object.hasOwn(state.reportSourceProjections, reportId) ? state.reportSourceProjections[reportId] : null; const latestKnownVersion = Math.max( Number.isInteger(report?.sourceVersion) ? report.sourceVersion : 0, Number.isInteger(report?.invalidationVersion) ? report.invalidationVersion : 0 ); if (!isRecord(report) || report.reportId !== reportId || sourceVersion <= latestKnownVersion) { return nextState; } const withdrawn = fixture.type === "REPORT_WITHDRAWN"; const resultSnapshotProjections = Object.fromEntries( Object.entries(state.resultSnapshotProjections ?? {}).map( ([resultRef, result]) => isRecord(result) && result.reportId === reportId ? [resultRef, { ...result, valid: false, invalidatedBy: fixture.id, invalidatedReason: withdrawn ? "REPORT_WITHDRAWN" : "REPORT_CORRECTED", invalidatedSourceVersion: sourceVersion }] : [resultRef, result] ) ); const reportSourceProjections = { ...state.reportSourceProjections, [reportId]: { ...report, interpretationStatus: "INVALIDATED", invalidationVersion: sourceVersion, invalidationHash: sourceHash, invalidatedAt: fixture.occurredAt, invalidationReason: withdrawn ? "REPORT_WITHDRAWN" : "REPORT_CORRECTED" } }; const patientWorkItems = Array.isArray(state.patientWorkItems) ? state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? { ...item, status: "ACTION_REQUIRED", stage: "REPORT_INVALIDATED", title: withdrawn ? "\u62A5\u544A\u5DF2\u64A4\u56DE" : "\u62A5\u544A\u5DF2\u66F4\u6B63", description: withdrawn ? "\u533B\u9662\u5DF2\u64A4\u56DE\u5F53\u524D\u62A5\u544A\uFF0C\u8BF7\u4EE5\u533B\u9662\u540E\u7EED\u901A\u77E5\u4E3A\u51C6" : "\u533B\u9662\u5DF2\u66F4\u65B0\u62A5\u544A\uFF0C\u539F\u8F85\u52A9\u89E3\u91CA\u5DF2\u5931\u6548", primaryAction: "VIEW_REPORT", secondaryAction: null } : item) : state.patientWorkItems; const isActiveReport = state.overlays?.reportId === reportId; return { ...nextState, reportSourceProjections, patientWorkItems, resultSnapshotProjections, overlays: isActiveReport ? { ...state.overlays, consentId: null, consentScope: null, reportView: "REPORT_UNAVAILABLE", reportUnavailableReason: "REPORT_INVALIDATED", reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null } : state.overlays }; }; function applyServerProjectionFixture(state, fixture) { if (!isRecord(state) || !Array.isArray(state.appliedProjectionFixtureIds) || !isRecord(fixture) || typeof fixture.id !== "string" || fixture.id.trim().length === 0 || typeof fixture.type !== "string" || fixture.type.trim().length === 0) { return state; } if (state.appliedProjectionFixtureIds.includes(fixture.id)) { return state; } const isReportInvalidation = fixture.type === "REPORT_CORRECTED" || fixture.type === "REPORT_WITHDRAWN"; if (isReportInvalidation && !readReportInvalidationFixture(fixture)) { return state; } const issuedReport = fixture.type === "REPORT_ISSUED" ? readReportIssuedFixture(fixture) : null; if (fixture.type === "REPORT_ISSUED" && !issuedReport) { return state; } const nextState = { ...state, appliedProjectionFixtureIds: [ ...state.appliedProjectionFixtureIds, fixture.id ] }; if (fixture.type === "REPORT_ISSUED") { const report = issuedReport; const isExamination = report.family === "EXAM"; const reportId = report.reportId; const currentReport = state.reportSourceProjections?.[reportId]; const currentVersion = currentReport?.sourceVersion; const invalidationVersion = currentReport?.invalidationVersion; const conflictsWithInvalidation = Number.isInteger(invalidationVersion) && (report.sourceVersion < invalidationVersion || report.sourceVersion === invalidationVersion && report.sourceHash !== currentReport.invalidationHash); if (currentReport && (!Number.isInteger(report.sourceVersion) || !Number.isInteger(currentVersion) || report.sourceVersion <= currentVersion || conflictsWithInvalidation)) { return nextState; } const resultRef = reportResultSnapshotId(reportId); const currentResult = isRecord(state.resultSnapshotProjections) && Object.hasOwn(state.resultSnapshotProjections, resultRef) ? state.resultSnapshotProjections[resultRef] : null; const resultSnapshotProjections = currentReport && isRecord(currentResult) ? { ...state.resultSnapshotProjections, [resultRef]: { ...currentResult, valid: false, invalidatedReason: "SOURCE_UPDATED", invalidatedAt: fixture.occurredAt ?? state.demoNow } } : state.resultSnapshotProjections; const consentId = reportConsentId(reportId); const currentConsent = isRecord(state.consents) && Object.hasOwn(state.consents, consentId) ? state.consents[consentId] : null; const consents = currentReport && isRecord(currentConsent) ? { ...state.consents, [consentId]: { ...currentConsent, status: "REVOKED", revokedAt: fixture.occurredAt ?? state.demoNow } } : state.consents; const consentScopeBindings = isRecord(state.consentScopeBindings) ? Object.fromEntries( Object.entries(state.consentScopeBindings).filter( ([bindingId]) => !currentReport || bindingId !== consentId ) ) : state.consentScopeBindings; return { ...nextState, reportSourceProjections: { ...state.reportSourceProjections ?? {}, [reportId]: report }, resultSnapshotProjections, consents, consentScopeBindings, overlays: currentReport && state.overlays?.reportId === reportId ? { ...state.overlays, consentId: null, consentScope: null, reportView: "REPORT_DETAIL", reportUnavailableReason: null, reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null } : state.overlays, patientWorkItems: upsertProjection( state.patientWorkItems, { id: `work-item-${reportId}`, reportId, type: "REPORT_REVIEW", status: "ACTION_REQUIRED", stage: "REPORT_AVAILABLE", priority: 90, title: `${typeof report.title === "string" && report.title.trim() ? report.title.trim() : isExamination ? "\u68C0\u67E5" : "\u68C0\u9A8C"}\u62A5\u544A\u5DF2\u51FA`, description: isExamination ? "\u533B\u9662\u68C0\u67E5\u62A5\u544A\u5DF2\u5BA1\u6838\uFF0C\u53EF\u67E5\u770B\u62A5\u544A\u8BE6\u60C5" : "18\u9879\u6307\u6807\u4E2D\u67093\u9879\u8D85\u51FA\u53C2\u8003\u8303\u56F4", primaryAction: isExamination ? "VIEW_REPORT" : "INTERPRET_REPORT", secondaryAction: isExamination ? null : "VIEW_REPORT", expiresAt: null } ), messages: upsertProjection( state.messages, { id: `message-${reportId}`, type: "REPORT", reportId, unread: true, title: `${typeof report.title === "string" && report.title.trim() ? report.title.trim() : isExamination ? "\u68C0\u67E5" : "\u68C0\u9A8C"}\u62A5\u544A\u5DF2\u51FA`, createdAt: fixture.occurredAt } ) }; } if (isReportInvalidation) { return applyReportInvalidationProjection(state, nextState, fixture); } const payload = fixture.payload ?? {}; const appointmentId = payload.appointmentId ?? "appointment-001"; let patientWorkItems = state.patientWorkItems; let journeyProjections = state.journeyProjections; let messages = state.messages; switch (fixture.type) { case "APPOINTMENT_CREATED": { patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-${appointmentId}`, appointmentId, type: "APPOINTMENT", status: "UPCOMING", stage: "WAITING_ARRIVAL", priority: 80, title: `${payload.departmentName ?? "\u95E8\u8BCA"}\u9884\u7EA6\u6210\u529F`, description: `${payload.visitTime ?? "09:30"} \xB7 ${payload.doctorName ?? "\u63A5\u8BCA\u533B\u751F"}`, primaryAction: null, secondaryAction: null, expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "WAITING_ARRIVAL", { visitTime: payload.visitTime ?? "09:30", departmentName: payload.departmentName ?? "\u795E\u7ECF\u5185\u79D1", doctorName: payload.doctorName ?? "\u674E\u660E" } ); messages = appendJourneyMessage(messages, fixture, "\u9884\u7EA6\u6210\u529F\uFF0C\u8BF7\u6309\u65F6\u5230\u9662"); break; } case "CHECKIN_AVAILABLE": { patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-checkin-${appointmentId}`, appointmentId, type: "CHECK_IN", status: "ACTION_REQUIRED", stage: "CONFIRMATION_REQUIRED", priority: 95, title: "\u795E\u7ECF\u5185\u79D1\u53EF\u4EE5\u7B7E\u5230", description: `\u5DF2\u5230\u8FBE${payload.location ?? "\u95E8\u8BCA\u8BCA\u533A"}\uFF0C\u8BF7\u786E\u8BA4\u540E\u7B7E\u5230`, location: payload.location, primaryAction: "CONFIRM_CHECKIN", secondaryAction: "VIEW_ROUTE", expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "CHECKIN_AVAILABLE", { location: payload.location, arrivedAt: fixture.occurredAt } ); messages = appendJourneyMessage(messages, fixture, "\u5DF2\u5230\u8FBE\u8BCA\u533A\uFF0C\u53EF\u4EE5\u7B7E\u5230"); break; } case "CHECKIN_COMPLETED": { patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-checkin-${appointmentId}`, appointmentId, type: "CHECK_IN", status: "COMPLETED", stage: "COMPLETED", priority: 95, title: "\u7B7E\u5230\u5DF2\u5B8C\u6210", description: "\u5DF2\u8FDB\u5165\u5019\u8BCA\u961F\u5217", primaryAction: null, secondaryAction: null, expiresAt: null }); patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-queue-${appointmentId}`, appointmentId, type: "WAITING_QUEUE", status: "IN_PROGRESS", stage: "QUEUE_WAITING", priority: 90, title: "\u795E\u7ECF\u5185\u79D1\u5019\u8BCA\u4E2D", description: `\u6392\u961F\u53F7 ${payload.queueNumber ?? "\u5F85\u66F4\u65B0"}`, queueNumber: payload.queueNumber, room: payload.room, primaryAction: "VIEW_QUEUE", secondaryAction: "VIEW_ROUTE", expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "QUEUE_WAITING", { checkedInAt: fixture.occurredAt, queueNumber: payload.queueNumber, room: payload.room } ); messages = appendJourneyMessage(messages, fixture, "\u7B7E\u5230\u6210\u529F\uFF0C\u5DF2\u8FDB\u5165\u5019\u8BCA\u961F\u5217"); break; } case "QUEUE_UPDATED": { const currentQueue = patientWorkItems.find( (item) => item?.id === `work-item-queue-${appointmentId}` ); patientWorkItems = upsertProjection(patientWorkItems, { ...currentQueue ?? {}, id: `work-item-queue-${appointmentId}`, appointmentId, type: "WAITING_QUEUE", status: "IN_PROGRESS", stage: "QUEUE_WAITING", priority: 90, title: "\u795E\u7ECF\u5185\u79D1\u5019\u8BCA\u4E2D", description: `\u524D\u65B9 ${payload.peopleAhead ?? 0} \u4EBA\uFF0C\u9884\u8BA1 ${payload.estimatedMinutes ?? 0} \u5206\u949F`, queueNumber: payload.queueNumber ?? currentQueue?.queueNumber, peopleAhead: payload.peopleAhead, estimatedMinutes: payload.estimatedMinutes, room: payload.room ?? currentQueue?.room, delayed: Boolean(payload.delayed), updatedAt: fixture.occurredAt, primaryAction: "VIEW_QUEUE", secondaryAction: "VIEW_ROUTE", expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "QUEUE_WAITING", { queueNumber: payload.queueNumber, peopleAhead: payload.peopleAhead, estimatedMinutes: payload.estimatedMinutes, room: payload.room, delayed: Boolean(payload.delayed), updatedAt: fixture.occurredAt } ); messages = appendJourneyMessage(messages, fixture, "\u5019\u8BCA\u961F\u5217\u5DF2\u66F4\u65B0"); break; } case "VISIT_STARTED": { patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-queue-${appointmentId}`, appointmentId, type: "WAITING_QUEUE", status: "COMPLETED", stage: "COMPLETED", priority: 90, title: "\u5019\u8BCA\u5DF2\u7ED3\u675F", description: "\u533B\u751F\u5DF2\u5F00\u59CB\u63A5\u8BCA", primaryAction: null, secondaryAction: null, expiresAt: null }); patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-visit-${appointmentId}`, appointmentId, type: "VISIT", status: "IN_PROGRESS", stage: "VISIT_IN_PROGRESS", priority: 100, title: "\u533B\u751F\u63A5\u8BCA\u4E2D", description: payload.room ?? "\u8BF7\u5728\u8BCA\u5BA4\u5185\u5B8C\u6210\u5C31\u8BCA", primaryAction: null, secondaryAction: null, expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "VISIT_IN_PROGRESS", { room: payload.room, startedAt: fixture.occurredAt } ); messages = appendJourneyMessage(messages, fixture, "\u533B\u751F\u5DF2\u5F00\u59CB\u63A5\u8BCA"); break; } case "VISIT_COMPLETED": { patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-visit-${appointmentId}`, appointmentId, type: "VISIT", status: "COMPLETED", stage: "COMPLETED", priority: 100, title: "\u672C\u6B21\u5C31\u8BCA\u5DF2\u5B8C\u6210", description: "\u8BCA\u540E\u8BB0\u5F55\u4E0E\u670D\u52A1\u5DF2\u66F4\u65B0", primaryAction: null, secondaryAction: null, expiresAt: null }); patientWorkItems = upsertProjection(patientWorkItems, { id: `work-item-post-visit-${appointmentId}`, appointmentId, type: "POST_VISIT", status: "ACTION_REQUIRED", stage: "POST_VISIT", priority: 70, title: "\u67E5\u770B\u8BCA\u540E\u670D\u52A1", description: "\u67E5\u770B\u95E8\u8BCA\u8BB0\u5F55\u3001\u5904\u65B9\u548C\u590D\u8BCA\u5EFA\u8BAE", primaryAction: null, secondaryAction: null, expiresAt: null }); journeyProjections = updateJourneyProjection( journeyProjections, appointmentId, "POST_VISIT", { completedAt: fixture.occurredAt } ); messages = appendJourneyMessage(messages, fixture, "\u5C31\u8BCA\u5B8C\u6210\uFF0C\u8BCA\u540E\u670D\u52A1\u5DF2\u66F4\u65B0"); break; } default: return nextState; } return { ...nextState, patientWorkItems, journeyProjections, messages, activeAgentTaskProjection: state.activeAgentTaskProjection, commandProjections: state.commandProjections }; } function requestReportInterpretation(state, reportId) { if (!Array.isArray(state?.patientWorkItems)) { return state; } const itemIndex = state.patientWorkItems.findIndex( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ) ?? -1; if (itemIndex < 0) { return state; } const hasConsent = hasValidReportConsent(state, reportId); const gate = evaluateReportInterpretationGate(state, reportId); const consentId = reportConsentId(reportId); if (!gate.allowed) { const unavailableReason = [ "OCR_REVIEW_REQUIRED", "CRITICAL_NOTICE", "AI_UNAVAILABLE", "UNSUPPORTED" ].includes(gate.reason) ? gate.reason : "UNSUPPORTED"; const currentInterpretationStatus = state.reportSourceProjections?.[reportId]?.interpretationStatus; return { ...state, patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "REPORT_UNAVAILABLE" } : item), reportSourceProjections: { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: currentInterpretationStatus === "INVALIDATED" ? "INVALIDATED" : "NOT_REQUESTED" } }, overlays: { ...state.overlays, consentId: null, consentScope: null, reportId, reportView: "REPORT_UNAVAILABLE", reportUnavailableReason: unavailableReason } }; } const existingResult = selectValidReportResult(state, reportId); if (hasConsent && gate.allowed && state.reportSourceProjections?.[reportId]?.interpretationStatus === "READY" && existingResult) { return { ...state, patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "INTERPRETED" } : item), overlays: { ...state.overlays, consentId: null, consentScope: null, reportId, reportView: "INTERPRETED" } }; } const patientWorkItems = state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: hasConsent ? "INTERPRETING" : "CONSENT_REQUIRED" } : item); return { ...state, patientWorkItems, reportSourceProjections: hasConsent ? { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: "GENERATING" } } : state.reportSourceProjections, overlays: { ...state.overlays, consentId: hasConsent ? null : consentId, consentScope: hasConsent ? null : { patientId: state.activePatientId, reportId }, reportId, reportView: hasConsent ? "INTERPRETING" : state?.overlays?.reportView ?? null } }; } function evaluateReportInterpretationGate(state, reportId) { const report = state?.reportSourceProjections?.[reportId]; if (!report || typeof report !== "object" || Array.isArray(report)) { return { allowed: false, reason: "REPORT_NOT_FOUND" }; } if (report.family !== "LAB" || report.reportType !== "BLOOD_ROUTINE") { return { allowed: false, reason: "UNSUPPORTED" }; } if (report.sourceStatus !== "FINAL") { return { allowed: false, reason: "SOURCE_NOT_FINAL" }; } if (report.verificationStatus !== "VERIFIED") { return { allowed: false, reason: typeof report.verificationReason === "string" ? report.verificationReason : "VERIFICATION_REQUIRED" }; } if (report.criticalNotice === true) { return { allowed: false, reason: "CRITICAL_NOTICE" }; } if (report.interpretationStatus === "INVALIDATED") { return { allowed: false, reason: "REPORT_INVALIDATED" }; } if (report.interpretationUnavailableReason === "AI_UNAVAILABLE" || report.interpretationUnavailableReason === "UNSUPPORTED") { return { allowed: false, reason: report.interpretationUnavailableReason }; } return { allowed: true, reason: null }; } function grantConsent(state, consentId) { const reportId = state?.overlays?.reportId; const expectedConsentId = typeof reportId === "string" ? reportConsentId(reportId) : null; const scope = state?.overlays?.consentScope; if (consentId !== expectedConsentId || state?.overlays?.consentId !== consentId || scope?.patientId !== state?.activePatientId || scope?.reportId !== reportId || !Array.isArray(state?.patientWorkItems)) { return state; } const itemIndex = state?.patientWorkItems?.findIndex( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "CONSENT_REQUIRED" ) ?? -1; if (itemIndex < 0 || !evaluateReportInterpretationGate(state, reportId).allowed) { return state; } const consent = { id: consentId, purpose: "\u7528\u4E8E\u672C\u6B21\u62A5\u544A\u7684 AI \u8F85\u52A9\u89E3\u91CA", scope: "\u5F53\u524D\u8840\u5E38\u89C4\u62A5\u544A", expiresAt: "2026-07-09T23:59:59+08:00", revocable: true, status: "GRANTED" }; const binding = { patientId: state.activePatientId, reportId }; const pendingConsentState = { ...state, consents: { ...state.consents, [consentId]: consent }, consentScopeBindings: { ...state.consentScopeBindings, [consentId]: binding } }; if (!hasValidReportConsent(pendingConsentState, reportId)) { return { ...state, patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "REPORT_AVAILABLE" } : item), reportSourceProjections: { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: "NOT_REQUESTED" } }, overlays: { ...state.overlays, consentId: null, consentScope: null, reportView: "REPORT_DETAIL" } }; } return { ...state, consents: pendingConsentState.consents, consentScopeBindings: pendingConsentState.consentScopeBindings, reportSourceProjections: { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: "GENERATING" } }, patientWorkItems: state.patientWorkItems.map((item, index) => { if (index === itemIndex) { return { ...item, stage: "INTERPRETING" }; } return item; }), resultSnapshotProjections: { ...state.resultSnapshotProjections ?? {} }, overlays: { ...state.overlays, consentId: null, consentScope: null, reportView: "INTERPRETING" } }; } function openReportDetails(state, reportId) { const exists = state?.patientWorkItems?.some( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ); const report = state?.reportSourceProjections?.[reportId]; const hasConsistentSource = report && typeof report === "object" && !Array.isArray(report) && (report.reportId === void 0 || report.reportId === reportId); if (!exists || !hasConsistentSource) { return state; } const isExamination = report.family === "EXAM"; const gate = isExamination ? { allowed: true, reason: null } : evaluateReportInterpretationGate(state, reportId); const unavailableReason = gate.allowed ? null : [ "OCR_REVIEW_REQUIRED", "CRITICAL_NOTICE", "AI_UNAVAILABLE", "UNSUPPORTED" ].includes(gate.reason) ? gate.reason : "UNSUPPORTED"; return { ...state, reportSourceProjections: { ...state.reportSourceProjections ?? {}, [reportId]: { ...report, readStatus: "READ" } }, overlays: { ...state.overlays, reportId, reportView: unavailableReason ? "REPORT_UNAVAILABLE" : "REPORT_DETAIL", reportUnavailableReason: unavailableReason, reportExpanded: false, reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null } }; } function closeReportView(state) { if (!state?.overlays?.reportId && !state?.overlays?.reportView) { return state; } return { ...state, overlays: { ...state.overlays, consentId: null, consentScope: null, reportId: null, reportView: null, reportUnavailableReason: null, reportExpanded: false, reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null } }; } function rejectConsent(state, consentId) { const reportId = state?.overlays?.reportId; if (consentId !== reportConsentId(reportId) || state?.overlays?.consentId !== consentId || state?.overlays?.consentScope?.patientId !== state?.activePatientId || state?.overlays?.consentScope?.reportId !== reportId || !Array.isArray(state?.patientWorkItems)) { return state; } const hasPendingItem = state.patientWorkItems?.some( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "CONSENT_REQUIRED" ); if (typeof reportId !== "string" || !hasPendingItem) { return state; } return { ...state, patientWorkItems: state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? { ...item, stage: "REPORT_AVAILABLE" } : item), overlays: { ...state.overlays, consentId: null, consentScope: null, reportView: "REPORT_DETAIL" } }; } function applyReportInterpretationProjectionFixture(state, reportId) { const itemIndex = state?.patientWorkItems?.findIndex( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "INTERPRETING" ) ?? -1; const consentGranted = hasValidReportConsent(state, reportId); const gate = evaluateReportInterpretationGate(state, reportId); const source = state?.reportSourceProjections?.[reportId]; if (itemIndex < 0 || !consentGranted || !gate.allowed || !isRecord(source)) { return state; } const result = { ...reportInterpretationProjectionFixture, resultRef: reportResultSnapshotId(reportId), reportId, sourceVersion: source.sourceVersion, sourceHash: source.sourceHash, valid: true, interpretation: { ...reportInterpretationProjectionFixture.interpretation, highlights: reportInterpretationProjectionFixture.interpretation.highlights.map((highlight) => ({ ...highlight })), explanations: reportInterpretationProjectionFixture.interpretation.explanations.map((explanation) => ({ ...explanation })), canDo: [ ...reportInterpretationProjectionFixture.interpretation.canDo ], evidence: [ ...reportInterpretationProjectionFixture.interpretation.evidence ] } }; return { ...state, patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "INTERPRETED" } : item), reportSourceProjections: { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: "READY" } }, resultSnapshotProjections: { ...state.resultSnapshotProjections, [result.resultRef]: result }, overlays: { ...state.overlays, consentId: null, reportId, reportView: "INTERPRETED" } }; } function submitReportFeedback(state, payload = {}) { const reportId = payload?.reportId; const category = payload?.category; const reportSources = state?.reportSourceProjections; const report = typeof reportId === "string" && reportId.length > 0 && reportSources && typeof reportSources === "object" && !Array.isArray(reportSources) && Object.hasOwn(reportSources, reportId) ? reportSources[reportId] : null; const hasReportTask = Array.isArray(state?.patientWorkItems) && state.patientWorkItems.some((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId); const result = selectValidReportResult(state, reportId); if (!report || typeof report !== "object" || Array.isArray(report) || report.reportId !== reportId || !hasReportTask || !reportFeedbackCategories.has(category) || !Array.isArray(state?.reportFeedback) || !hasValidReportConsent(state, reportId) || !evaluateReportInterpretationGate(state, reportId).allowed || report.interpretationStatus !== "READY" || !result) { return state; } const feedback = { id: [ "feedback", encodeReportId(reportId), report.sourceVersion, category ].join("-"), reportId, sourceVersion: report.sourceVersion, sourceHash: report.sourceHash, resultRef: result.resultRef, resultVersion: result.resultVersion, category }; if (state.reportFeedback.some((item) => item?.id === feedback.id)) { return state; } return { ...state, reportFeedback: [...state.reportFeedback, feedback] }; } function revokeConsent(state, consentId) { if (!isRecord(state) || typeof consentId !== "string" || !isRecord(state.consents) || !isRecord(state.consentScopeBindings) || !isRecord(state.reportSourceProjections) || !isRecord(state.resultSnapshotProjections) || !isRecord(state.overlays) || !Array.isArray(state.patientWorkItems) || !Object.hasOwn(state.consents, consentId) || !Object.hasOwn(state.consentScopeBindings, consentId)) { return state; } const consent = state.consents[consentId]; const binding = state.consentScopeBindings[consentId]; if (!isRecord(consent) || !isRecord(binding) || typeof binding.reportId !== "string" || !Object.hasOwn(state.reportSourceProjections, binding.reportId) || !isRecord(state.reportSourceProjections[binding.reportId]) || consentId !== reportConsentId(binding.reportId) || consent?.id !== consentId || binding?.patientId !== state?.activePatientId) { return state; } const reportId = binding.reportId; const consentScopeBindings = Object.fromEntries( Object.entries(state.consentScopeBindings).filter( ([bindingConsentId]) => bindingConsentId !== consentId ) ); const resultSnapshotProjections = Object.fromEntries( Object.entries(state.resultSnapshotProjections ?? {}).filter( ([, result]) => result?.reportId !== reportId ) ); return { ...state, consents: { ...state.consents, [consentId]: { ...consent, status: "REVOKED" } }, consentScopeBindings, reportSourceProjections: { ...state.reportSourceProjections, [reportId]: { ...state.reportSourceProjections[reportId], interpretationStatus: "NOT_REQUESTED" } }, patientWorkItems: state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? { ...item, stage: "REPORT_AVAILABLE" } : item), resultSnapshotProjections, overlays: { ...state.overlays, consentId: null, consentScope: null, reportId, reportView: "REPORT_DETAIL", reportFeedbackOpen: false, reportFeedbackSubmitted: false, reportServiceNotice: null } }; } var scenarioNames = /* @__PURE__ */ new Set([ "REPORT_ARRIVED", "REPORT_DETAIL", "REPORT_CONSENT", "REPORT_GENERATING", "REPORT_READY", "REPORT_NEED_REVIEW", "REPORT_CRITICAL", "REPORT_INVALIDATED", "EMPTY_HOME", "REGISTRATION_QUERY", "COMMAND_PREPARED", "APPOINTMENT_CREATED", "CHECKIN_AVAILABLE", "QUEUE_UPDATED" ]); var loadRegistrationScenario = (base) => submitInteraction( base, registrationDemoText ); var loadLockedRegistrationScenario = (base) => { const queried = loadRegistrationScenario(base); return applyCandidateSelectionResponseFixture(queried, { id: "scenario-selection-success-001", outcome: "SUCCESS", resultRef: "result-slot-001", resultVersion: 1, candidateId: "candidate-slot-0930", validation: { tenantMatched: true, patientMatched: true, sessionMatched: true, versionMatched: true, notExpired: true, realtimeAvailable: true, l1LockSucceeded: true }, lock: { lockId: "lock-slot-001", expiresAt: "2026-07-03T09:05:00+08:00" } }); }; var loadCommandPreparedScenario = (base) => { const locked = loadLockedRegistrationScenario(base); return applyCommandProjectionFixture(locked, { id: "scenario-command-awaiting-001", commandId: "command-register-001", version: 1, riskLevel: "L2", status: "AWAITING_CONFIRMATION", details: { operationName: "\u786E\u8BA4\u6302\u53F7", patientName: "\u5F20\u5148\u751F", departmentName: "\u795E\u7ECF\u5185\u79D1", doctorName: "\u674E\u660E", doctorTitle: "\u4E3B\u4EFB\u533B\u5E08", visitAt: "2026-07-03 09:30", fee: "30\u5143", expiresAt: "2026-07-03T09:05:00+08:00", countdown: "\u5269\u4F59 5 \u5206\u949F", candidateId: "candidate-slot-0930" } }); }; function loadScenario(state, scenarioName) { const normalized = scenarioNames.has(scenarioName) ? scenarioName : "EMPTY_HOME"; const base = { ...createInitialState({ activePatientId: state?.activePatientId ?? "patient-zhang-san" }), demoScenario: normalized }; const reportFixture = { id: "scenario-report-arrived-001", type: "REPORT_ISSUED", occurredAt: "2026-07-03T09:30:00+08:00", payload: { reportId: "report-blood-20260702" } }; switch (normalized) { case "REPORT_ARRIVED": return { ...applyServerProjectionFixture(base, reportFixture), demoScenario: normalized }; case "REPORT_DETAIL": { const report = applyServerProjectionFixture(base, reportFixture); return { ...openReportDetails(report, "report-blood-20260702"), demoScenario: normalized }; } case "REPORT_CONSENT": { const report = openReportDetails( applyServerProjectionFixture(base, reportFixture), "report-blood-20260702" ); return { ...requestReportInterpretation(report, "report-blood-20260702"), demoScenario: normalized }; } case "REPORT_GENERATING": { const pending = loadScenario(base, "REPORT_CONSENT"); return { ...grantConsent( pending, "consent-ai-report-blood-20260702" ), demoScenario: normalized }; } case "REPORT_NEED_REVIEW": { const report = applyServerProjectionFixture(base, { ...reportFixture, payload: { ...reportFixture.payload, verificationStatus: "NEED_REVIEW", verificationReason: "OCR_REVIEW_REQUIRED" } }); return { ...openReportDetails(report, "report-blood-20260702"), demoScenario: normalized }; } case "REPORT_CRITICAL": { const report = applyServerProjectionFixture(base, { ...reportFixture, payload: { ...reportFixture.payload, criticalNotice: true } }); return { ...openReportDetails(report, "report-blood-20260702"), demoScenario: normalized }; } case "REPORT_INVALIDATED": { const ready = loadScenario(base, "REPORT_READY"); return { ...applyServerProjectionFixture(ready, { id: "scenario-report-corrected-002", type: "REPORT_CORRECTED", occurredAt: "2026-07-03T11:00:00+08:00", payload: { reportId: "report-blood-20260702", sourceVersion: 2, sourceHash: "sha256:blood-v2" } }), demoScenario: normalized }; } case "REPORT_READY": { const report = applyServerProjectionFixture(base, reportFixture); const pending = requestReportInterpretation( report, "report-blood-20260702" ); const interpreting = grantConsent( pending, "consent-ai-report-blood-20260702" ); return { ...applyReportInterpretationProjectionFixture( interpreting, "report-blood-20260702" ), demoScenario: normalized }; } case "REGISTRATION_QUERY": return { ...loadRegistrationScenario(base), demoScenario: normalized }; case "COMMAND_PREPARED": return { ...loadCommandPreparedScenario(base), demoScenario: normalized }; case "APPOINTMENT_CREATED": { const awaiting = loadCommandPreparedScenario(base); return { ...applyCommandProjectionFixture(awaiting, { id: "scenario-command-succeeded-001", commandId: "command-register-001", status: "SUCCEEDED", resultFixtureId: "appointment-created-001" }), activeTab: "home", demoScenario: normalized }; } case "CHECKIN_AVAILABLE": { const appointment = loadScenario(base, "APPOINTMENT_CREATED"); return { ...applyServerProjectionFixture(appointment, { id: "scenario-checkin-available-001", type: "CHECKIN_AVAILABLE", occurredAt: "2026-07-03T09:10:00+08:00", payload: { appointmentId: "appointment-001", location: "\u95E8\u8BCA\u4E09\u697C\u795E\u7ECF\u5185\u79D1\u8BCA\u533A" } }), demoScenario: normalized }; } case "QUEUE_UPDATED": { const arrived = loadScenario(base, "CHECKIN_AVAILABLE"); const checkedIn = applyServerProjectionFixture(arrived, { id: "scenario-checkin-completed-001", type: "CHECKIN_COMPLETED", occurredAt: "2026-07-03T09:12:00+08:00", payload: { appointmentId: "appointment-001", queueNumber: "A023", room: "3F-08\u8BCA\u5BA4" } }); return { ...applyServerProjectionFixture(checkedIn, { id: "scenario-queue-updated-001", type: "QUEUE_UPDATED", occurredAt: "2026-07-03T09:18:00+08:00", payload: { appointmentId: "appointment-001", queueNumber: "A023", peopleAhead: 4, estimatedMinutes: 18, room: "3F-08\u8BCA\u5BA4", delayed: true } }), demoScenario: normalized }; } default: return base; } } // docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js var navItems = Object.freeze([ { id: "home", label: "\u9996\u9875", icon: "house" }, { id: "care", label: "\u5C31\u533B", icon: "stethoscope" }, { id: "assistant", label: "AI\u52A9\u624B", icon: "sparkles" }, { id: "records", label: "\u5065\u5EB7\u6863\u6848", icon: "folder-heart" }, { id: "profile", label: "\u6211\u7684", icon: "user-round" } ]); var demoScenarioGroups = Object.freeze({ REPORT_CLOSURE: Object.freeze([ "REPORT_ARRIVED", "REPORT_DETAIL", "REPORT_CONSENT", "REPORT_GENERATING", "REPORT_READY", "REPORT_NEED_REVIEW", "REPORT_CRITICAL", "REPORT_INVALIDATED" ]), CARE_CLOSURE: Object.freeze([ "EMPTY_HOME", "REGISTRATION_QUERY", "COMMAND_PREPARED", "APPOINTMENT_CREATED", "CHECKIN_AVAILABLE", "QUEUE_UPDATED" ]) }); var demoScenarios = Object.freeze( Object.values(demoScenarioGroups).flat() ); var scenarioLabels = { EMPTY_HOME: "\u7A7A\u95F2\u9996\u9875", REPORT_ARRIVED: "\u62A5\u544A\u5230\u8FBE", REPORT_DETAIL: "\u62A5\u544A\u8BE6\u60C5", REPORT_CONSENT: "\u62A5\u544A\u6388\u6743", REPORT_GENERATING: "\u89E3\u8BFB\u751F\u6210\u4E2D", REPORT_READY: "\u62A5\u544A\u5DF2\u89E3\u8BFB", REPORT_NEED_REVIEW: "\u62A5\u544A\u9700\u590D\u6838", REPORT_CRITICAL: "\u62A5\u544A\u5371\u6025\u503C", REPORT_INVALIDATED: "\u62A5\u544A\u5DF2\u5931\u6548", REGISTRATION_QUERY: "\u81EA\u7136\u8BED\u8A00\u67E5\u53F7", COMMAND_PREPARED: "\u6302\u53F7\u5F85\u786E\u8BA4", APPOINTMENT_CREATED: "\u6302\u53F7\u6210\u529F", CHECKIN_AVAILABLE: "\u5230\u9662\u53EF\u7B7E\u5230", QUEUE_UPDATED: "\u5019\u8BCA\u66F4\u65B0" }; var homeServices = Object.freeze([ { label: "\u9884\u7EA6\u6302\u53F7", icon: "calendar-plus" }, { label: "\u667A\u80FD\u5BFC\u8BCA", icon: "scan-heart" }, { label: "\u62A5\u544A\u67E5\u8BE2", icon: "file-chart-column" }, { label: "\u95E8\u8BCA\u7F34\u8D39", icon: "wallet-cards" }, { label: "\u68C0\u67E5\u9884\u7EA6", icon: "clipboard-clock" }, { label: "\u4F4F\u9662\u670D\u52A1", icon: "bed" }, { label: "\u9662\u5185\u5BFC\u822A", icon: "map-pinned" }, { label: "\u5728\u7EBF\u95EE\u8BCA", icon: "messages-square" } ]); var actionLabels = { INTERPRET_REPORT: "AI \u89E3\u8BFB\u62A5\u544A", VIEW_REPORT: "\u67E5\u770B\u539F\u62A5\u544A", CONFIRM_CHECKIN: "\u786E\u8BA4\u7B7E\u5230", VIEW_ROUTE: "\u67E5\u770B\u8DEF\u7EBF", VIEW_QUEUE: "\u67E5\u770B\u5019\u8BCA" }; var actionTokens = { INTERPRET_REPORT: "interpret-report", VIEW_REPORT: "view-report", CONFIRM_CHECKIN: "open-checkin-confirmation", VIEW_ROUTE: "open-route", VIEW_QUEUE: "open-queue" }; var statusPresentation = { ACTION_REQUIRED: { className: "action-required", icon: "circle-alert", label: "\u9700\u8981\u5904\u7406" }, IN_PROGRESS: { className: "in-progress", icon: "loader-circle", label: "\u8FDB\u884C\u4E2D" }, UPCOMING: { className: "upcoming", icon: "calendar-clock", label: "\u5373\u5C06\u5F00\u59CB" }, COMPLETED: { className: "completed", icon: "circle-check-big", label: "\u5DF2\u5B8C\u6210" } }; var unknownStatusPresentation = { className: "unknown", icon: "circle-help", label: "\u72B6\u6001\u5F85\u786E\u8BA4" }; var escapeHtml = (value) => String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"); var getActionLabel = (action) => typeof action === "string" && Object.hasOwn(actionLabels, action) ? actionLabels[action] : null; var renderActionAttributes = (action, task) => { const token = typeof action === "string" && Object.hasOwn(actionTokens, action) ? actionTokens[action] : null; if (!token) { return ""; } if (typeof task?.reportId === "string") { return ` data-action="${token}" data-report-id="${escapeHtml(task.reportId)}"`; } if (typeof task?.appointmentId === "string") { return ` data-action="${token}" data-appointment-id="${escapeHtml(task.appointmentId)}"`; } return ""; }; var getStatusPresentation = (status) => typeof status === "string" && Object.hasOwn(statusPresentation, status) ? statusPresentation[status] : unknownStatusPresentation; var getShellElements = (root) => { if (!root?.getElementById) { return null; } const appHeader = root.getElementById("appHeader"); const appView = root.getElementById("appView"); const bottomNav = root.getElementById("bottomNav"); return appHeader && appView && bottomNav ? { appHeader, appView, bottomNav } : null; }; var renderIcons = () => { const lucide = globalThis.lucide; if (lucide?.createIcons) { lucide.createIcons({ attrs: { "aria-hidden": "true" } }); } }; var patientDisplayNames = { "patient-zhang-san": "\u5F20\u5148\u751F", "patient-li-na": "\u674E\u5973\u58EB" }; var patientDisplayName = (state) => patientDisplayNames[state?.activePatientId] ?? "\u5F53\u524D\u5C31\u8BCA\u4EBA"; var renderHeader = (state) => { const messages = Array.isArray(state?.messages) ? state.messages : []; const unreadCount = messages.filter((message) => message?.unread).length; const messageLabel = unreadCount > 0 ? `\u6D88\u606F\uFF0C${unreadCount} \u6761\u672A\u8BFB` : "\u6D88\u606F"; return `
\u7A7A\u6D77\u533B\u9662
\u5F53\u524D\u9662\u533A \xB7 \u7A7A\u6D77\u533B\u9662\u672C\u9662
${escapeHtml(journey.location ?? journey.room ?? "\u9662\u5185\u884C\u7A0B\u6301\u7EED\u66F4\u65B0")}
AI \u5C31\u533B\u52A9\u624B
${activeTaskCount > 0 ? `\u6709 ${activeTaskCount} \u9879\u5C31\u533B\u4E8B\u9879\u9700\u8981\u5173\u6CE8` : "\u4ECA\u5929\u6682\u65E0\u5F85\u5904\u7406\u7684\u5C31\u533B\u4E8B\u9879"}
\u5C31\u533B\u4EFB\u52A1
\u65B0\u7684\u62A5\u544A\u3001\u7F34\u8D39\u6216\u9884\u7EA6\u63D0\u9192\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002
\u53D1\u751F\u4E86\u4EC0\u4E48\uFF1A${escapeHtml(task.description)}
\u4E3A\u4EC0\u4E48\u5173\u6CE8\uFF1A${isCompleted ? "\u8BE5\u4E8B\u9879\u5DF2\u5904\u7406\u5B8C\u6210\u3002" : "\u53CA\u65F6\u5904\u7406\u6709\u52A9\u4E8E\u987A\u5229\u63A8\u8FDB\u540E\u7EED\u5C31\u533B\u5B89\u6392\u3002"}
\u9662\u5185\u8FDB\u5EA6
\u6302\u53F7\u6216\u68C0\u67E5\u5B89\u6392\u786E\u8BA4\u540E\uFF0C\u5C06\u5C55\u793A\u524D\u540E\u6B65\u9AA4\u548C\u5F53\u524D\u8282\u70B9\u3002
\u5FEB\u6377\u5165\u53E3
\u5065\u5EB7\u63D0\u9192
\u7528\u836F\u3001\u590D\u8BCA\u4E0E\u68C0\u67E5\u51C6\u5907\u63D0\u9192\u4F1A\u5728\u786E\u8BA4\u540E\u5C55\u793A\u3002
\u81EA\u7136\u8BED\u8A00\u5C31\u533B\u670D\u52A1
${escapeHtml(result.text)}
${candidates.length > 0 ? `\u5F53\u524D\u5C31\u8BCA\u4EBA ${escapeHtml(patientDisplayName(state))}
\u53EF\u4EE5\u8C03\u6574\u62A5\u544A\u7C7B\u578B\u6216\u9605\u8BFB\u72B6\u6001\u3002
\u533B\u9662\u539F\u59CB\u62A5\u544A
${escapeHtml(report?.hospitalName || "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${escapeHtml(report?.departmentName || "\u79D1\u5BA4\u5F85\u786E\u8BA4")}
\u51FA\u5177\u65F6\u95F4\uFF1A${escapeHtml(report?.issuedAt || "\u5F85\u786E\u8BA4")}
${escapeHtml(reportSourceStatusLabels[report?.sourceStatus] ?? "\u62A5\u544A\u72B6\u6001\u5F85\u786E\u8BA4")}AI \u8F85\u52A9\u89E3\u8BFB\u4EC5\u4F9B\u53C2\u8003\uFF0C\u62A5\u544A\u7ED3\u679C\u9700\u7ED3\u5408\u75C7\u72B6\u3001\u75C5\u53F2\u5E76\u7531\u533B\u751F\u5224\u65AD\u3002
\u6307\u6807\u6982\u89C8
\u533B\u9662\u68C0\u67E5\u62A5\u544A
\u68C0\u67E5\u90E8\u4F4D\uFF1A${safeReportText(report?.bodyPart, "\u5F85\u786E\u8BA4")}
${safeReportText(report?.hospitalName, "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${safeReportText(report?.departmentName, "\u79D1\u5BA4\u5F85\u786E\u8BA4")}
\u51FA\u5177\u65F6\u95F4\uFF1A${safeReportText(report?.issuedAt, "\u5F85\u786E\u8BA4")}
\u68C0\u67E5\u6240\u89C1
${safeReportText(report?.findings, "\u68C0\u67E5\u6240\u89C1\u5F85\u786E\u8BA4")}
\u62A5\u544A\u7ED3\u8BBA
${safeReportText(report?.conclusion, "\u62A5\u544A\u7ED3\u8BBA\u5F85\u786E\u8BA4")}
\u62A5\u544A\u533B\u751F\uFF1A${safeReportText(report?.reportingDoctor, "\u62A5\u544A\u533B\u751F\u5F85\u786E\u8BA4")}
\u5BA1\u6838\u72B6\u6001\uFF1A${safeReportText( report?.reviewStatus, reportSourceStatusLabels[report?.sourceStatus] ?? "\u5BA1\u6838\u72B6\u6001\u5F85\u786E\u8BA4" )}
\u8BE5\u7C7B\u578B\u6682\u672A\u5F00\u653EAI\u8F85\u52A9\u89E3\u8BFB
\u533B\u9662\u68C0\u67E5\u62A5\u544A\u539F\u4EF6
${safeReportText(report?.originalFormat, "\u56FE\u50CF\u6216PDF")}
\u539F\u59CB\u56FE\u50CF\u6216PDF\u7531\u533B\u9662\u62A5\u544A\u7CFB\u7EDF\u63D0\u4F9B\uFF0C\u672C\u539F\u578B\u5C55\u793A\u7EDF\u4E00\u67E5\u770B\u5165\u53E3\u3002
\u533B\u9662\u539F\u59CB\u62A5\u544A
${escapeHtml(report?.title || "\u672A\u547D\u540D\u62A5\u544A")}
${escapeHtml(report?.hospitalName || "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${escapeHtml(report?.departmentName || "\u79D1\u5BA4\u5F85\u786E\u8BA4")}
\u51FA\u5177\u65F6\u95F4\uFF1A${escapeHtml(report?.issuedAt || "\u5F85\u786E\u8BA4")}
AI \u8F85\u52A9\u89E3\u91CA
\u6B63\u5728\u7ED3\u5408\u53C2\u8003\u8303\u56F4\u6574\u7406\u4FBF\u4E8E\u7406\u89E3\u7684\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u5019\u3002\u7ED3\u679C\u4EC5\u4F9B\u8F85\u52A9\u7406\u89E3\uFF0C\u4E0D\u4EE3\u8868\u8BCA\u65AD\u7ED3\u8BBA\u3002
${escapeHtml(presentation.description)}
${typeof serviceNotice === "string" && serviceNotice ? `${escapeHtml(serviceNotice)}
` : ""}AI \u8F85\u52A9\u89E3\u91CA \xB7 \u7ED3\u679C\u7248\u672C ${escapeHtml(result.resultVersion)}
${escapeHtml(interpretation.summary)}
${escapeHtml(interpretation.safety)}
${escapeHtml(item.explanation)}
\u4E0A\u8FF0\u56E0\u7D20\u53EA\u662F\u5E2E\u52A9\u7406\u89E3\u6307\u6807\u7684\u5E38\u89C1\u60C5\u51B5\uFF0C\u4E0D\u4EE3\u8868\u4F60\u60A3\u6709\u76F8\u5E94\u75BE\u75C5\uFF0C\u4E5F\u4E0D\u80FD\u4F5C\u4E3A\u8BCA\u65AD\u7ED3\u679C\u3002
\u7ED3\u679C\u7248\u672C ${escapeHtml(result.resultVersion)} \xB7 \u5BF9\u5E94\u5F53\u524D\u533B\u9662\u539F\u59CB\u62A5\u544A
${escapeHtml(serviceNotice)}
` : ""}\u8BF7\u9009\u62E9\u6700\u7B26\u5408\u7684\u4E00\u9879\uFF0C\u53CD\u9988\u4EC5\u7528\u4E8E\u6539\u8FDB\u672C\u6B21\u8F85\u52A9\u89E3\u91CA\u3002
\u611F\u8C22\u53CD\u9988\uFF0C\u5DF2\u8BB0\u5F55\u4F60\u7684\u9009\u62E9\u3002
` : ""}\u5B8C\u6210\u7B7E\u5230\u540E\uFF0C\u6392\u961F\u53F7\u548C\u9884\u8BA1\u65F6\u95F4\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002
\u795E\u7ECF\u5185\u79D1 \xB7 \u5B9E\u65F6\u5019\u8BCA
${escapeHtml(queue.room ?? "\u8BCA\u5BA4\u5F85\u5206\u914D")}
\u5F53\u524D\u8BCA\u5BA4${escapeHtml(queue.room ?? "\u5F85\u5206\u914D")}
\u961F\u5217\u66F4\u65B0\u65F6\u95F4${escapeHtml(updatedAt)}
\u9662\u5185\u8DEF\u7EBF
\u4ECE\u95E8\u8BCA\u5927\u5385\u4E58 2 \u53F7\u7535\u68AF\u81F3\u4E09\u697C\uFF0C\u51FA\u7535\u68AF\u540E\u53F3\u8F6C\u7EA6 30 \u7C73\u3002
\u9884\u8BA1\u6B65\u884C4 \u5206\u949F
\u65E0\u969C\u788D\u8DEF\u7EBF\u53EF\u7528
${eyebrow}
${description}
${escapeHtml(item.description ?? "\u72B6\u6001\u548C\u53EF\u7528\u64CD\u4F5C\u6301\u7EED\u66F4\u65B0")}
${entry.meta}
\u65B0\u7684\u62A5\u544A\u548C\u5C31\u533B\u63D0\u9192\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002
${description}
\u65B9\u6848\u6F14\u793A
${escapeHtml(scenarioLabels[activeScenario])}\u2190/\u2192 \u5207\u6362 \xB7 R \u91CD\u7F6E \xB7 P \u663E\u793A/\u9690\u85CF
\u5230\u9662\u7B7E\u5230
\u7B7E\u5230\u9700\u8981\u60A3\u8005\u660E\u786E\u786E\u8BA4\uFF1B\u672C\u9875\u9762\u4E0D\u4F1A\u6839\u636E\u4F4D\u7F6E\u81EA\u52A8\u5B8C\u6210\u7B7E\u5230\u3002
\u786E\u8BA4\u540E\u4ECD\u4EE5\u533B\u9662\u7B7E\u5230\u63A5\u53E3\u8FD4\u56DE\u7ED3\u679C\u4E3A\u51C6\u3002