nx_supplement.nx source
↩ module page · 635 lines · 27247 B
1// nx_supplement.nx -- C14.0: S-class unified front-door CLI.
2//
3// Git-style subcommand dispatcher. Single binary, discoverable
4// commands, self-validating. Stays bits-up sovereign.
5//
6// USAGE:
7// nx_supplement # show help
8// nx_supplement help # show help
9// nx_supplement check FILE # single-sample JSON
10// nx_supplement batch C R FILES # multi-sample NDJSON
11// nx_supplement csv R FILES # CSV table for Excel
12// nx_supplement compounds # list all reference compounds
13// nx_supplement explain ID # show details for compound ID
14// nx_supplement selftest # verify install via bundled examples
15// nx_supplement version # show version
16
17import "nx_chem.nx"
18import "nx_chem_molecule.nx"
19import "nx_chem_smiles.nx"
20import "nx_chem_periodic.nx"
21import "nx_chem_valence.nx"
22import "nx_chem_mass.nx"
23import "nx_chem_isotope_pattern.nx"
24import "nx_chem_adulterant_db.nx"
25import "nx_chem_peak_list.nx"
26import "nx_chem_report_json.nx"
27import "nx_chem_report_csv.nx"
28
29func nx_sup_version() -> *u8 {
30 return "nx_supplement 1.0.0 (substrate 2026-05-20)" as *u8
31}
32
33// =================================================================
34// Strcmp-ish: compare null-terminated strings. Returns 0 if equal.
35// =================================================================
36func str_eq(a: *u8, b: *u8) -> nx_int {
37 var i: nx_int = 0
38 var done: nx_int = 0
39 var result: nx_int = 1
40 while done == 0 {
41 let ca: nx_int = a[i] as nx_int
42 let cb: nx_int = b[i] as nx_int
43 if ca != cb { result = 0; done = 1 }
44 else {
45 if ca == 0 { done = 1 }
46 else { i = i + 1 }
47 }
48 }
49 return result
50}
51
52func parse_int_arg(s: *u8) -> nx_int {
53 var i: nx_int = 0
54 var v: nx_int = 0
55 var done: nx_int = 0
56 while done == 0 {
57 let c: nx_int = s[i] as nx_int
58 if c == 0 { done = 1 }
59 else {
60 if c < 48 { done = 1 }
61 else { if c > 57 { done = 1 }
62 else { v = v * 10 + (c - 48); i = i + 1 } }
63 }
64 }
65 return v
66}
67
68func is_dash(p: *u8) -> nx_int {
69 if p[0] as nx_int != 45 { return 0 }
70 if p[1] as nx_int != 0 { return 0 }
71 return 1
72}
73
74// =================================================================
75// HELP -- comprehensive top-level help with copy-paste examples
76// =================================================================
77func cmd_help() -> i64 {
78 println("nx_supplement -- supplement adulterant detector" as *u8)
79 println("Bits-up sovereign LC-MS adulterant detection with regulatory citations" as *u8)
80 println("" as *u8)
81 println("USAGE" as *u8)
82 println(" nx_supplement <subcommand> [args]" as *u8)
83 println("" as *u8)
84 println("SUBCOMMANDS" as *u8)
85 println(" check FILE Single peak file -> JSON verdict to stdout" as *u8)
86 println(" batch CMP CAL FILES... Multi-sample NDJSON (CMP/CAL accept '-' for none)" as *u8)
87 println(" csv CAL FILES... Multi-sample CSV table for Excel" as *u8)
88 println(" compounds List all reference compounds in the DB" as *u8)
89 println(" explain ID Show full details for compound ID (e.g., 4 = sibutramine)" as *u8)
90 println(" selftest Verify the install by running bundled example data" as *u8)
91 println(" validate Self-test that every DB compound is detected by its own [M+H]+" as *u8)
92 println(" help Show this help" as *u8)
93 println(" version Show version" as *u8)
94 println("" as *u8)
95 println("COMMON LAB EXAMPLES" as *u8)
96 println(" # one sample, no calibration" as *u8)
97 println(" nx_supplement check my_sample.csv" as *u8)
98 println("" as *u8)
99 println(" # batch with RT calibration" as *u8)
100 println(" nx_supplement batch - rt_cal.csv samples/*.csv" as *u8)
101 println("" as *u8)
102 println(" # daily run -> JSON archive + Excel report" as *u8)
103 println(" nx_supplement batch lab_compounds.tsv rt_cal.csv samples/*.csv > today.ndjson" as *u8)
104 println(" nx_supplement csv rt_cal.csv samples/*.csv > today.csv" as *u8)
105 println("" as *u8)
106 println(" # discover the DB" as *u8)
107 println(" nx_supplement compounds" as *u8)
108 println(" nx_supplement explain 4 # = sibutramine details" as *u8)
109 println("" as *u8)
110 println(" # verify the substrate works" as *u8)
111 println(" nx_supplement selftest" as *u8)
112 println("" as *u8)
113 println("EXIT CODES" as *u8)
114 println(" 0 no BANNED ingredients detected (recommendation: ACCEPT)" as *u8)
115 println(" 1 bad arguments / file unreadable" as *u8)
116 println(" N number of BANNED ingredients (for shell branching)" as *u8)
117 println("" as *u8)
118 println("DOCS" as *u8)
119 println(" User guide: nxc2/docs/NISHI_SUPPLEMENT_ADULTERANT_DETECTOR_USER_GUIDE.md" as *u8)
120 println(" Examples: nxc2/examples/supplement-arc/" as *u8)
121 return 0
122}
123
124// =================================================================
125// VERSION
126// =================================================================
127func cmd_version() -> i64 {
128 let _w: i64 = println(nx_sup_version())
129 return 0
130}
131
132// =================================================================
133// COMPOUNDS -- list all reference compounds in the DB as a table
134// =================================================================
135func cmd_compounds() -> i64 {
136 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
137 println("Reference compounds in built-in DB:" as *u8)
138 println("" as *u8)
139 println(" ID [M+H]+ REG NAME / citation" as *u8)
140 println(" -- -------- ----------- ----------------------------------" as *u8)
141 var i: nx_int = 0
142 while i < db.n {
143 let e: *AdulterantEntry = ((db.entries as nx_int) + (i * NX_ADULTERANT_ENTRY_BYTES)) as *AdulterantEntry
144 let _p1: i64 = print(" " as *u8)
145 if e.id < 10 {
146 let _z: i64 = print(" " as *u8)
147 }
148 let _p2: i64 = print_i64(e.id as i64)
149 let _p3: i64 = print(" " as *u8)
150 let _p4: i64 = print_q4_mass(e.mh_plus_q4)
151 let _p5: i64 = print(" " as *u8)
152 let _p6: i64 = print(nx_chem_regulatory_str(e.regulatory))
153 // Pad regulatory column to 11 chars
154 let regs: *u8 = nx_chem_regulatory_str(e.regulatory)
155 var reg_len: nx_int = 0
156 while regs[reg_len] as nx_int != 0 { reg_len = reg_len + 1 }
157 var pad: nx_int = 11 - reg_len
158 while pad > 0 {
159 let _sp: i64 = print(" " as *u8)
160 pad = pad - 1
161 }
162 let _p7: i64 = print(" " as *u8)
163 let _p8: i64 = print(e.name_ptr)
164 let _p9: i64 = println("" as *u8)
165 i = i + 1
166 }
167 println("" as *u8)
168 let _t1: i64 = print("Total: " as *u8)
169 let _t2: i64 = print_i64(db.n as i64)
170 let _t3: i64 = println(" compounds" as *u8)
171 println("" as *u8)
172 println("Use 'nx_supplement explain ID' for full details on a compound." as *u8)
173 return 0
174}
175
176// =================================================================
177// EXPLAIN -- drill into one compound
178// =================================================================
179func cmd_explain(id_str: *u8) -> i64 {
180 let id: nx_int = parse_int_arg(id_str)
181 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
182 let e: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, id)
183 if (e as nx_int) == 0 {
184 let _e1: i64 = print("nx_supplement explain: no compound with id=" as *u8)
185 let _e2: i64 = print_i64(id as i64)
186 let _e3: i64 = println("" as *u8)
187 println("Use 'nx_supplement compounds' to see the full list." as *u8)
188 return 1
189 }
190 println("==================================================================" as *u8)
191 let _h1: i64 = print(" COMPOUND DETAIL: id=" as *u8)
192 let _h2: i64 = print_i64(e.id as i64)
193 let _h3: i64 = print(" " as *u8)
194 let _h4: i64 = println(e.name_ptr)
195 println("==================================================================" as *u8)
196 let _r1: i64 = print(" Regulatory status : " as *u8)
197 let _r2: i64 = println(nx_chem_regulatory_str(e.regulatory))
198 let _r3: i64 = print(" Citation : " as *u8)
199 let _r4: i64 = println(e.citation_ptr)
200 println("" as *u8)
201 println(" Analytical chemistry" as *u8)
202 let _a1: i64 = print(" Monoisotopic mass: " as *u8)
203 let _a2: i64 = print_q4_mass(e.monoiso_q4)
204 let _a3: i64 = println(" Da" as *u8)
205 let _a4: i64 = print(" [M+H]+ m/z : " as *u8)
206 let _a5: i64 = print_q4_mass(e.mh_plus_q4)
207 let _a6: i64 = println(" Da (LC-ESI+ target)" as *u8)
208 let _a7: i64 = print(" Heavy atoms : " as *u8)
209 let _a8: i64 = print_i64(e.n_heavy as i64)
210 let _a9: i64 = println("" as *u8)
211 let _b1: i64 = print(" M+1 intensity : " as *u8)
212 let _b2: i64 = print_i64(e.m1_q4 as i64)
213 let _b3: i64 = println("/10000 of M (isotopologue ratio)" as *u8)
214 let _b4: i64 = print(" M+2 intensity : " as *u8)
215 let _b5: i64 = print_i64(e.m2_q4 as i64)
216 let _b6: i64 = println("/10000 of M" as *u8)
217 let _c1: i64 = print(" Halogen sig : " as *u8)
218 if e.halogen_sig == 0 {
219 let _x: i64 = println("0 (no Cl/Br)" as *u8)
220 }
221 if e.halogen_sig == 1 {
222 let _x: i64 = println("1 (single Cl -- expect 3:1 M:M+2 pattern)" as *u8)
223 }
224 if e.halogen_sig == 2 {
225 let _x: i64 = println("2 (single Br -- expect 1:1 M:M+2 pattern)" as *u8)
226 }
227 if e.halogen_sig == 3 {
228 let _x: i64 = println("3 (multi-halogen; binomial expansion needed)" as *u8)
229 }
230 println("" as *u8)
231 if e.ref_rt_q3 > 0 {
232 println(" Retention time (loaded from calibration)" as *u8)
233 let _r5: i64 = print(" Reference RT : " as *u8)
234 let _r6: i64 = print_i64((e.ref_rt_q3 / 1000) as i64)
235 let _r7: i64 = print(" s (tolerance +/- " as *u8)
236 let _r8: i64 = print_i64((e.rt_tol_q3 / 1000) as i64)
237 let _r9: i64 = println(" s)" as *u8)
238 }
239 if e.ref_rt_q3 == 0 {
240 println(" Retention time : uncalibrated (use rt_cal.csv)" as *u8)
241 }
242 println("==================================================================" as *u8)
243 return 0
244}
245
246// =================================================================
247// CHECK -- mirror of nx_supplement_check
248// =================================================================
249func cmd_check(path: *u8) -> i64 {
250 let pl: *PeakList = nx_chem_peak_list_read_file(path)
251 if pl.n == 0 {
252 let msg: *u8 = "nx_supplement check: empty / unreadable peak file\n" as *u8
253 var n: i64 = 0
254 while msg[n] != 0 { n = n + 1 }
255 let _w: i64 = sys_write(2, msg, n)
256 return 1
257 }
258 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
259 let buf: *u8 = sys_mmap(32768)
260 let sample_id: *u8 = sys_mmap(256)
261 let _bn: nx_int = nx_chem_basename(path, sample_id)
262 let n_json: nx_int = nx_chem_emit_report_json(buf, pl, db, sample_id, 5)
263 let _w: i64 = sys_write(1, buf, n_json as i64)
264 let nl: *u8 = sys_mmap(8); nl[0] = 0x0A
265 let _w2: i64 = sys_write(1, nl, 1)
266 var n_banned: nx_int = 0
267 var pi: nx_int = 0
268 while pi < pl.n {
269 let p: *PeakObservation = ((pl.peaks as nx_int) + (pi * NX_PEAK_OBS_BYTES)) as *PeakObservation
270 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
271 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, p.mz_q4, p.rt_q3, 5, outs, 4)
272 var mi: nx_int = 0
273 while mi < nm {
274 let rr: *MatchResult = ((outs as nx_int) + (mi * NX_MATCH_RESULT_BYTES)) as *MatchResult
275 let ee: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, rr.entry_id)
276 if ee.regulatory == NX_REG_BANNED { n_banned = n_banned + 1 }
277 mi = mi + 1
278 }
279 pi = pi + 1
280 }
281 return n_banned as i64
282}
283
284// =================================================================
285// VALIDATE -- exhaustive self-validation: every compound in the DB
286// is detected by its own [M+H]+. Gives lab partner Day-1 confidence:
287// "this tool detects every compound it claims to detect".
288//
289// Algorithm:
290// For each entry e in db.entries:
291// 1. Build a synthetic peak at mz = e.mh_plus_q4
292// 2. Look up in db (within 5 ppm)
293// 3. Verify a match returns entry_id == e.id
294// Report per-compound result + overall verdict.
295// =================================================================
296func cmd_validate() -> i64 {
297 println("Validating: each compound's own [M+H]+ matches itself..." as *u8)
298 println("" as *u8)
299 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
300 var n_pass: nx_int = 0
301 var n_fail: nx_int = 0
302 var i: nx_int = 0
303 while i < db.n {
304 let e: *AdulterantEntry = ((db.entries as nx_int) + (i * NX_ADULTERANT_ENTRY_BYTES)) as *AdulterantEntry
305 // Build a single-peak PeakList at this entry's [M+H]+
306 let pl: *PeakList = nx_chem_peak_list_new(2)
307 let _a: nx_int = nx_chem_peak_list_add(pl, e.mh_plus_q4, 100000, 0)
308 // Lookup at 5 ppm tolerance
309 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
310 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, e.mh_plus_q4, 0, 5, outs, 4)
311 var ok: nx_int = 0
312 if nm > 0 {
313 let r0: *MatchResult = ((outs as nx_int) + (0 * NX_MATCH_RESULT_BYTES)) as *MatchResult
314 if r0.entry_id == e.id { ok = 1 }
315 }
316 let _t1: i64 = print(" [")
317 if ok == 1 {
318 let _t2: i64 = print("PASS" as *u8)
319 n_pass = n_pass + 1
320 }
321 if ok == 0 {
322 let _t2f: i64 = print("FAIL" as *u8)
323 n_fail = n_fail + 1
324 }
325 let _t3: i64 = print("] id=" as *u8)
326 if e.id < 10 { let _z: i64 = print(" " as *u8) }
327 let _t4: i64 = print_i64(e.id as i64)
328 let _t5: i64 = print(" [M+H]+ " as *u8)
329 let _t6: i64 = print_q4_mass(e.mh_plus_q4)
330 let _t7: i64 = print(" " as *u8)
331 let _t8: i64 = println(e.name_ptr)
332 i = i + 1
333 }
334 println("" as *u8)
335 let _v1: i64 = print("Result: " as *u8)
336 let _v2: i64 = print_i64(n_pass as i64)
337 let _v3: i64 = print(" PASS, " as *u8)
338 let _v4: i64 = print_i64(n_fail as i64)
339 let _v5: i64 = print(" FAIL (of " as *u8)
340 let _v6: i64 = print_i64(db.n as i64)
341 let _v7: i64 = println(" compounds in DB)" as *u8)
342 if n_fail == 0 {
343 println("" as *u8)
344 println("OK -- substrate detects every reference compound it claims to detect." as *u8)
345 println(" Lab partnership ready: pipe LC-MS peaks through nx_supplement check." as *u8)
346 return 0
347 }
348 return n_fail as i64
349}
350
351// =================================================================
352// SELFTEST -- run bundled examples + verify expected outcomes
353// =================================================================
354func cmd_selftest() -> i64 {
355 println("Running selftest on bundled example corpus..." as *u8)
356 println("" as *u8)
357 // Probe for the example dir. Literal paths need nx_chem_path_from_literal
358 // before being passed to syscalls (see litpath quirk; runtime/
359 // nx_litpath_quirk_test.nx documents the bug class).
360 let try1: *u8 = nx_chem_path_from_literal("examples/supplement-arc/sample_clean.csv" as *u8)
361 let try2: *u8 = nx_chem_path_from_literal("nxc2/examples/supplement-arc/sample_clean.csv" as *u8)
362 let pl_try1: *PeakList = nx_chem_peak_list_read_file(try1)
363 let pl_try2: *PeakList = nx_chem_peak_list_read_file(try2)
364 var prefix: *u8 = 0 as *u8
365 if pl_try1.n > 0 {
366 prefix = nx_chem_path_from_literal("examples/supplement-arc/" as *u8)
367 }
368 if (prefix as nx_int) == 0 {
369 if pl_try2.n > 0 {
370 prefix = nx_chem_path_from_literal("nxc2/examples/supplement-arc/" as *u8)
371 }
372 }
373 if (prefix as nx_int) == 0 {
374 println(" FAIL: cannot find bundled example data" as *u8)
375 println(" Tried: examples/supplement-arc/sample_clean.csv" as *u8)
376 println(" nxc2/examples/supplement-arc/sample_clean.csv" as *u8)
377 println("" as *u8)
378 println(" Run selftest from the nxc2 directory (or the parent of nxc2/)." as *u8)
379 return 1
380 }
381 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
382 var n_pass: nx_int = 0
383 var n_fail: nx_int = 0
384 // Test 1: clean sample -> ACCEPT
385 let path_clean: *u8 = sys_mmap(256)
386 let plen1: nx_int = nx_chem_emit_str(path_clean, 0, prefix)
387 let _x1: nx_int = nx_chem_emit_str(path_clean, plen1, "sample_clean.csv" as *u8)
388 let pl_clean: *PeakList = nx_chem_peak_list_read_file(path_clean)
389 if pl_clean.n == 1 {
390 let _t1: i64 = println(" [PASS] sample_clean.csv: 1 peak parsed (caffeine)" as *u8)
391 n_pass = n_pass + 1
392 }
393 if pl_clean.n != 1 {
394 let _t1f: i64 = println(" [FAIL] sample_clean.csv: expected 1 peak" as *u8)
395 n_fail = n_fail + 1
396 }
397 // Test 2: contaminated sample -> 3 peaks, 2 BANNED + 1 RESTRICTED
398 let path_contam: *u8 = sys_mmap(256)
399 let plen2: nx_int = nx_chem_emit_str(path_contam, 0, prefix)
400 let _x2: nx_int = nx_chem_emit_str(path_contam, plen2, "sample_contaminated.csv" as *u8)
401 let pl_contam: *PeakList = nx_chem_peak_list_read_file(path_contam)
402 if pl_contam.n == 3 {
403 let _t2: i64 = println(" [PASS] sample_contaminated.csv: 3 peaks parsed" as *u8)
404 n_pass = n_pass + 1
405 }
406 if pl_contam.n != 3 {
407 let _t2f: i64 = println(" [FAIL] sample_contaminated.csv: expected 3 peaks" as *u8)
408 n_fail = n_fail + 1
409 }
410 // Test 3: count banned matches in contaminated sample
411 var n_banned_contam: nx_int = 0
412 var ci: nx_int = 0
413 while ci < pl_contam.n {
414 let p: *PeakObservation = ((pl_contam.peaks as nx_int) + (ci * NX_PEAK_OBS_BYTES)) as *PeakObservation
415 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
416 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, p.mz_q4, p.rt_q3, 5, outs, 4)
417 var mi: nx_int = 0
418 while mi < nm {
419 let rr: *MatchResult = ((outs as nx_int) + (mi * NX_MATCH_RESULT_BYTES)) as *MatchResult
420 let ee: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, rr.entry_id)
421 if ee.regulatory == NX_REG_BANNED { n_banned_contam = n_banned_contam + 1 }
422 mi = mi + 1
423 }
424 ci = ci + 1
425 }
426 if n_banned_contam == 2 {
427 let _t3: i64 = println(" [PASS] sample_contaminated.csv: 2 BANNED matches (REJECT verdict)" as *u8)
428 n_pass = n_pass + 1
429 }
430 if n_banned_contam != 2 {
431 let _t3f: i64 = print(" [FAIL] sample_contaminated.csv: expected 2 banned, got " as *u8)
432 let _t3f2: i64 = print_i64(n_banned_contam as i64)
433 let _t3f3: i64 = println("" as *u8)
434 n_fail = n_fail + 1
435 }
436 // Test 4: DB has >= 24 compounds
437 if db.n >= 24 {
438 let _t4: i64 = print(" [PASS] built-in compound DB populated (" as *u8)
439 let _t4n: i64 = print_i64(db.n as i64)
440 let _t4e: i64 = println(" compounds)" as *u8)
441 n_pass = n_pass + 1
442 }
443 if db.n < 24 {
444 let _t4f: i64 = println(" [FAIL] expected >= 24 compounds in built-in seed" as *u8)
445 n_fail = n_fail + 1
446 }
447 // Test 5: substrate version
448 let _t5: i64 = print(" [INFO] " as *u8)
449 let _t5n: i64 = println(nx_sup_version())
450 println("" as *u8)
451 if n_fail == 0 {
452 let _r1: i64 = print("OK -- " as *u8)
453 let _r2: i64 = print_i64(n_pass as i64)
454 let _r3: i64 = println(" of 4 checks passed. Substrate ready for production use." as *u8)
455 return 0
456 }
457 let _f1: i64 = print("FAILED -- " as *u8)
458 let _f2: i64 = print_i64(n_fail as i64)
459 let _f3: i64 = print(" of " as *u8)
460 let _f4: i64 = print_i64((n_pass + n_fail) as i64)
461 let _f5: i64 = println(" checks failed. Substrate may not be properly built." as *u8)
462 return n_fail as i64
463}
464
465// =================================================================
466// BATCH -- mirror of nx_supplement_batch (3-arg form)
467// =================================================================
468func process_one_sample_batch(db: *AdulterantDB, peak_path: *u8) -> nx_int {
469 let pl: *PeakList = nx_chem_peak_list_read_file(peak_path)
470 if pl.n == 0 {
471 let err_buf: *u8 = sys_mmap(256)
472 var w: nx_int = 0
473 w = w + nx_chem_emit_str(err_buf, w, "{\"sample_id\":" as *u8)
474 let sid: *u8 = sys_mmap(256)
475 let _b1: nx_int = nx_chem_basename(peak_path, sid)
476 w = w + nx_chem_emit_str_escaped(err_buf, w, sid)
477 w = w + nx_chem_emit_str(err_buf, w, ",\"error\":\"empty or unreadable peak file\"}\n" as *u8)
478 let _w: i64 = sys_write(1, err_buf, w as i64)
479 return 0
480 }
481 let sample_id: *u8 = sys_mmap(256)
482 let _bn: nx_int = nx_chem_basename(peak_path, sample_id)
483 let buf: *u8 = sys_mmap(32768)
484 let n_json: nx_int = nx_chem_emit_report_json(buf, pl, db, sample_id, 5)
485 let _w1: i64 = sys_write(1, buf, n_json as i64)
486 let nl: *u8 = sys_mmap(8); nl[0] = 0x0A
487 let _w2: i64 = sys_write(1, nl, 1)
488 var n_banned: nx_int = 0
489 var pi: nx_int = 0
490 while pi < pl.n {
491 let p: *PeakObservation = ((pl.peaks as nx_int) + (pi * NX_PEAK_OBS_BYTES)) as *PeakObservation
492 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
493 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, p.mz_q4, p.rt_q3, 5, outs, 4)
494 var mi: nx_int = 0
495 while mi < nm {
496 let rr: *MatchResult = ((outs as nx_int) + (mi * NX_MATCH_RESULT_BYTES)) as *MatchResult
497 let ee: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, rr.entry_id)
498 if ee.regulatory == NX_REG_BANNED { n_banned = n_banned + 1 }
499 mi = mi + 1
500 }
501 pi = pi + 1
502 }
503 return n_banned
504}
505
506func cmd_batch(argc: i64, argv: *i64, start_idx: i64) -> i64 {
507 if argc - start_idx < 3 {
508 let msg: *u8 = "nx_supplement batch: usage: <compounds-or-dash> <cal-or-dash> <peaks...>\n" as *u8
509 var n: i64 = 0
510 while msg[n] != 0 { n = n + 1 }
511 let _w: i64 = sys_write(2, msg, n)
512 return 1
513 }
514 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
515 let cmp_path: *u8 = argv[start_idx] as *u8
516 if is_dash(cmp_path) == 0 {
517 let _nc: nx_int = nx_chem_load_compounds_from_file(db, cmp_path)
518 }
519 let cal_path: *u8 = argv[start_idx + 1] as *u8
520 if is_dash(cal_path) == 0 {
521 let _nr: nx_int = nx_chem_load_rt_calibration(db, cal_path)
522 }
523 var total_banned: i64 = 0
524 var i: i64 = start_idx + 2
525 while i < argc {
526 let peak_path: *u8 = argv[i] as *u8
527 let b: nx_int = process_one_sample_batch(db, peak_path)
528 total_banned = total_banned + (b as i64)
529 i = i + 1
530 }
531 return total_banned
532}
533
534// =================================================================
535// CSV -- mirror of nx_supplement_csv
536// =================================================================
537func process_one_csv_row(db: *AdulterantDB, peak_path: *u8) -> nx_int {
538 let pl: *PeakList = nx_chem_peak_list_read_file(peak_path)
539 if pl.n == 0 { return 0 }
540 let sample_id: *u8 = sys_mmap(256)
541 let _bn: nx_int = nx_chem_basename(peak_path, sample_id)
542 let buf: *u8 = sys_mmap(32768)
543 let n: nx_int = nx_chem_emit_report_csv_rows_only(buf, pl, db, sample_id, 5)
544 let _w: i64 = sys_write(1, buf, n as i64)
545 var n_banned: nx_int = 0
546 var pi: nx_int = 0
547 while pi < pl.n {
548 let p: *PeakObservation = ((pl.peaks as nx_int) + (pi * NX_PEAK_OBS_BYTES)) as *PeakObservation
549 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
550 let nm: nx_int = nx_chem_adulterant_db_lookup_with_rt(db, p.mz_q4, p.rt_q3, 5, outs, 4)
551 var mi: nx_int = 0
552 while mi < nm {
553 let rr: *MatchResult = ((outs as nx_int) + (mi * NX_MATCH_RESULT_BYTES)) as *MatchResult
554 let ee: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, rr.entry_id)
555 if ee.regulatory == NX_REG_BANNED { n_banned = n_banned + 1 }
556 mi = mi + 1
557 }
558 pi = pi + 1
559 }
560 return n_banned
561}
562
563func cmd_csv(argc: i64, argv: *i64, start_idx: i64) -> i64 {
564 if argc - start_idx < 2 {
565 let msg: *u8 = "nx_supplement csv: usage: <cal-or-dash> <peaks...>\n" as *u8
566 var n: i64 = 0
567 while msg[n] != 0 { n = n + 1 }
568 let _w: i64 = sys_write(2, msg, n)
569 return 1
570 }
571 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
572 let cal_path: *u8 = argv[start_idx] as *u8
573 if is_dash(cal_path) == 0 {
574 let _nr: nx_int = nx_chem_load_rt_calibration(db, cal_path)
575 }
576 let hbuf: *u8 = sys_mmap(256)
577 let nh: nx_int = nx_chem_emit_csv_header(hbuf, 0)
578 let _wh: i64 = sys_write(1, hbuf, nh as i64)
579 var total_banned: i64 = 0
580 var i: i64 = start_idx + 1
581 while i < argc {
582 let peak_path: *u8 = argv[i] as *u8
583 let b: nx_int = process_one_csv_row(db, peak_path)
584 total_banned = total_banned + (b as i64)
585 i = i + 1
586 }
587 return total_banned
588}
589
590// =================================================================
591// MAIN DISPATCH
592// =================================================================
593func main(argc: i64, argv: *i64) -> i64 {
594 if argc < 2 { return cmd_help() }
595 let sub: *u8 = argv[1] as *u8
596 if str_eq(sub, "help" as *u8) == 1 { return cmd_help() }
597 if str_eq(sub, "--help" as *u8) == 1 { return cmd_help() }
598 if str_eq(sub, "-h" as *u8) == 1 { return cmd_help() }
599 if str_eq(sub, "version" as *u8) == 1 { return cmd_version() }
600 if str_eq(sub, "--version" as *u8) == 1 { return cmd_version() }
601 if str_eq(sub, "compounds" as *u8) == 1 { return cmd_compounds() }
602 if str_eq(sub, "selftest" as *u8) == 1 { return cmd_selftest() }
603 if str_eq(sub, "validate" as *u8) == 1 { return cmd_validate() }
604 if str_eq(sub, "explain" as *u8) == 1 {
605 if argc < 3 {
606 let msg: *u8 = "nx_supplement explain: usage: explain <id>\n" as *u8
607 var n: i64 = 0
608 while msg[n] != 0 { n = n + 1 }
609 let _w: i64 = sys_write(2, msg, n)
610 return 1
611 }
612 return cmd_explain(argv[2] as *u8)
613 }
614 if str_eq(sub, "check" as *u8) == 1 {
615 if argc < 3 {
616 let msg: *u8 = "nx_supplement check: usage: check <peak-file>\n" as *u8
617 var n: i64 = 0
618 while msg[n] != 0 { n = n + 1 }
619 let _w: i64 = sys_write(2, msg, n)
620 return 1
621 }
622 return cmd_check(argv[2] as *u8)
623 }
624 if str_eq(sub, "batch" as *u8) == 1 {
625 return cmd_batch(argc, argv, 2)
626 }
627 if str_eq(sub, "csv" as *u8) == 1 {
628 return cmd_csv(argc, argv, 2)
629 }
630 let msg: *u8 = "nx_supplement: unknown subcommand. Run 'nx_supplement help' for usage.\n" as *u8
631 var n: i64 = 0
632 while msg[n] != 0 { n = n + 1 }
633 let _w: i64 = sys_write(2, msg, n)
634 return 1
635}