Spaces:
Sleeping
Sleeping
File size: 15,525 Bytes
acaf471 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | """
Deterministic rule-based clinical inference engine.
Applies expert-validated If-Then rules to structured pain ontology data.
NO probabilistic reasoning or LLM-based inference - all clinical logic is
deterministic and traceable.
This module implements the symbolic reasoning component of the neuro-symbolic
hybrid architecture, ensuring medical decisions are explainable and evidence-based.
"""
from typing import List, Dict, Callable
from dataclasses import dataclass
import sys
import os
# Add Backend to path for imports
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from models.pain_schema import PainOntology, ClinicalRecommendation
@dataclass
class ClinicalRule:
"""
Represents a single clinical decision rule.
Each rule consists of:
- Condition: A function that evaluates PainOntology and returns True/False
- Action: The recommendation to make if condition is met
- Evidence fields: Which PainOntology fields to include as evidence
- Guideline reference: Citation to clinical guideline supporting the rule
"""
rule_id: str
name: str
condition: Callable[[PainOntology], bool]
recommendation: str
evidence_fields: List[str]
guideline_reference: str = None
confidence: str = "high"
class RuleEngine:
"""
Expert system rule engine for pain assessment.
Applies deterministic clinical rules to structured pain data and generates
explainable recommendations with complete evidence chains.
All rules are:
1. Based on established clinical guidelines
2. Deterministic (no probabilistic inference)
3. Fully explainable (evidence is explicitly tracked)
4. Independently verifiable by medical experts
"""
def __init__(self):
"""Initialize rule engine and load clinical decision rules."""
self.rules: List[ClinicalRule] = []
self._initialize_rules()
def _initialize_rules(self):
"""
Initialize clinical decision rules.
Each rule is based on established clinical guidelines and pain management
best practices. Rules are evaluated in order of priority.
"""
# ========== RULE A: Chronic Pain + Depressive Symptoms → Behavioral Therapy ==========
def rule_a_condition(pain_data: PainOntology) -> bool:
"""
Chronic pain with significant affective distress requires multimodal approach.
Based on: Wisconsin Medical Examining Board Guidelines for Chronic Pain Management
Rationale: Chronic pain with depression benefits from CBT and non-pharmacologic
interventions before considering pharmacological escalation.
"""
temporal_chronic = (
pain_data.temporal_pattern and
("chronic" in pain_data.temporal_pattern.lower() or "months" in pain_data.temporal_pattern.lower())
)
emotion_depressed = pain_data.emotion and any(
term in pain_data.emotion.lower()
for term in ["depressed", "depression", "despair", "hopeless"]
)
return temporal_chronic and emotion_depressed
self.rules.append(ClinicalRule(
rule_id="RULE_A",
name="Chronic Pain + Depression → Behavioral Therapy",
condition=rule_a_condition,
recommendation=(
"Recommend behavioral therapy (CBT) per Wisconsin chronic pain guidelines. "
"Chronic pain with significant depressive symptoms benefits from "
"culturally concordant behavioral interventions. Prioritize non-pharmacologic "
"multidisciplinary care before considering pharmacological escalation."
),
evidence_fields=["temporal_pattern", "emotion"],
guideline_reference="Wisconsin Medical Examining Board Guidelines for Chronic Pain Management",
confidence="high"
))
# ========== RULE B: Neuropathic Pain in Distal Extremities → Peripheral Neuropathy Screening ==========
def rule_b_condition(pain_data: PainOntology) -> bool:
"""
Neuropathic pain in hands/feet suggests peripheral neuropathy.
Rationale: Classic presentation of peripheral neuropathy includes neuropathic
pain descriptors (electric-shock, tingling, burning) in distal extremities.
Requires screening for underlying causes (diabetes, vitamin B12 deficiency, etc.)
"""
neuropathic = pain_data.pain_type and "neuropathic" in pain_data.pain_type.lower()
distal_location = pain_data.location and any(
loc in pain_data.location.lower()
for loc in ["lower extremities", "feet", "hands", "legs", "arms", "extremities"]
)
return neuropathic and distal_location
self.rules.append(ClinicalRule(
rule_id="RULE_B",
name="Neuropathic Pain in Distal Extremities → Peripheral Neuropathy Screening",
condition=rule_b_condition,
recommendation=(
"Recommend peripheral neuropathy screening. "
"Neuropathic pain in distal extremities suggests possible peripheral nerve pathology. "
"Consider neurological examination and investigation of potential underlying causes "
"(diabetes mellitus, vitamin B12 deficiency, autoimmune conditions, medication toxicity)."
),
evidence_fields=["pain_type", "location"],
confidence="high"
))
# ========== RULE C: Severe Functional Impact → Multidisciplinary Pain Clinic Referral ==========
def rule_c_condition(pain_data: PainOntology) -> bool:
"""
Severe functional impairment requires comprehensive pain management.
Rationale: Pain causing significant functional disability (sleep, work, mobility)
often requires multidisciplinary approach beyond primary care.
"""
has_functional_impact = pain_data.functional_impact is not None
severe_impact = has_functional_impact and any(
term in pain_data.functional_impact.lower()
for term in ["severe", "unable", "cannot", "impossible", "interfere", "disability"]
)
return severe_impact
self.rules.append(ClinicalRule(
rule_id="RULE_C",
name="Severe Functional Impact → Multidisciplinary Pain Clinic",
condition=rule_c_condition,
recommendation=(
"Consider referral to multidisciplinary pain clinic. "
"Severe functional impairment indicates need for comprehensive pain management "
"involving physical therapy, occupational therapy, psychological support, and "
"coordinated medical management."
),
evidence_fields=["functional_impact", "temporal_pattern"],
confidence="high"
))
# ========== RULE D: Burning Pain → Consider Inflammatory or Neuropathic Etiology ==========
def rule_d_condition(pain_data: PainOntology) -> bool:
"""
Burning pain quality suggests specific etiologies.
Rationale: Burning pain can indicate inflammatory processes or small fiber neuropathy.
"""
burning_pain = pain_data.pain_type and "burning" in pain_data.pain_type.lower()
return burning_pain
self.rules.append(ClinicalRule(
rule_id="RULE_D",
name="Burning Pain → Inflammatory/Neuropathic Workup",
condition=rule_d_condition,
recommendation=(
"Burning pain quality suggests possible inflammatory or small fiber neuropathic etiology. "
"Consider evaluation for inflammatory conditions, nerve injury, or small fiber neuropathy. "
"May benefit from topical treatments or neuropathic pain medications."
),
evidence_fields=["pain_type", "location"],
confidence="medium"
))
def evaluate(self, pain_data: PainOntology) -> List[ClinicalRecommendation]:
"""
Apply all rules to the pain data and return triggered recommendations.
Each recommendation includes:
- The clinical recommendation text
- Which rule triggered it
- The specific evidence (field values) that triggered the rule
- Confidence level
- Guideline reference
Args:
pain_data: Structured pain ontology data
Returns:
List of clinical recommendations with complete evidence chains
Example:
>>> pain = PainOntology(
... pain_type="Neuropathic (Electric-shock-like)",
... location="Lower extremities",
... temporal_pattern="Chronic (4 months)",
... emotion="Depressed"
... )
>>> engine = RuleEngine()
>>> recommendations = engine.evaluate(pain)
>>> # Returns recommendations for RULE_A and RULE_B
"""
recommendations = []
for rule in self.rules:
try:
if rule.condition(pain_data):
# Extract evidence from specified fields
evidence = {}
for field in rule.evidence_fields:
if hasattr(pain_data, field):
value = getattr(pain_data, field)
if value is not None: # Only include non-None values
evidence[field] = value
recommendations.append(ClinicalRecommendation(
recommendation=rule.recommendation,
triggered_by_rule=f"{rule.rule_id}: {rule.name}",
evidence=evidence,
confidence=rule.confidence,
guideline_reference=rule.guideline_reference
))
except Exception as e:
# Log error but continue evaluating other rules
print(f"Warning: Error evaluating {rule.rule_id}: {str(e)}")
continue
return recommendations
def generate_reasoning_chain(
self,
pain_data: PainOntology,
recommendations: List[ClinicalRecommendation],
ontology_mappings: List[Dict]
) -> List[str]:
"""
Generate human-readable reasoning chain showing complete decision pathway.
This provides full transparency from patient input to clinical recommendations,
enabling clinical validation and building trust in the system.
The reasoning chain includes:
1. Ontology mapping (Chinese → English medical terms)
2. Structured pain data extraction
3. Rule evaluation and triggers
4. Final recommendations with evidence
Args:
pain_data: Structured pain ontology data
recommendations: List of triggered recommendations
ontology_mappings: List of multilingual→English mappings
Returns:
List of reasoning step strings
Example output:
[
"=== Ontology Mapping ===",
"Input '电击一样' → Mapped to 'Electric-shock-like (Neuropathic)'",
"=== Structured Pain Data ===",
"Pain Type: Neuropathic (Electric-shock-like)",
"=== Rule Engine Evaluation ===",
"✓ Triggered: RULE_B",
" Evidence: {'pain_type': 'Neuropathic', 'location': 'Lower extremities'}"
]
"""
chain = []
# ===== Step 1: Show ontology mappings =====
chain.append("=== Ontology Mapping ===")
if ontology_mappings:
for mapping in ontology_mappings:
# Show: matched text in user input → dictionary term → English translation
matched = mapping.get('matched_text', mapping.get('original_term', 'N/A'))
original = mapping.get('original_term', 'N/A')
english = mapping.get('mapped_english', 'N/A')
# Display: what user said → what it matches → English term
if matched != original:
display_input = f"{matched} (matches '{original}')"
else:
display_input = matched
chain.append(
f"Input '{display_input}' → "
f"Mapped to '{english}' "
f"({mapping.get('dimension', 'N/A')}, confidence: {mapping.get('confidence', 'N/A')})"
)
else:
chain.append("No specific pain descriptors were mapped from ontology dictionary.")
# ===== Step 2: Show structured data extraction =====
chain.append("\n=== Structured Pain Data ===")
chain.append(f"Pain Type: {pain_data.pain_type}")
chain.append(f"Location: {pain_data.location}")
chain.append(f"Temporal Pattern: {pain_data.temporal_pattern}")
if pain_data.intensity and pain_data.intensity != "Not explicitly stated":
chain.append(f"Intensity: {pain_data.intensity}")
if pain_data.emotion:
chain.append(f"Emotional Dimension: {pain_data.emotion}")
if pain_data.functional_impact:
chain.append(f"Functional Impact: {pain_data.functional_impact}")
# ===== Step 3: Show rule triggers and recommendations =====
chain.append("\n=== Rule Engine Evaluation ===")
if recommendations:
for rec in recommendations:
chain.append(f"✓ Triggered: {rec.triggered_by_rule}")
chain.append(f" Evidence: {rec.evidence}")
chain.append(f" → Recommendation: {rec.recommendation}")
if rec.guideline_reference:
chain.append(f" → Guideline: {rec.guideline_reference}")
chain.append("") # Blank line for readability
else:
chain.append("No specific clinical rules triggered.")
chain.append("Standard pain assessment and management pathway recommended.")
return chain
def add_rule(self, rule: ClinicalRule):
"""
Add a custom clinical rule to the engine.
This allows for dynamic rule expansion and customization based on
specific clinical contexts or institutional guidelines.
Args:
rule: ClinicalRule instance to add
"""
self.rules.append(rule)
def get_rule_count(self) -> int:
"""Return the number of active rules in the engine."""
return len(self.rules)
def get_rule_ids(self) -> List[str]:
"""Return list of all rule IDs for reference."""
return [rule.rule_id for rule in self.rules]
|