code wiki / _hdl_build / nx_claim_extract.nx
nx_claim_extract.nx source
↩ module page · 133 lines · 5759 B
1// nx_claim_extract.nx -- MECHANIZED CLAIM EXTRACTION FROM RAW PROSE (closes the one axis where the
2// Claude deep-research still led: it used an LLM to pull claims from prose; this is the deterministic,
3// REPRODUCIBLE analog). A research claim, mechanically, is a fact-bearing sentence: one that carries a
4// MEASURED QUANTITY (a number adjacent to a unit -- "10 um gate", "952 transistors", "20 cm2/Vs",
5// "94% yield", "8 MHz"). The extractor segments prose into sentences, flags the fact-bearing ones, and
6// pulls the value+unit -- turning free prose into structured claim candidates that rs_scan +
7// rx_contested then corroborate/verify. Same input bytes -> same claims, every run (the LLM can't
8// promise that).
9// HONEST RESIDUAL (flagged, not hidden): this extracts MEASURABLE facts; nuanced/implicit/qualitative
10// claims ("X has largely supplanted Y") still need richer NLP -- the narrowed remaining gap.
11// LAWS: struct-free, integer-only. license_tier: ORIGINAL
12import "nx_syscalls.nx"
13
14func ce_is_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
15
16func ce_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
17
18// does NUL-terminated needle occur at buf[pos] (within [0,n))? (case-sensitive)
19func ce_match_at(buf: *u8, pos: i64, n: i64, needle: *u8) -> i64 {
20 var k: i64 = 0
21 while needle[k] != (0 as u8) {
22 if pos + k >= n { return 0 }
23 if (buf[pos+k] & 0xff) != (needle[k] & 0xff) { return 0 }
24 k = k + 1
25 }
26 return 1
27}
28
29// is a UNIT token present anywhere in buf[start,end)? returns 1/0. The unit vocabulary is the
30// measurement surface real research uses (calibrated to our corpora, extendable, data-not-code-ideal).
31func ce_has_unit(buf: *u8, start: i64, end: i64) -> i64 {
32 var i: i64 = start
33 while i < end {
34 if ce_match_at(buf, i, end, "um" as *u8) == 1 { return 1 }
35 if ce_match_at(buf, i, end, "nm" as *u8) == 1 { return 1 }
36 if ce_match_at(buf, i, end, "mm" as *u8) == 1 { return 1 }
37 if ce_match_at(buf, i, end, "%" as *u8) == 1 { return 1 }
38 if ce_match_at(buf, i, end, "MHz" as *u8) == 1 { return 1 }
39 if ce_match_at(buf, i, end, "GHz" as *u8) == 1 { return 1 }
40 if ce_match_at(buf, i, end, "cm" as *u8) == 1 { return 1 }
41 if ce_match_at(buf, i, end, "transistor" as *u8) == 1 { return 1 }
42 if ce_match_at(buf, i, end, "micron" as *u8) == 1 { return 1 }
43 if ce_match_at(buf, i, end, "fL" as *u8) == 1 { return 1 }
44 if ce_match_at(buf, i, end, "yield" as *u8) == 1 { return 1 }
45 if ce_match_at(buf, i, end, "node" as *u8) == 1 { return 1 }
46 i = i + 1
47 }
48 return 0
49}
50
51// is there a digit in buf[start,end)?
52func ce_has_digit(buf: *u8, start: i64, end: i64) -> i64 {
53 var i: i64 = start
54 while i < end { if ce_is_digit(buf[i] as i64) == 1 { return 1 } i = i + 1 }
55 return 0
56}
57
58// a sentence is FACT-BEARING iff it carries a number AND a unit (a measured quantity = a claim)
59func ce_is_fact_sentence(buf: *u8, start: i64, end: i64) -> i64 {
60 if ce_has_digit(buf, start, end) == 0 { return 0 }
61 if ce_has_unit(buf, start, end) == 0 { return 0 }
62 return 1
63}
64
65// is c an ASCII letter?
66func ce_is_alpha(c: i64) -> i64 {
67 if c >= 97 { if c <= 122 { return 1 } }
68 if c >= 65 { if c <= 90 { return 1 } }
69 return 0
70}
71
72// first MEASUREMENT value in buf[start,end) (-1 if none). A measurement number is NOT glued to a
73// preceding letter -- that excludes name digits like the "2" in "Z2", keeping "952".
74func ce_first_value(buf: *u8, start: i64, end: i64) -> i64 {
75 var i: i64 = start
76 while i < end {
77 if ce_is_digit(buf[i] as i64) == 1 {
78 var preceded_by_letter: i64 = 0
79 if i > start { if ce_is_alpha(buf[i-1] as i64) == 1 { preceded_by_letter = 1 } }
80 if preceded_by_letter == 0 {
81 var v: i64 = 0
82 while i < end { if ce_is_digit(buf[i] as i64) == 1 { v = v*10 + (buf[i] as i64 - 48); i = i + 1 } else { break } }
83 return v
84 }
85 // skip this glued-to-letter digit run (advance past it), then keep scanning
86 while i < end { if ce_is_digit(buf[i] as i64) == 1 { i = i + 1 } else { break } }
87 } else {
88 i = i + 1
89 }
90 }
91 return 0 - 1
92}
93
94// scan prose into sentences (split on '.' or newline) and tally claims. out[0]=sentences
95// out[1]=fact_sentences(=claims_extracted) out[2]=first_claim_value. returns claims_extracted.
96func ce_extract(buf: *u8, n: i64, out: *i64) -> i64 {
97 var sentences: i64 = 0
98 var facts: i64 = 0
99 var first_val: i64 = 0 - 1
100 var start: i64 = 0
101 var i: i64 = 0
102 while i <= n {
103 var is_end: i64 = 0
104 if i == n { is_end = 1 }
105 if i < n { let c: i64 = buf[i] as i64; if c == 46 { is_end = 1 } if c == 10 { is_end = 1 } } // '.' or '\n'
106 if is_end == 1 {
107 if i > start {
108 sentences = sentences + 1
109 if ce_is_fact_sentence(buf, start, i) == 1 {
110 facts = facts + 1
111 if first_val < 0 { first_val = ce_first_value(buf, start, i) }
112 }
113 }
114 start = i + 1
115 }
116 i = i + 1
117 }
118 out[0] = sentences
119 out[1] = facts
120 out[2] = first_val
121 return facts
122}
123
124// read a file into buf; returns length (0 on fail)
125func ce_read_file(path: *u8, buf: *u8, cap: i64) -> i64 {
126 let fd: i64 = sys_openat_rd(path)
127 if fd < 0 { return 0 }
128 var total: i64 = 0
129 var r: i64 = sys_read(fd, buf as *u8, cap)
130 while r > 0 { total = total + r; if total >= cap { r = 0 } else { r = sys_read(fd, (buf as i64 + total) as *u8, cap - total) } }
131 sys_close(fd)
132 return total
133}