code wiki / (root) / nx_medbill_recon.nx

nx_medbill_recon.nx source

↩ module page · 275 lines · 10395 B

1// nx_medbill_recon.nx -- M2 of the MEDICAL BILLING ladder (/compare/medbilling): parse an ITEMIZED BILL 2// or EOB text document into structured claim lines, then RECONCILE bill-vs-EOB with exact no-float cents. 3// A claim line = a text line carrying ALL THREE of: a date, a 5-char procedure code (CPT/HCPCS shape, 4// >=4 digits), and at least one $-money token -- header/footer prose never qualifies (an account number 5// alone is not a claim line). Money REQUIRES the $ prefix (a bare year is never money). EOB lines carry 6// billed/allowed/patient columns (first/second/last money); a bill line's charge = its LAST money. 7// RECON is a CONSUMING matcher (code+date, each EOB line matches at most once -- so a duplicate bill line 8// has nothing left to match = NOT-ON-EOB) with flags: 1=NOT_ON_EOB (amount=charge), 2=BILLED_MISMATCH 9// (|bill - EOB billed|), 3=ABOVE_ALLOWED (charge - allowed). It reports; it never fabricates a line and 10// never auto-acts (findings feed the fail-safe R8 dispute letter). license_tier: ORIGINAL 11import "nx_syscalls.nx" 12const K_MAGIC_1900: i64 = 1900 13const K_MAGIC_10000: i64 = 10000 14 15// ---- ctx layout (i64 slots): 0=n 1=codes-arena(16B/entry) 2=dates 3=units 4=m0 5=m1 6=m2 7=nmoney 8=cap 16func mbr_ctx_new(cap: i64) -> *i64 { 17 let ctx: *i64 = sys_mmap(8 * 16) as *i64 18 ctx[0] = 0 19 let arena: *u8 = sys_mmap(16 * cap) 20 ctx[1] = arena as i64 21 ctx[2] = sys_mmap(8 * cap) 22 ctx[3] = sys_mmap(8 * cap) 23 ctx[4] = sys_mmap(8 * cap) 24 ctx[5] = sys_mmap(8 * cap) 25 ctx[6] = sys_mmap(8 * cap) 26 ctx[7] = sys_mmap(8 * cap) 27 ctx[8] = cap 28 return ctx 29} 30func mbr_code_at(ctx: *i64, i: i64) -> *u8 { return ((ctx[1] as i64) + 16 * i) as *u8 } 31func mbr_geti(ctx: *i64, slot: i64, i: i64) -> i64 { let p: *i64 = ctx[slot] as *i64; return p[i] } 32 33func mbr_is_digit(c: u8) -> i64 { if (c as i64) >= 48 { if (c as i64) <= 57 { return 1 } } return 0 } 34func mbr_is_upper(c: u8) -> i64 { if (c as i64) >= 65 { if (c as i64) <= 90 { return 1 } } return 0 } 35 36// money token: $D[,DDD]*[.CC] -> cents, or -1 37func mbr_tok_money(t: *u8, ts: i64, te: i64) -> i64 { 38 if (t[ts] as i64) != 36 { return 0 - 1 } 39 var v: i64 = 0 40 var seen: i64 = 0 41 var i: i64 = ts + 1 42 while i < te { 43 let c: i64 = t[i] as i64 44 if c == 44 { i = i + 1 } else { 45 if c == 46 { 46 if te - i != 3 { return 0 - 1 } 47 if mbr_is_digit(t[i + 1]) == 0 { return 0 - 1 } 48 if mbr_is_digit(t[i + 2]) == 0 { return 0 - 1 } 49 let cc: i64 = ((t[i + 1] as i64) - 48) * 10 + ((t[i + 2] as i64) - 48) 50 if seen == 0 { return 0 - 1 } 51 return v * 100 + cc 52 } 53 if mbr_is_digit(t[i]) == 0 { return 0 - 1 } 54 v = v * 10 + (c - 48) 55 seen = 1 56 i = i + 1 57 } 58 } 59 if seen == 0 { return 0 - 1 } 60 return v * 100 61} 62 63// date token: M/D/YYYY or MM/DD/YYYY or YYYY-MM-DD -> yyyymmdd, or -1 64func mbr_tok_date(t: *u8, ts: i64, te: i64) -> i64 { 65 var p1: i64 = 0 - 1 66 var p2: i64 = 0 - 1 67 var sep: i64 = 0 68 var i: i64 = ts 69 while i < te { 70 let c: i64 = t[i] as i64 71 if c == 47 { if p1 < 0 { p1 = i } else { if p2 < 0 { p2 = i } } sep = 47 } 72 if c == 45 { if p1 < 0 { p1 = i } else { if p2 < 0 { p2 = i } } sep = 45 } 73 i = i + 1 74 } 75 if p1 < 0 { return 0 - 1 } 76 if p2 < 0 { return 0 - 1 } 77 var a: i64 = 0 78 i = ts 79 while i < p1 { if mbr_is_digit(t[i]) == 0 { return 0 - 1 } a = a * 10 + ((t[i] as i64) - 48); i = i + 1 } 80 var b: i64 = 0 81 i = p1 + 1 82 while i < p2 { if mbr_is_digit(t[i]) == 0 { return 0 - 1 } b = b * 10 + ((t[i] as i64) - 48); i = i + 1 } 83 var c3: i64 = 0 84 i = p2 + 1 85 while i < te { if mbr_is_digit(t[i]) == 0 { return 0 - 1 } c3 = c3 * 10 + ((t[i] as i64) - 48); i = i + 1 } 86 if sep == 47 { 87 if a < 1 { return 0 - 1 } 88 if a > 12 { return 0 - 1 } 89 if b < 1 { return 0 - 1 } 90 if b > 31 { return 0 - 1 } 91 if c3 < K_MAGIC_1900 { return 0 - 1 } 92 return c3 * K_MAGIC_10000 + a * 100 + b 93 } 94 if a < K_MAGIC_1900 { return 0 - 1 } 95 if b < 1 { return 0 - 1 } 96 if b > 12 { return 0 - 1 } 97 if c3 < 1 { return 0 - 1 } 98 if c3 > 31 { return 0 - 1 } 99 return a * K_MAGIC_10000 + b * 100 + c3 100} 101 102// units token: x8 / X8 -> count, or -1 103func mbr_tok_units(t: *u8, ts: i64, te: i64) -> i64 { 104 let c0: i64 = t[ts] as i64 105 if c0 != 120 { if c0 != 88 { return 0 - 1 } } 106 if te - ts < 2 { return 0 - 1 } 107 var v: i64 = 0 108 var i: i64 = ts + 1 109 while i < te { if mbr_is_digit(t[i]) == 0 { return 0 - 1 } v = v * 10 + ((t[i] as i64) - 48); i = i + 1 } 110 if v < 1 { return 0 - 1 } 111 return v 112} 113 114// procedure-code shape: exactly 5 chars, each digit or A-Z, at least 4 digits 115func mbr_tok_code(t: *u8, ts: i64, te: i64) -> i64 { 116 if te - ts != 5 { return 0 } 117 var digits: i64 = 0 118 var i: i64 = ts 119 while i < te { 120 let d: i64 = mbr_is_digit(t[i]) 121 if d == 1 { digits = digits + 1 } else { 122 if mbr_is_upper(t[i]) == 0 { return 0 } 123 } 124 i = i + 1 125 } 126 if digits >= 4 { return 1 } 127 return 0 128} 129 130// parse a whole document into ctx; returns number of claim lines found 131func mbr_parse(text: *u8, n: i64, ctx: *i64) -> i64 { 132 var i: i64 = 0 133 while i < n { 134 var e: i64 = i 135 var stop: i64 = 0 136 while stop == 0 { 137 if e >= n { stop = 1 } else { 138 if text[e] == (10 as u8) { stop = 1 } else { e = e + 1 } 139 } 140 } 141 // ---- scan tokens of line [i..e) 142 var codeS: i64 = 0 - 1 143 var codeE: i64 = 0 - 1 144 var date: i64 = 0 - 1 145 var units: i64 = 1 146 var m0: i64 = 0 - 1 147 var m1: i64 = 0 - 1 148 var m2: i64 = 0 - 1 149 var nm: i64 = 0 150 var p: i64 = i 151 while p < e { 152 var sk: i64 = 1 153 while sk == 1 { 154 if p >= e { sk = 0 } else { 155 let cw: i64 = text[p] as i64 156 if cw == 32 { p = p + 1 } else { if cw == 9 { p = p + 1 } else { if cw == 13 { p = p + 1 } else { sk = 0 } } } 157 } 158 } 159 if p < e { 160 var q: i64 = p 161 var run: i64 = 1 162 while run == 1 { 163 if q >= e { run = 0 } else { 164 let cq: i64 = text[q] as i64 165 if cq == 32 { run = 0 } else { if cq == 9 { run = 0 } else { if cq == 13 { run = 0 } else { q = q + 1 } } } 166 } 167 } 168 let mv: i64 = mbr_tok_money(text, p, q) 169 if mv >= 0 { 170 if nm == 0 { m0 = mv } else { if nm == 1 { m1 = mv } } 171 m2 = mv 172 nm = nm + 1 173 } else { 174 let dv: i64 = mbr_tok_date(text, p, q) 175 if dv > 0 { if date < 0 { date = dv } } else { 176 let uv: i64 = mbr_tok_units(text, p, q) 177 if uv > 0 { units = uv } else { 178 if codeS < 0 { if mbr_tok_code(text, p, q) == 1 { codeS = p; codeE = q } } 179 } 180 } 181 } 182 p = q 183 } 184 } 185 // ---- claim line iff code AND date AND money 186 if codeS >= 0 { if date > 0 { if nm >= 1 { 187 let k: i64 = ctx[0] 188 if k < ctx[8] { 189 let dst: *u8 = mbr_code_at(ctx, k) 190 var w: i64 = 0 191 var r: i64 = codeS 192 while r < codeE { dst[w] = text[r]; w = w + 1; r = r + 1 } 193 dst[w] = 0 as u8 194 let dp: *i64 = ctx[2] as *i64 195 dp[k] = date 196 let up: *i64 = ctx[3] as *i64 197 up[k] = units 198 let a0: *i64 = ctx[4] as *i64 199 a0[k] = m0 200 let a1: *i64 = ctx[5] as *i64 201 a1[k] = m1 202 let a2: *i64 = ctx[6] as *i64 203 a2[k] = m2 204 let np: *i64 = ctx[7] as *i64 205 np[k] = nm 206 ctx[0] = k + 1 207 } 208 } } } 209 i = e + 1 210 } 211 return ctx[0] 212} 213 214func mbr_code_eq(a: *u8, b: *u8) -> i64 { 215 var i: i64 = 0 216 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 217 if b[i] == (0 as u8) { return 1 } 218 return 0 219} 220 221// RECONCILE bill ctx vs EOB ctx. Consuming code+date matcher. Flags (parallel out arrays): 222// type 1 NOT_ON_EOB amount = bill charge (last money on the bill line) 223// type 2 BILLED_MISMATCH amount = |bill charge - EOB billed(first money)| 224// type 3 ABOVE_ALLOWED amount = bill charge - EOB allowed(second money) (needs EOB >=2 moneys) 225// Returns flag count. Zero flags on a fully-supported bill (the neg-control). 226func mbr_recon(bctx: *i64, ectx: *i64, ftyp: *i64, fidx: *i64, famt: *i64) -> i64 { 227 let bn: i64 = bctx[0] 228 let en: i64 = ectx[0] 229 let used: *i64 = sys_mmap(8 * (en + 1)) as *i64 230 var nf: i64 = 0 231 var i: i64 = 0 232 while i < bn { 233 let bcode: *u8 = mbr_code_at(bctx, i) 234 let bdate: i64 = mbr_geti(bctx, 2, i) 235 let bill: i64 = mbr_geti(bctx, 6, i) 236 var hit: i64 = 0 - 1 237 var j: i64 = 0 238 while j < en { 239 if used[j] == 0 { 240 if mbr_geti(ectx, 2, j) == bdate { 241 let ecode: *u8 = mbr_code_at(ectx, j) 242 if mbr_code_eq(bcode, ecode) == 1 { used[j] = 1; hit = j; j = en } 243 } 244 } 245 j = j + 1 246 } 247 if hit < 0 { 248 ftyp[nf] = 1 249 fidx[nf] = i 250 famt[nf] = bill 251 nf = nf + 1 252 } else { 253 let eb: i64 = mbr_geti(ectx, 4, hit) 254 if eb >= 0 { if bill != eb { 255 var d: i64 = bill - eb 256 if d < 0 { d = 0 - d } 257 ftyp[nf] = 2 258 fidx[nf] = i 259 famt[nf] = d 260 nf = nf + 1 261 } } 262 if mbr_geti(ectx, 7, hit) >= 2 { 263 let allow: i64 = mbr_geti(ectx, 5, hit) 264 if allow >= 0 { if bill > allow { 265 ftyp[nf] = 3 266 fidx[nf] = i 267 famt[nf] = bill - allow 268 nf = nf + 1 269 } } 270 } 271 } 272 i = i + 1 273 } 274 return nf 275}