Skip to main content

Overview

Laboratory result standardization is essential for healthcare interoperability. This guide demonstrates how to map local laboratory tests to LOINC (Logical Observation Identifiers Names and Codes) using the OMOPHub API, enabling consistent interpretation of lab results across different healthcare systems.
Use Case: Automatically map laboratory test codes and results to LOINC standards for data exchange, clinical decision support, and population health analytics.

Business Problem

Healthcare organizations face significant challenges with laboratory data:
  • Data Silos: Different labs use proprietary codes and naming conventions
  • Interoperability Issues: Unable to aggregate lab data across systems
  • Clinical Decision Support: Inconsistent reference ranges and units
  • Quality Reporting: Difficulty in population health analytics
  • Regulatory Compliance: HL7 FHIR and CMS requirements for standardized codes

Solution Architecture

Implementation Guide

Step 1: Set Up Laboratory Mapping Service

from omophub import OMOPHubClient
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass
from decimal import Decimal
import re
import logging

@dataclass
class LabResult:
    local_code: str
    local_name: str
    result_value: str
    result_unit: str
    reference_range: str
    specimen_type: str
    result_status: str
    id: Optional[str] = None
    collection_datetime: Optional[str] = None
    result_datetime: Optional[str] = None

@dataclass
class StandardizedLabResult:
    loinc_code: str
    loinc_name: str
    local_code: str
    local_name: str
    standardized_value: float
    standardized_unit: str
    standardized_reference_range: str
    specimen_type: str
    result_status: str
    conversion_applied: bool
    mapping_confidence: float

class LabResultMapper:
    def __init__(self, api_key: str):
        self.client = OMOPHubClient(api_key=api_key)
        self.logger = logging.getLogger(__name__)
        
        # Common unit conversions
        self.unit_conversions = {
            # Glucose: mg/dL to mmol/L
            ("glucose", "mg/dl", "mmol/l"): lambda x: x * 0.0555,
            ("glucose", "mmol/l", "mg/dl"): lambda x: x / 0.0555,
            
            # Cholesterol: mg/dL to mmol/L
            ("cholesterol", "mg/dl", "mmol/l"): lambda x: x * 0.02586,
            ("cholesterol", "mmol/l", "mg/dl"): lambda x: x / 0.02586,
            
            # Hemoglobin: g/dL to g/L
            ("hemoglobin", "g/dl", "g/l"): lambda x: x * 10,
            ("hemoglobin", "g/l", "g/dl"): lambda x: x / 10,
            
            # Creatinine: mg/dL to μmol/L (clinical factor: 1 mg/dL = 88.4 μmol/L)
            ("creatinine", "mg/dl", "umol/l"): lambda x: x * 88.4,
            ("creatinine", "umol/l", "mg/dl"): lambda x: x * 0.0113,
        }
        
        # Common specimen types mapping
        self.specimen_mapping = {
            "serum": "SER",
            "plasma": "PLAS", 
            "whole blood": "BLD",
            "urine": "UR",
            "cerebrospinal fluid": "CSF",
            "saliva": "SAL"
        }
    
    def map_lab_result(self, lab_result: LabResult) -> Optional[StandardizedLabResult]:
        """Map a single lab result to LOINC standard"""
        try:
            # Step 1: Find LOINC code
            loinc_mapping = self.find_loinc_code(lab_result)
            if not loinc_mapping:
                self.logger.warning(f"No LOINC mapping found for {lab_result.local_name}")
                return None
            
            # Step 2: Standardize units and values
            standardized_value, standardized_unit = self.standardize_units(
                lab_result.result_value,
                lab_result.result_unit,
                loinc_mapping["preferred_unit"],
                lab_result.local_name  # Use descriptive name for analyte identification
            )
            
            # Step 3: Standardize reference range
            standardized_ref_range = self.standardize_reference_range(
                lab_result.reference_range,
                lab_result.result_unit,
                standardized_unit,
                loinc_mapping["loinc_name"]
            )
            
            return StandardizedLabResult(
                loinc_code=loinc_mapping["loinc_code"],
                loinc_name=loinc_mapping["loinc_name"],
                local_code=lab_result.local_code,
                local_name=lab_result.local_name,
                standardized_value=standardized_value,
                standardized_unit=standardized_unit,
                standardized_reference_range=standardized_ref_range,
                specimen_type=self.standardize_specimen_type(lab_result.specimen_type),
                result_status=lab_result.result_status,
                conversion_applied=lab_result.result_unit.lower() != standardized_unit.lower(),
                mapping_confidence=loinc_mapping["confidence"]
            )
            
        except Exception as e:
            self.logger.error(f"Error mapping lab result {lab_result.local_name}: {e}")
            return None
    
    def find_loinc_code(self, lab_result: LabResult) -> Optional[Dict[str, Any]]:
        """Find appropriate LOINC code for lab test"""
        # Search strategies in order of preference
        search_strategies = [
            # Strategy 1: Exact local code lookup
            {
                "query": lab_result.local_code,
                "vocabularies": ["LOINC"],
                "domains": ["Measurement"],
                "search_type": "code"
            },
            # Strategy 2: Test name search
            {
                "query": lab_result.local_name,
                "vocabularies": ["LOINC"],
                "domains": ["Measurement"], 
                "search_type": "name"
            },
            # Strategy 3: Broader search with specimen context
            {
                "query": f"{lab_result.local_name} {lab_result.specimen_type}",
                "vocabularies": ["LOINC"],
                "domains": ["Measurement"],
                "search_type": "contextual"
            }
        ]
        
        for strategy in search_strategies:
            try:
                if strategy["search_type"] == "code":
                    # Direct code lookup
                    result = self.client.get_concept_by_code("LOINC", lab_result.local_code)
                    if result:
                        return {
                            "loinc_code": result["concept_code"],
                            "loinc_name": result["concept_name"],
                            "preferred_unit": self.extract_preferred_unit(result),
                            "confidence": 1.0
                        }
                else:
                    # Text search
                    search_results = self.client.search_concepts({
                        "query": strategy["query"],
                        "vocabularies": strategy["vocabularies"],
                        "domains": strategy["domains"],
                        "standard_concepts_only": True,
                        "limit": 10
                    })
                    
                    if search_results["concepts"]:
                        # Find best match considering specimen type
                        best_match = self.find_best_loinc_match(
                            search_results["concepts"],
                            lab_result
                        )
                        
                        if best_match:
                            return {
                                "loinc_code": best_match["concept_code"],
                                "loinc_name": best_match["concept_name"],
                                "preferred_unit": self.extract_preferred_unit(best_match),
                                "confidence": best_match.get("relevance_score", 0.8)
                            }
                            
            except Exception as e:
                self.logger.debug(f"Search strategy {strategy['search_type']} failed: {e}")
                continue
        
        return None
    
    def find_best_loinc_match(self, loinc_concepts: List[Dict[str, Any]], lab_result: LabResult) -> Optional[Dict[str, Any]]:
        """Find best LOINC match considering context"""
        scored_matches = []
        
        for concept in loinc_concepts:
            score = 0
            concept_name = concept["concept_name"].lower()
            local_name = lab_result.local_name.lower()
            
            # Base relevance score
            score += concept.get("relevance_score", 0.5)
            
            # Specimen type matching bonus
            specimen_keywords = {
                "serum": ["ser", "serum"],
                "plasma": ["plas", "plasma"],
                "whole blood": ["bld", "blood", "whole"],
                "urine": ["ur", "urine"],
                "csf": ["csf", "cerebrospinal"]
            }
            
            specimen_type = lab_result.specimen_type.lower()
            if specimen_type in specimen_keywords:
                keywords = specimen_keywords[specimen_type]
                if any(keyword in concept_name for keyword in keywords):
                    score += 0.2
            
            # Method matching bonus
            if any(method in concept_name for method in ["enzymatic", "immunoassay", "chromatography"]):
                score += 0.1
                
            scored_matches.append((score, concept))
        
        if scored_matches:
            # Sort by score and return best match
            scored_matches.sort(key=lambda x: x[0], reverse=True)
            return scored_matches[0][1]
        
        return None
    
    def extract_preferred_unit(self, loinc_concept: Dict[str, Any]) -> str:
        """Extract preferred unit from LOINC concept"""
        concept_name = loinc_concept.get("concept_name", "")
        
        # Common unit patterns in LOINC names
        unit_patterns = {
            r"mg/dL": "mg/dL",
            r"mmol/L": "mmol/L", 
            r"g/dL": "g/dL",
            r"g/L": "g/L",
            r"μmol/L": "μmol/L",
            r"umol/L": "μmol/L",
            r"mEq/L": "mEq/L",
            r"IU/L": "IU/L",
            r"U/L": "U/L",
            r"10\^9/L": "10^9/L",
            r"10\^6/μL": "10^6/μL"
        }
        
        for pattern, unit in unit_patterns.items():
            if re.search(pattern, concept_name, re.IGNORECASE):
                return unit
        
        # Default fallback
        return "units"

Step 2: Unit Standardization and Conversion

def standardize_units(self, result_value: str, current_unit: str, preferred_unit: str, analyte_name: str) -> Tuple[float, str]:
    """Standardize lab result units
    
    Args:
        result_value: Numeric result value as string
        current_unit: Original unit of measurement
        preferred_unit: Target unit for standardization
        analyte_name: Descriptive name/hint for the analyte (e.g., "glucose", "cholesterol")
    """
    try:
        # Parse numeric value
        numeric_value = self.parse_numeric_value(result_value)
        if numeric_value is None:
            return float('nan'), current_unit
        
        # Normalize unit strings
        current_unit_norm = self.normalize_unit_string(current_unit)
        preferred_unit_norm = self.normalize_unit_string(preferred_unit)
        
        # If units are the same, no conversion needed
        if current_unit_norm == preferred_unit_norm:
            return numeric_value, preferred_unit
        
        # Find conversion factor using analyte name
        conversion_factor = self.find_unit_conversion(
            analyte_name, current_unit_norm, preferred_unit_norm
        )
        
        if conversion_factor:
            converted_value = conversion_factor(numeric_value)
            return converted_value, preferred_unit
        else:
            # No conversion available, keep original
            self.logger.warning(f"No conversion available from {current_unit} to {preferred_unit}")
            return numeric_value, current_unit
            
    except Exception as e:
        self.logger.error(f"Error in unit standardization: {e}")
        return float('nan'), current_unit

def parse_numeric_value(self, result_value: str) -> Optional[float]:
    """Extract numeric value from result string"""
    # Handle common result formats
    result_value = result_value.strip()
    
    # Handle qualitative results
    qualitative_mappings = {
        "positive": 1.0,
        "negative": 0.0,
        "detected": 1.0,
        "not detected": 0.0,
        "reactive": 1.0,
        "non-reactive": 0.0
    }
    
    if result_value.lower() in qualitative_mappings:
        return qualitative_mappings[result_value.lower()]
    
    # Handle numeric ranges (e.g., "5.0-10.0")
    range_match = re.match(r'(\d+\.?\d*)\s*-\s*(\d+\.?\d*)', result_value)
    if range_match:
        # Take the midpoint of the range
        lower = float(range_match.group(1))
        upper = float(range_match.group(2))
        return (lower + upper) / 2
    
    # Handle comparison operators (e.g., ">100", "<0.5")
    comparison_match = re.match(r'[<>=]+\s*(\d+\.?\d*)', result_value)
    if comparison_match:
        return float(comparison_match.group(1))
    
    # Handle standard numeric values
    numeric_match = re.search(r'(\d+\.?\d*)', result_value)
    if numeric_match:
        return float(numeric_match.group(1))
    
    return None

def normalize_unit_string(self, unit: str) -> str:
    """Normalize unit string for comparison and handle micro symbols"""
    if not unit:
        return ""
    
    # First handle micro symbols
    unit_cleaned = unit.replace("μ", "u").replace("µ", "u")
    normalized = unit_cleaned.strip().lower()
    
    # Common unit normalizations
    normalizations = {
        "mg/dl": "mg/dl",
        "mg/dL": "mg/dl", 
        "MG/DL": "mg/dl",
        "mmol/l": "mmol/l",
        "mmol/L": "mmol/l",
        "MMOL/L": "mmol/l",
        "g/dl": "g/dl",
        "g/dL": "g/dl",
        "G/DL": "g/dl",
        "g/l": "g/l",
        "g/L": "g/l",
        "G/L": "g/l",
        # Micro symbol variants (all converted to u)
        "umol/l": "umol/l",
        "umol/L": "umol/l",
        "UMOL/L": "umol/l",
        "μmol/l": "umol/l",
        "μmol/L": "umol/l",
        "µmol/l": "umol/l", 
        "µmol/L": "umol/l",
        # Microgram variants
        "mcg": "ug",
        "μg": "ug",
        "µg": "ug",
        "ug": "ug"
    }
    
    # Apply direct mappings
    for original, standard in normalizations.items():
        if normalized == original.lower():
            return standard
    
    # Handle special characters
    normalized = normalized.replace("μ", "u").replace("µ", "u")
    
    return normalized

def find_unit_conversion(self, result_name: str, from_unit: str, to_unit: str) -> Optional[callable]:
    """Find appropriate unit conversion function"""
    # Determine analyte type from result name
    analyte_type = self.determine_analyte_type(result_name)
    
    # Look up conversion
    conversion_key = (analyte_type, from_unit, to_unit)
    
    return self.unit_conversions.get(conversion_key)

def determine_analyte_type(self, result_name: str) -> str:
    """Determine analyte type for unit conversion"""
    name_lower = result_name.lower()
    
    # Glucose variants
    if any(term in name_lower for term in ["glucose", "blood sugar", "bg"]):
        return "glucose"
    
    # Cholesterol variants
    if any(term in name_lower for term in ["cholesterol", "chol", "ldl", "hdl"]):
        return "cholesterol"
    
    # Hemoglobin variants
    if any(term in name_lower for term in ["hemoglobin", "hgb", "hb"]):
        return "hemoglobin"
    
    # Creatinine variants
    if any(term in name_lower for term in ["creatinine", "creat", "cr"]):
        return "creatinine"
    
    # Default
    return "unknown"

def standardize_reference_range(self, reference_range: str, original_unit: str, 
                              new_unit: str, analyte_name: str) -> str:
    """Standardize reference range with unit conversion"""
    if not reference_range or original_unit.lower() == new_unit.lower():
        return reference_range
    
    try:
        # Parse reference range (e.g., "3.5-5.0", "< 100", "> 10")
        range_patterns = [
            r'(\d+\.?\d*)\s*-\s*(\d+\.?\d*)',  # Range: "3.5-5.0"
            r'<\s*(\d+\.?\d*)',                 # Less than: "< 100"
            r'>\s*(\d+\.?\d*)',                 # Greater than: "> 10"
            r'(\d+\.?\d*)'                      # Single value: "100"
        ]
        
        for pattern in range_patterns:
            match = re.search(pattern, reference_range)
            if match:
                # Get conversion function
                analyte_type = self.determine_analyte_type(analyte_name)
                original_unit_norm = self.normalize_unit_string(original_unit)
                new_unit_norm = self.normalize_unit_string(new_unit)
                
                conversion_func = self.unit_conversions.get(
                    (analyte_type, original_unit_norm, new_unit_norm)
                )
                
                if conversion_func:
                    if len(match.groups()) == 2:  # Range
                        lower = conversion_func(float(match.group(1)))
                        upper = conversion_func(float(match.group(2)))
                        return f"{lower:.2f}-{upper:.2f}"
                    else:  # Single value or comparison
                        converted = conversion_func(float(match.group(1)))
                        if '<' in reference_range:
                            return f"< {converted:.2f}"
                        elif '>' in reference_range:
                            return f"> {converted:.2f}"
                        else:
                            return f"{converted:.2f}"
                
                break
        
        return reference_range  # Return original if no conversion possible
        
    except Exception as e:
        self.logger.error(f"Error standardizing reference range: {e}")
        return reference_range

def standardize_specimen_type(self, specimen_type: str) -> str:
    """Standardize specimen type to common abbreviations"""
    if not specimen_type:
        return "Unknown"
    
    specimen_lower = specimen_type.lower().strip()
    
    return self.specimen_mapping.get(specimen_lower, specimen_type)

Step 3: Batch Lab Processing and FHIR Generation

import math
from datetime import datetime

def process_lab_batch(self, lab_results: List[LabResult], patient_id: str) -> Dict[str, Any]:
    """Process multiple lab results for a patient"""
    processed_results = []
    mapping_summary = {
        "total_results": len(lab_results),
        "successful_mappings": 0,
        "failed_mappings": 0,
        "unit_conversions": 0,
        "confidence_scores": []
    }
    
    for lab_result in lab_results:
        try:
            standardized = self.map_lab_result(lab_result)
            if standardized:
                processed_results.append(standardized)
                mapping_summary["successful_mappings"] += 1
                mapping_summary["confidence_scores"].append(standardized.mapping_confidence)
                
                if standardized.conversion_applied:
                    mapping_summary["unit_conversions"] += 1
            else:
                mapping_summary["failed_mappings"] += 1
                
        except Exception as e:
            self.logger.error(f"Error processing lab result {lab_result.local_name}: {e}")
            mapping_summary["failed_mappings"] += 1
    
    # Generate FHIR resources
    fhir_resources = self.generate_fhir_resources(processed_results, patient_id)
    
    # Calculate quality metrics
    quality_metrics = self.calculate_quality_metrics(processed_results, mapping_summary)
    
    return {
        "patient_id": patient_id,
        "processed_results": processed_results,
        "fhir_resources": fhir_resources,
        "mapping_summary": mapping_summary,
        "quality_metrics": quality_metrics,
        "processed_at": datetime.now().isoformat()
    }

def generate_fhir_resources(self, standardized_results: List[StandardizedLabResult], 
                          patient_id: str) -> List[Dict[str, Any]]:
    """Generate FHIR Specimen and Observation resources for lab results"""
    fhir_resources = []
    
    for result in standardized_results:
        # Generate deterministic specimen ID
        if hasattr(result, 'id') and result.id:
            specimen_id = f"{result.id}-specimen"
        else:
            # Create deterministic ID from patient + local_code + timestamp
            specimen_id = f"spec-{patient_id}-{result.local_code}-{int(datetime.now().timestamp())}"
        
        # Create FHIR Specimen resource first
        specimen = {
            "resourceType": "Specimen",
            "id": specimen_id,
            "subject": {
                "reference": f"Patient/{patient_id}"
            },
            "type": {
                "coding": [{
                    "system": "http://snomed.info/sct",
                    "code": self.get_snomed_specimen_code(result.specimen_type),
                    "display": result.specimen_type
                }]
            },
            "collection": {
                "collectedDateTime": (
                    getattr(result, 'collection_datetime', None) or
                    getattr(result, 'result_datetime', None) or
                    datetime.now().isoformat()
                )
            }
        }
        
        # Add specimen to resources
        fhir_resources.append(specimen)
        
        # Determine value type
        if isinstance(result.standardized_value, (int, float)) and not math.isnan(result.standardized_value):
            value_element = {
                "valueQuantity": {
                    "value": result.standardized_value,
                    "unit": result.standardized_unit,
                    "system": "http://unitsofmeasure.org",
                    "code": self.get_ucum_code(result.standardized_unit)
                }
            }
        else:
            # Handle qualitative results
            value_element = {
                "valueString": str(result.standardized_value)
            }
        
        # Create FHIR Observation resource
        observation = {
            "resourceType": "Observation",
            "id": f"lab-{result.local_code}-{int(datetime.now().timestamp())}",
            "status": self.map_result_status(result.result_status),
            "category": [{
                "coding": [{
                    "system": "http://terminology.hl7.org/CodeSystem/observation-category",
                    "code": "laboratory",
                    "display": "Laboratory"
                }]
            }],
            "code": {
                "coding": [
                    {
                        "system": "http://loinc.org",
                        "code": result.loinc_code,
                        "display": result.loinc_name
                    },
                    {
                        "system": "http://example.org/local-codes",
                        "code": result.local_code,
                        "display": result.local_name
                    }
                ]
            },
            "subject": {
                "reference": f"Patient/{patient_id}"
            },
            "specimen": {
                "reference": f"Specimen/{specimen_id}",
                "display": result.specimen_type
            },
            **value_element
        }
        
        # Add reference range if available
        if result.standardized_reference_range and result.standardized_reference_range != "":
            ref_range = self.parse_reference_range(result.standardized_reference_range)
            if ref_range:
                observation["referenceRange"] = [{
                    "low": {"value": ref_range["low"], "unit": result.standardized_unit} if ref_range.get("low") else None,
                    "high": {"value": ref_range["high"], "unit": result.standardized_unit} if ref_range.get("high") else None,
                    "type": {
                        "coding": [{
                            "system": "http://terminology.hl7.org/CodeSystem/referencerange-meaning",
                            "code": "normal",
                            "display": "Normal Range"
                        }]
                    }
                }]
        
        fhir_resources.append(observation)
    
    return fhir_resources

def get_snomed_specimen_code(self, specimen_type: str) -> str:
    """Get SNOMED code for specimen type"""
    specimen_mappings = {
        "serum": "119364003",
        "plasma": "119361006",
        "whole blood": "119297000",
        "urine": "122575003",
        "cerebrospinal fluid": "258450006",
        "saliva": "119342007",
        "tissue": "119376003"
    }
    return specimen_mappings.get(specimen_type.lower(), "123038009")  # Default: specimen

def parse_reference_range(self, range_str: str) -> Optional[Dict[str, float]]:
    """Parse reference range string into low/high values"""
    if not range_str:
        return None
    
    import re
    
    # Handle various reference range formats
    patterns = [
        (r'^([\d.]+)\s*-\s*([\d.]+)$', 'range'),  # "70-100"
        (r'^<\s*([\d.]+)$', 'upper'),             # "<200"
        (r'^>\s*([\d.]+)$', 'lower'),             # ">5"
        (r'^([\d.]+)\s*-\s*$', 'lower_only'),     # "70-"
        (r'^\s*-\s*([\d.]+)$', 'upper_only')      # "-100"
    ]
    
    for pattern, range_type in patterns:
        match = re.match(pattern, range_str.strip())
        if match:
            if range_type == 'range':
                return {
                    'low': float(match.group(1)),
                    'high': float(match.group(2))
                }
            elif range_type == 'upper':
                return {'low': None, 'high': float(match.group(1))}
            elif range_type == 'lower':
                return {'low': float(match.group(1)), 'high': None}
            elif range_type == 'lower_only':
                return {'low': float(match.group(1)), 'high': None}
            elif range_type == 'upper_only':
                return {'low': None, 'high': float(match.group(1))}
    
    return None

def get_ucum_code(self, unit: str) -> str:
    """Get UCUM code for unit"""
    ucum_mappings = {
        "mg/dL": "mg/dL",
        "mmol/L": "mmol/L",
        "g/dL": "g/dL", 
        "g/L": "g/L",
        "μmol/L": "umol/L",
        "mEq/L": "meq/L",
        "IU/L": "[IU]/L",
        "U/L": "U/L"
    }
    
    return ucum_mappings.get(unit, unit)

def map_result_status(self, local_status: str) -> str:
    """Map local result status to FHIR status"""
    status_mappings = {
        "final": "final",
        "complete": "final", 
        "resulted": "final",
        "preliminary": "preliminary",
        "pending": "preliminary",
        "corrected": "amended",
        "amended": "amended",
        "cancelled": "cancelled"
    }
    
    return status_mappings.get(local_status.lower(), "final")

def calculate_quality_metrics(self, results: List[StandardizedLabResult], 
                            mapping_summary: Dict[str, Any]) -> Dict[str, Any]:
    """Calculate mapping quality metrics"""
    if not results:
        return {"overall_quality": 0.0}
    
    # Calculate average confidence
    confidence_scores = mapping_summary.get("confidence_scores", [])
    avg_confidence = sum(confidence_scores) / len(confidence_scores) if confidence_scores else 0.0
    
    # Calculate mapping success rate
    total_results = mapping_summary["total_results"]
    success_rate = mapping_summary["successful_mappings"] / total_results if total_results > 0 else 0.0
    
    # Calculate unit standardization rate
    unit_conversion_rate = mapping_summary["unit_conversions"] / len(results) if results else 0.0
    
    # Overall quality score (weighted average)
    overall_quality = (
        avg_confidence * 0.4 +
        success_rate * 0.4 +
        unit_conversion_rate * 0.2
    )
    
    return {
        "overall_quality": overall_quality,
        "average_confidence": avg_confidence,
        "mapping_success_rate": success_rate,
        "unit_conversion_rate": unit_conversion_rate,
        "total_processed": len(results),
        "quality_grade": self.get_quality_grade(overall_quality)
    }

def get_quality_grade(self, quality_score: float) -> str:
    """Convert quality score to letter grade"""
    if quality_score >= 0.9:
        return "A"
    elif quality_score >= 0.8:
        return "B"
    elif quality_score >= 0.7:
        return "C"
    elif quality_score >= 0.6:
        return "D"
    else:
        return "F"

Example Implementation

Sample Lab Results

Lab Results for Patient ID: PT12345
1. Glucose: 126 mg/dL (Ref: 70-100 mg/dL) - Serum
2. Total Cholesterol: 245 mg/dL (Ref: <200 mg/dL) - Serum  
3. Hemoglobin: 12.5 g/dL (Ref: 12.0-15.5 g/dL) - Whole Blood
4. Creatinine: 1.2 mg/dL (Ref: 0.7-1.3 mg/dL) - Serum
5. Urinalysis Protein: Trace (Ref: Negative) - Urine

Processing Report

# Initialize the mapper
mapper = LabResultMapper("your_api_key")

# Sample lab results
lab_results = [
    LabResult(
        local_code="GLUC",
        local_name="Glucose",
        result_value="126",
        result_unit="mg/dL",
        reference_range="70-100",
        specimen_type="serum",
        result_status="final"
    ),
    LabResult(
        local_code="CHOL",
        local_name="Total Cholesterol", 
        result_value="245",
        result_unit="mg/dL",
        reference_range="<200",
        specimen_type="serum",
        result_status="final"
    ),
    LabResult(
        local_code="HGB",
        local_name="Hemoglobin",
        result_value="12.5", 
        result_unit="g/dL",
        reference_range="12.0-15.5",
        specimen_type="whole blood",
        result_status="final"
    ),
    LabResult(
        local_code="CREAT",
        local_name="Creatinine",
        result_value="1.2",
        result_unit="mg/dL", 
        reference_range="0.7-1.3",
        specimen_type="serum",
        result_status="final"
    )
]

# Process batch
report = mapper.process_lab_batch(lab_results, "PT12345")

# Print results
print("=== LABORATORY RESULT MAPPING REPORT ===")
print(f"Patient ID: {report['patient_id']}")
print(f"Quality Grade: {report['quality_metrics']['quality_grade']}")
print(f"Mapping Success Rate: {report['quality_metrics']['mapping_success_rate']:.1%}")

print("\n=== STANDARDIZED RESULTS ===")
for result in report['processed_results']:
    print(f"✅ {result.local_name}")
    print(f"   LOINC: {result.loinc_code} - {result.loinc_name}")
    print(f"   Value: {result.standardized_value} {result.standardized_unit}")
    print(f"   Conversion Applied: {'Yes' if result.conversion_applied else 'No'}")
    print(f"   Confidence: {result.mapping_confidence:.1%}")
    print()

print("=== FHIR RESOURCES GENERATED ===")
print(f"Generated {len(report['fhir_resources'])} FHIR Observation resources")
for resource in report['fhir_resources'][:2]:  # Show first 2
    print(f"  Resource ID: {resource['id']}")
    print(f"  LOINC Code: {resource['code']['coding'][0]['code']}")
    print(f"  Value: {resource.get('valueQuantity', resource.get('valueString', 'N/A'))}")
    print()

Expected Output

=== LABORATORY RESULT MAPPING REPORT ===
Patient ID: PT12345
Quality Grade: A
Mapping Success Rate: 100.0%

=== STANDARDIZED RESULTS ===
✅ Glucose
   LOINC: 2345-7 - Glucose [Mass/volume] in Serum or Plasma
   Value: 126.0 mg/dL
   Conversion Applied: No
   Confidence: 95.0%

✅ Total Cholesterol
   LOINC: 2093-3 - Cholesterol [Mass/volume] in Serum or Plasma
   Value: 245.0 mg/dL
   Conversion Applied: No
   Confidence: 92.0%

✅ Hemoglobin
   LOINC: 718-7 - Hemoglobin [Mass/volume] in Blood
   Value: 12.5 g/dL
   Conversion Applied: No
   Confidence: 98.0%

✅ Creatinine
   LOINC: 2160-0 - Creatinine [Mass/volume] in Serum or Plasma
   Value: 1.2 mg/dL
   Conversion Applied: No
   Confidence: 96.0%

=== FHIR RESOURCES GENERATED ===
Generated 4 FHIR Observation resources
  Resource ID: lab-GLUC-1703123456789
  LOINC Code: 2345-7
  Value: {value: 126.0, unit: 'mg/dL'}

  Resource ID: lab-CHOL-1703123456790  
  LOINC Code: 2093-3
  Value: {value: 245.0, unit: 'mg/dL'}

Integration Patterns

1. EHR Integration with HL7 FHIR

import requests

class FHIRLabIntegration:
    def __init__(self, omophub_api_key: str, fhir_server_url: str):
        self.mapper = LabResultMapper(omophub_api_key)
        self.fhir_server = fhir_server_url
        
    def sync_lab_results(self, patient_id: str, local_results: List[LabResult]) -> Dict[str, Any]:
        """Sync local lab results to FHIR server"""
        
        # Process and standardize results
        report = self.mapper.process_lab_batch(local_results, patient_id)
        
        # Upload FHIR resources
        upload_results = []
        for fhir_resource in report['fhir_resources']:
            try:
                response = self.upload_fhir_observation(fhir_resource)
                upload_results.append({
                    "resource_id": fhir_resource['id'],
                    "status": "success",
                    "fhir_id": response.get('id'),
                    "server_url": f"{self.fhir_server}/Observation/{response.get('id')}"
                })
            except Exception as e:
                upload_results.append({
                    "resource_id": fhir_resource['id'],
                    "status": "failed",
                    "error": str(e)
                })
        
        return {
            "processing_report": report,
            "fhir_upload_results": upload_results,
            "summary": {
                "total_resources": len(report['fhir_resources']),
                "successful_uploads": len([r for r in upload_results if r['status'] == 'success']),
                "failed_uploads": len([r for r in upload_results if r['status'] == 'failed'])
            }
        }
    
    def upload_fhir_observation(self, observation: Dict[str, Any]) -> Dict[str, Any]:
        """Upload FHIR Observation to server"""
        headers = {
            'Content-Type': 'application/fhir+json',
            'Accept': 'application/fhir+json'
        }
        
        response = requests.post(
            f"{self.fhir_server}/Observation",
            json=observation,
            headers=headers
        )
        
        if response.status_code in [200, 201]:
            return response.json()
        else:
            raise Exception(f"FHIR upload failed: {response.status_code} - {response.text}")

2. Population Health Analytics

import math
import numpy as np

class PopulationHealthAnalytics:
    def __init__(self, lab_mapper: LabResultMapper):
        self.mapper = lab_mapper
        
    def analyze_population_trends(self, lab_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze population health trends from standardized lab data"""
        
        # Group results by LOINC code
        grouped_results = {}
        for patient_data in lab_data:
            patient_id = patient_data['patient_id']
            
            for result in patient_data['standardized_results']:
                loinc_code = result['loinc_code']
                if loinc_code not in grouped_results:
                    grouped_results[loinc_code] = {
                        'test_name': result['loinc_name'],
                        'values': [],
                        'patients': set()
                    }
                
                if not math.isnan(result['standardized_value']):
                    grouped_results[loinc_code]['values'].append(result['standardized_value'])
                    grouped_results[loinc_code]['patients'].add(patient_id)
        
        # Calculate statistics for each test
        analytics_report = {
            'test_analytics': {},
            'population_summary': {},
            'risk_indicators': []
        }
        
        for loinc_code, data in grouped_results.items():
            if len(data['values']) < 10:  # Skip tests with insufficient data
                continue
                
            values = np.array(data['values'])
            
            test_stats = {
                'loinc_code': loinc_code,
                'test_name': data['test_name'],
                'patient_count': len(data['patients']),
                'result_count': len(values),
                'mean': np.mean(values),
                'median': np.median(values),
                'std_dev': np.std(values),
                'percentiles': {
                    'p25': np.percentile(values, 25),
                    'p75': np.percentile(values, 75),
                    'p95': np.percentile(values, 95)
                },
                'abnormal_percentage': self.calculate_abnormal_percentage(loinc_code, values)
            }
            
            analytics_report['test_analytics'][loinc_code] = test_stats
            
            # Identify risk indicators
            if test_stats['abnormal_percentage'] > 20:  # >20% abnormal results
                analytics_report['risk_indicators'].append({
                    'test_name': data['test_name'],
                    'loinc_code': loinc_code,
                    'abnormal_percentage': test_stats['abnormal_percentage'],
                    'risk_level': 'HIGH' if test_stats['abnormal_percentage'] > 40 else 'MEDIUM'
                })
        
        # Population summary
        analytics_report['population_summary'] = {
            'total_patients': len(set().union(*[data['patients'] for data in grouped_results.values()])),
            'total_tests': len(grouped_results),
            'total_results': sum(len(data['values']) for data in grouped_results.values()),
            'high_risk_indicators': len([r for r in analytics_report['risk_indicators'] if r['risk_level'] == 'HIGH'])
        }
        
        return analytics_report
    
    def calculate_abnormal_percentage(self, loinc_code: str, values: np.ndarray) -> float:
        """Calculate percentage of abnormal results based on reference ranges"""
        # This would typically use established reference ranges for each LOINC code
        # For demonstration, using simplified thresholds
        
        reference_ranges = {
            '2345-7': (70, 100),    # Glucose mg/dL
            '2093-3': (0, 200),     # Total Cholesterol mg/dL  
            '718-7': (12.0, 15.5),  # Hemoglobin g/dL
            '2160-0': (0.7, 1.3)    # Creatinine mg/dL
        }
        
        if loinc_code not in reference_ranges:
            return 0.0
            
        low, high = reference_ranges[loinc_code]
        abnormal_count = np.sum((values < low) | (values > high))
        
        return (abnormal_count / len(values)) * 100 if len(values) > 0 else 0.0

Best Practices

1. Quality Assurance and Validation

import math

class LabMappingValidator:
    def __init__(self):
        self.validation_rules = {
            'glucose': {
                'reasonable_range': (20, 800),  # mg/dL
                'critical_values': {'low': 40, 'high': 400}
            },
            'cholesterol': {
                'reasonable_range': (50, 1000),  # mg/dL
                'critical_values': {'low': 100, 'high': 500}
            },
            'hemoglobin': {
                'reasonable_range': (3.0, 25.0),  # g/dL
                'critical_values': {'low': 7.0, 'high': 20.0}
            }
        }
    
    def validate_mapping_result(self, result: StandardizedLabResult) -> Dict[str, Any]:
        """Validate a standardized lab result"""
        validation_result = {
            'is_valid': True,
            'warnings': [],
            'errors': [],
            'quality_score': 1.0
        }
        
        # Check mapping confidence
        if result.mapping_confidence < 0.7:
            validation_result['warnings'].append(
                f"Low mapping confidence: {result.mapping_confidence:.1%}"
            )
            validation_result['quality_score'] -= 0.2
        
        # Check value reasonableness
        if not math.isnan(result.standardized_value):
            analyte_type = self.determine_analyte_type(result.loinc_name)
            if analyte_type in self.validation_rules:
                rules = self.validation_rules[analyte_type]
                low, high = rules['reasonable_range']
                
                if not (low <= result.standardized_value <= high):
                    validation_result['errors'].append(
                        f"Value {result.standardized_value} outside reasonable range ({low}-{high})"
                    )
                    validation_result['is_valid'] = False
                
                # Check critical values
                critical = rules.get('critical_values', {})
                if 'low' in critical and result.standardized_value < critical['low']:
                    validation_result['warnings'].append(
                        f"Critical low value: {result.standardized_value} < {critical['low']}"
                    )
                elif 'high' in critical and result.standardized_value > critical['high']:
                    validation_result['warnings'].append(
                        f"Critical high value: {result.standardized_value} > {critical['high']}"
                    )
        
        # Check unit consistency
        expected_units = self.get_expected_units(result.loinc_code)
        if expected_units and result.standardized_unit not in expected_units:
            validation_result['warnings'].append(
                f"Unexpected unit: {result.standardized_unit}, expected one of {expected_units}"
            )
            validation_result['quality_score'] -= 0.1
        
        # Calculate final quality score
        if validation_result['errors']:
            validation_result['quality_score'] = 0.0
        elif len(validation_result['warnings']) > 2:
            validation_result['quality_score'] -= 0.3
        
        return validation_result
    
    def get_expected_units(self, loinc_code: str) -> Optional[List[str]]:
        """Get expected units for a LOINC code"""
        unit_mappings = {
            '2345-7': ['mg/dL', 'mmol/L'],    # Glucose
            '2093-3': ['mg/dL', 'mmol/L'],    # Cholesterol
            '718-7': ['g/dL', 'g/L'],         # Hemoglobin
            '2160-0': ['mg/dL', 'μmol/L']     # Creatinine
        }
        
        return unit_mappings.get(loinc_code)

Next Steps

I