患者服务台血常规闭环原型实施计划.md 29 KB


doc_id: DEV-202607-002 feature_id: FEAT-202606-001-unified-entry-client type: dev-progress title: 患者服务台血常规闭环原型实施计划 status: draft owner: 医梦研发团队 created_at: 2026-07-05 updated_at: 2026-07-05 reviewers:

  • 产品负责人
  • 终端负责人 related_docs:
  • DES-202607-001
  • ODS-202606-002
  • ADR-202606-001
  • TST-202607-001 related_modules:
  • emoon-terminal-client tags:
  • 患者服务台
  • 血常规
  • 报告闭环
  • 移动端原型
  • Open Design ---

患者服务台血常规闭环原型实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在现有 patient-service-desk-v3 中加入患者端血常规深闭环,同时保留 独立的挂号、签到和候诊演示链。

Architecture: 继续使用纯前端事件投影原型。状态模型保存报告来源快照、 患者事项、授权、解释结果、反馈和失效状态;视图根据投影渲染报告中心、详情、 授权、处理中、结果和降级页面。报告解读不创建挂号命令,检查报告只增加统一入口 和扩展框架。

Tech Stack: HTML5、CSS3、原生JavaScript ES Modules、Node.js内置 node:test、esbuild经典浏览器包、Lucide图标、Open Design HTML Artifact。


1. 范围与文件

1.1 修改文件

文件 职责
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs 报告来源、状态迁移、反馈和场景fixture
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js 报告中心、详情、结果、降级和控制器渲染
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css 报告组件、状态提示和双视口布局
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs 状态、渲染、交互和安全边界测试
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.bundle.js 从JS入口重新生成的经典浏览器包
docs/prototypes/patient-service-desk-v3/使用说明.md 场景、操作和验证方式
docs/initiatives/FEAT-202606-001-unified-entry-client/测试计划.md 报告闭环验收项
docs/initiatives/FEAT-202606-001-unified-entry-client/测试结果.md 最终执行记录

1.2 不修改

  • 不修改FEAT-006生产接口和开发代码;
  • 不连接真实LIS、FastGPT或医院接口;
  • 不设计医护OCR审核工作台;
  • 不增加AI推荐科室、用药、治疗或具体复查周期;
  • 不把报告结果串入挂号TaskState;
  • 不修改历史v1/v2原型。

2. 目标状态模型

在初始状态中增加报告源投影和反馈集合:

{
  reportSourceProjections: {},
  reportFeedback: [],
  reportFilters: {
    family: "ALL",
    status: "ALL"
  }
}

血常规报告源fixture使用以下稳定结构:

{
  reportId: "report-blood-20260702",
  family: "LAB",
  reportType: "BLOOD_ROUTINE",
  title: "血常规",
  hospitalName: "空海医院",
  departmentName: "检验科",
  issuedAt: "2026-07-03 09:30",
  sourceStatus: "FINAL",
  interpretationStatus: "NOT_REQUESTED",
  sourceVersion: 1,
  sourceHash: "sha256:blood-v1",
  metrics: [
    {
      metricId: "metric-wbc",
      code: "WBC",
      name: "白细胞计数",
      value: "11.2",
      unit: "10^9/L",
      referenceRange: "3.5–9.5",
      sourceFlag: "HIGH"
    }
  ]
}

原型允许的患者端解释状态:

NOT_REQUESTED
CONSENT_REQUIRED
GENERATING
READY
NEED_REVIEW
CRITICAL_NOTICE
AI_UNAVAILABLE
INVALIDATED

3. 实施任务

Task 1:锁定场景分组与报告源契约

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

新增测试,要求控制器存在两个场景组,并锁定报告组八个场景:

assert.deepEqual(demoScenarioGroups.REPORT_CLOSURE, [
  "REPORT_ARRIVED",
  "REPORT_DETAIL",
  "REPORT_CONSENT",
  "REPORT_GENERATING",
  "REPORT_READY",
  "REPORT_NEED_REVIEW",
  "REPORT_CRITICAL",
  "REPORT_INVALIDATED",
]);
assert.ok(demoScenarioGroups.CARE_CLOSURE.includes("REGISTRATION_QUERY"));
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="scenario groups" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,提示demoScenarioGroups尚未导出。

  • Step 3:实现最小场景契约

patient-service-desk-v3.js导出冻结分组,并让demoScenarios由分组扁平化生成:

export const 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",
  ]),
});

export const demoScenarios = Object.freeze(
  Object.values(demoScenarioGroups).flat(),
);
  • [ ] Step 4:运行测试确认通过

    node --test --test-name-pattern="scenario groups" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "test: define patient report closure scenarios"
    

Task 2:建立报告源投影与筛选状态

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

覆盖报告出具、详情读取和筛选:

const bloodRoutineReportFixture = {
  reportId: "report-blood-20260702",
  family: "LAB",
  reportType: "BLOOD_ROUTINE",
  title: "血常规",
  hospitalName: "空海医院",
  departmentName: "检验科",
  issuedAt: "2026-07-03 09:30",
  sourceStatus: "FINAL",
  interpretationStatus: "NOT_REQUESTED",
  verificationStatus: "VERIFIED",
  sourceVersion: 1,
  sourceHash: "sha256:blood-v1",
  readStatus: "UNREAD",
  metrics: [],
};
const arrived = applyServerProjectionFixture(createInitialState(), {
  id: "report-issued-blood-v1",
  type: "REPORT_ISSUED",
  occurredAt: "2026-07-03T09:30:00+08:00",
  payload: bloodRoutineReportFixture,
});

assert.equal(
  arrived.reportSourceProjections["report-blood-20260702"].sourceStatus,
  "FINAL",
);
assert.equal(arrived.patientWorkItems.length, 1);
assert.equal(arrived.activeAgentTaskProjection, null);
assert.deepEqual(
  selectReports(arrived, { family: "LAB", status: "UNREAD" })
    .map((report) => report.reportId),
  ["report-blood-20260702"],
);
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="report source|report filters" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,报告源集合或selectReports不存在。

  • Step 3:实现报告源投影

createInitialState增加:

reportSourceProjections: {},
reportFeedback: [],
reportFilters: { family: "ALL", status: "ALL" },

REPORT_ISSUED只新增或版本化更新报告源、患者事项和消息,不创建活动Agent任务。 导出纯函数:

export function selectReports(
  state,
  { family = "ALL", status = "ALL" } = {},
) {
  return Object.values(state?.reportSourceProjections ?? {})
    .filter((report) => family === "ALL" || report.family === family)
    .filter((report) => status === "ALL" || report.readStatus === status)
    .sort((a, b) => b.issuedAt.localeCompare(a.issuedAt));
}
  • [ ] Step 4:运行报告状态测试

    node --test --test-name-pattern="report source|report filters" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: add patient report source projections"
    

Task 3:实现报告中心与血常规详情

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

    const reportState = loadScenario(createInitialState(), "REPORT_ARRIVED");
    const centerHtml = renderReportCenter(reportState);
    assert.match(centerHtml, /全部/);
    assert.match(centerHtml, /检验报告/);
    assert.match(centerHtml, /检查报告/);
    assert.match(centerHtml, /血常规/);
    
    const detailHtml = renderReportDetail(
    reportState.reportSourceProjections["report-blood-20260702"],
    );
    assert.match(detailHtml, /医院原始报告/);
    assert.match(detailHtml, /白细胞计数/);
    assert.match(detailHtml, /11\\.2/);
    assert.match(detailHtml, /10\\^9\\/L/);
    assert.match(detailHtml, /3\\.5–9\\.5/);
    assert.doesNotMatch(detailHtml, /建议挂.*科|建议服用|确诊/);
    
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="report center|blood routine detail" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,渲染接口或完整内容不存在。

  • Step 3:实现页面与交互

新增并导出:

export const renderReportCenter = (state) => {
  const reports = selectReports(state, state?.reportFilters);
  return `
    <section class="report-center" aria-labelledby="reportCenterTitle">
      <h1 id="reportCenterTitle">检验检查报告</h1>
      <div class="report-filters" aria-label="报告类型筛选">
        <button data-action="set-report-family-filter" data-filter="ALL">全部</button>
        <button data-action="set-report-family-filter" data-filter="LAB">检验报告</button>
        <button data-action="set-report-family-filter" data-filter="EXAM">检查报告</button>
      </div>
      <div class="report-list">
        ${reports.map((report) => `
          <button
            class="report-list-item"
            data-action="view-report"
            data-report-id="${escapeHtml(report.reportId)}"
          >
            <strong>${escapeHtml(report.title)}</strong>
            <span>${escapeHtml(report.hospitalName)} · ${escapeHtml(report.issuedAt)}</span>
          </button>
        `).join("")}
      </div>
    </section>
  `;
};

export const renderReportDetail = (report, expanded = false) => {
  const metrics = expanded ? report.metrics : report.metrics.slice(0, 3);
  return `
    <section class="report-detail" aria-labelledby="reportDetailTitle">
      <h1 id="reportDetailTitle">${escapeHtml(report.title)}</h1>
      <p>${escapeHtml(report.hospitalName)} · ${escapeHtml(report.departmentName)}</p>
      <h2>医院原始报告</h2>
      <dl>
        ${metrics.map((metric) => `
          <div>
            <dt>${escapeHtml(metric.name)}</dt>
            <dd>
              ${escapeHtml(metric.value)} ${escapeHtml(metric.unit)}
              · 参考 ${escapeHtml(metric.referenceRange)}
            </dd>
          </div>
        `).join("")}
      </dl>
      <button data-action="interpret-report"
        data-report-id="${escapeHtml(report.reportId)}">AI辅助解读</button>
    </section>
  `;
};

增加操作:

open-report-center
set-report-family-filter
set-report-status-filter
view-report
toggle-report-metrics

报告详情中的主按钮为“AI辅助解读”,不能出现“推荐复诊”或“预约相关科室”。

  • [ ] Step 4:运行对应测试

    node --test --test-name-pattern="report center|blood routine detail" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: add blood routine report center and detail"
    

Task 4:收紧授权、可信门禁与处理中状态

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

测试授权绑定和可信门禁:

const reportId = "report-blood-20260702";
const reportState = loadScenario(createInitialState(), "REPORT_ARRIVED");
const requested = requestReportInterpretation(reportState, reportId);
assert.equal(requested.overlays.consentId, `consent-ai-${reportId}`);

const granted = grantConsent(requested, `consent-ai-${reportId}`);
assert.equal(granted.reportSourceProjections[reportId].interpretationStatus,
  "GENERATING");

for (const reason of [
  "SOURCE_NOT_FINAL",
  "OCR_REVIEW_REQUIRED",
  "REFERENCE_CONFLICT",
]) {
  const blockedState = {
    ...reportState,
    reportSourceProjections: {
      ...reportState.reportSourceProjections,
      [reportId]: {
        ...reportState.reportSourceProjections[reportId],
        verificationStatus: "NEED_REVIEW",
        verificationReason: reason,
      },
    },
  };
  const blocked = evaluateReportInterpretationGate(
    blockedState,
    reportId,
  );
  assert.equal(blocked.allowed, false);
}
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="report consent boundary|interpretation gate" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,门禁函数或绑定结构不存在。

  • Step 3:实现最小门禁

导出:

export function evaluateReportInterpretationGate(state, reportId) {
  const report = state?.reportSourceProjections?.[reportId];
  if (!report) return { allowed: false, reason: "REPORT_NOT_FOUND" };
  if (report.sourceStatus !== "FINAL") {
    return { allowed: false, reason: "SOURCE_NOT_FINAL" };
  }
  if (report.verificationStatus !== "VERIFIED") {
    return { allowed: false, reason: report.verificationReason };
  }
  if (report.criticalNotice === true) {
    return { allowed: false, reason: "CRITICAL_NOTICE" };
  }
  return { allowed: true, reason: null };
}

授权只绑定当前患者和当前报告。门禁失败时不创建结果,不启动解释计时器。 同时将现有grantConsent改为接受动态consent-ai-${reportId},并从当前overlay校验 患者和报告绑定;不新增第二套授权函数。

  • [ ] Step 4:运行授权与门禁测试

    node --test --test-name-pattern="report consent boundary|interpretation gate" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: enforce patient report interpretation gate"
    

Task 5:实现安全解读结果与反馈

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

    const reportId = "report-blood-20260702";
    const generating = loadScenario(createInitialState(), "REPORT_GENERATING");
    const ready = applyReportInterpretationProjectionFixture(generating, reportId);
    const html = renderReportInterpretationResult(
    ready.resultSnapshotProjections["result-report-interpretation-001"],
    );
    
    for (const text of [
    "结果概括",
    "需要关注",
    "指标解释",
    "常见相关因素",
    "可以怎么做",
    "内容依据",
    ]) {
    assert.match(html, new RegExp(text));
    }
    assert.doesNotMatch(html, /推荐科室|复查周期|治疗方案|建议服用/);
    
    const feedback = submitReportFeedback(ready, {
    reportId,
    category: "HARD_TO_UNDERSTAND",
    });
    assert.equal(feedback.reportFeedback.at(-1).category, "HARD_TO_UNDERSTAND");
    
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="safe report result|report feedback" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,六层结果或反馈接口不存在。

  • Step 3:实现结果与反馈

结果只提供以下操作:

查看原始报告
咨询开单医生
在线咨询
联系人工服务
提交反馈
撤回授权

导出纯函数:

export function submitReportFeedback(state, { reportId, category }) {
  const allowed = new Set([
    "HELPFUL",
    "METRIC_INACCURATE",
    "HARD_TO_UNDERSTAND",
    "DIFFERS_FROM_DOCTOR",
    "OTHER",
  ]);
  if (!allowed.has(category)) return state;
  return {
    ...state,
    reportFeedback: [
      ...(state.reportFeedback ?? []),
      { id: `feedback-${reportId}-${category}`, reportId, category },
    ],
  };
}
  • [ ] Step 4:运行结果与反馈测试

    node --test --test-name-pattern="safe report result|report feedback" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: add safe blood report result and feedback"
    

Task 6:实现患者端降级、危急值与失效

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

    const reportId = "report-blood-20260702";
    const readyState = loadScenario(createInitialState(), "REPORT_READY");
    const needsReview = loadScenario(createInitialState(), "REPORT_NEED_REVIEW");
    assert.match(renderReportUnavailable({
    reason: "OCR_REVIEW_REQUIRED",
    }), /暂不适合自动解读/);
    assert.doesNotMatch(renderReportUnavailable({
    reason: "OCR_REVIEW_REQUIRED",
    }), /继续生成/);
    
    const critical = loadScenario(createInitialState(), "REPORT_CRITICAL");
    assert.match(renderReportUnavailable({
    reason: "CRITICAL_NOTICE",
    }), /请按医院提示及时联系/);
    assert.doesNotMatch(renderReportUnavailable({
    reason: "CRITICAL_NOTICE",
    }), /AI解读|推荐科室/);
    
    const invalidated = applyServerProjectionFixture(readyState, {
    id: "report-corrected-blood-v2",
    type: "REPORT_CORRECTED",
    occurredAt: "2026-07-03T11:00:00+08:00",
    payload: {
    reportId,
    sourceVersion: 2,
    sourceHash: "sha256:blood-v2",
    },
    });
    assert.equal(
    invalidated.reportSourceProjections[reportId].interpretationStatus,
    "INVALIDATED",
    );
    assert.equal(
    invalidated.resultSnapshotProjections[
    "result-report-interpretation-001"
    ].valid,
    false,
    );
    
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="report degradation|critical notice|report invalidation" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,降级页面或更正事件未实现。

  • Step 3:实现状态和页面

统一降级组件接收原因码:

const REPORT_UNAVAILABLE_PRESENTATION = Object.freeze({
  OCR_REVIEW_REQUIRED: {
    title: "当前报告暂不适合自动解读",
    description: "报告内容需要人工核对,您仍可查看医院原始报告。",
  },
  CRITICAL_NOTICE: {
    title: "请按医院提示及时联系",
    description: "当前报告需要按医院既有流程处理。",
  },
  AI_UNAVAILABLE: {
    title: "AI辅助解读暂不可用",
    description: "医院原始报告仍可正常查看,请稍后重试。",
  },
  UNSUPPORTED: {
    title: "暂不支持自动解读",
    description: "您可以查看原始报告或联系人工服务。",
  },
});

export const renderReportUnavailable = ({ reason }) => {
  const presentation = REPORT_UNAVAILABLE_PRESENTATION[reason]
    ?? REPORT_UNAVAILABLE_PRESENTATION.UNSUPPORTED;
  return `
    <section class="report-unavailable" role="status">
      <h1>${escapeHtml(presentation.title)}</h1>
      <p>${escapeHtml(presentation.description)}</p>
      <button data-action="open-original-report">查看原始报告</button>
      <button data-action="contact-human-service">联系人工服务</button>
    </section>
  `;
};

REPORT_CORRECTEDREPORT_WITHDRAWN必须使旧结果失效;旧结果可保留在状态中用于 演示审计,但不能由当前页面读取为有效结果。

  • [ ] Step 4:运行降级和失效测试

    node --test --test-name-pattern="report degradation|critical notice|report invalidation" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: add patient report safety degradation states"
    

Task 7:增加检查报告扩展框架和四入口联动

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写失败测试

    const reportState = loadScenario(createInitialState(), "REPORT_ARRIVED");
    const imaging = {
    reportId: "report-ct-20260702",
    family: "EXAM",
    reportType: "IMAGING",
    title: "胸部CT",
    bodyPart: "胸部",
    findings: "双肺纹理清晰。",
    conclusion: "本次检查未见明确异常。",
    sourceStatus: "FINAL",
    };
    const html = renderExaminationReportDetail(imaging);
    assert.match(html, /检查所见/);
    assert.match(html, /报告结论/);
    assert.doesNotMatch(html, /参考范围|异常指标列表/);
    assert.match(html, /该类型暂未开放AI辅助解读/);
    
    assert.match(renderHome(reportState), /血常规报告已出/);
    assert.match(renderMessages(reportState), /血常规报告/);
    assert.match(renderRecords({
    ...reportState,
    activeTab: "records",
    }), /检验报告/);
    assert.match(renderAssistant({
    ...reportState,
    activeTab: "assistant",
    }), /帮我看报告/);
    
  • [ ] Step 2:运行测试确认失败

    node --test --test-name-pattern="examination report framework|report entry points" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:FAIL,检查报告详情或入口联动不足。

  • Step 3:实现扩展框架

检查报告只渲染:

检查名称和部位
检查所见
报告结论
报告医生与审核状态
图像或PDF入口
暂未开放AI辅助解读

不得复用检验报告的指标、单位和参考区间组件。

  • [ ] Step 4:运行测试

    node --test --test-name-pattern="examination report framework|report entry points" \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:PASS。

  • [ ] Step 5:提交

    git add docs/prototypes/patient-service-desk-v3
    git commit -m "feat: add examination report extension shell"
    

Task 8:完成控制器、可访问性、bundle和发布验证

Files:

  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css
  • Modify: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.bundle.js
  • Modify: docs/prototypes/patient-service-desk-v3/使用说明.md
  • Modify: docs/initiatives/FEAT-202606-001-unified-entry-client/测试计划.md
  • Modify: docs/initiatives/FEAT-202606-001-unified-entry-client/测试结果.md
  • Test: docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs

  • [ ] Step 1:写完整故事线失败测试

    for (const scenario of demoScenarioGroups.REPORT_CLOSURE) {
    const state = loadScenario(createInitialState(), scenario);
    assert.equal(state.demoScenario, scenario);
    assert.equal(state.activeAgentTaskProjection, null);
    }
    
    const reportStory = [
    "REPORT_ARRIVED",
    "REPORT_DETAIL",
    "REPORT_CONSENT",
    "REPORT_GENERATING",
    "REPORT_READY",
    "REPORT_INVALIDATED",
    ];
    assert.deepEqual(
    reportStory.map((scenario) => (
    loadScenario(createInitialState(), scenario).demoScenario
    )),
    reportStory,
    );
    

同时断言:

  • 弹窗仍在手机画布内部;
  • 390×844和430×932无横向溢出;
  • 控件触控区不小于44px;
  • 对话框焦点进入、Tab循环和焦点恢复有效;
  • 患者视图不出现OCR、RAG、Prompt、Workflow、traceId和L0/L1等术语;
  • HTML继续通过经典bundle支持file://直接打开。

  • [ ] Step 2:运行全部测试确认尚有失败

    node --test \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    

预期:新增完整故事线测试FAIL,既有测试不得出现无关失败。

  • Step 3:完成控制器与样式

遥控器分组显示“报告闭环”和“就医闭环”,保持:

上一步
下一步
重置
390×844
430×932
隐藏控制器

所有报告弹窗挂载在.phone-shell内部,不得覆盖外部页面。

  • [ ] Step 4:重新生成经典bundle

    /Users/destiny/node_modules/.bin/esbuild \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js \
    --bundle \
    --format=iife \
    --platform=browser \
    --target=es2022 \
    --banner:js='/* Generated from patient-service-desk-v3.js for direct file:// opening. */' \
    --outfile=docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.bundle.js
    

预期:生成成功,无未解析import。

  • [ ] Step 5:运行完整验证

    node --test \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
    node --check \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
    node --check \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
    node --check \
    docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.bundle.js
    git diff --check
    

预期:全部测试通过、语法检查通过、差异检查无输出。

  • [ ] Step 6:同步Open Design发布副本

    OPEN_DESIGN="/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/web"
    
    cp docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.html \
    "$OPEN_DESIGN/patient-service-desk-v3.html"
    cp docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css \
    "$OPEN_DESIGN/patient-service-desk-v3.css"
    cp docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js \
    "$OPEN_DESIGN/patient-service-desk-v3.js"
    cp docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs \
    "$OPEN_DESIGN/patient-service-desk-v3.state.mjs"
    cp docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.bundle.js \
    "$OPEN_DESIGN/patient-service-desk-v3.bundle.js"
    

逐个使用cmp确认发布副本与仓库源码一致。

  • Step 7:更新文档

更新测试计划、测试结果和使用说明,记录:

  • 报告闭环场景;
  • 实际测试数量;
  • 真实视觉复核状态;
  • Open Design发布哈希;
  • 检查报告仅为扩展框架;
  • 不包含真实LIS、FastGPT和医护审核台。

  • [ ] Step 8:提交

    git add \
    docs/prototypes/patient-service-desk-v3 \
    docs/initiatives/FEAT-202606-001-unified-entry-client/测试计划.md \
    docs/initiatives/FEAT-202606-001-unified-entry-client/测试结果.md
    git commit -m "docs: complete patient blood report prototype delivery"
    

4. 最终验收

完成后必须满足:

  1. 报告闭环和就医闭环在控制器中分组且可独立加载;
  2. REPORT_ISSUED只创建患者事项,不自动启动AI解读;
  3. 报告详情先展示医院原始事实;
  4. 授权绑定患者、报告、用途和有效期;
  5. 只有可信门禁通过才进入生成态;
  6. 患者结果不包含诊断、用药、治疗、AI推荐科室和具体复查周期;
  7. OCR待确认、来源冲突和危急值不会硬生成解读;
  8. 报告更正、撤回或作废使旧结果失效;
  9. 报告反馈结构化保存在医梦原型状态;
  10. 检查报告不复用检验指标组件;
  11. 报告闭环不创建挂号Command或活动挂号TaskState;
  12. 原挂号、签到和候诊测试继续通过;
  13. 双视口、弹窗、焦点、44px触控和直接打开能力保持有效;
  14. Open Design发布副本与仓库源码一致。

5. 阶段记录

日期 进展 风险 下一步
2026-07-05 完成功能策略、边界和逐页设计评审 原型仍是fixture,不代表生产接口 按Task 1开始TDD实现

6. 阻塞项

  • 内置浏览器安全策略可能继续拒绝本地file://地址;自动测试和静态契约不能替代 产品负责人在可访问环境中的真实双视口视觉复核。
  • Open Design属于本地发布目录,不纳入Git;每次修改必须显式同步并使用cmp验证。