Kulcare Docs
DeveloperCare plansLhr

Clinical Coding Support - Basic Implementation

Overview

The LHR system includes basic clinical coding support for India and USA to standardize medical terminology. This enables interoperability and compliance with healthcare standards in both countries.

Supported Code Systems:

  • India: ICD-10 (mandatory for insurance claims)
  • USA: ICD-10-CM, CPT (procedures), RxNorm (medications)

Database Schema

Clinical Codes Lookup Table (Simplified)

# db/migrate/[timestamp]_create_clinical_codes.rb
class CreateClinicalCodes < ActiveRecord::Migration[7.0]
  def change
    create_table :clinical_codes do |t|
      # Core fields
      t.string :code_system, null: false # 'ICD10', 'ICD10CM', 'CPT', 'RxNorm'
      t.string :code, null: false # The actual code (e.g., 'E11.9', '99213')
      t.string :display_name, null: false # Human readable name
      t.text :description # Detailed description
      
      # Country and category
      t.string :country # 'IN' for India, 'US' for USA, null for both
      t.string :category # 'diagnosis', 'procedure', 'medication'
      t.string :status, default: 'active' # active, deprecated
      
      # Search optimization
      t.text :searchable_text # Concatenated searchable content
      
      # Usage tracking
      t.integer :usage_count, default: 0
      t.datetime :last_used_at
      
      t.timestamps
    end
    
    # Indexes for performance
    add_index :clinical_codes, [:code_system, :code], unique: true
    add_index :clinical_codes, :display_name
    add_index :clinical_codes, [:country, :category]
    add_index :clinical_codes, :searchable_text, using: :gin
    add_index :clinical_codes, :usage_count
  end
end

Model Implementation

ClinicalCode Model (Simplified)

# app/models/clinical_code.rb
class ClinicalCode < ApplicationRecord
  # Validations
  validates :code_system, :code, :display_name, presence: true
  validates :code, uniqueness: { scope: :code_system }
  
  # Callbacks
  before_save :generate_searchable_text
  
  # Scopes
  scope :active, -> { where(status: 'active') }
  scope :for_country, ->(country) { where(country: [country, nil]) }
  scope :for_category, ->(category) { where(category: category) }
  scope :diagnosis_codes, -> { where(category: 'diagnosis') }
  scope :procedure_codes, -> { where(category: 'procedure') }
  scope :medication_codes, -> { where(category: 'medication') }
  
  # Search methods
  def self.search(query, options = {})
    scope = active
    
    # Filter by country if clinic country is provided
    if options[:country].present?
      scope = scope.for_country(options[:country])
    end
    
    # Filter by category
    if options[:category].present?
      scope = scope.for_category(options[:category])
    end
    
    # Filter by code system
    if options[:code_system].present?
      scope = scope.where(code_system: options[:code_system])
    end
    
    # Search in code and display name
    if query.present?
      search_term = "%#{query.downcase}%"
      scope = scope.where(
        "LOWER(code) LIKE ? OR LOWER(display_name) LIKE ? OR LOWER(searchable_text) LIKE ?",
        search_term, search_term, search_term
      )
      
      # Order by relevance: exact code match, exact name match, then others
      scope = scope.order(
        Arel.sql("CASE 
          WHEN LOWER(code) = '#{query.downcase}' THEN 1
          WHEN LOWER(display_name) = '#{query.downcase}' THEN 2  
          ELSE 3 END, usage_count DESC")
      )
    end
    
    scope.limit(options[:limit] || 10)
  end
  
  # Instance methods
  def full_display
    "#{code} - #{display_name}"
  end
  
  def record_usage!
    increment!(:usage_count)
    touch(:last_used_at)
  end
  
  private
  
  def generate_searchable_text
    self.searchable_text = [code, display_name, description].compact.join(' ').downcase
  end
end

Integration with Encounters

Add Clinical Codes to Complaints

# Add to encounter_complaints table
add_column :encounter_complaints, :icd10_code, :string
add_column :encounter_complaints, :icd11_code, :string # Future support

Add Medication Codes

# Add to encounter_medicines table
add_column :encounter_medicines, :ndc_code, :string # National Drug Code (USA)
add_column :encounter_medicines, :generic_name, :string

Service Layer

Clinical Coding Service (Simplified)

# app/services/clinical_coding_service.rb
class ClinicalCodingService
  attr_reader :country
  
  def initialize(clinic_country = 'IN')
    @country = clinic_country # 'IN' for India, 'US' for USA
  end
  
  # Search for diagnosis codes based on text
  def find_diagnosis_codes(text, limit: 5)
    return [] if text.blank?
    
    # Determine which code system to use based on country
    code_system = @country == 'US' ? 'ICD10CM' : 'ICD10'
    
    ClinicalCode.search(text, 
      country: @country,
      category: 'diagnosis',
      code_system: code_system,
      limit: limit
    )
  end
  
  # Search for procedure codes (USA only)
  def find_procedure_codes(text, limit: 5)
    return [] unless @country == 'US' && text.present?
    
    ClinicalCode.search(text,
      country: 'US',
      category: 'procedure', 
      code_system: 'CPT',
      limit: limit
    )
  end
  
  # Search for medication codes
  def find_medication_codes(text, limit: 5)
    return [] if text.blank?
    
    ClinicalCode.search(text,
      country: @country,
      category: 'medication',
      code_system: 'RxNorm',
      limit: limit
    )
  end
  
  # Get commonly used diagnosis codes
  def common_diagnosis_codes
    ClinicalCode
      .diagnosis_codes
      .for_country(@country)
      .where('usage_count > ?', 10)
      .order(usage_count: :desc)
      .limit(20)
  end
  
  # Get commonly used codes by specialty
  def codes_by_specialty(specialty)
    # Map specialties to common conditions
    specialty_conditions = {
      'cardiology' => ['hypertension', 'heart', 'cardiac', 'coronary'],
      'diabetology' => ['diabetes', 'glucose', 'insulin'],
      'general' => ['fever', 'cough', 'cold', 'headache']
    }
    
    search_terms = specialty_conditions[specialty.downcase] || []
    results = []
    
    search_terms.each do |term|
      results += find_diagnosis_codes(term, limit: 3)
    end
    
    results.uniq.take(10)
  end
end

API Endpoints

Clinical Codes Controller

# app/controllers/api/clinical_codes_controller.rb  
class Api::ClinicalCodesController < ApiController
  before_action :authenticate_user!
  before_action :set_clinic_country
  
  # GET /api/clinical_codes/search?q=diabetes&category=diagnosis
  def search
    service = ClinicalCodingService.new(@clinic_country)
    
    codes = case params[:category]
            when 'diagnosis'
              service.find_diagnosis_codes(params[:q])
            when 'procedure'
              service.find_procedure_codes(params[:q])
            when 'medication'
              service.find_medication_codes(params[:q])
            else
              ClinicalCode.search(params[:q], country: @clinic_country)
            end
    
    render json: {
      query: params[:q],
      country: @clinic_country,
      results: codes.map { |code| serialize_code(code) }
    }
  end
  
  # GET /api/clinical_codes/common
  def common
    service = ClinicalCodingService.new(@clinic_country)
    
    codes = if params[:specialty].present?
              service.codes_by_specialty(params[:specialty])
            else
              service.common_diagnosis_codes
            end
    
    render json: {
      country: @clinic_country,
      codes: codes.map { |code| serialize_code(code) }
    }
  end
  
  private
  
  def set_clinic_country
    # Get country from user's clinic or default to India
    @clinic_country = current_user.clinic&.country || 'IN'
  end
  
  def serialize_code(code)
    {
      id: code.id,
      code_system: code.code_system,
      code: code.code,
      display_name: code.display_name,
      category: code.category,
      full_display: code.full_display
    }
  end
end

Seed Data Examples

# db/seeds/clinical_codes.rb
# Common ICD-10 codes for India
indian_codes = [
  { code: 'J06.9', display_name: 'Acute upper respiratory infection', description: 'Common cold' },
  { code: 'I10', display_name: 'Essential hypertension', description: 'High blood pressure' },
  { code: 'E11.9', display_name: 'Type 2 diabetes mellitus', description: 'Diabetes without complications' },
  { code: 'J44.0', display_name: 'COPD with acute lower respiratory infection', description: 'Chronic obstructive pulmonary disease' },
  { code: 'N39.0', display_name: 'Urinary tract infection', description: 'UTI' }
]

indian_codes.each do |code_data|
  ClinicalCode.create!(
    code_system: 'ICD10',
    code: code_data[:code],
    display_name: code_data[:display_name],
    description: code_data[:description],
    country: 'IN',
    category: 'diagnosis',
    status: 'active'
  )
end

# Common ICD-10-CM codes for USA
usa_diagnosis_codes = [
  { code: 'I10', display_name: 'Essential (primary) hypertension', description: 'High blood pressure' },
  { code: 'E11.9', display_name: 'Type 2 diabetes mellitus without complications', description: 'Diabetes' },
  { code: 'J06.9', display_name: 'Acute upper respiratory infection, unspecified', description: 'URI' }
]

usa_diagnosis_codes.each do |code_data|
  ClinicalCode.create!(
    code_system: 'ICD10CM',
    code: code_data[:code],
    display_name: code_data[:display_name],
    description: code_data[:description],
    country: 'US',
    category: 'diagnosis',
    status: 'active'
  )
end

# Common CPT procedure codes for USA
usa_procedure_codes = [
  { code: '99213', display_name: 'Office visit, established patient, 15 minutes', description: 'Level 3 office visit' },
  { code: '99214', display_name: 'Office visit, established patient, 25 minutes', description: 'Level 4 office visit' },
  { code: '90834', display_name: 'Psychotherapy, 45 minutes', description: 'Individual psychotherapy' }
]

usa_procedure_codes.each do |code_data|
  ClinicalCode.create!(
    code_system: 'CPT',
    code: code_data[:code],
    display_name: code_data[:display_name],
    description: code_data[:description],
    country: 'US',
    category: 'procedure',
    status: 'active'
  )
end

# Common RxNorm medication codes (used in both countries)
medication_codes = [
  { code: '29046', display_name: 'Lisinopril', description: 'ACE inhibitor for hypertension' },
  { code: '6809', display_name: 'Metformin', description: 'Diabetes medication' },
  { code: '1719', display_name: 'Azithromycin', description: 'Antibiotic' }
]

medication_codes.each do |code_data|
  ClinicalCode.create!(
    code_system: 'RxNorm',
    code: code_data[:code],
    display_name: code_data[:display_name],
    description: code_data[:description],
    country: nil, # Available in both countries
    category: 'medication',
    status: 'active'
  )
end

Usage in UI

// Frontend component for code selection
const DiagnosisCodeSelector = ({ country, onSelect }) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [codes, setCodes] = useState([]);
  
  const searchCodes = async (query) => {
    const response = await api.get('/api/clinical_codes/search', {
      params: { q: query, category: 'diagnosis' }
    });
    setCodes(response.data.results);
  };
  
  return (
    <div>
      <input
        type="text"
        placeholder={country === 'US' ? "Search ICD-10-CM codes..." : "Search ICD-10 codes..."}
        value={searchTerm}
        onChange={(e) => {
          setSearchTerm(e.target.value);
          if (e.target.value.length > 2) {
            searchCodes(e.target.value);
          }
        }}
      />
      
      <ul>
        {codes.map(code => (
          <li key={code.id} onClick={() => onSelect(code)}>
            {code.full_display}
          </li>
        ))}
      </ul>
    </div>
  );
};

Benefits of This Simplified Approach:

✅ Country-Specific: Supports both Indian (ICD-10) and USA (ICD-10-CM, CPT) requirements
✅ Simple Implementation: No complex hierarchies or relationships
✅ Fast Search: Optimized for quick code lookup
✅ Usage Tracking: Learns from user behavior
✅ Easy to Maintain: Straightforward data model

Next: See api-design.md for API implementation details.

On this page