code wiki / _hdl_build / nx_analyst_data.nx
nx_analyst_data.nx source
↩ module page · 120 lines · 5610 B
1// nx_analyst_data.nx -- LIB: the GENERAL DATA ANALYST the operator said "isn't there" (2026-07-10). The
2// existing nx_analyst is a forensic COST tracer for one vertical; this one points at an ARBITRARY i64 column
3// and PROFILES it (count / distinct / min / max / mean / median / stddev / p95 / p99 / outliers) then EXPLAINS
4// it in words (cardinality class, skew, outliers) -- the "profile + find patterns + explain" front door.
5// Composes the MEASURED primitives: nx_dataframe (exact aggregation) + nx_dataframe_approx (HLL distinct at
6// scale). Data-driven anomaly flags, no hardcoded thresholds beyond the named statistical constants.
7// license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "_hdl_build/nx_dataframe.nx"
10import "_hdl_build/nx_dataframe_approx.nx"
11
12// profile slots (out_prof must hold >=12 i64):
13const AD_COUNT: i64 = 0
14const AD_DISTINCT: i64 = 1
15const AD_MIN: i64 = 2
16const AD_MAX: i64 = 3
17const AD_MEAN: i64 = 4
18const AD_MEDIAN: i64 = 5
19const AD_STDDEV: i64 = 6
20const AD_P95: i64 = 7
21const AD_P99: i64 = 8
22const AD_OUT_HI: i64 = 9
23const AD_OUT_LO: i64 = 10
24const AD_DISTINCT_PCT: i64 = 11
25
26// compute the full profile of col[0..n) into out_prof. lg_k for the HLL distinct estimate (10 = impl max).
27func ad_profile(col: *i64, n: i64, lg_k: i64, out_prof: *i64) -> i64 {
28 if n <= 0 { var z: i64 = 0; while z < 12 { out_prof[z] = 0; z = z + 1 } return 0 }
29 out_prof[AD_COUNT] = n
30 out_prof[AD_DISTINCT] = dfa_distinct(col, n, lg_k)
31 out_prof[AD_MIN] = df_min(col, n)
32 out_prof[AD_MAX] = df_max(col, n)
33 let rem: *i64 = sys_mmap(8) as *i64
34 out_prof[AD_MEAN] = df_mean(col, n, rem)
35 out_prof[AD_MEDIAN] = df_quantile(col, n, 500)
36 let vm: i64 = df_var_milli(col, n)
37 out_prof[AD_STDDEV] = df_isqrt(vm / 1000)
38 out_prof[AD_P95] = df_quantile(col, n, 950)
39 out_prof[AD_P99] = df_quantile(col, n, 990)
40 // outliers beyond 3 sigma (data-driven: mean +/- 3*stddev)
41 let mean: i64 = out_prof[AD_MEAN]
42 let sd: i64 = out_prof[AD_STDDEV]
43 let hi_thr: i64 = mean + 3 * sd
44 let lo_thr: i64 = mean - 3 * sd
45 out_prof[AD_OUT_HI] = df_filter_count(col, n, 3, hi_thr) // > hi
46 out_prof[AD_OUT_LO] = df_filter_count(col, n, 1, lo_thr) // < lo
47 var dp: i64 = 0
48 if out_prof[AD_DISTINCT] > 0 { dp = (out_prof[AD_DISTINCT] * 100) / n }
49 out_prof[AD_DISTINCT_PCT] = dp
50 return 0
51}
52
53func adx_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i }
54
55func ad_catn(dst: *u8, off: i64, v: i64) -> i64 {
56 var o: i64 = off
57 var m: i64 = v
58 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
59 let t: *u8 = sys_mmap(24)
60 var k: i64 = 0
61 if m == 0 { t[0] = 48 as u8; k = 1 }
62 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
63 var i: i64 = k - 1
64 while i >= 0 { dst[o] = t[i]; o = o + 1; i = i - 1 }
65 return o
66}
67
68// render a human-readable profile + explanation from a computed profile. Returns bytes written.
69func ad_report(name: *u8, out_prof: *i64, out: *u8, cap: i64) -> i64 {
70 if cap < 1024 { return 0 }
71 var o: i64 = 0
72 o = adx_cat(out, o, "DATA PROFILE: " as *u8)
73 o = adx_cat(out, o, name)
74 o = adx_cat(out, o, "\n rows=" as *u8)
75 o = ad_catn(out, o, out_prof[AD_COUNT])
76 o = adx_cat(out, o, " distinct~" as *u8)
77 o = ad_catn(out, o, out_prof[AD_DISTINCT])
78 o = adx_cat(out, o, " (" as *u8)
79 o = ad_catn(out, o, out_prof[AD_DISTINCT_PCT])
80 o = adx_cat(out, o, "% unique)\n min=" as *u8)
81 o = ad_catn(out, o, out_prof[AD_MIN])
82 o = adx_cat(out, o, " max=" as *u8)
83 o = ad_catn(out, o, out_prof[AD_MAX])
84 o = adx_cat(out, o, " mean=" as *u8)
85 o = ad_catn(out, o, out_prof[AD_MEAN])
86 o = adx_cat(out, o, " median=" as *u8)
87 o = ad_catn(out, o, out_prof[AD_MEDIAN])
88 o = adx_cat(out, o, " stddev=" as *u8)
89 o = ad_catn(out, o, out_prof[AD_STDDEV])
90 o = adx_cat(out, o, "\n p95=" as *u8)
91 o = ad_catn(out, o, out_prof[AD_P95])
92 o = adx_cat(out, o, " p99=" as *u8)
93 o = ad_catn(out, o, out_prof[AD_P99])
94 o = adx_cat(out, o, " outliers(>3sd)=" as *u8)
95 o = ad_catn(out, o, out_prof[AD_OUT_HI] + out_prof[AD_OUT_LO])
96 o = adx_cat(out, o, "\n READ: " as *u8)
97
98 // ---- data-driven explanation ----
99 let d: i64 = out_prof[AD_DISTINCT]
100 let dp: i64 = out_prof[AD_DISTINCT_PCT]
101 let mean: i64 = out_prof[AD_MEAN]
102 let median: i64 = out_prof[AD_MEDIAN]
103 var wrote: i64 = 0
104 if d <= 1 { o = adx_cat(out, o,"CONSTANT column (single value). " as *u8); wrote = 1 }
105 if wrote == 0 { if d < 20 { o = adx_cat(out, o,"low-cardinality / categorical (" as *u8); o = ad_catn(out, o, d); o = adx_cat(out, o," classes). " as *u8); wrote = 1 } }
106 if wrote == 0 { if dp >= 90 { o = adx_cat(out, o,"near-unique -- likely an id/key column. " as *u8); wrote = 1 } }
107 if wrote == 0 { o = adx_cat(out, o,"continuous numeric. " as *u8) }
108 // skew: compare mean vs median (>20% gap)
109 if median > 0 {
110 if mean * 10 > median * 12 { o = adx_cat(out, o,"RIGHT-SKEWED (mean >> median -- a high tail pulls the average up). " as *u8) }
111 if median * 10 > mean * 12 { o = adx_cat(out, o,"LEFT-SKEWED (median >> mean). " as *u8) }
112 }
113 let outl: i64 = out_prof[AD_OUT_HI] + out_prof[AD_OUT_LO]
114 if outl > 0 { o = adx_cat(out, o,"Has " as *u8); o = ad_catn(out, o, outl); o = adx_cat(out, o," outliers beyond 3 sigma -- inspect. " as *u8) }
115 if outl == 0 { o = adx_cat(out, o,"No 3-sigma outliers. " as *u8) }
116 o = adx_cat(out, o, "\n" as *u8)
117 if o + 2 >= cap { return o }
118 out[o] = 0 as u8
119 return o
120}