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) }