Skip to content

Find a result and fix a mistake

In this lesson a colleague discovers that the film’s sharp peak came from a saturated detector. You will find the measurement and record corrected values without hiding the mistake. By the end you will see both versions and know why the earlier normalisation still points to the original. It takes about ten minutes.

Read Look underneath a result first if checkpoints and record versions are unfamiliar. The example then carries on from the session you built in lessons 1 and 3: it corrects the very spectrum you recorded and normalised.

Corrections need a deliberate target. A readable name is convenient, but several measurements can share one, and choosing the first match would hide that ambiguity. sample.select returns active handles matching every supplied filter exactly; sample.one asks for exactly one and refuses both missing and ambiguous selections. Both accept name, run_id, checkpoint_id and record_id.

Here we first count the results named ftir-replicate-01, then narrow the request with the original run’s identifier. In a larger archive, that extra filter lets you say which measurement you intend rather than relying on its position in a list.

Remember the distinction from lesson 4: the checkpoint names the logical result, while the record names one version. sample.correct(result, data) keeps the active checkpoint’s identity and commits a fresh record version in one revision. It fixes this measurement rather than creating an unrelated measurement with a similar name.

The normalisation you committed in lesson 3 now earns its keep: it gives us an earlier analysis to inspect after the correction. The replacement peak below is synthetic, like the original; it stands in for the corrected values you would have accepted after investigating the detector problem.

print(f"matches by name: {len(sample.select(name='ftir-replicate-01'))}")
target = sample.one(name="ftir-replicate-01", run_id=raw.run_id)
corrected_data = target.to_numpy()
corrected_data["absorbance"] = 0.015 * (axis - 2000.0) / 1000.0
corrected_data["absorbance"] += 0.6 * np.exp(-((axis - 2450.0) / 60.0) ** 2)
corrected = sample.correct(target, corrected_data)
print(f"same checkpoint: {corrected.checkpoint_id == raw.checkpoint_id}")
print(f"new record version: {corrected.record_id != raw.record_id}")
print(f"selection finds correction: {sample.one(checkpoint_id=raw.checkpoint_id) == corrected}")
print(f"matches by original record: {len(sample.select(record_id=raw.record_id))}")
print(f"original peak: {round(float(raw.to_numpy()['absorbance'].max()), 4)}")
print(f"normalised peak: {float(normalised.to_numpy()['absorbance'].max())}")
print(sample.summary())
matches by name: 1
same checkpoint: True
new record version: True
selection finds correction: True
matches by original record: 0
original peak: 1.0075
normalised peak: 1.0
Sample 'polymer-film-01' — 2 active records; 0 active claims
Records (commit order):
[0] 'normalised' 101 values, shape=(101,)
fields: wavenumber (cm^-1), absorbance (a.u.)
[1] 'ftir-replicate-01' 101 values, shape=(101,)
fields: wavenumber (cm^-1), absorbance (a.u.)

The three True lines show the same checkpoint, a different record and an active selection that now finds the correction. The 0 shows that active selection no longer returns the original version. Yet the old handle still reads its original peak, and the earlier normalisation still peaks at 1.0.

The summary the example prints recaps the state after the correction: its header counts two active records — the replaced version no longer inflates that count — and the listing shows the normalised result first and the corrected version last, in commit order. The superseded version stays in the stored history. The text carries no generated identifiers, so the same recorded work always prints the same summary.

That last result can look reassuring until you ask which spectrum was normalised. The normalised result from lesson 3 still names an analysis of the mistaken input version. A correction does not retarget descendants or recompute a mean, because doing so would rewrite what happened.

To analyse the accepted values, call transform again with the returned corrected handle. The new result will record the corrected input version, leaving the earlier normalisation available as part of the history. Deciding which analyses need repeating is your scientific responsibility; MTRL keeps the inputs explicit so you can make it.

You selected one active measurement, corrected it and kept a link back to the exact version it replaced. The old handle stayed pinned to its original values while active selection moved to the corrected version. Neither the mistaken measurement nor the normalisation derived from it disappeared.

This is why a correction is more useful than silently editing an array. You can explain what you first believed, what changed and which analyses still depend on the old values.

One corrected measurement is a useful start. Combine replicates adds two more measurements beside it and records a mean with all three exact inputs.