3. 实验元数据与报告上下文建模

在自动报告工程中,报告内容的质量并不只取决于绘图是否美观,更取决于“报告是否知道自己在描述什么”。同一张标准曲线图,如果缺少样本类型、稀释倍数、试剂批号或仪器检出限,读者就无法判断结果是否可信。实验元数据与报告上下文建模要解决的核心问题是:把散落在实验记录、仪器日志、LIMS 和质控文件中的信息,抽象为一份结构化、可校验、可追溯的上下文对象,让报告生成器在渲染任何图表之前,先获得完整的“实验身份”。

元数据的四类标准化描述

实验元数据可以按来源和用途划分为四类,每类承担不同的报告职责。样本信息描述“测的是什么”,包括样本编号、样本类型(血清、组织匀浆、培养上清等)、采集时间、处理方式、稀释倍数和分组标签。实验条件描述“在什么环境下测的”,包括实验日期、操作人、试剂批号、缓冲液配方、孵育温度与时间。仪器参数描述“用什么测的”,包括仪器型号、序列号、检测通道、激发/发射波长、增益、积分时间、板型与孔位。质控指标描述“测得可不可信”,包括标准曲线 R²、斜率、截距、空白值、阳性对照回收率、复孔 CV 和检出限。

这四类信息在报告中的角色不同:样本信息决定结果归属,实验条件决定结果的可比性,仪器参数决定结果的可复现性,质控指标决定结果是否被采纳。因此,元数据建模不能只做字段罗列,而要明确每个字段的必填性、取值域和校验规则。

报告上下文对象模型

报告上下文对象(ReportContext)是报告生成器的唯一输入契约。它不是一个扁平字典,而是带有层级和引用关系的对象。顶层包含 batch(批次标识)、samples(样本数组)、conditions(实验条件)、instrument(仪器参数)、qc(质控指标)和 derived(派生量,如浓度、归一化值)。每个样本对象通过 sample_id 与原始数据表关联,通过 group 与统计模块关联。质控对象中的 accepted 布尔字段由校验规则自动计算,报告模板据此决定是否在图表上叠加“质控通过”水印或直接阻断输出。

对象模型的关键设计原则是“引用而非复制”:仪器参数只存一份,所有样本共享;实验条件按批次存储,不随样本重复。这样既减少冗余,也避免同一批次内出现矛盾的元数据。

报告上下文对象模型与元数据校验链 样本信息 ID / 类型 / 稀释 / 分组 实验条件 日期 / 试剂批号 / 温度 仪器参数 型号 / 波长 / 增益 / 板型 质控指标 R² / CV / 回收率 / LOD ReportContext batch: 批次标识 samples[]: 样本数组 conditions: 实验条件 instrument: 仪器参数 qc: 质控指标 derived: 派生量 校验规则引擎 必填字段完整性检查 取值域与单位一致性 质控阈值判定 accepted 报告模板渲染 图表 + 元数据表 + 质控水印 阻断 / 告警 缺失字段或质控失败时触发 注入 校验 渲染

批次元数据模板的构建

下面给出一个可直接用于实验批次登记的元数据模板。字段设计遵循“报告生成器需要什么就登记什么”的原则,避免无关字段。模板以 JSON 结构表达,便于程序解析,也便于人工核对。

{
  "batch_id": "EXP-2024-0312-A",
  "samples": [
    {
      "sample_id": "S001",
      "type": "serum",
      "dilution": 10,
      "group": "control",
      "collected_at": "2024-03-10T08:30:00"
    },
    {
      "sample_id": "S002",
      "type": "serum",
      "dilution": 10,
      "group": "treated",
      "collected_at": "2024-03-10T08:35:00"
    }
  ],
  "conditions": {
    "date": "2024-03-12",
    "operator": "Zhang",
    "reagent_lot": "R20240228",
    "incubation_temp_c": 37,
    "incubation_time_min": 30
  },
  "instrument": {
    "model": "SpectraMax i3x",
    "serial": "SN-88231",
    "mode": "fluorescence",
    "ex_nm": 485,
    "em_nm": 535,
    "gain": 80,
    "plate": "96-well flat"
  },
  "qc": {
    "std_curve_r2": 0.9987,
    "slope": 0.00412,
    "intercept": 0.031,
    "blank_mean": 0.028,
    "positive_recovery_pct": 96.4,
    "replicate_cv_pct": 3.2,
    "lod_ng_ml": 0.15,
    "accepted": true
  }
}

模板中的 accepted 字段不是人工填写,而是由校验规则计算得出。例如:R² ≥ 0.995、复孔 CV ≤ 5%、阳性回收率在 90%~110% 之间、空白值低于 0.05,四项同时满足才置为 true。任一不满足,报告生成器应输出告警而非静默通过。

模板验证:从字段到判断

构建完模板后,必须用真实或模拟批次数据做验证。验证分三层:第一层是结构验证,检查必填字段是否存在、类型是否正确、单位是否统一(如温度统一为摄氏度、浓度统一为 ng/mL)。第二层是逻辑验证,检查样本 ID 是否唯一、稀释倍数是否为正数、分组标签是否在允许集合内。第三层是质控验证,按上述阈值计算 accepted,并记录未通过的具体原因。

以示例批次 EXP-2024-0312-A 为例,假设某次运行中复孔 CV 为 6.1%,超过 5% 阈值。校验引擎应输出:qc.replicate_cv_pct = 6.1 > 5.0,accepted = false,原因:复孔精密度不足。报告模板收到该上下文后,不生成正式结论页,而是生成一张“质控未通过”的说明页,列出所有样本的原始信号值和 CV 值,供实验人员排查。这就是上下文建模的实际价值:把“能不能出报告”的判断从人的经验转移到可复现的规则上。

建议在模板中为每个字段标注 required 和 unit 两个属性,例如 incubation_temp_c: {value: 37, unit: "°C", required: true}。这样校验引擎可以自动检查缺失和单位错误,而不需要在代码中硬编码字段名。

最后需要强调,元数据模板不是一次设计就固定不变的。当实验类型从荧光检测扩展到化学发光或 qPCR 时,仪器参数和质控指标会变化,但样本信息、实验条件和上下文对象的层级结构可以复用。把变化的部分限制在 instrument 和 qc 两个子对象内,报告生成器的主体逻辑就不需要重写。这种“稳定骨架 + 可变叶节点”的设计,是实验元数据建模能够长期维护的关键。