3. 实验记录数据模型设计

ELN 的数据模型决定了系统能否既忠实还原实验过程,又支撑检索、审计与复用。设计时先要区分两类信息:一类是实验本身的结构(实验、步骤、样品、仪器、试剂、附件、版本),另一类是实验过程中产生的数据(测量值、图谱、观察记录)。前者适合用关系模式保证一致性,后者往往结构多变,适合用 JSON 文档承载。本节围绕这两条线展开。

核心实体与关系模式

把实验记录拆成实体,关键是找到“谁被谁引用”。一个实验(experiment)包含有序的步骤(step);每个步骤消耗或产出样品(sample);步骤使用仪器(instrument)和试剂(reagent);附件(attachment)挂在实验或步骤上;版本(version)记录实验的每次修订。用关系模式表达时,主键与外键构成骨架:

experiment(id PK, title, owner_id, status, created_at, current_version)
step(id PK, experiment_id FK, seq_no, name, started_at, ended_at)
sample(id PK, experiment_id FK, parent_sample_id FK, name, type, amount, unit)
step_sample(step_id FK, sample_id FK, role)   -- role: input/output
step_instrument(step_id FK, instrument_id FK, setting_json)
step_reagent(step_id FK, reagent_id FK, amount, unit)
attachment(id PK, experiment_id FK, step_id FK NULL, file_uri, mime, sha256)
version(id PK, experiment_id FK, version_no, diff_json, author_id, created_at)

这里有两个设计要点。第一,step_sample 用 role 区分输入与输出,而不是给样品表加“方向”字段,因为同一样品可能既是某步的产物又是下一步的原料,用关联表更自然。第二,version 不存整份快照,而存 diff_json,既节省空间,又能精确回答“谁在什么时候改了哪一步的哪个字段”。

JSON 文档模式承载步骤数据

步骤内的具体数据(如反应温度曲线、称量记录、观察描述)结构差异大,硬塞进关系表会导致大量空列。更合适的做法是:关系表保存可检索的骨架字段,步骤数据以 JSON 文档存入 step.data_json。例如一个加热步骤:

{
  "step_type": "heating",
  "params": { "target_temp_C": 80, "ramp_min": 15, "hold_min": 30 },
  "observations": [
    { "t_min": 0,  "temp_C": 25, "note": "开始升温" },
    { "t_min": 15, "temp_C": 80, "note": "到达目标温度" },
    { "t_min": 45, "temp_C": 80, "note": "保温结束,溶液澄清" }
  ],
  "deviations": []
}

为了让 JSON 可校验,应约定 step_type 对应的 schema,例如 heating 必须含 target_temp_C 与 hold_min。这样既保留灵活性,又能在录入时拦截缺字段、单位错误等问题。

版本与附件的一致性

版本号建议采用“实验级递增 + 步骤级 diff”的组合:每次提交生成新的 version_no,diff_json 记录被修改的步骤 ID 与字段路径。附件则通过 sha256 去重,同一文件被多个实验引用时只存一份,避免重复占用存储。审计时,用 version 表可以重建任意历史状态,用 attachment 表可以追溯原始图谱文件。

建模顺序建议:先画实体关系图确定主外键,再为每个 step_type 定义 JSON schema,最后补版本与附件的审计字段。不要一开始就把所有字段塞进一张宽表。
实验记录核心实体关系与 JSON 数据承载 experiment id / title / owner_id status / current_version step id / experiment_id / seq_no name / started_at / ended_at version version_no / diff_json author_id / created_at sample id / parent_sample_id name / type / amount / unit instrument id / name / model / calib_due step_instrument.setting_json reagent id / name / lot_no / purity step_reagent.amount / unit attachment id / experiment_id / step_id file_uri / mime / sha256 step.data_json params / observations deviations 1:N 1:N 1:N N:M N:M 1:N 承载

一个可落地的建模检查清单

把上面的设计落到具体项目时,可以用下面这份清单逐项核对,避免遗漏关键字段:

实体必填字段关键约束
experimentid, title, owner_id, statusstatus 取值 draft/running/done/archived
stepid, experiment_id, seq_no, nameseq_no 在同一实验内唯一且连续
sampleid, name, type, amount, unitamount 为正数,unit 受控词表
instrumentid, name, model, calib_duecalib_due 早于步骤开始时间时告警
reagentid, name, lot_no, puritylot_no 用于追溯批次
attachmentid, file_uri, sha256sha256 唯一,支持去重
versionid, experiment_id, version_no, diff_jsonversion_no 递增,diff_json 可解析

实际录入时,一个常见错误是把“样品”和“试剂”混为一谈。试剂是库存中的物料,样品是实验中流转的对象;同一种试剂可以生成多个样品,因此两者应分表存储,通过 step_reagent 与 step_sample 分别关联。另一个错误是忽略 parent_sample_id,导致无法追溯样品从哪一步、哪一批次派生而来。补上这个自引用外键后,就能回答“这批产物来自哪次实验的哪一步”这类审计问题。

JSON 文档虽然灵活,但不要把可检索字段(如温度、时间、样品名)只放在 JSON 里。凡是需要按条件查询的字段,都应在关系表中保留对应列或生成索引列,否则检索会退化为全表扫描。

综合来看,关系模式负责骨架与一致性,JSON 文档负责步骤数据的弹性,版本与附件负责审计与追溯。三者配合,才能让 ELN 既严谨又实用。