lumnik. / scenarios All scenarios · Français

High-resolution mass spectrometry: the QC drifts to 3.84 ppm, the samples after it know

Goal Read the QC export of an LC-HRMS screening without losing a decimal, and flag every sample acquired after a QC outside its mass tolerance.

An environmental laboratory screens micropollutants in water on an Orbitrap. The transients and spectra stay on the instrument; lumnik reads what the processing software exports.

HRMS-26-1106-A.csv
Batch;HRMS-26-1106-A
Instrument;FTMS-01 (Orbitrap, positive ESI)
Method;Screening_pos_R70k
Mass calibration;2026-11-05 17:40 (external, positive calibration solution)
QC acceptance;|Δm| <= 3.0 ppm on caffeine, MRFA and reserpine

Inj;Type;Sample;Acquired;Compound;Ion;m/z theo;m/z meas;Δm;Intensity;R;Max |Δm|
;;;;;;(Th);(Th);(ppm);(counts);(FWHM);(ppm)
1;QC;QC-SST;06/11/2026 07:30;Caffeine;[C8H10N4O2+H]+;195.08765;195.08771;0.30;25894746;69900;0.52
;;;;MRFA;[C23H37N7O5S+H]+;524.26496;524.26524;0.52;117023040;42400;
;;;;Reserpine;[C33H40N2O9+H]+;609.28066;609.28075;0.15;69156004;40600;
2;Blank;Solvent blank;06/11/2026 07:48;Atrazine;[C8H14ClN5+H]+;216.10105;;;n.d.;;
;;;;Carbamazepine;[C15H12N2O+H]+;237.10224;;;n.d.;;
;;;;Sulfamethoxazole;[C10H11N3O3S+H]+;254.05939;;;n.d.;;
;;;;Diclofenac;[C14H11Cl2NO2+H]+;296.02396;;;n.d.;;
3;Sample;W-26-0401;06/11/2026 08:06;Atrazine;[C8H14ClN5+H]+;216.10105;216.10099;-0.28;2620037;;
;;;;Carbamazepine;[C15H12N2O+H]+;237.10224;;;n.d.;;
;;;;Sulfamethoxazole;[C10H11N3O3S+H]+;254.05939;;;n.d.;;
;;;;Diclofenac;[C14H11Cl2NO2+H]+;296.02396;296.02400;0.13;406567;;
4;Sample;W-26-0402;06/11/2026 08:24;Atrazine;[C8H14ClN5+H]+;216.10105;;;n.d.;;

… 49 more lines

manifests/10-qc-export.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-hrms-a  connector_type: csv-file  scopes: [qc-hrms-a]spec:  transport:    kind: local    path: /tmp/lumnik-scenario/hrms-qc-mass-accuracy/a/*.csv   # the folder the processing software exports to  parser:    delimiter: ";"    has_header: false              # the header runs over two lines, under a key;value block    columns: [inj, type, sample, acquired, compound, ion, mz_theo, mz_meas, dm_ppm, intensity, resolution, qc_max_ppm]    short_rows: skip-leading       # the key;value block before the table: skipped, never a dead letter  after_process: none  mapping:    dataset:      # the two header lines (the names, then the units) carry no theoretical m/z      - filter-where: { column: mz_theo, regex: "[0-9]+\\.[0-9]+" }    cell:      # the injection's fields are printed on its first line only; inj is left as written: one line per injection      - fill-down: { columns: [type, sample, acquired] }      # the last QC speaks for the samples after it, until the instrument is recalibrated      - fill-down: { columns: [qc_max_ppm], reset-when: { column: type, in: [Cal] } }      # n.d.: no peak in the window; < LOD: a peak below the limit of detection. Kept apart from the number.      - regex-extract: { column: intensity, pattern: "^(n\\.d\\.|< LOD)$", groups: { 1: detection } }      - regex-replace: { column: intensity, pattern: "^(n\\.d\\.|< LOD)$", replacement: "" }      - parse-number: { column: inj, integer: true }      - parse-number: { column: mz_theo, decimal-separator: "." }      - parse-number: { column: mz_meas, decimal-separator: "." }      - parse-number: { column: dm_ppm, decimal-separator: "." }      - parse-number: { column: intensity, integer: true }      - parse-number: { column: resolution, integer: true }      - parse-number: { column: qc_max_ppm, decimal-separator: "." }      - parse-date: { column: acquired, formats: ["dd/MM/yyyy HH:mm"], zone: Europe/Paris }
manifests/20-after-a-failed-qc.yaml
apiVersion: apps.lumnik.io/v1kind: Processmetadata:  name: scenario-hrms-qc-aspec:  scope: qc-hrms-a  on: { event: data.row.changed, table: connector.t_scenario_hrms_a }  when:    - type=Sample    - inj>0                          # the injection's first line: one decision per sample, not per compound    - qc_max_ppm>3                   # the QC acceptance: |Δm| <= 3.0 ppm on every calibrant ion  emit:    type: decision.after-failed-qc-a    message: "{sample} (injection {inj}): the QC before it reads {qc_max_ppm} ppm, acceptance 3.0 ppm"    key: sample
HRMS-26-1106-B.csv
Batch;HRMS-26-1106-B
Instrument;FTMS-01 (Orbitrap, positive ESI)
Method;Screening_pos_R70k
Mass calibration;2026-11-05 17:40 (external, positive calibration solution)
QC acceptance;|Δm| <= 3.0 ppm on caffeine, MRFA and reserpine

Inj;Type;Sample;Acquired;Compound;Ion;m/z theo;m/z meas;Δm;Intensity;R;Max |Δm|
;;;;;;(Th);(Th);(ppm);(counts);(FWHM);(ppm)
1;QC;QC-SST;06/11/2026 13:30;Caffeine;[C8H10N4O2+H]+;195.08765;195.08770;0.25;24385043;70900;0.35
;;;;MRFA;[C23H37N7O5S+H]+;524.26496;524.26515;0.35;117614317;43000;
;;;;Reserpine;[C33H40N2O9+H]+;609.28066;609.28082;0.27;62832434;40800;
2;Sample;W-26-0413;06/11/2026 13:48;Atrazine;[C8H14ClN5+H]+;216.10105;216.10114;0.42;123219;;
;;;;Carbamazepine;[C15H12N2O+H]+;237.10224;237.10245;0.89;1641533;;
;;;;Sulfamethoxazole;[C10H11N3O3S+H]+;254.05939;254.05960;0.83;5112642;;
;;;;Diclofenac;[C14H11Cl2NO2+H]+;296.02396;296.02395;-0.04;4000786;;
3;Sample;W-26-0414;06/11/2026 14:06;Atrazine;[C8H14ClN5+H]+;216.10105;;;n.d.;;
;;;;Carbamazepine;[C15H12N2O+H]+;237.10224;;;n.d.;;
;;;;Sulfamethoxazole;[C10H11N3O3S+H]+;254.05939;254.05949;0.40;57442;;
;;;;Diclofenac;[C14H11Cl2NO2+H]+;296.02396;296.02414;0.61;92461;;
4;Sample;W-26-0415;06/11/2026 14:24;Atrazine;[C8H14ClN5+H]+;216.10105;216.10121;0.74;55256;;

… 56 more lines

Limits

  • The rule reads the preceding QC, not the bracket: W-26-0417 and 0418, acquired before the failed QC while the drift built up, reach 2.6 and 3.3 ppm and are not flagged.
  • A sample acquired after a recalibration and before the next QC is covered by no QC; the page shows it, no decision says it.
  • Only the mass error is judged. The QC's resolving power and intensity are read; no rule judges them here.
  • A lost identification caused by the drift (a false n.d.) is not detected: the decision only says not to trust the sample.
  • Samples, intensities and drift synthetic; the theoretical masses are exact.

Laboratory · HRMS

A QC standard between each series of samples.

Caffeine, MRFA, reserpine: their mass error tells whether the samples after them can be trusted.

195.08765 · 524.26496 · 609.28066

The problem

The QC reads 3.84 ppm. The batch goes on.

  • The tolerance is 3.0 ppm.
  • Four samples are acquired after it, as if nothing happened.

What is not changed

The instrument and its software stay as they are.

  • The export is the processing software's own.
  • No spectrum leaves the instrument.

What had to be written

fill-down: { columns: [qc_max_ppm],
  reset-when: { column: type, in: [Cal] } }

The last QC speaks for the samples after it.

  • Until the recalibration.
  • One rule: qc_max_ppm>3.

What was fixed

The hub kept four decimals (#1862).

  • 195.08765 became 195.0877: 0.26 ppm of added error.
  • The header block is no longer a dead letter (#1863).

Try it

The scenario replays locally.

Every step is published with its output.

This scenario runs on lumnik Open, free and open source.

Try lumnik Open →