Kulcare Docs
DeveloperCare plansLhr

Health Scoring System - Detailed Algorithms

Overview

This document provides detailed scoring algorithms that extend the health scoring implementation in service-architecture.md. The LHR health scoring system uses a 0-100 scale with four weighted components.

Component Scoring Algorithms

1. Vital Signs Score (25% weight)

Purpose: Assess physiological health based on recent vital sign measurements

def calculate_vital_score(patient)
  recent_vitals = patient.patient_vitals.recent.limit(10)
  return 85.0 if recent_vitals.empty? # Default score if no vitals
  
  normal_count = 0
  total_measurements = 0
  
  recent_vitals.each do |vital|
    # Blood Pressure Assessment
    if vital.systolic_bp.present? && vital.diastolic_bp.present?
      total_measurements += 1
      if vital_bp_normal?(vital.systolic_bp, vital.diastolic_bp)
        normal_count += 1
      end
    end
    
    # Heart Rate Assessment
    if vital.heart_rate.present?
      total_measurements += 1
      if vital_hr_normal?(vital.heart_rate)
        normal_count += 1
      end
    end
    
    # Temperature Assessment
    if vital.temperature.present?
      total_measurements += 1
      if vital_temp_normal?(vital.temperature)
        normal_count += 1
      end
    end
  end
  
  return 85.0 if total_measurements == 0
  
  base_score = (normal_count.to_f / total_measurements * 100).round(2)
  
  # Bonus for recent measurements (within 30 days)
  recent_bonus = recent_vitals.where('created_at > ?', 30.days.ago).any? ? 5 : 0
  
  final_score = base_score + recent_bonus
  [[final_score, 100].min, 0].max.round(2)
end

private

def vital_bp_normal?(systolic, diastolic)
  # Normal BP: Systolic 90-130, Diastolic 60-85
  systolic.between?(90, 130) && diastolic.between?(60, 85)
end

def vital_hr_normal?(heart_rate)
  # Normal heart rate: 60-100 bpm
  heart_rate.between?(60, 100)
end

def vital_temp_normal?(temperature)
  # Normal temperature: 97.0-99.5°F (36.1-37.5°C)
  if temperature > 35 && temperature < 40  # Celsius
    temperature.between?(36.1, 37.5)
  else  # Fahrenheit
    temperature.between?(97.0, 99.5)
  end
end

2. Problem Severity Score (30% weight)

Purpose: Assess health impact of active medical problems

def calculate_problem_score(patient)
  active_problems = patient.encounter_complaints.where(problem_status: ['active', 'chronic'])
  base_score = 100.0
  
  # Deduct points based on problem severity and type
  active_problems.each do |problem|
    deduction = calculate_problem_deduction(problem)
    base_score -= deduction
  end
  
  # Additional penalties for problem patterns
  chronic_penalty = calculate_chronic_condition_penalty(active_problems)
  unresolved_penalty = calculate_unresolved_problem_penalty(active_problems)
  
  final_score = base_score - chronic_penalty - unresolved_penalty
  [[final_score, 100].min, 0].max.round(2)
end

private

def calculate_problem_deduction(problem)
  # Base deduction by severity
  severity_deduction = case problem.severity_level
                      when 'critical' then 25
                      when 'severe' then 20
                      when 'moderate' then 12
                      when 'mild' then 6
                      else 10 # unknown severity
                      end
  
  # Multipliers for problem characteristics
  multiplier = 1.0
  
  # Chronic conditions have greater impact
  multiplier *= 1.3 if problem.chronic_condition?
  
  # Long-standing problems without resolution
  if problem.onset_date.present?
    days_active = (Date.current - problem.onset_date).to_i
    if days_active > 365  # More than 1 year
      multiplier *= 1.2
    elsif days_active > 90  # More than 3 months
      multiplier *= 1.1
    end
  end
  
  # Problems affecting health score calculation
  multiplier *= 1.1 if problem.affects_health_score?
  
  (severity_deduction * multiplier).round(2)
end

def calculate_chronic_condition_penalty(problems)
  chronic_count = problems.select(&:chronic_condition?).count
  
  # Progressive penalty for multiple chronic conditions
  case chronic_count
  when 0 then 0
  when 1 then 2
  when 2 then 5
  when 3 then 10
  else 15  # 4+ chronic conditions
  end
end

def calculate_unresolved_problem_penalty(problems)
  old_unresolved = problems.select do |p|
    p.onset_date.present? && 
    p.onset_date < 6.months.ago && 
    p.problem_status == 'active'
  end
  
  old_unresolved.count * 3  # 3 points per old unresolved problem
end

3. Medication Adherence Score (25% weight)

Purpose: Assess medication management and safety

def calculate_medication_score(patient)
  active_medications = patient.encounter_medicines.where(medication_status: 'active')
  return 95.0 if active_medications.empty?  # Good score if no medications needed
  
  base_score = 90.0  # Start with high baseline
  
  # Medication management factors
  chronic_medication_bonus = calculate_chronic_medication_bonus(active_medications)
  interaction_penalty = calculate_interaction_penalty(active_medications)
  # Note: adherence bonus removed for MVP - will be added in future phases
  adherence_bonus = 0
  complexity_penalty = calculate_medication_complexity_penalty(active_medications)
  
  final_score = base_score + chronic_medication_bonus + adherence_bonus - interaction_penalty - complexity_penalty
  [[final_score, 100].min, 0].max.round(2)
end

private

def calculate_chronic_medication_bonus(medications)
  chronic_meds = medications.where(prescription_type: 'chronic')
  
  # Bonus for properly tracking chronic medications
  if chronic_meds.count > 0
    5  # Bonus for having chronic medications tracked
  else
    0  # No bonus if no chronic medications
  end
end

def calculate_interaction_penalty(medications)
  interactions = []
  
  medications.each do |med|
    if med.drug_interactions.present? && med.drug_interactions.any?
      interactions.concat(med.drug_interactions)
    end
  end
  
  # Penalty based on interaction severity
  penalty = 0
  interactions.each do |interaction|
    case interaction['severity']
    when 'high' then penalty += 8
    when 'moderate' then penalty += 5
    when 'low' then penalty += 2
    end
  end
  
  penalty
end

# Note: calculate_adherence_bonus method removed for MVP - adherence tracking will be added in future phases

def calculate_medication_complexity_penalty(medications)
  # Penalty for high medication burden
  med_count = medications.count
  
  case med_count
  when 0..2 then 0
  when 3..5 then 2
  when 6..8 then 5
  when 9..12 then 8
  else 12  # 13+ medications
  end
end

4. Preventive Care Score (20% weight)

Purpose: Assess engagement with preventive healthcare measures

def calculate_preventive_care_score(patient)
  age = patient.patient_age
  gender = patient.gender&.downcase
  
  # Age-based baseline expectations
  baseline_score = age_based_preventive_baseline(age)
  
  # Specific preventive care assessments
  immunization_score = calculate_immunization_score(patient, age)
  screening_score = calculate_screening_score(patient, age, gender)
  wellness_visit_score = calculate_wellness_visit_score(patient)
  
  # Weighted combination
  final_score = (
    baseline_score * 0.4 +
    immunization_score * 0.3 +
    screening_score * 0.2 +
    wellness_visit_score * 0.1
  )
  
  [[final_score, 100].min, 0].max.round(2)
end

private

def age_based_preventive_baseline(age)
  case age
  when 0..2 then 90    # Infants - high preventive care engagement expected
  when 3..17 then 85   # Children - good preventive care expected
  when 18..25 then 70  # Young adults - often skip preventive care
  when 26..40 then 75  # Adults - moderate preventive care
  when 41..65 then 80  # Middle age - higher health awareness
  else 85              # Seniors - typically good healthcare engagement
  end
end

def calculate_immunization_score(patient, age)
  # Simplified immunization scoring
  recent_immunizations = patient.patient_immunizations
                               .where('created_at > ?', 1.year.ago)
                               .count
  
  if recent_immunizations >= 1
    90  # Has recent immunizations
  else
    60  # No recent immunizations
  end
end

def calculate_screening_score(patient, age, gender)
  # Basic screening score
  75  # Default score - actual implementation would check screening records
end

def calculate_wellness_visit_score(patient)
  recent_encounters = patient.encounters
                            .where('created_at > ?', 1.year.ago)
                            .where(encounter_type: ['routine', 'followup'])
                            .count
  
  case recent_encounters
  when 0 then 60      # No wellness visits
  when 1 then 80      # One wellness visit
  else 90             # Good engagement
  end
end

Trend Analysis

def calculate_health_score_trend(patient, period_months = 6)
  scores = patient.patient_health_scores
                 .where('calculated_date >= ?', period_months.months.ago)
                 .order(:calculated_date)
  
  return 'insufficient_data' if scores.count < 2
  
  # Simple trend based on first and last scores
  first_score = scores.first.overall_score
  last_score = scores.last.overall_score
  change = last_score - first_score
  
  # Categorize trend
  if change > 5
    'improving'
  elsif change < -5
    'declining'  
  else
    'stable'
  end
end

Health Score Interpretation

Clinical Risk Stratification

def interpret_health_score(score)
  interpretation = {
    score: score,
    category: score_category(score),
    risk_level: risk_level(score),
    recommendations: generate_recommendations(score),
    alert_level: alert_level(score)
  }
end

private

def score_category(score)
  case score
  when 90..100 then 'excellent'
  when 80..89 then 'good'
  when 70..79 then 'fair'  
  when 60..69 then 'poor'
  else 'critical'
  end
end

def risk_level(score)
  case score
  when 90..100 then 'low'
  when 70..89 then 'moderate'
  when 50..69 then 'high'
  else 'very_high'
  end
end

def generate_recommendations(score)
  recommendations = []
  
  if score < 60
    recommendations << 'Immediate clinical review recommended'
    recommendations << 'Consider care coordination with specialists'
  elsif score < 70
    recommendations << 'Enhanced monitoring and follow-up needed'
    recommendations << 'Review medication adherence and problem management'
  elsif score < 80
    recommendations << 'Focus on preventive care and lifestyle modifications'
    recommendations << 'Regular monitoring recommended'
  else
    recommendations << 'Continue current care approach'
    recommendations << 'Maintain preventive care schedule'
  end
  
  recommendations
end

def alert_level(score)
  case score
  when 0..49 then 'critical'
  when 50..59 then 'high'
  when 60..69 then 'medium'
  else 'low'
  end
end

Key Algorithms Provided:

✅ Vital Signs Scoring: Normal range assessments for BP, heart rate, temperature
✅ Problem Severity Calculation: Deduction-based scoring with chronic condition factors
✅ Medication Safety Scoring: Interaction penalties and chronic medication tracking
✅ Preventive Care Assessment: Simplified immunization and wellness visit tracking
✅ Score Interpretation: Category thresholds and risk levels

Note: This document complements the health scoring implementation in service-architecture.md by providing detailed algorithms for specific calculations.

On this page