qualia_core_db/medical/
comorbidity_eval.rs1use crate::modalities::paraconsistent::route_paraconsistent;
8use crate::q_hash;
9use crate::NQuin;
10
11pub const MAX_CONDITION_SLOTS: usize = 32;
12pub const MAX_COMORBIDITY_VERDICTS: usize = 64;
13pub const NESTED_SUBJECT_MASK: u64 = 1u64 << 63;
14pub const NESTED_PAYLOAD_MASK: u64 = 0x0FFF_FFFF_FFFF_FFFF;
15
16pub const PRED_EXACERBATES: u64 = q_hash("q42:exacerbates");
17pub const PRED_HAS_CONDITION: u64 = q_hash("q42:hasCondition");
18pub const PRED_HAS_SEVERITY: u64 = q_hash("q42:hasSeverity");
19
20const INLINE_TAG_DECIMAL: u64 = 0b010u64 << 60;
21const INLINE_TAG_MASK: u64 = 0b111u64 << 60;
22const INLINE_VALUE_MASK: u64 = 0x0FFF_FFFF_FFFF_FFFF;
23
24#[repr(u8)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ComorbidityStatus {
27 Active = 0,
28 Isolated = 1,
29 Defeated = 2,
30}
31
32#[repr(C, align(8))]
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct ComorbidityVerdict {
35 pub condition_hash: u64,
36 pub compounded_risk_milli: u32,
37 pub status: ComorbidityStatus,
38 pub _pad: [u8; 3],
39}
40
41#[derive(Debug, PartialEq, Eq)]
42pub enum ComorbidityError {
43 OutputBufferFull,
44}
45
46#[inline]
48pub fn nested_claim_fingerprint(ante: u64, pred: u64, cons: u64) -> u64 {
49 let body = (ante ^ pred ^ cons) & NESTED_PAYLOAD_MASK;
50 body | NESTED_SUBJECT_MASK
51}
52
53#[inline]
54pub fn is_nested_subject(subject: u64) -> bool {
55 (subject & NESTED_SUBJECT_MASK) != 0
56}
57
58#[inline]
59fn decode_severity_milli(object: u64) -> u32 {
60 if (object & INLINE_TAG_MASK) == INLINE_TAG_DECIMAL {
61 let scaled = (object & INLINE_VALUE_MASK) as u32;
62 ((scaled as u64 * 1000) / 1_000_000).min(1000) as u32
64 } else {
65 500
66 }
67}
68
69#[inline]
70fn encode_severity_object(severity: f32) -> u64 {
71 let clamped = severity.clamp(0.0, 1.0);
72 let scaled = (clamped * 1_000_000.0).round() as u64;
73 (scaled & INLINE_VALUE_MASK) | INLINE_TAG_DECIMAL
74}
75
76pub fn compile_exacerbation_quins(
78 ante_condition: u64,
79 cons_condition: u64,
80 patient_context: u64,
81 severity: f32,
82 out: &mut [NQuin; 2],
83) -> usize {
84 let nested = nested_claim_fingerprint(ante_condition, PRED_EXACERBATES, cons_condition);
85
86 let mut edge = NQuin::default();
87 edge.subject = ante_condition;
88 edge.predicate = PRED_EXACERBATES;
89 edge.object = cons_condition;
90 edge.context = patient_context;
91 edge.parity = edge.subject ^ edge.predicate ^ edge.object ^ edge.context;
92 out[0] = edge;
93
94 let mut severity_quin = NQuin::default();
95 severity_quin.subject = nested;
96 severity_quin.predicate = PRED_HAS_SEVERITY;
97 severity_quin.object = encode_severity_object(severity);
98 severity_quin.context = patient_context;
99 severity_quin.parity = severity_quin.subject
100 ^ severity_quin.predicate
101 ^ severity_quin.object
102 ^ severity_quin.context;
103 out[1] = severity_quin;
104
105 2
106}
107
108#[inline]
110fn condition_intersects_organ(condition_hash: u64, target_organ_hash: u64) -> bool {
111 if target_organ_hash == 0 {
112 return true;
113 }
114 if condition_hash == target_organ_hash {
115 return true;
116 }
117 let cardio = q_hash("Heart");
119 let diabetes = q_hash("Type 2 Diabetes Mellitus");
120 let neuropathy = q_hash("Diabetic Neuropathy");
121 let hypertension = q_hash("Hypertension");
122
123 if target_organ_hash == cardio {
124 return matches!(
125 condition_hash,
126 x if x == diabetes || x == hypertension || x == cardio || x == neuropathy
127 );
128 }
129
130 condition_hash == target_organ_hash
131}
132
133pub fn eval_comorbidity(
135 patient_did_hash: u64,
136 target_organ_hash: u64,
137 quins: &[NQuin],
138 out: &mut [ComorbidityVerdict],
139) -> Result<usize, ComorbidityError> {
140 let mut consistent_buf = [NQuin::default(); 128];
141 let mut isolated_buf = [NQuin::default(); 32];
142 let (consistent_count, isolated_count) =
143 route_paraconsistent(quins, &mut consistent_buf, &mut isolated_buf)
144 .map_err(|_| ComorbidityError::OutputBufferFull)?;
145 let graph = &consistent_buf[..consistent_count];
146
147 let mut conditions = [0u64; MAX_CONDITION_SLOTS];
148 let mut condition_count = 0usize;
149 let mut severities = [(0u64, 0u32); MAX_CONDITION_SLOTS];
150 let mut severity_count = 0usize;
151
152 for quin in graph {
153 if quin.context != patient_did_hash && quin.subject != patient_did_hash {
154 continue;
155 }
156
157 if quin.predicate == PRED_HAS_CONDITION && quin.subject == patient_did_hash {
158 if condition_count < MAX_CONDITION_SLOTS {
159 conditions[condition_count] = quin.object;
160 condition_count += 1;
161 }
162 }
163
164 if is_nested_subject(quin.subject) && quin.predicate == PRED_HAS_SEVERITY {
165 if severity_count < MAX_CONDITION_SLOTS {
166 severities[severity_count] = (quin.subject, decode_severity_milli(quin.object));
167 severity_count += 1;
168 }
169 }
170 }
171
172 let mut emitted = 0usize;
173
174 for i in 0..condition_count {
175 let condition = conditions[i];
176 if !condition_intersects_organ(condition, target_organ_hash) {
177 continue;
178 }
179
180 let mut risk_milli: u32 = 400;
181
182 for quin in graph {
183 if quin.predicate != PRED_EXACERBATES || quin.context != patient_did_hash {
184 continue;
185 }
186 if quin.subject != condition && quin.object != condition {
187 continue;
188 }
189 let nested_fp = nested_claim_fingerprint(quin.subject, quin.predicate, quin.object);
190 let mut matched_severity = 0u32;
191 for j in 0..severity_count {
192 if severities[j].0 == nested_fp {
193 matched_severity = severities[j].1;
194 break;
195 }
196 }
197 if matched_severity > 0 {
198 risk_milli = risk_milli.saturating_add(matched_severity / 2);
199 risk_milli = ((risk_milli as u64 * 14) / 10).min(1000) as u32;
200 } else {
201 risk_milli = ((risk_milli as u64 * 12) / 10).min(1000) as u32;
202 }
203 }
204
205 if emitted >= out.len() {
206 return Err(ComorbidityError::OutputBufferFull);
207 }
208
209 out[emitted] = ComorbidityVerdict {
210 condition_hash: condition,
211 compounded_risk_milli: risk_milli.min(1000),
212 status: ComorbidityStatus::Active,
213 _pad: [0; 3],
214 };
215 emitted += 1;
216 }
217
218 for quin in &isolated_buf[..isolated_count] {
219 if quin.subject != patient_did_hash && quin.context != patient_did_hash {
220 continue;
221 }
222 if emitted >= out.len() {
223 return Err(ComorbidityError::OutputBufferFull);
224 }
225 out[emitted] = ComorbidityVerdict {
226 condition_hash: quin.object,
227 compounded_risk_milli: 0,
228 status: ComorbidityStatus::Isolated,
229 _pad: [0; 3],
230 };
231 emitted += 1;
232 }
233
234 Ok(emitted)
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 fn patient_ctx() -> u64 {
242 q_hash("did:patient:comorb-test")
243 }
244
245 fn compile_demo_graph(patient: u64, out: &mut [NQuin; 16]) -> usize {
246 let diabetes = q_hash("Type 2 Diabetes Mellitus");
247 let neuropathy = q_hash("Diabetic Neuropathy");
248 let heart = q_hash("Heart");
249
250 let mut idx = 0usize;
251
252 let mut has_diabetes = NQuin::default();
253 has_diabetes.subject = patient;
254 has_diabetes.predicate = PRED_HAS_CONDITION;
255 has_diabetes.object = diabetes;
256 has_diabetes.context = patient;
257 has_diabetes.parity = has_diabetes.subject
258 ^ has_diabetes.predicate
259 ^ has_diabetes.object
260 ^ has_diabetes.context;
261 out[idx] = has_diabetes;
262 idx += 1;
263
264 let mut has_neuropathy = NQuin::default();
265 has_neuropathy.subject = patient;
266 has_neuropathy.predicate = PRED_HAS_CONDITION;
267 has_neuropathy.object = neuropathy;
268 has_neuropathy.context = patient;
269 has_neuropathy.parity = has_neuropathy.subject
270 ^ has_neuropathy.predicate
271 ^ has_neuropathy.object
272 ^ has_neuropathy.context;
273 out[idx] = has_neuropathy;
274 idx += 1;
275
276 let mut pair = [NQuin::default(); 2];
277 let wrote = compile_exacerbation_quins(diabetes, neuropathy, patient, 0.85, &mut pair);
278 out[idx..idx + wrote].copy_from_slice(&pair[..wrote]);
279 idx += wrote;
280
281 let mut cardio = NQuin::default();
282 cardio.subject = patient;
283 cardio.predicate = PRED_HAS_CONDITION;
284 cardio.object = heart;
285 cardio.context = patient;
286 cardio.parity = cardio.subject ^ cardio.predicate ^ cardio.object ^ cardio.context;
287 out[idx] = cardio;
288 idx += 1;
289
290 idx
291 }
292
293 #[test]
294 fn nested_fingerprint_sets_msb() {
295 let fp = nested_claim_fingerprint(1, 2, 3);
296 assert!(is_nested_subject(fp));
297 }
298
299 #[test]
300 fn eval_finds_compounded_diabetes_neuropathy_risk() {
301 let patient = patient_ctx();
302 let mut graph = [NQuin::default(); 16];
303 let n = compile_demo_graph(patient, &mut graph);
304
305 let mut verdicts = [ComorbidityVerdict {
306 condition_hash: 0,
307 compounded_risk_milli: 0,
308 status: ComorbidityStatus::Defeated,
309 _pad: [0; 3],
310 }; 8];
311
312 let count = eval_comorbidity(patient, q_hash("Heart"), &graph[..n], &mut verdicts).unwrap();
313 assert!(count >= 2);
314 assert!(verdicts[0].compounded_risk_milli > 400);
315 }
316
317 #[test]
318 fn zero_heap_eval_comorbidity() {
319 let patient = patient_ctx();
320 let mut graph = [NQuin::default(); 16];
321 let n = compile_demo_graph(patient, &mut graph);
322
323 let _profiler = dhat::Profiler::builder().testing().build();
324 let mut verdicts = [ComorbidityVerdict {
325 condition_hash: 0,
326 compounded_risk_milli: 0,
327 status: ComorbidityStatus::Active,
328 _pad: [0; 3],
329 }; MAX_COMORBIDITY_VERDICTS];
330
331 let result = eval_comorbidity(patient, q_hash("Heart"), &graph[..n], &mut verdicts);
332 assert!(result.is_ok());
333
334 let stats = dhat::HeapStats::get();
335 assert_eq!(
336 stats.curr_blocks, 0,
337 "eval_comorbidity must not allocate on the heap"
338 );
339 assert_eq!(stats.curr_bytes, 0);
340 }
341}