nx_supplement_check.nx source
↩ module page · 93 lines · 3735 B
1// nx_supplement_check.nx -- C5.2 milestone: argv-aware CLI tool.
2//
3// The standalone "lab analyst's tool" that closes the bits-up usability
4// arc. Takes a peak file path on the command line, runs the full
5// supplement-adulterant detection pipeline, emits JSON to stdout, and
6// returns banned-count as exit status so shell scripts can branch on it.
7//
8// USAGE:
9// nx_supplement_check <peak-file>
10//
11// EXAMPLES:
12// $ cat > /tmp/sample.txt <<EOF
13// # LC-MS peak list
14// 280.18 10000 742.3
15// 102.13 8000 45.2
16// EOF
17// $ qemu-riscv64-static nx_supplement_check.elf /tmp/sample.txt
18// {"sample_id":"argv-supplied","method":"LC-ESI+-MS",...,"recommendation":"REJECT"}
19// $ echo $?
20// 2 # number of BANNED ingredients detected
21//
22// EXIT CODE CONVENTION:
23// 0 no banned, no restricted, all peaks identified (ACCEPT)
24// 1 bad arguments / file missing / parse error
25// N number of banned ingredients (REJECT or REVIEW recommended)
26//
27// This makes shell pipelines natural:
28// nx_supplement_check $peaks_file && echo "lot OK" || echo "REJECT"
29
30import "nx_chem.nx"
31import "nx_chem_molecule.nx"
32import "nx_chem_smiles.nx"
33import "nx_chem_periodic.nx"
34import "nx_chem_valence.nx"
35import "nx_chem_mass.nx"
36import "nx_chem_isotope_pattern.nx"
37import "nx_chem_adulterant_db.nx"
38import "nx_chem_peak_list.nx"
39import "nx_chem_report_json.nx"
40
41func main(argc: i64, argv: *i64) -> i64 {
42 if argc < 2 {
43 let msg: *u8 = "nx_supplement_check: usage: <peak-file> [rt-cal-file]\n" as *u8
44 var n: i64 = 0
45 while msg[n] != 0 { n = n + 1 }
46 let _w: i64 = sys_write(2, msg, n)
47 return 1
48 }
49 let path: *u8 = argv[1] as *u8
50 let pl: *PeakList = nx_chem_peak_list_read_file(path)
51 if pl.n == 0 {
52 let msg: *u8 = "nx_supplement_check: empty / unreadable peak file\n" as *u8
53 var n: i64 = 0
54 while msg[n] != 0 { n = n + 1 }
55 let _w: i64 = sys_write(2, msg, n)
56 return 1
57 }
58 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
59 // Optional 2nd argv: RT calibration CSV path. If absent, lookup
60 // falls back to mass-only. If provided, RT filter activates for
61 // entries the file mentions.
62 if argc >= 3 {
63 let rt_cal_path: *u8 = argv[2] as *u8
64 let _n: nx_int = nx_chem_load_rt_calibration(db, rt_cal_path)
65 }
66 let buf: *u8 = sys_mmap(32768)
67 // Derive sample_id from peak-file basename so JSON identifies the
68 // sample without the analyst having to provide it separately.
69 let sample_id: *u8 = sys_mmap(256)
70 let _bn: nx_int = nx_chem_basename(path, sample_id)
71 let n_json: nx_int = nx_chem_emit_report_json(buf, pl, db, sample_id, 5)
72 let _w: i64 = sys_write(1, buf, n_json as i64)
73 // Trailing newline so shells see clean line-oriented output
74 let nl: *u8 = sys_mmap(8); nl[0] = 0x0A
75 let _w2: i64 = sys_write(1, nl, 1)
76 // Re-walk peaks to compute banned count for exit code
77 var n_banned: nx_int = 0
78 var pi: nx_int = 0
79 while pi < pl.n {
80 let p: *PeakObservation = ((pl.peaks as nx_int) + (pi * NX_PEAK_OBS_BYTES)) as *PeakObservation
81 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
82 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, p.mz_q4, p.rt_q3, 5, outs, 4)
83 var mi: nx_int = 0
84 while mi < nm {
85 let rr: *MatchResult = ((outs as nx_int) + (mi * NX_MATCH_RESULT_BYTES)) as *MatchResult
86 let ee: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, rr.entry_id)
87 if ee.regulatory == NX_REG_BANNED { n_banned = n_banned + 1 }
88 mi = mi + 1
89 }
90 pi = pi + 1
91 }
92 return n_banned as i64
93}