nx_arousal_clinical.nx source
↩ module page · 205 lines · 10519 B
1// nx_arousal_clinical.nx -- the THERAPY READOUT. Given a dual-control profile (SES/SIS1/SIS2),
2// identify WHICH axis is limiting this person and what changing it would actually buy them, in
3// minutes rather than in permil.
4//
5// ★WHY THIS AND NOT A SCORE: the clinically load-bearing finding is that most arousal difficulty is
6// EXCESS INHIBITION, not deficit excitation -- and SIS1 (threat of performance failure) vs SIS2
7// (threat of consequences) point to DIFFERENT treatments. A single "arousal score" collapses exactly
8// the distinction the clinician needs. So this organ reports COUNTERFACTUALS per axis, never a scalar.
9//
10// ★AND IT REFUSES TO GUESS. When two axes are within `margin` of each other the limiting factor is
11// genuinely ambiguous, and the organ says AMBIGUOUS instead of naming one. An instrument that always
12// names a cause is a horoscope; the refusal is the difference between a measurement and a reading.
13//
14// ★TIME IS THE UNIT A PERSON FEELS. Permil drive means nothing to a user; "this adds 9 minutes to
15// your time-to-plateau" is the thing a therapy session can act on.
16//
17// usage: nx_arousal_clinical profile <stim> <ses> <sis1> <sis2> [sex0m1f] [target_permil]
18// nx_arousal_clinical selftest
19// license_tier: ORIGINAL expect_exit: 0
20import "nx_arousal_lib.nx"
21const CL_MAGIC_185000: i64 = 185000
22const CL_MAGIC_210000: i64 = 210000
23
24const CL_MAXMS: i64 = 3600000
25const CL_MARGIN: i64 = 40
26
27// time in ms for perfusion to first reach `target` under a constant drive; CL_MAXMS if never.
28func cl_time_to(target: i64, drive: i64, tau: i64) -> i64 {
29 if drive <= 0 { return CL_MAXMS }
30 if target <= 0 { return 0 }
31 var cur: i64 = 0
32 let tgt: i64 = drive * 1000
33 var t: i64 = 0
34 while t < CL_MAXMS {
35 cur = a_lag(cur, tgt, tau)
36 t = t + 50
37 if cur / 1000 >= target { return t }
38 }
39 return CL_MAXMS
40}
41func cl_name(ax: i64) -> *u8 {
42 if ax == 0 { return "SIS1_performance_threat" as *u8 }
43 if ax == 1 { return "SIS2_consequence_threat" as *u8 }
44 return "SES_excitation_deficit" as *u8
45}
46// what a clinician should actually do about the winning axis
47func cl_advice(ax: i64) -> *u8 {
48 if ax == 0 { return "performance-anxiety pathway: sensate focus, non-demand touch, remove goal" as *u8 }
49 if ax == 1 { return "consequence/context pathway: privacy, safety, relational or situational" as *u8 }
50 return "excitation pathway: stimulus intensity, novelty, directed self-focus" as *u8
51}
52
53func main(argc: i64, argv: *i64) -> i64 {
54 if argc < 2 { a_puts("REFUSED reason=no_verb\n" as *u8); return 2 }
55 let verb: *u8 = argv[1] as *u8
56 let cl2: *i64 = sys_mmap(16) as *i64
57 cl2[0] = 0
58 let cbuf: *u8 = sys_read_file("arousal_params.conf" as *u8, cl2)
59 let cn: i64 = cl2[0]
60
61 if verb[0] == (115 as u8) {
62 a_puts("CLINICAL-SELFTEST margin=" as *u8); a_putn(CL_MARGIN)
63 a_puts(" conf_bytes=" as *u8); a_putn(cn)
64 a_puts("\n" as *u8)
65 return 0
66 }
67 // ★OBSERVED MODE: diagnose from what session data can ACTUALLY recover.
68 // The `profile` verb needs SES and SIS1 separately -- precisely the pair nx_arousal_profile
69 // proves are structurally confounded. So the two organs could not be composed at all, which
70 // would have shipped as an architecture that quietly requires a questionnaire nobody ran.
71 // SIS2 IS identifiable (it is the intercept), so this much is always decidable: is the limiting
72 // factor consequence-threat, or the net excitation term? The net term is then reported as
73 // un-attributable BY CONSTRUCTION rather than guessed at.
74 if verb[0] == (111 as u8) {
75 if argc < 5 { a_puts("REFUSED reason=observed_needs_stim_net_sis2\n" as *u8); return 2 }
76 let ostim: i64 = a_atoi(argv[2] as *u8)
77 let onet: i64 = a_atoi(argv[3] as *u8)
78 let osis2: i64 = a_atoi(argv[4] as *u8)
79 if ostim < 0 { a_puts("REFUSED reason=stim_out_of_range\n" as *u8); return 3 }
80 if ostim > 1000 { a_puts("REFUSED reason=stim_out_of_range\n" as *u8); return 3 }
81 if osis2 < 0 { a_puts("REFUSED reason=sis2_out_of_range\n" as *u8); return 3 }
82 if osis2 > 1000 { a_puts("REFUSED reason=sis2_out_of_range\n" as *u8); return 3 }
83 if onet > 1000 { a_puts("REFUSED reason=net_out_of_range\n" as *u8); return 3 }
84 // ★COUNTERFACTUALS MUST BE COMPUTED UNCLAMPED. drive clamps to [0,1000], so a profile whose
85 // true drive is -220 reports 0 -- and differencing against that clamped 0 understates the
86 // gain from removing the term that pushed it negative. That distortion can FLIP which axis
87 // looks limiting, which is the one error a treatment recommendation must not make.
88 // Same law as refusing a saturated session: a clamp is a bound, not a measurement.
89 let raw: i64 = (onet * ostim) / 1000 - osis2
90 let raw_a: i64 = (onet * ostim) / 1000
91 let raw_b: i64 = ostim - osis2
92 let ga: i64 = raw_a - raw
93 let gb: i64 = raw_b - raw
94 var obase: i64 = raw
95 if obase < 0 { obase = 0 }
96 if obase > 1000 { obase = 1000 }
97 var clamped: i64 = 0
98 if raw != obase { clamped = 1 }
99 a_puts("ok drive=" as *u8); a_putn(obase)
100 a_puts(" raw_drive=" as *u8); a_putn(raw)
101 a_puts(" clamped=" as *u8); a_putn(clamped)
102 a_puts(" gain_remove_sis2=" as *u8); a_putn(ga)
103 a_puts(" gain_max_net=" as *u8); a_putn(gb)
104 var diff: i64 = ga - gb
105 if diff < 0 { diff = 0 - diff }
106 if diff < CL_MARGIN {
107 a_puts(" limiting=AMBIGUOUS margin=" as *u8); a_putn(diff)
108 a_puts(" verdict=REFUSED_TOO_CLOSE_TO_CALL\n" as *u8)
109 return 0
110 }
111 if ga > gb {
112 a_puts(" limiting=SIS2_consequence_threat attributable=YES" as *u8)
113 a_puts(" pathway=" as *u8); a_puts(cl_advice(1))
114 a_puts("\n" as *u8)
115 return 0
116 }
117 // ★the net term wins -- and it CANNOT be split into SES vs SIS1 from session data.
118 a_puts(" limiting=NET_EXCITATION_TERM attributable=NO" as *u8)
119 a_puts(" note=cannot_split_ses_from_sis1_without_the_SIS/SES_instrument\n" as *u8)
120 return 0
121 }
122 if verb[0] != (112 as u8) { a_puts("REFUSED reason=unknown_verb\n" as *u8); return 2 }
123 if argc < 6 { a_puts("REFUSED reason=profile_needs_4_args\n" as *u8); return 2 }
124
125 let stim: i64 = a_atoi(argv[2] as *u8)
126 let ses: i64 = a_atoi(argv[3] as *u8)
127 let sis1: i64 = a_atoi(argv[4] as *u8)
128 let sis2: i64 = a_atoi(argv[5] as *u8)
129 var sex: i64 = 1
130 if argc >= 7 { sex = a_atoi(argv[6] as *u8) }
131 var target: i64 = 650
132 if argc >= 8 { target = a_atoi(argv[7] as *u8) }
133 if stim < 0 { a_puts("REFUSED reason=stim_out_of_range\n" as *u8); return 3 }
134 if stim > 1000 { a_puts("REFUSED reason=stim_out_of_range\n" as *u8); return 3 }
135 if ses < 0 { a_puts("REFUSED reason=ses_out_of_range\n" as *u8); return 3 }
136 if ses > 1000 { a_puts("REFUSED reason=ses_out_of_range\n" as *u8); return 3 }
137 if sis1 < 0 { a_puts("REFUSED reason=sis1_out_of_range\n" as *u8); return 3 }
138 if sis1 > 1000 { a_puts("REFUSED reason=sis1_out_of_range\n" as *u8); return 3 }
139 if sis2 < 0 { a_puts("REFUSED reason=sis2_out_of_range\n" as *u8); return 3 }
140 if sis2 > 1000 { a_puts("REFUSED reason=sis2_out_of_range\n" as *u8); return 3 }
141 if target <= 0 { a_puts("REFUSED reason=target_out_of_range\n" as *u8); return 3 }
142 if target > 1000 { a_puts("REFUSED reason=target_out_of_range\n" as *u8); return 3 }
143
144 var tau: i64 = a_conf(cbuf, cn, "tau_slow_m_ms" as *u8, CL_MAGIC_185000)
145 if sex == 1 { tau = a_conf(cbuf, cn, "tau_slow_f_ms" as *u8, CL_MAGIC_210000) }
146
147 // baseline and the three single-axis counterfactuals
148 let base: i64 = a_drive(stim, ses, sis1, sis2)
149 let d_no1: i64 = a_drive(stim, ses, 0, sis2)
150 let d_no2: i64 = a_drive(stim, ses, sis1, 0)
151 let d_ses: i64 = a_drive(stim, 1000, sis1, sis2)
152 let g1: i64 = d_no1 - base
153 let g2: i64 = d_no2 - base
154 let g3: i64 = d_ses - base
155
156 // rank the gains; the limiting axis is the one whose removal buys the most
157 var top: i64 = 0
158 var tg: i64 = g1
159 if g2 > tg { top = 1; tg = g2 }
160 if g3 > tg { top = 2; tg = g3 }
161 var second: i64 = 0
162 if top == 0 { second = g2; if g3 > second { second = g3 } }
163 if top == 1 { second = g1; if g3 > second { second = g3 } }
164 if top == 2 { second = g1; if g2 > second { second = g2 } }
165
166 let t_base: i64 = cl_time_to(target, base, tau)
167 let t_fix: i64 = cl_time_to(target, base + tg, tau)
168 // ★REACHABILITY IS THE HEADLINE, NOT THE CLOCK. Perfusion asymptotes at the drive, so a profile
169 // whose drive sits below the target NEVER reaches it -- time is unbounded, not large. Reporting
170 // the CL_MAXMS cap as if it were a duration would turn "never reaches plateau" into "takes a
171 // while", which understates the finding in the one direction a clinical readout must not.
172 var reach_base: i64 = 1
173 if t_base >= CL_MAXMS { reach_base = 0 }
174 var reach_fix: i64 = 1
175 if t_fix >= CL_MAXMS { reach_fix = 0 }
176 var saved: i64 = 0
177 if reach_base == 1 { if reach_fix == 1 { saved = t_base - t_fix } }
178 if saved < 0 { saved = 0 }
179
180 a_puts("ok drive=" as *u8); a_putn(base)
181 a_puts(" gain_sis1=" as *u8); a_putn(g1)
182 a_puts(" gain_sis2=" as *u8); a_putn(g2)
183 a_puts(" gain_ses=" as *u8); a_putn(g3)
184 a_puts(" reach_base=" as *u8); a_putn(reach_base)
185 a_puts(" reach_fixed=" as *u8); a_putn(reach_fix)
186 if reach_base == 1 { a_puts(" t_base_s=" as *u8); a_putn(t_base / 1000) } else { a_puts(" t_base_s=NEVER" as *u8) }
187 if reach_fix == 1 { a_puts(" t_fixed_s=" as *u8); a_putn(t_fix / 1000) } else { a_puts(" t_fixed_s=NEVER" as *u8) }
188 if reach_base == 1 { a_puts(" saved_s=" as *u8); a_putn(saved / 1000) } else { a_puts(" saved_s=UNBOUNDED" as *u8) }
189
190 // ★THE REFUSAL: no axis dominates -> say so. Naming one here would be invention.
191 if tg <= 0 {
192 a_puts(" limiting=NONE verdict=NOT_INHIBITION_LIMITED\n" as *u8)
193 return 0
194 }
195 if tg - second < CL_MARGIN {
196 a_puts(" limiting=AMBIGUOUS margin=" as *u8); a_putn(tg - second)
197 a_puts(" verdict=REFUSED_TOO_CLOSE_TO_CALL\n" as *u8)
198 return 0
199 }
200 a_puts(" limiting=" as *u8); a_puts(cl_name(top))
201 a_puts(" margin=" as *u8); a_putn(tg - second)
202 a_puts(" pathway=" as *u8); a_puts(cl_advice(top))
203 a_puts("\n" as *u8)
204 return 0
205}