code wiki / (root) / nx_supplement_check.nx

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}