code wiki / _hdl_build / nx_survey_sentiment_gate.nx
nx_survey_sentiment_gate.nx source
↩ module page · 108 lines · 6787 B
1import "nx_gate_gn.nx"
2// nx_survey_sentiment_gate.nx -- GATE for the sovereign open-text sentiment scorer. Every compound score is
3// checked against a HAND-COMPUTED known answer (the VADER-style integer formula S/sqrt(S^2+15e6) in permille),
4// and the ADVERSARIAL controls prove the two things a naive bag-of-words gets WRONG: negation ("not helpful"
5// must score NEGATIVE, not positive) and mixed answers (must net near-neutral, not falsely strong). A live
6// aggregate over a hermetic /tmp store proves the end-to-end path. Exits 0 iff ALL pass.
7// license_tier: ORIGINAL expect_exit: 0
8import "nx_syscalls.nx"
9import "nx_survey_sentiment.nx"
10
11func gp(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
12func chk(name: *u8, got: i64, want: i64, st: *i64) -> i64 {
13 if got == want { st[0] = st[0] + 1; gp(" PASS " as *u8); gp(name); gp("\n" as *u8) }
14 else { st[1] = st[1] + 1; gp(" FAIL " as *u8); gp(name); gp(" got=" as *u8); gn(got); gp(" want=" as *u8); gn(want); gp("\n" as *u8) }
15 return 0
16}
17// score a plain string convenience wrapper
18func sc(words: *i64, kinds: *i64, vals: *i64, n: i64, s: *u8) -> i64 {
19 return sn_score(words, kinds, vals, n, s, se_slen(s))
20}
21
22func main() -> i64 {
23 let st: *i64 = sys_mmap(16) as *i64
24 st[0] = 0
25 st[1] = 0
26
27 // ---- load the embedded lexicon ----
28 let words: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
29 let kinds: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
30 let vals: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
31 let nlex: i64 = sn_load(SN_LEX, words, kinds, vals, SN_MAXLEX)
32 var lex_ok: i64 = 0
33 if nlex > 100 { if nlex < SN_MAXLEX { lex_ok = 1 } }
34 chk("V1 lexicon loads (>100 entries, under cap)" as *u8, lex_ok, 1, st)
35 // spot-check the parse of a NEGATIVE valence written "0 - 2100"
36 let bi: i64 = sn_find(words, nlex, "bad" as *u8)
37 var badneg: i64 = 0
38 if bi >= 0 { if vals[bi] == (0 - 2100) { if kinds[bi] == 119 { badneg = 1 } } }
39 chk("V1b 'bad' parsed as w / valence -2100 (the 0 - N literal path)" as *u8, badneg, 1, st)
40 let ni: i64 = sn_find(words, nlex, "not" as *u8)
41 var negkind: i64 = 0
42 if ni >= 0 { if kinds[ni] == 110 { negkind = 1 } }
43 chk("V1c 'not' parsed as negation kind" as *u8, negkind, 1, st)
44
45 // ---- KAT compounds (hand-computed: compound_permille = 1000*S / isqrt(S*S + 15000000)) ----
46 // "very helpful"(+2586) + "kind"(+2000) -> S=4586 -> 4586000/6002 = 764
47 chk("V2 'the staff were very helpful and kind' = +764" as *u8, sc(words, kinds, vals, nlex, "the staff were very helpful and kind" as *u8), 764, st)
48 // NEGATION: "not helpful"(-1480) -> S=-1480 -> -1480000/4146 = -356 (a naive scorer would say +458)
49 chk("V3 NEGATION 'not helpful at all' = -356 (flipped, not positive)" as *u8, sc(words, kinds, vals, nlex, "not helpful at all" as *u8), 0 - 356, st)
50 chk("V3b that classifies NEGATIVE" as *u8, sn_class(sc(words, kinds, vals, nlex, "not helpful at all" as *u8)), 0 - 1, st)
51 // neutral: no lexicon words
52 chk("V4 'the meeting is at noon' = 0 (neutral, no affect words)" as *u8, sc(words, kinds, vals, nlex, "the meeting is at noon" as *u8), 0, st)
53 chk("V4b classifies NEUTRAL" as *u8, sn_class(sc(words, kinds, vals, nlex, "the meeting is at noon" as *u8)), 0, st)
54 // intensified negative: "so worried"(-2586) + "stressed"(-2000) -> S=-4586 -> -764
55 chk("V5 'i am so worried and stressed about the rent' = -764" as *u8, sc(words, kinds, vals, nlex, "i am so worried and stressed about the rent" as *u8), 0 - 764, st)
56 // ---- ADVERSARY: intensifier must INCREASE magnitude ----
57 // "helpful"(2000) -> 2000000/4358 = 458 ; "very helpful"(2586) -> 2586000/4656 = 555
58 let plain: i64 = sc(words, kinds, vals, nlex, "helpful" as *u8)
59 let inten: i64 = sc(words, kinds, vals, nlex, "very helpful" as *u8)
60 chk("V6 'helpful' = +458" as *u8, plain, 458, st)
61 chk("V6b 'very helpful' = +555 (intensifier amplified)" as *u8, inten, 555, st)
62 var amp: i64 = 0
63 if inten > plain { amp = 1 }
64 chk("V6c very-helpful STRICTLY stronger than helpful" as *u8, amp, 1, st)
65 // ---- ADVERSARY: a genuinely MIXED answer nets near-neutral, not falsely strong ----
66 // "good"(1900) + "difficult"(-1800) -> S=100 -> 100000/3874 = 25 -> neutral
67 chk("V7 MIXED 'good food but a difficult month' = +25" as *u8, sc(words, kinds, vals, nlex, "good food but a difficult month" as *u8), 25, st)
68 chk("V7b mixed answer classifies NEUTRAL (not falsely strong)" as *u8, sn_class(sc(words, kinds, vals, nlex, "good food but a difficult month" as *u8)), 0, st)
69 // ---- ADVERSARY: gibberish / empty -> 0 ----
70 chk("V8 gibberish 'asdf qwer zxcv' = 0" as *u8, sc(words, kinds, vals, nlex, "asdf qwer zxcv" as *u8), 0, st)
71 chk("V8b empty string = 0" as *u8, sc(words, kinds, vals, nlex, "" as *u8), 0, st)
72
73 // ---- AGGREGATE end-to-end over a hermetic /tmp store ----
74 let pfx: *u8 = sys_mmap(64)
75 var pp: i64 = se_cat(pfx, 0, "/tmp/svys_" as *u8)
76 pp = se_catn(pfx, pp, sys_now_us())
77 pp = se_catc(pfx, pp, 45)
78 pfx[pp] = 0 as u8
79 let idb: *u8 = sys_mmap(64)
80 let spec: *u8 = "@survey feel\n@title How are things\nQ|c1|T|In a sentence, how are things going?\n" as *u8
81 chk("V9 load 1 text-question survey" as *u8, se_load(pfx, spec, se_slen(spec), idb, 64), 1, st)
82 let aq: *i64 = sys_mmap(8 * 4) as *i64
83 let av: *i64 = sys_mmap(8 * 4) as *i64
84 aq[0] = "c1" as *u8 as i64
85 av[0] = "the staff were very helpful and kind" as *u8 as i64
86 chk("V9a positive answer recorded" as *u8, se_respond(pfx, "feel" as *u8, "aa-tok0" as *u8, aq, av, 1), 1, st)
87 av[0] = "not helpful at all" as *u8 as i64
88 chk("V9b negative answer recorded" as *u8, se_respond(pfx, "feel" as *u8, "bb-tok0" as *u8, aq, av, 1), 1, st)
89 av[0] = "the meeting is at noon" as *u8 as i64
90 chk("V9c neutral answer recorded" as *u8, se_respond(pfx, "feel" as *u8, "cc-tok0" as *u8, aq, av, 1), 1, st)
91 let out: *i64 = sys_mmap(8 * 8) as *i64
92 let nsc: i64 = sn_aggregate(pfx, "feel" as *u8, "c1" as *u8, words, kinds, vals, nlex, out)
93 chk("V10 aggregate scored n=3" as *u8, nsc, 3, st)
94 chk("V10a positives=1" as *u8, out[2], 1, st)
95 chk("V10b neutrals=1" as *u8, out[3], 1, st)
96 chk("V10c negatives=1" as *u8, out[4], 1, st)
97 // mean = (764 + (-356) + 0)/3 = 408/3 = 136
98 chk("V10d mean compound = +136 permille" as *u8, out[1], 136, st)
99
100 gp("nx_survey_sentiment_gate: PASS=" as *u8)
101 gn(st[0])
102 gp(" FAIL=" as *u8)
103 gn(st[1])
104 gp("\n" as *u8)
105 if st[1] == 0 { gp("VERDICT: GREEN (VADER-style integer sentiment: lexicon + negation-flip + intensifier + compound norm, KAT-exact, adversary-guarded)\n" as *u8); return 0 }
106 gp("VERDICT: RED\n" as *u8)
107 return 1
108}