code wiki / (root) / nx_facet_filter.nx

nx_facet_filter.nx source

↩ module page · 117 lines · 6065 B

1// nx_facet_filter.nx -- FACETED SEARCH for the onsite-search arc (closes its "facet" worklist gap). The 2// post-ranking faceting layer: given BM25-ranked hit doc-ids + a per-doc Dublin-Core facet schema (subject / 3// type / creator / date -- dc:subject, dc:type, dc:creator, dc:date), (1) FILTER the hits by selected facet 4// values (AND across facets) and (2) return per-facet-value COUNTS (the facet UI: "Subject: law (4), ..."). 5// Research-driven: faceted search (the feature) + Dublin Core DCMI Terms (the facet vocabulary), both 6// sovereign-fetched + cited (ledger #24). Integer facet-value ids (strings interned upstream). Reusable: wire 7// the BM25 hits from nx_onsite_search into ff_filter/ff_count. Sovereign (nx_cc->nxasm). license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10const FF_NDOCS: i64 = 12 11const FF_NFACET: i64 = 4 // DC facets: 0=subject 1=type 2=creator 3=date 12const FF_NOSEL: i64 = 0 - 1 // sentinel: facet not selected (no filter on it) 13 14func ff_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 15func ff_putn(v: i64) -> i64 { 16 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 17 var m: i64 = v 18 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 19 let d: *u8 = sys_mmap(24); var k: i64 = 0 20 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 21 var j: i64 = k - 1 22 while j >= 0 { sys_write(1, ((d as i64)+j) as *u8, 1); j = j - 1 } 23 return 0 24} 25 26// vals[f*NDOCS + d] = doc d's value-id for facet f. 27// keep hits where, for EVERY selected facet f (sel[f] != FF_NOSEL), vals[f][doc] == sel[f]. out gets the kept ids. 28func ff_filter(hits: *i64, n: i64, vals: *i64, sel: *i64, out: *i64) -> i64 { 29 var k: i64 = 0 30 var i: i64 = 0 31 while i < n { 32 let d: i64 = hits[i] 33 var pass: i64 = 1 34 var f: i64 = 0 35 while f < FF_NFACET { 36 if sel[f] != FF_NOSEL { if vals[f * FF_NDOCS + d] != sel[f] { pass = 0 } } 37 f = f + 1 38 } 39 if pass == 1 { out[k] = d; k = k + 1 } 40 i = i + 1 41 } 42 return k 43} 44 45// for facet f, tally the hit set per value-id into counts[] (pre-zeroed, size >= max value-id+1) 46func ff_count(hits: *i64, n: i64, vals: *i64, f: i64, counts: *i64) -> i64 { 47 var i: i64 = 0 48 while i < n { let v: i64 = vals[f * FF_NDOCS + hits[i]]; counts[v] = counts[v] + 1; i = i + 1 } 49 return 0 50} 51 52func main() -> i64 { 53 // per-doc DC facet values (synthetic, deterministic): subject=d%3, type=d%2, creator=d%4, date=d%5 54 let vals: *i64 = sys_mmap(FF_NFACET * FF_NDOCS * 8) as *i64 55 var d: i64 = 0 56 while d < FF_NDOCS { 57 vals[0 * FF_NDOCS + d] = d % 3 // subject in {0,1,2} 58 vals[1 * FF_NDOCS + d] = d % 2 // type in {0,1} 59 vals[2 * FF_NDOCS + d] = d % 4 // creator in {0..3} 60 vals[3 * FF_NDOCS + d] = d % 5 // date in {0..4} 61 d = d + 1 62 } 63 // BM25 hit set = all docs (in real use, the ranked results from nx_onsite_search) 64 let hits: *i64 = sys_mmap(FF_NDOCS * 8) as *i64 65 var i: i64 = 0 66 while i < FF_NDOCS { hits[i] = i; i = i + 1 } 67 let nh: i64 = FF_NDOCS 68 69 ff_puts("=== nx_facet_filter: faceted search over Dublin-Core facets (subject/type/creator/date) ===\n") 70 71 // facet COUNTS for subject (the facet UI distribution) 72 let cnt: *i64 = sys_mmap(8 * 8) as *i64 73 var z: i64 = 0 74 while z < 8 { cnt[z] = 0; z = z + 1 } 75 ff_count(hits, nh, vals, 0, cnt) 76 ff_puts(" facet 'subject' counts over "); ff_putn(nh); ff_puts(" hits: v0="); ff_putn(cnt[0]) 77 ff_puts(" v1="); ff_putn(cnt[1]); ff_puts(" v2="); ff_putn(cnt[2]); ff_puts("\n") 78 79 // FILTER: subject=1 (no other facet selected) 80 let sel: *i64 = sys_mmap(FF_NFACET * 8) as *i64 81 sel[0] = 1; sel[1] = FF_NOSEL; sel[2] = FF_NOSEL; sel[3] = FF_NOSEL 82 let out: *i64 = sys_mmap(FF_NDOCS * 8) as *i64 83 let nf1: i64 = ff_filter(hits, nh, vals, sel, out) 84 ff_puts(" filter subject=1 -> "); ff_putn(nf1); ff_puts(" docs: ") 85 i = 0 86 while i < nf1 { ff_putn(out[i]); ff_puts(" "); i = i + 1 } 87 ff_puts("\n") 88 89 // FILTER: subject=1 AND type=1 (AND across facets) 90 sel[1] = 1 91 let nf2: i64 = ff_filter(hits, nh, vals, sel, out) 92 ff_puts(" filter subject=1 AND type=1 -> "); ff_putn(nf2); ff_puts(" docs: ") 93 i = 0 94 while i < nf2 { ff_putn(out[i]); ff_puts(" "); i = i + 1 } 95 ff_puts("\n") 96 97 // neg-controls 98 sel[0] = FF_NOSEL; sel[1] = FF_NOSEL 99 let nall: i64 = ff_filter(hits, nh, vals, sel, out) // no filter -> all 100 sel[0] = 99 101 let nimp: i64 = ff_filter(hits, nh, vals, sel, out) // impossible -> 0 102 103 var pass: i64 = 0 104 var fail: i64 = 0 105 // T1: subject=1 yields exactly docs 1,4,7,10 (d%3==1) = 4 106 if nf1 == 4 { ff_puts(" T1 subject=1 -> 4 docs (1,4,7,10): PASS\n"); pass = pass + 1 } else { ff_puts(" T1: FAIL\n"); fail = fail + 1 } 107 // T2: counts consistent -- subject counts sum to nh, and cnt[1] == nf1 108 if cnt[0] + cnt[1] + cnt[2] == nh { if cnt[1] == nf1 { ff_puts(" T2 facet counts consistent (sum=hits, cnt[1]=filtered): PASS\n"); pass = pass + 1 } else { ff_puts(" T2: FAIL\n"); fail = fail + 1 } } else { ff_puts(" T2: FAIL\n"); fail = fail + 1 } 109 // T3: AND-faceting narrows (subject=1 AND type=1 -> docs 1,7 = d%3==1 & d%2==1 = 2) 110 if nf2 == 2 { if nf2 <= nf1 { ff_puts(" T3 AND-facet narrows (subject&type -> 2): PASS\n"); pass = pass + 1 } else { ff_puts(" T3: FAIL\n"); fail = fail + 1 } } else { ff_puts(" T3: FAIL\n"); fail = fail + 1 } 111 // T4: neg-controls -- no filter = all, impossible = 0 112 if nall == nh { if nimp == 0 { ff_puts(" T4 neg-controls (no-filter=all, impossible=0): PASS\n"); pass = pass + 1 } else { ff_puts(" T4: FAIL\n"); fail = fail + 1 } } else { ff_puts(" T4: FAIL\n"); fail = fail + 1 } 113 114 ff_puts("\n PASS="); ff_putn(pass); ff_puts("/4 ") 115 if fail == 0 { ff_puts("VERDICT=GREEN (sovereign faceted search: DC-facet filter + counts -- closes the onsite-search facet gap)\n"); sys_exit(0); return 0 } 116 ff_puts("VERDICT=RED\n"); sys_exit(1); return 1 117}