nx_chem_peak_list_test.nx source
↩ module page · 474 lines · 21905 B
1// nx_chem_peak_list_test.nx -- C5.0 KAT + integrated lab-pipeline demo.
2//
3// Tests the text-format peak list parser then composes with C4.0
4// adulterant DB lookup to produce a verdict report from "raw lab
5// output text" -- exactly the workflow a real analyst would run.
6//
7// expect_exit: 0
8// license_tier: ORIGINAL
9
10import "nx_chem.nx"
11import "nx_chem_molecule.nx"
12import "nx_chem_smiles.nx"
13import "nx_chem_periodic.nx"
14import "nx_chem_valence.nx"
15import "nx_chem_mass.nx"
16import "nx_chem_isotope_pattern.nx"
17import "nx_chem_adulterant_db.nx"
18import "nx_chem_peak_list.nx"
19
20// Helper: write ASCII bytes from a string-literal-style int sequence
21// into a buffer. Returns buf. Used to build synthetic raw text in
22// the KAT.
23func bytes_from_chars(out_buf: *u8, src_text: *u8, n: nx_int) -> *u8 {
24 var i: nx_int = 0
25 while i < n {
26 out_buf[i] = src_text[i]
27 i = i + 1
28 }
29 out_buf[n] = 0
30 return out_buf
31}
32
33// =================================================================
34// A -- parse single decimal "280.1826" -> Q4 = 2801826
35// =================================================================
36func a_parse_decimal() -> nx_int {
37 let s: *u8 = sys_mmap(16)
38 s[0] = 0x32; s[1] = 0x38; s[2] = 0x30; s[3] = 0x2E
39 s[4] = 0x31; s[5] = 0x38; s[6] = 0x32; s[7] = 0x36
40 var v: nx_int = 0
41 let n: nx_int = nx_chem_parse_decimal_q4(s, 0, 8, &v)
42 if n != 8 { return 11 }
43 if v != 2801826 { return 12 }
44 return 0
45}
46
47// =================================================================
48// B -- parse short decimal "12.5" with padding -> Q4 = 125000
49// =================================================================
50func b_parse_short_decimal() -> nx_int {
51 let s: *u8 = sys_mmap(16)
52 s[0] = 0x31; s[1] = 0x32; s[2] = 0x2E; s[3] = 0x35
53 var v: nx_int = 0
54 let n: nx_int = nx_chem_parse_decimal_q4(s, 0, 4, &v)
55 if n != 4 { return 21 }
56 if v != 125000 { return 22 }
57 return 0
58}
59
60// =================================================================
61// C -- parse integer-only "100" -> Q4 = 1000000
62// =================================================================
63func c_parse_int() -> nx_int {
64 let s: *u8 = sys_mmap(8)
65 s[0] = 0x31; s[1] = 0x30; s[2] = 0x30
66 var v: nx_int = 0
67 let n: nx_int = nx_chem_parse_decimal_q4(s, 0, 3, &v)
68 if n != 3 { return 31 }
69 if v != 1000000 { return 32 }
70 return 0
71}
72
73// =================================================================
74// D -- non-digit at start returns 0 consumed
75// =================================================================
76func d_parse_no_digit() -> nx_int {
77 let s: *u8 = sys_mmap(8); s[0] = 0x61 // 'a'
78 var v: nx_int = 0
79 let n: nx_int = nx_chem_parse_decimal_q4(s, 0, 1, &v)
80 if n != 0 { return 41 }
81 return 0
82}
83
84// =================================================================
85// E -- parse trailing whitespace stops at space
86// "100 200" parses "100" then stops at space
87// =================================================================
88func e_parse_stops_at_space() -> nx_int {
89 let s: *u8 = sys_mmap(16)
90 s[0] = 0x31; s[1] = 0x30; s[2] = 0x30; s[3] = 0x20 // "100 "
91 var v: nx_int = 0
92 let n: nx_int = nx_chem_parse_decimal_q4(s, 0, 4, &v)
93 if n != 3 { return 51 }
94 if v != 1000000 { return 52 }
95 return 0
96}
97
98// =================================================================
99// F -- parse simple two-peak text into PeakList
100// "280.18 10000\n102.13 5000\n"
101// =================================================================
102func f_parse_two_peaks() -> nx_int {
103 let s: *u8 = sys_mmap(64)
104 s[0] = 0x32; s[1] = 0x38; s[2] = 0x30; s[3] = 0x2E
105 s[4] = 0x31; s[5] = 0x38; s[6] = 0x20
106 s[7] = 0x31; s[8] = 0x30; s[9] = 0x30; s[10] = 0x30; s[11] = 0x30
107 s[12] = 0x0A
108 s[13] = 0x31; s[14] = 0x30; s[15] = 0x32; s[16] = 0x2E
109 s[17] = 0x31; s[18] = 0x33; s[19] = 0x20
110 s[20] = 0x35; s[21] = 0x30; s[22] = 0x30; s[23] = 0x30
111 s[24] = 0x0A
112 let pl: *PeakList = nx_chem_peak_list_parse(s, 25)
113 if pl.n != 2 { return 61 }
114 let p0: *PeakObservation = ((pl.peaks as nx_int) + (0 * NX_PEAK_OBS_BYTES)) as *PeakObservation
115 if p0.mz_q4 != 2801800 { return 62 }
116 if p0.intensity_q4 != 100000000 { return 63 }
117 let p1: *PeakObservation = ((pl.peaks as nx_int) + (1 * NX_PEAK_OBS_BYTES)) as *PeakObservation
118 if p1.mz_q4 != 1021300 { return 64 }
119 return 0
120}
121
122// =================================================================
123// G -- '#' comment lines and blank lines skipped
124// "# comment\n280.18 10000\n\n102.13 5000\n" -> 2 peaks
125// =================================================================
126func g_parse_comments_and_blanks() -> nx_int {
127 let s: *u8 = sys_mmap(64)
128 s[0] = 0x23; s[1] = 0x20; s[2] = 0x63; s[3] = 0x6F // "# co"
129 s[4] = 0x6D; s[5] = 0x6D; s[6] = 0x65; s[7] = 0x6E // "mmen"
130 s[8] = 0x74 // "t"
131 s[9] = 0x0A // \n
132 s[10] = 0x32; s[11] = 0x38; s[12] = 0x30; s[13] = 0x2E
133 s[14] = 0x31; s[15] = 0x38; s[16] = 0x20
134 s[17] = 0x31; s[18] = 0x30; s[19] = 0x30; s[20] = 0x30; s[21] = 0x30
135 s[22] = 0x0A
136 s[23] = 0x0A // blank line
137 s[24] = 0x31; s[25] = 0x30; s[26] = 0x32; s[27] = 0x2E
138 s[28] = 0x31; s[29] = 0x33; s[30] = 0x20
139 s[31] = 0x35; s[32] = 0x30; s[33] = 0x30; s[34] = 0x30
140 s[35] = 0x0A
141 let pl: *PeakList = nx_chem_peak_list_parse(s, 36)
142 if pl.n != 2 { return 71 }
143 return 0
144}
145
146// =================================================================
147// H -- m/z + intensity + rt: full 3-column "280.1826 10000 742.3"
148// =================================================================
149func h_parse_three_col() -> nx_int {
150 let s: *u8 = sys_mmap(64)
151 // "280.1826 10000 742.3\n"
152 s[0] = 0x32; s[1] = 0x38; s[2] = 0x30; s[3] = 0x2E
153 s[4] = 0x31; s[5] = 0x38; s[6] = 0x32; s[7] = 0x36
154 s[8] = 0x20
155 s[9] = 0x31; s[10] = 0x30; s[11] = 0x30; s[12] = 0x30; s[13] = 0x30
156 s[14] = 0x20
157 s[15] = 0x37; s[16] = 0x34; s[17] = 0x32; s[18] = 0x2E; s[19] = 0x33
158 s[20] = 0x0A
159 let pl: *PeakList = nx_chem_peak_list_parse(s, 21)
160 if pl.n != 1 { return 81 }
161 let p0: *PeakObservation = ((pl.peaks as nx_int) + (0 * NX_PEAK_OBS_BYTES)) as *PeakObservation
162 if p0.mz_q4 != 2801826 { return 82 }
163 if p0.intensity_q4 != 100000000 { return 83 }
164 // rt 742.3 sec * 1000 / 10 ... let me trace
165 // rt_q4 = 7423000 (742.3 in Q4)
166 // rt_q3 = rt_q4 / 10 = 742300 (Q3 milliseconds = 742300 ms = 742.3 sec)
167 if p0.rt_q3 != 742300 { return 84 }
168 return 0
169}
170
171// =================================================================
172// I -- disk round-trip: write a peak file, read it back, parse,
173// verify content matches.
174// =================================================================
175func i_file_roundtrip() -> nx_int {
176 // Build text in memory
177 let src: *u8 = sys_mmap(128)
178 src[0] = 0x32; src[1] = 0x38; src[2] = 0x30; src[3] = 0x2E
179 src[4] = 0x31; src[5] = 0x38; src[6] = 0x20
180 src[7] = 0x31; src[8] = 0x30; src[9] = 0x30
181 src[10] = 0x0A
182 src[11] = 0x31; src[12] = 0x30; src[13] = 0x32; src[14] = 0x2E
183 src[15] = 0x31; src[16] = 0x33; src[17] = 0x20
184 src[18] = 0x32; src[19] = 0x30; src[20] = 0x30
185 src[21] = 0x0A
186 let n_src: nx_int = 22
187 // Path "/tmp/nx_chem_peaks_kat.txt"
188 let path: *u8 = sys_mmap(64)
189 path[0] = 0x2F; path[1] = 0x74; path[2] = 0x6D; path[3] = 0x70
190 path[4] = 0x2F; path[5] = 0x6E; path[6] = 0x78
191 path[7] = 0x5F; path[8] = 0x63; path[9] = 0x68; path[10] = 0x65; path[11] = 0x6D
192 path[12] = 0x5F; path[13] = 0x70; path[14] = 0x65; path[15] = 0x61; path[16] = 0x6B; path[17] = 0x73
193 path[18] = 0x5F; path[19] = 0x6B; path[20] = 0x61; path[21] = 0x74
194 path[22] = 0x2E; path[23] = 0x74; path[24] = 0x78; path[25] = 0x74
195 path[26] = 0
196 // Write file
197 let n_wrote: nx_int = nx_chem_write_file(path, src, n_src)
198 if n_wrote != n_src { return 91 }
199 // Read back
200 let pl: *PeakList = nx_chem_peak_list_read_file(path)
201 if pl.n != 2 { return 92 }
202 let p0: *PeakObservation = ((pl.peaks as nx_int) + (0 * NX_PEAK_OBS_BYTES)) as *PeakObservation
203 if p0.mz_q4 != 2801800 { return 93 }
204 let p1: *PeakObservation = ((pl.peaks as nx_int) + (1 * NX_PEAK_OBS_BYTES)) as *PeakObservation
205 if p1.mz_q4 != 1021300 { return 94 }
206 return 0
207}
208
209// =================================================================
210// J -- missing-file gracefully returns empty list (n=0)
211// =================================================================
212func j_missing_file() -> nx_int {
213 let path: *u8 = sys_mmap(64)
214 path[0] = 0x2F; path[1] = 0x74; path[2] = 0x6D; path[3] = 0x70
215 path[4] = 0x2F; path[5] = 0x6E; path[6] = 0x78; path[7] = 0x5F
216 path[8] = 0x6E; path[9] = 0x6F; path[10] = 0x6E; path[11] = 0x65
217 path[12] = 0x78; path[13] = 0x69; path[14] = 0x73; path[15] = 0x74
218 path[16] = 0x65; path[17] = 0x6E; path[18] = 0x74; path[19] = 0x2E
219 path[20] = 0x74; path[21] = 0x78; path[22] = 0x74
220 path[23] = 0
221 let pl: *PeakList = nx_chem_peak_list_read_file(path)
222 if pl.n != 0 { return 101 }
223 return 0
224}
225
226// =================================================================
227// K -- basename extraction: full path -> bare name w/o extension
228// =================================================================
229func k_basename() -> nx_int {
230 // "/tmp/SUSPECT-2026-001.txt" -> "SUSPECT-2026-001"
231 let path: *u8 = sys_mmap(64)
232 let chars: *u8 = "/tmp/SUSPECT-2026-001.txt" as *u8
233 var i: nx_int = 0
234 while chars[i] as nx_int != 0 { path[i] = chars[i]; i = i + 1 }
235 path[i] = 0
236 let out: *u8 = sys_mmap(64)
237 let n: nx_int = nx_chem_basename(path, out)
238 if n != 16 { return 121 }
239 // Verify content "SUSPECT-2026-001"
240 if out[0] as nx_int != 0x53 { return 122 } // 'S'
241 if out[7] as nx_int != 0x2D { return 123 } // '-'
242 if out[14] as nx_int != 0x30 { return 124 } // '0'
243 if out[15] as nx_int != 0x31 { return 125 } // '1'
244 if out[16] as nx_int != 0 { return 126 } // null
245 // Test 2: no path, just filename
246 let p2: *u8 = sys_mmap(32); p2[0] = 0x73; p2[1] = 0x2E; p2[2] = 0x74; p2[3] = 0x78; p2[4] = 0x74; p2[5] = 0
247 let o2: *u8 = sys_mmap(32)
248 let n2: nx_int = nx_chem_basename(p2, o2)
249 if n2 != 1 { return 127 }
250 if o2[0] as nx_int != 0x73 { return 128 } // 's'
251 return 0
252}
253
254// =================================================================
255// L -- C5.5: CSV peak format works alongside TSV
256// "m/z,intensity,rt\n280.18,99000,742.3\n"
257// =================================================================
258func l_csv_peak_format() -> nx_int {
259 let s: *u8 = sys_mmap(64)
260 // Line 1: header "mz,intensity,rt" (non-digit start; parser skips line)
261 s[0] = 0x6D; s[1] = 0x7A
262 s[2] = 0x2C
263 s[3] = 0x69; s[4] = 0x6E; s[5] = 0x74; s[6] = 0x65; s[7] = 0x6E; s[8] = 0x73
264 s[9] = 0x2C
265 s[10] = 0x72; s[11] = 0x74
266 s[12] = 0x0A
267 // Line 2: "280.18,99000,742.3"
268 s[13] = 0x32; s[14] = 0x38; s[15] = 0x30; s[16] = 0x2E
269 s[17] = 0x31; s[18] = 0x38; s[19] = 0x2C
270 s[20] = 0x39; s[21] = 0x39; s[22] = 0x30; s[23] = 0x30; s[24] = 0x30; s[25] = 0x2C
271 s[26] = 0x37; s[27] = 0x34; s[28] = 0x32; s[29] = 0x2E; s[30] = 0x33
272 s[31] = 0x0A
273 let pl: *PeakList = nx_chem_peak_list_parse(s, 32)
274 // Header line skipped, data line parsed -> 1 peak
275 if pl.n != 1 { return 131 }
276 let p: *PeakObservation = ((pl.peaks as nx_int) + (0 * NX_PEAK_OBS_BYTES)) as *PeakObservation
277 if p.mz_q4 != 2801800 { return 132 }
278 if p.intensity_q4 != 990000000 { return 133 }
279 // rt_q3 = 7423000 / 10 = 742300 ms
280 if p.rt_q3 != 742300 { return 134 }
281 return 0
282}
283
284// =================================================================
285// END-TO-END DEMO: parse a "raw lab text" peak list + run adulterant
286// detection pipeline on it. Print verdict.
287// =================================================================
288func run_pipeline_demo() -> nx_int {
289 // Build synthetic raw text -- emulates what a lab analyst pastes
290 // from their HPLC-MS output.
291 let raw: *u8 = sys_mmap(512)
292 // Line 1: header comment
293 raw[0] = 0x23; raw[1] = 0x20; raw[2] = 0x4C; raw[3] = 0x43; raw[4] = 0x2D
294 raw[5] = 0x4D; raw[6] = 0x53; raw[7] = 0x20; raw[8] = 0x70; raw[9] = 0x65; raw[10] = 0x61; raw[11] = 0x6B; raw[12] = 0x73
295 raw[13] = 0x0A
296 // Line 2: "# mz intensity rt(sec)"
297 raw[14] = 0x23; raw[15] = 0x20; raw[16] = 0x6D; raw[17] = 0x2F; raw[18] = 0x7A; raw[19] = 0x20
298 raw[20] = 0x69; raw[21] = 0x6E; raw[22] = 0x74; raw[23] = 0x65; raw[24] = 0x6E; raw[25] = 0x73; raw[26] = 0x69; raw[27] = 0x74; raw[28] = 0x79
299 raw[29] = 0x20
300 raw[30] = 0x72; raw[31] = 0x74
301 raw[32] = 0x0A
302 // Line 3: "280.1826 99523 742.3" (sibutramine, BANNED)
303 raw[33] = 0x32; raw[34] = 0x38; raw[35] = 0x30; raw[36] = 0x2E
304 raw[37] = 0x31; raw[38] = 0x38; raw[39] = 0x32; raw[40] = 0x36
305 raw[41] = 0x20
306 raw[42] = 0x39; raw[43] = 0x39; raw[44] = 0x35; raw[45] = 0x32; raw[46] = 0x33
307 raw[47] = 0x20
308 raw[48] = 0x37; raw[49] = 0x34; raw[50] = 0x32; raw[51] = 0x2E; raw[52] = 0x33
309 raw[53] = 0x0A
310 // Line 4: "102.1274 87431 45.2" (DMAA, BANNED)
311 raw[54] = 0x31; raw[55] = 0x30; raw[56] = 0x32; raw[57] = 0x2E
312 raw[58] = 0x31; raw[59] = 0x32; raw[60] = 0x37; raw[61] = 0x34
313 raw[62] = 0x20
314 raw[63] = 0x38; raw[64] = 0x37; raw[65] = 0x34; raw[66] = 0x33; raw[67] = 0x31
315 raw[68] = 0x20
316 raw[69] = 0x34; raw[70] = 0x35; raw[71] = 0x2E; raw[72] = 0x32
317 raw[73] = 0x0A
318 // Line 5: "195.0875 150000 234.1" (caffeine, APPROVED)
319 raw[74] = 0x31; raw[75] = 0x39; raw[76] = 0x35; raw[77] = 0x2E
320 raw[78] = 0x30; raw[79] = 0x38; raw[80] = 0x37; raw[81] = 0x35
321 raw[82] = 0x20
322 raw[83] = 0x31; raw[84] = 0x35; raw[85] = 0x30; raw[86] = 0x30; raw[87] = 0x30; raw[88] = 0x30
323 raw[89] = 0x20
324 raw[90] = 0x32; raw[91] = 0x33; raw[92] = 0x34; raw[93] = 0x2E; raw[94] = 0x31
325 raw[95] = 0x0A
326 // Line 6: "999.9999 100 12.3" (unidentified -- no DB match)
327 raw[96] = 0x39; raw[97] = 0x39; raw[98] = 0x39; raw[99] = 0x2E
328 raw[100] = 0x39; raw[101] = 0x39; raw[102] = 0x39; raw[103] = 0x39
329 raw[104] = 0x20
330 raw[105] = 0x31; raw[106] = 0x30; raw[107] = 0x30
331 raw[108] = 0x20
332 raw[109] = 0x31; raw[110] = 0x32; raw[111] = 0x2E; raw[112] = 0x33
333 raw[113] = 0x0A
334 // Line 7: "166.1223 32100 89.5" (ephedrine, RESTRICTED)
335 raw[114] = 0x31; raw[115] = 0x36; raw[116] = 0x36; raw[117] = 0x2E
336 raw[118] = 0x31; raw[119] = 0x32; raw[120] = 0x32; raw[121] = 0x33
337 raw[122] = 0x20
338 raw[123] = 0x33; raw[124] = 0x32; raw[125] = 0x31; raw[126] = 0x30; raw[127] = 0x30
339 raw[128] = 0x20
340 raw[129] = 0x38; raw[130] = 0x39; raw[131] = 0x2E; raw[132] = 0x35
341 raw[133] = 0x0A
342 let total_bytes: nx_int = 134
343
344 println("" as *u8)
345 println("==================================================================" as *u8)
346 println(" END-TO-END LAB PIPELINE: text peak list -> verdict report" as *u8)
347 println("==================================================================" as *u8)
348 println(" STEP 1: parse raw text input (134 bytes simulating LC-MS output)" as *u8)
349 let pl: *PeakList = nx_chem_peak_list_parse(raw, total_bytes)
350 let _q1: i64 = print(" parsed " as *u8)
351 let _q2: i64 = print_i64(pl.n as i64)
352 let _q3: i64 = println(" peaks from raw text" as *u8)
353 println("" as *u8)
354 println(" STEP 2: lookup each peak in adulterant DB (5 ppm tolerance)" as *u8)
355 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
356 var n_banned: nx_int = 0
357 var n_restricted: nx_int = 0
358 var n_unknown: nx_int = 0
359 var pi: nx_int = 0
360 while pi < pl.n {
361 let p: *PeakObservation = ((pl.peaks as nx_int) + (pi * NX_PEAK_OBS_BYTES)) as *PeakObservation
362 let outs: *MatchResult = (sys_mmap((4 * NX_MATCH_RESULT_BYTES) as i64)) as *MatchResult
363 let nm: nx_int = nx_chem_adulterant_db_lookup_mh_plus(db, p.mz_q4, 5, outs, 4)
364 if nm == 0 {
365 let _u1: i64 = print(" m/z " as *u8)
366 let _u2: i64 = print_q4_mass(p.mz_q4)
367 let _u3: i64 = print(" rt=" as *u8)
368 let _u4: i64 = print_i64((p.rt_q3 / 1000) as i64)
369 let _u5: i64 = println("s -> NO MATCH (unidentified)" as *u8)
370 n_unknown = n_unknown + 1
371 }
372 if nm > 0 {
373 let rr: *MatchResult = ((outs as nx_int) + (0 * NX_MATCH_RESULT_BYTES)) as *MatchResult
374 let ee: *AdulterantEntry = ((db.entries as nx_int) + (rr.entry_id * NX_ADULTERANT_ENTRY_BYTES)) as *AdulterantEntry
375 let _m1: i64 = print(" m/z " as *u8)
376 let _m2: i64 = print_q4_mass(p.mz_q4)
377 let _m3: i64 = print(" rt=" as *u8)
378 let _m4: i64 = print_i64((p.rt_q3 / 1000) as i64)
379 let _m5: i64 = print("s -> " as *u8)
380 let _m6: i64 = print(nx_chem_adulterant_name(rr.entry_id))
381 let _m7: i64 = print(" [" as *u8)
382 let _m8: i64 = print(nx_chem_regulatory_str(ee.regulatory))
383 let _m9: i64 = println("]" as *u8)
384 if ee.regulatory == NX_REG_BANNED { n_banned = n_banned + 1 }
385 if ee.regulatory == NX_REG_RESTRICTED { n_restricted = n_restricted + 1 }
386 }
387 pi = pi + 1
388 }
389 println("" as *u8)
390 println(" STEP 3: verdict" as *u8)
391 let _v1: i64 = print(" banned = " as *u8)
392 let _v2: i64 = print_i64(n_banned as i64); let _v3: i64 = println("" as *u8)
393 let _v4: i64 = print(" restricted = " as *u8)
394 let _v5: i64 = print_i64(n_restricted as i64); let _v6: i64 = println("" as *u8)
395 let _v7: i64 = print(" unknown = " as *u8)
396 let _v8: i64 = print_i64(n_unknown as i64); let _v9: i64 = println("" as *u8)
397 if n_banned > 0 {
398 println("" as *u8)
399 println(" *** RECOMMENDATION: REJECT SAMPLE ***" as *u8)
400 println(" Banned-substance contamination detected. Lab analyst should:" as *u8)
401 println(" 1. Confirm result with orthogonal method (NMR/IR or LC-MS/MS)" as *u8)
402 println(" 2. Reject product lot from supplier" as *u8)
403 println(" 3. File adverse-event report with regulator if applicable" as *u8)
404 }
405 println("==================================================================" as *u8)
406 return 0
407}
408
409func main() -> nx_exit {
410 println("=== nx_chem_peak_list -- C5.0 KAT: text-format peak parser ===" as *u8)
411
412 let ra: nx_int = a_parse_decimal()
413 if ra != 0 { println("A parse_decimal FAIL" as *u8); return ra }
414 println("A parse_decimal PASS '280.1826' -> 2801826 Q4" as *u8)
415
416 let rb: nx_int = b_parse_short_decimal()
417 if rb != 0 { println("B parse_short_decimal FAIL" as *u8); return rb }
418 println("B parse_short_decimal PASS '12.5' -> 125000 (zero-padded)" as *u8)
419
420 let rc: nx_int = c_parse_int()
421 if rc != 0 { println("C parse_int FAIL" as *u8); return rc }
422 println("C parse_int PASS '100' -> 1000000 (no fractional)" as *u8)
423
424 let rd: nx_int = d_parse_no_digit()
425 if rd != 0 { println("D parse_no_digit FAIL" as *u8); return rd }
426 println("D parse_no_digit PASS non-digit returns 0 bytes consumed" as *u8)
427
428 let re: nx_int = e_parse_stops_at_space()
429 if re != 0 { println("E parse_stops_at_space FAIL" as *u8); return re }
430 println("E parse_stops_at_space PASS '100 200' parses '100' and stops at space" as *u8)
431
432 let rf: nx_int = f_parse_two_peaks()
433 if rf != 0 { println("F parse_two_peaks FAIL" as *u8); return rf }
434 println("F parse_two_peaks PASS 2 peaks from m/z+intensity text" as *u8)
435
436 let rg: nx_int = g_parse_comments_and_blanks()
437 if rg != 0 { println("G parse_comments_and_blanks FAIL" as *u8); return rg }
438 println("G parse_comments PASS '#' lines + blank lines skipped" as *u8)
439
440 let rh: nx_int = h_parse_three_col()
441 if rh != 0 { println("H parse_three_col FAIL" as *u8); return rh }
442 println("H parse_three_col PASS m/z + intensity + rt all parsed" as *u8)
443
444 let ri: nx_int = i_file_roundtrip()
445 if ri != 0 { println("I file_roundtrip FAIL" as *u8); return ri }
446 println("I file_roundtrip PASS disk write -> read -> parse round-trip" as *u8)
447
448 let rj: nx_int = j_missing_file()
449 if rj != 0 { println("J missing_file FAIL" as *u8); return rj }
450 println("J missing_file PASS absent file -> empty PeakList (graceful)" as *u8)
451
452 let rk: nx_int = k_basename()
453 if rk != 0 { println("K basename FAIL" as *u8); return rk }
454 println("K basename PASS '/tmp/X.txt' -> 'X'" as *u8)
455
456 let rl: nx_int = l_csv_peak_format()
457 if rl != 0 { println("L csv_peak_format FAIL" as *u8); return rl }
458 println("L csv_peak_format PASS CSV peak format parsed identically to TSV" as *u8)
459
460 let _demo: nx_int = run_pipeline_demo()
461
462 println("" as *u8)
463 println("=== C5.0 substrate milestone PASS ===" as *u8)
464 println(" PeakObservation struct + PeakList container" as *u8)
465 println(" nx_chem_parse_decimal_q4: bits-up decimal parser (Q4 m/z)" as *u8)
466 println(" nx_chem_peak_list_parse: text-format parser with comments + 3 cols" as *u8)
467 println(" END-TO-END: raw lab-text -> PeakList -> adulterant DB lookup -> verdict" as *u8)
468 println(" A lab analyst pastes HPLC output, gets a verdict report." as *u8)
469 println("" as *u8)
470 println(" Honest gaps: mzML XML parsing (C5.1), JCAMP-DX (C5.2), retention-time" as *u8)
471 println(" cross-validation (C5.3), adduct/charge auto-assignment" as *u8)
472 println(" (C5.3 a la CAMERA), persistent DB serialization (C4.2)." as *u8)
473 return 0
474}