code wiki / (root) / nx_doc_extract.nx

nx_doc_extract.nx source

↩ module page · 155 lines · 6296 B

1// nx_doc_extract.nx -- R1 of THE NISHI DOCUMENT-INTELLIGENCE arc: READ a document's text into STRUCTURED 2// DATA. The "consuming documents -> structured data" leg (the other leg, making documents, is nx_fin_dispute 3// / the emitters). Sovereign + deterministic: a tolerant line/field parser (no OCR/ML dependency) that turns 4// an itemized medical-bill statement into structured line-items (date, CPT code, units, billed cents) that 5// flow straight into nx_fin_audit -- so a scanned/typed bill becomes auditable structured data with ZERO 6// manual entry. This is the format-specific deterministic rung; the research corpus (knowledge/fetched/ 7// doc_*.raw: layout-analysis, IE, NER, classification) grounds the roadmap to arbitrary-layout + classified 8// extraction. A line is a line-item iff it begins with a MM/DD/YYYY date AND carries a 5-digit code AND a 9// $amount; headers/totals/blank lines are skipped by construction. No floats (exact cents), no hardware 10// writes. license_tier: ORIGINAL 11import "nx_syscalls.nx" 12 13// digit value 0..9, or -1 if not an ASCII digit. 14func dx_dig(c: i64) -> i64 { 15 if c < 0x30 { return 0 - 1 } 16 if c > 0x39 { return 0 - 1 } 17 return c - 0x30 18} 19 20// parse "MM/DD/YYYY" (length 10) at line[s..] -> YYYYMMDD, or -1. 21func dx_tok_date(line: *u8, s: i64, l: i64) -> i64 { 22 if l != 10 { return 0 - 1 } 23 if line[s + 2] != (0x2f as u8) { return 0 - 1 } 24 if line[s + 5] != (0x2f as u8) { return 0 - 1 } 25 let m1: i64 = dx_dig(line[s]); let m2: i64 = dx_dig(line[s + 1]) 26 let d1: i64 = dx_dig(line[s + 3]); let d2: i64 = dx_dig(line[s + 4]) 27 let y1: i64 = dx_dig(line[s + 6]); let y2: i64 = dx_dig(line[s + 7]) 28 let y3: i64 = dx_dig(line[s + 8]); let y4: i64 = dx_dig(line[s + 9]) 29 if m1 < 0 { return 0 - 1 } 30 if m2 < 0 { return 0 - 1 } 31 if d1 < 0 { return 0 - 1 } 32 if d2 < 0 { return 0 - 1 } 33 if y1 < 0 { return 0 - 1 } 34 if y2 < 0 { return 0 - 1 } 35 if y3 < 0 { return 0 - 1 } 36 if y4 < 0 { return 0 - 1 } 37 let mm: i64 = m1 * 10 + m2 38 let dd: i64 = d1 * 10 + d2 39 let yyyy: i64 = y1 * 1000 + y2 * 100 + y3 * 10 + y4 40 return yyyy * 10000 + mm * 100 + dd 41} 42 43// parse a pure integer token at line[s..s+l); -1 if any non-digit. 44func dx_tok_int(line: *u8, s: i64, l: i64) -> i64 { 45 if l <= 0 { return 0 - 1 } 46 var v: i64 = 0 47 var i: i64 = 0 48 while i < l { 49 let d: i64 = dx_dig(line[s + i]) 50 if d < 0 { return 0 - 1 } 51 v = v * 10 + d 52 i = i + 1 53 } 54 return v 55} 56 57// 1 if the token is exactly 5 digits (a CPT-shaped code). 58func dx_is5(line: *u8, s: i64, l: i64) -> i64 { 59 if l != 5 { return 0 } 60 var i: i64 = 0 61 while i < 5 { if dx_dig(line[s + i]) < 0 { return 0 } i = i + 1 } 62 return 1 63} 64 65// parse a "$D,DDD.CC" money token -> cents, or -1 (requires leading '$' and at least one digit). 66func dx_tok_money(line: *u8, s: i64, l: i64) -> i64 { 67 if l < 2 { return 0 - 1 } 68 if line[s] != (0x24 as u8) { return 0 - 1 } 69 var dollars: i64 = 0 70 var cc: i64 = 0 71 var sawdig: i64 = 0 72 var dot: i64 = 0 73 var ccd: i64 = 0 74 var i: i64 = 1 75 while i < l { 76 let c: i64 = line[s + i] 77 if c == 0x2e { dot = 1 } 78 else { 79 if c == 0x2c { dot = dot } 80 else { 81 let d: i64 = dx_dig(c) 82 if d < 0 { return 0 - 1 } 83 if dot == 0 { dollars = dollars * 10 + d; sawdig = 1 } 84 else { if ccd < 2 { cc = cc * 10 + d; ccd = ccd + 1 } } 85 } 86 } 87 i = i + 1 88 } 89 if sawdig == 0 { return 0 - 1 } 90 if ccd == 1 { cc = cc * 10 } 91 return dollars * 100 + cc 92} 93 94// tokenize a line into (start,len) pairs on whitespace runs; returns token count (capped). 95func dx_tokenize(line: *u8, lstart: i64, llen: i64, tstart: *i64, tlen: *i64, cap: i64) -> i64 { 96 var nt: i64 = 0 97 var i: i64 = 0 98 while i < llen { 99 let c: i64 = line[lstart + i] 100 if c == 0x20 { i = i + 1 } 101 else { if c == 0x09 { i = i + 1 } 102 else { 103 let ts: i64 = i 104 var go: i64 = 1 105 while go == 1 { 106 if i >= llen { go = 0 } 107 else { let c2: i64 = line[lstart + i]; if c2 == 0x20 { go = 0 } else { if c2 == 0x09 { go = 0 } else { i = i + 1 } } } 108 } 109 if nt < cap { tstart[nt] = lstart + ts; tlen[nt] = i - ts; nt = nt + 1 } 110 } } 111 } 112 return nt 113} 114 115// EXTRACT structured line-items from bill/statement text. Writes parallel arrays out_codes/out_dates/ 116// out_units/out_billed; returns the number of line-items found. 117func dx_extract_bill(text: *u8, n: i64, out_codes: *i64, out_dates: *i64, out_units: *i64, out_billed: *i64, cap: i64) -> i64 { 118 let tstart: *i64 = sys_mmap(8 * 128) as *i64 119 let tlen: *i64 = sys_mmap(8 * 128) as *i64 120 var cnt: i64 = 0 121 var i: i64 = 0 122 while i < n { 123 // line = [i, j) 124 var j: i64 = i 125 var adv: i64 = 1 126 while adv == 1 { if j >= n { adv = 0 } else { if text[j] == (10 as u8) { adv = 0 } else { j = j + 1 } } } 127 let llen: i64 = j - i 128 let nt: i64 = dx_tokenize(text, i, llen, tstart, tlen, 128) 129 if nt >= 3 { 130 let d: i64 = dx_tok_date(text, tstart[0], tlen[0]) 131 if d >= 0 { 132 var cpt: i64 = 0 - 1 133 var cptpos: i64 = 0 - 1 134 var t: i64 = 1 135 while t < nt { 136 if cptpos < 0 { if dx_is5(text, tstart[t], tlen[t]) == 1 { cpt = dx_tok_int(text, tstart[t], tlen[t]); cptpos = t } } 137 t = t + 1 138 } 139 var units: i64 = 0 - 1 140 if cptpos >= 0 { 141 t = cptpos + 1 142 while t < nt { if units < 0 { let v: i64 = dx_tok_int(text, tstart[t], tlen[t]); if v >= 0 { units = v } } t = t + 1 } 143 } 144 var amt: i64 = 0 - 1 145 t = 0 146 while t < nt { let mm: i64 = dx_tok_money(text, tstart[t], tlen[t]); if mm >= 0 { amt = mm } t = t + 1 } 147 if cpt >= 0 { if units >= 0 { if amt >= 0 { if cnt < cap { 148 out_dates[cnt] = d; out_codes[cnt] = cpt; out_units[cnt] = units; out_billed[cnt] = amt; cnt = cnt + 1 149 } } } } 150 } 151 } 152 i = j + 1 153 } 154 return cnt 155}