code wiki / (root) / nx_edgar_lib.nx

nx_edgar_lib.nx source

↩ module page · 306 lines · 12711 B

1// nx_edgar_lib.nx -- F986: SEC EDGAR real-data extraction. GROUNDED, NOT MADE UP. 2// 3// The sovereign fetcher reaches data.sec.gov over our own TLS (proven live: Apple StockholdersEquity 4// pulled 200 OK, current through the latest 10-Q). This parses the SEC companyconcept JSON so the 5// valuation engines run on REAL filings instead of hand-entered figures -- the operator's standing rule 6// for the whole finance arc. Every extracted fact carries its PROVENANCE: the form (10-K/10-Q) and the 7// filing date, so a number is always traceable to a specific SEC filing, never a floating assertion. 8// 9// ★FAIL-CLOSED + COMPOSES the pipeline: the no-value sentinel EDGAR_NOVAL is the SAME value nx_xbrl and 10// nx_piotroski use for a missing fact (-999999999). So an unparseable or empty response flows through as 11// "missing" -- the F-score reads INCOMPLETE, the identity check refuses -- rather than silently becoming a 12// zero that would corrupt a valuation. Fetch failure never turns into a fabricated financial fact. 13// 14// SCOPE (declared): extracts the LATEST fact value + its provenance and the entity name from the 15// companyconcept JSON. Full submissions parsing, all periods, and unit handling beyond USD are follow-ons. 16// Strings/keys are built at runtime (NishiLang literals cannot hold a double-quote). license_tier: ORIGINAL LIB. 17 18import "nx_matter_lib.nx" 19 20const EDGAR_NOVAL: i64 = 0 - 999999999 // no value found (matches nx_xbrl XB_NO_FACT / nx_piotroski PIO_NA) 21const EDGAR_Q: i64 = 34 // double-quote 22const EDGAR_COLON: i64 = 58 23 24func edgar_read(path: *u8, buf: *u8, cap: i64) -> i64 { 25 let fd: i64 = sys_openat_rd(path) 26 if fd < 0 { return 0 - 1 } 27 var n: i64 = 0 28 var go: i64 = 1 29 while go == 1 { 30 let base: i64 = buf as i64 31 let r: i64 = sys_read(fd, (base + n) as *u8, cap - n) 32 if r <= 0 { go = 0 } else { n = n + r } 33 if n >= cap { go = 0 } 34 } 35 sys_close(fd) 36 return n 37} 38 39// build the JSON key-needle "<key>": at runtime (quotes cannot be string literals). returns len. 40func edgar_mkkey(key: *u8, out: *u8) -> i64 { 41 out[0] = EDGAR_Q as u8 42 var o: i64 = 1 43 var i: i64 = 0 44 while key[i] != (0 as u8) { out[o] = key[i]; o = o + 1; i = i + 1 } 45 out[o] = EDGAR_Q as u8 46 o = o + 1 47 out[o] = EDGAR_COLON as u8 48 o = o + 1 49 out[o] = 0 as u8 50 return o 51} 52 53// index of the LAST occurrence of needle in hay[0..hl), or -1. 54func edgar_find_last(hay: *u8, hl: i64, needle: *u8, nl: i64) -> i64 { 55 var last: i64 = 0 - 1 56 var i: i64 = 0 57 while i + nl <= hl { 58 var j: i64 = 0 59 while j < nl { 60 if hay[i + j] == needle[j] { j = j + 1 } else { j = nl + 1 } 61 } 62 if j == nl { last = i } 63 i = i + 1 64 } 65 return last 66} 67 68// index of the FIRST occurrence of needle in hay[0..hl), or -1. 69func edgar_find_first(hay: *u8, hl: i64, needle: *u8, nl: i64) -> i64 { 70 var i: i64 = 0 71 while i + nl <= hl { 72 var j: i64 = 0 73 while j < nl { 74 if hay[i + j] == needle[j] { j = j + 1 } else { j = nl + 1 } 75 } 76 if j == nl { return i } 77 i = i + 1 78 } 79 return 0 - 1 80} 81 82// integer value after the LAST "<key>": in the JSON. EDGAR_NOVAL if the key is absent or not numeric. 83func edgar_num_last(json: *u8, jl: i64, key: *u8) -> i64 { 84 let nk: *u8 = sys_mmap(128) 85 let nl: i64 = edgar_mkkey(key, nk) 86 let p: i64 = edgar_find_last(json, jl, nk, nl) 87 if p < 0 { return EDGAR_NOVAL } 88 var i: i64 = p + nl 89 var neg: i64 = 0 90 if i < jl { if json[i] == (45 as u8) { neg = 1; i = i + 1 } } 91 var v: i64 = 0 92 var any: i64 = 0 93 var go: i64 = 1 94 while go == 1 { 95 if i >= jl { go = 0 } else { 96 let c: i64 = json[i] as i64 97 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; i = i + 1 } else { go = 0 } } else { go = 0 } 98 } 99 } 100 if any == 0 { return EDGAR_NOVAL } 101 if neg == 1 { return 0 - v } 102 return v 103} 104 105// string value after a "<key>":" ; helper takes the located index p (or -1). returns len or -1. 106func edgar_str_at(json: *u8, jl: i64, p: i64, nl: i64, out: *u8) -> i64 { 107 if p < 0 { return 0 - 1 } 108 var i: i64 = p + nl 109 if i >= jl { return 0 - 1 } 110 if json[i] != (EDGAR_Q as u8) { return 0 - 1 } 111 i = i + 1 112 var t: i64 = 0 113 var go: i64 = 1 114 while go == 1 { 115 if i >= jl { go = 0 } else { 116 if json[i] == (EDGAR_Q as u8) { go = 0 } else { out[t] = json[i]; t = t + 1; i = i + 1 } 117 } 118 } 119 out[t] = 0 as u8 120 return t 121} 122 123func edgar_str_last(json: *u8, jl: i64, key: *u8, out: *u8) -> i64 { 124 let nk: *u8 = sys_mmap(128) 125 let nl: i64 = edgar_mkkey(key, nk) 126 return edgar_str_at(json, jl, edgar_find_last(json, jl, nk, nl), nl, out) 127} 128func edgar_str_first(json: *u8, jl: i64, key: *u8, out: *u8) -> i64 { 129 let nk: *u8 = sys_mmap(128) 130 let nl: i64 = edgar_mkkey(key, nk) 131 return edgar_str_at(json, jl, edgar_find_first(json, jl, nk, nl), nl, out) 132} 133 134// ★convenience accessors over a companyconcept JSON. 135// ---- DECLARED OUTBOUND IDENTITY (2026-08-15) ------------------------------------------------- 136// ONE definition, shared by every EDGAR consumer. Authored inside nx_fin_edgar_fetch first; a second 137// copy in the submissions organ would be the duplicate-ruler defect, so it lives here instead. 138// FAIL-CLOSED: absent/empty conf returns 0 and the caller sends NO User-Agent rather than a 139// placeholder. A fabricated contact to a federal service is worse than an omission. 140func edgar_load_ua(dst: *u8, cap: i64) -> i64 { 141 let lb: *i64 = sys_mmap(16) as *i64 142 lb[0] = 0 143 let b: *u8 = sys_read_file("knowledge/fetch_identity.conf" as *u8, lb) 144 let n: i64 = lb[0] 145 if (b as i64) == 0 { return 0 } 146 if n <= 0 { return 0 } 147 var i: i64 = 0 148 while i < n { 149 let ls: i64 = i 150 var le: i64 = ls 151 var go: i64 = 1 152 while go == 1 { if le >= n { go = 0 } else { if b[le] == (10 as u8) { go = 0 } else { le = le + 1 } } } 153 i = le + 1 154 if le > ls { if b[ls] != (35 as u8) { 155 var t: i64 = 0 - 1 156 var j: i64 = ls 157 while j < le { if b[j] == (9 as u8) { if t < 0 { t = j } } j = j + 1 } 158 if t > ls { 159 let nl: i64 = t - ls 160 if nl == 2 { if b[ls] == (117 as u8) { if b[ls + 1] == (97 as u8) { 161 let vl: i64 = le - t - 1 162 if vl > 0 { if vl < cap - 1 { 163 var c: i64 = 0 164 while c < vl { dst[c] = b[t + 1 + c]; c = c + 1 } 165 dst[vl] = 0 as u8 166 return vl 167 } } 168 } } } 169 } 170 } } 171 } 172 return 0 173} 174 175// Build "User-Agent: <ua>\r\n" into xh; returns bytes written (0 = no identity declared, send none). 176// Callers pass the result straight to nx_https_fetch_follow_hdr_best, where xhdr_len=0 is 177// byte-identical to the plain fetch -- so an undeclared identity costs nothing and changes nothing. 178func edgar_ua_header(xh: *u8, cap: i64) -> i64 { 179 let ua: *u8 = sys_mmap(256) 180 let ual: i64 = edgar_load_ua(ua, 256) 181 if ual <= 0 { xh[0] = 0 as u8; return 0 } 182 let lit: *u8 = "User-Agent: " as *u8 183 var o: i64 = 0 184 while lit[o] != (0 as u8) { xh[o] = lit[o]; o = o + 1 } 185 if o + ual + 3 >= cap { xh[0] = 0 as u8; return 0 } 186 var c: i64 = 0 187 while c < ual { xh[o + c] = ua[c]; c = c + 1 } 188 o = o + ual 189 xh[o] = 13 as u8; xh[o + 1] = 10 as u8; xh[o + 2] = 0 as u8 190 return o + 2 191} 192 193// ---- FULL SUBMISSIONS PARSING (2026-08-15) --------------------------------------------------- 194// This file's own SCOPE note named "Full submissions parsing" as a follow-on. It is the binding that 195// turns issuer-keyed facts into a PERSON's career: SEC assigns a CIK to INDIVIDUALS as well as 196// companies, and data.sec.gov/submissions/CIK##########.json enumerates that filer's history. 197// A person filing Forms 3/4/5 is an insider AT an issuer, so the form histogram alone separates a 198// person-CIK from a company-CIK without fetching a single filing. 199// Array bounds for "key":[ ... ]. box[0]=first byte AFTER '[', box[1]=index OF ']'. Returns 1 on 200// success, 0 when the key or its array is absent -- never a guessed span. 201func edgar_arr_bounds(json: *u8, jl: i64, key: *u8, box: *i64) -> i64 { 202 let k: *u8 = sys_mmap(64) 203 let kl: i64 = edgar_mkkey(key, k) 204 let p: i64 = edgar_find_first(json, jl, k, kl) 205 if p < 0 { return 0 } 206 var i: i64 = p + kl 207 var go: i64 = 1 208 var s: i64 = 0 - 1 209 while go == 1 { 210 if i >= jl { go = 0 } else { 211 let c: i64 = json[i] as i64 212 if c == 91 { s = i + 1; go = 0 } else { if c == 32 { i = i + 1 } else { if c == 58 { i = i + 1 } else { go = 0 } } } 213 } 214 } 215 if s < 0 { return 0 } 216 var e: i64 = s 217 var g2: i64 = 1 218 while g2 == 1 { 219 if e >= jl { g2 = 0 } else { if (json[e] as i64) == 93 { g2 = 0 } else { e = e + 1 } } 220 } 221 if e >= jl { return 0 } 222 box[0] = s 223 box[1] = e 224 return 1 225} 226// The idx-th quoted string inside json[s..e), 0-based. Returns its length, or -1 past the end. 227// WHY POSITIONAL ACCESS IS REQUIRED: the submissions arrays are PARALLEL -- form[i], 228// accessionNumber[i] and primaryDocument[i] all describe the SAME filing. Counting alone can say a 229// filer has 138 Form 4s; only pairing by index can say WHICH filing each one is, and that is the 230// difference between a histogram and a career graph. 231func edgar_arr_nth(json: *u8, s: i64, e: i64, idx: i64, out: *u8) -> i64 { 232 var cnt: i64 = 0 233 var i: i64 = s 234 while i < e { 235 if (json[i] as i64) == 34 { 236 let a: i64 = i + 1 237 var j: i64 = a 238 var go: i64 = 1 239 while go == 1 { if j >= e { go = 0 } else { if (json[j] as i64) == 34 { go = 0 } else { j = j + 1 } } } 240 if j >= e { return 0 - 1 } 241 if cnt == idx { 242 var t: i64 = 0 243 var k: i64 = a 244 while k < j { out[t] = json[k]; t = t + 1; k = k + 1 } 245 out[t] = 0 as u8 246 return t 247 } 248 cnt = cnt + 1 249 i = j + 1 250 } else { i = i + 1 } 251 } 252 return 0 - 1 253} 254// Index of the first array element exactly equal to val, else -1. Composes edgar_arr_nth so there is 255// ONE array walker. Exact match, not substring: "4" must not match "4/A" (an AMENDED Form 4 is a 256// different filing) and "3" must not match "3/A". 257func edgar_arr_index_of(json: *u8, s: i64, e: i64, val: *u8) -> i64 { 258 let tmp: *u8 = sys_mmap(256) 259 var idx: i64 = 0 260 var go: i64 = 1 261 while go == 1 { 262 let l: i64 = edgar_arr_nth(json, s, e, idx, tmp) 263 if l < 0 { go = 0 } else { 264 var same: i64 = 1 265 var k: i64 = 0 266 while val[k] != (0 as u8) { if tmp[k] != val[k] { same = 0 } k = k + 1 } 267 if same == 1 { if tmp[k] == (0 as u8) { return idx } } 268 idx = idx + 1 269 } 270 } 271 return 0 - 1 272} 273// count of the exact quoted string val within json[s..e). Used for the FORM HISTOGRAM: how many 274// Form 4s, Form 3s, 10-Ks this filer has. Counting is the whole point -- a filer's KIND is a 275// distribution over form types, not a single latest value. 276// quoted VALUE "v" -- deliberately NOT edgar_mkkey, which appends a COLON because it builds a KEY. 277// An array element is "4", never "4": -- reusing mkkey here would match nothing and the counter would 278// read 0 for every form type forever: a fabricated constant wearing the shape of a measurement. 279func edgar_mkval(val: *u8, out: *u8) -> i64 { 280 out[0] = EDGAR_Q as u8 281 var o: i64 = 1 282 var i: i64 = 0 283 while val[i] != (0 as u8) { out[o] = val[i]; o = o + 1; i = i + 1 } 284 out[o] = EDGAR_Q as u8 285 o = o + 1 286 out[o] = 0 as u8 287 return o 288} 289func edgar_arr_count_val(json: *u8, s: i64, e: i64, val: *u8) -> i64 { 290 let q: *u8 = sys_mmap(64) 291 let ql: i64 = edgar_mkval(val, q) 292 if ql <= 0 { return 0 } 293 var n: i64 = 0 294 var i: i64 = s 295 while i + ql <= e { 296 var m: i64 = 1 297 var j: i64 = 0 298 while j < ql { if json[i + j] != q[j] { m = 0; j = ql } else { j = j + 1 } } 299 if m == 1 { n = n + 1; i = i + ql } else { i = i + 1 } 300 } 301 return n 302} 303func edgar_latest_value(json: *u8, jl: i64) -> i64 { return edgar_num_last(json, jl, "val" as *u8) } 304func edgar_entity(json: *u8, jl: i64, out: *u8) -> i64 { return edgar_str_first(json, jl, "entityName" as *u8, out) } 305func edgar_latest_form(json: *u8, jl: i64, out: *u8) -> i64 { return edgar_str_last(json, jl, "form" as *u8, out) } 306func edgar_latest_filed(json: *u8, jl: i64, out: *u8) -> i64 { return edgar_str_last(json, jl, "filed" as *u8, out) }