code wiki / (root) / nx_chem_peak_list_test.nx

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}