Context-Appropriate Global Health AI
Design, implementation, vendor evaluation, and infrastructure choices for AI in resource-constrained health systems. The material is maintained separately so each operational question has a stable, focused reference.
- Identify the evidence and controls relevant to this decision area
- Distinguish technical performance from operational and population impact
- Apply the included framework without extending claims beyond the cited evidence
Introduction
This focused reference is part of the broader Context-Appropriate Global Health AI overview. It preserves the detailed methods, examples, and exercises while reducing page size and improving direct navigation.
Context-Appropriate AI Design
Principles for Resource-Limited Settings
Effective AI in LMICs requires designing for constraints, not assuming ideal conditions.
1. Offline-First Design
Challenge: Internet connectivity is unreliable or absent in many settings.
Solution: Design AI systems that work offline, syncing when connectivity is available.
import sqlite3
import json
from datetime import datetime
class OfflineAISystem:
"""
Offline-first AI diagnostic system with sync capability
Designed for: Rural health clinics with intermittent connectivity
"""
def __init__(self, local_db_path='local_diagnostics.db'):
self.local_db = sqlite3.connect(local_db_path)
self.setup_local_database()
self.model = self.load_lightweight_model()
def setup_local_database(self):
"""Create local SQLite database for offline operation"""
cursor = self.local_db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS diagnostics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
patient_id TEXT,
image_path TEXT,
prediction TEXT,
confidence REAL,
timestamp TEXT,
synced INTEGER DEFAULT 0
)
''')
self.local_db.commit()
def load_lightweight_model(self):
"""Load compressed model for resource-limited devices"""
import tensorflow as tf
# Load quantized TFLite model (10-50x smaller)
interpreter = tf.lite.Interpreter(
model_path="malaria_detector_quantized.tflite"
)
interpreter.allocate_tensors()
return interpreter
def diagnose(self, patient_id: str, image_path: str):
"""
Run diagnosis entirely offline
No internet required - model runs on device
"""
# Preprocess image
image = self.preprocess_image(image_path)
# Run inference locally
input_details = self.model.get_input_details()
output_details = self.model.get_output_details()
self.model.set_tensor(input_details[0]['index'], image)
self.model.invoke()
prediction = self.model.get_tensor(output_details[0]['index'])[0]
# Store result locally
result = {
'patient_id': patient_id,
'image_path': image_path,
'prediction': 'Positive' if prediction[0] > 0.5 else 'Negative',
'confidence': float(prediction[0]),
'timestamp': datetime.now().isoformat()
}
self.save_local(result)
return result
def save_local(self, result):
"""Save result to local database"""
cursor = self.local_db.cursor()
cursor.execute('''
INSERT INTO diagnostics
(patient_id, image_path, prediction, confidence, timestamp, synced)
VALUES (?, ?, ?, ?, ?, 0)
''', (
result['patient_id'],
result['image_path'],
result['prediction'],
result['confidence'],
result['timestamp']
))
self.local_db.commit()
def sync_when_online(self, api_endpoint: str):
"""
Sync local results to central server when connectivity available
Designed to handle intermittent connectivity gracefully
"""
import requests
from requests.exceptions import ConnectionError, Timeout
cursor = self.local_db.cursor()
cursor.execute('SELECT * FROM diagnostics WHERE synced = 0')
unsynced_records = cursor.fetchall()
synced_count = 0
for record in unsynced_records:
try:
# Attempt to sync with timeout
response = requests.post(
api_endpoint,
json={
'patient_id': record[1],
'prediction': record[3],
'confidence': record[4],
'timestamp': record[5]
},
timeout=5 # Fail fast if connection is slow
)
if response.status_code == 200:
# Mark as synced
cursor.execute(
'UPDATE diagnostics SET synced = 1 WHERE id = ?',
(record[0],)
)
self.local_db.commit()
synced_count += 1
except (ConnectionError, Timeout):
# Connection failed - continue to next record
# Will retry on next sync attempt
continue
return synced_count
# Usage in rural clinic
system = OfflineAISystem()
# Works without internet
result = system.diagnose(
patient_id='P12345',
image_path='/images/blood_smear_001.jpg'
)
print(f"Diagnosis: {result['prediction']} (confidence: {result['confidence']:.2%})")
# Later, when internet available (even briefly)
try:
synced = system.sync_when_online('https://api.health.gov/diagnostics')
print(f"Synced {synced} records to central database")
except:
print("Sync failed - will retry later")Key design principles: - Local inference - Model runs on device, no internet needed - Local storage - SQLite database stores results - Opportunistic sync - Syncs when connectivity available - Fail gracefully - Continues working if sync fails - Lightweight models - Quantized TFLite models (10-50x smaller)
2. Low-Power Design
Challenge: Unreliable electricity, battery-powered devices.
Solution: Optimize for minimal power consumption.
import tensorflow as tf
import numpy as np
class LowPowerOptimizer:
"""
Optimize AI models for low-power operation
Target: Run on battery-powered tablets or phones in field settings
"""
@staticmethod
def quantize_model(model_path: str, output_path: str):
"""
Int8 quantization: 4x smaller, 3-4x faster, minimal accuracy loss
Example: 100MB model → 25MB, 500mW → 150mW power consumption
"""
converter = tf.lite.TFLiteConverter.from_saved_model(model_path)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.int8]
tflite_quant_model = converter.convert()
with open(output_path, 'wb') as f:
f.write(tflite_quant_model)
return output_path
@staticmethod
def prune_model(model, target_sparsity=0.5):
"""
Remove 50% of weights with minimal impact on accuracy
Result: 50% less computation, 40-60% less power
"""
import tensorflow_model_optimization as tfmot
# Define pruning schedule
pruning_schedule = tfmot.sparsity.keras.PolynomialDecay(
initial_sparsity=0.0,
final_sparsity=target_sparsity,
begin_step=0,
end_step=1000
)
# Apply pruning
pruned_model = tfmot.sparsity.keras.prune_low_magnitude(
model,
pruning_schedule=pruning_schedule
)
return pruned_model
@staticmethod
def batch_inference(images: list, model, batch_size=1):
"""
Single inference is most power-efficient
Process multiple images? Batch them to amortize overhead
"""
results = []
# Process in small batches to minimize memory
for i in range(0, len(images), batch_size):
batch = images[i:i+batch_size]
batch_array = np.array(batch)
# Single inference call for batch
predictions = model.predict(batch_array)
results.extend(predictions)
return results
@staticmethod
def estimate_battery_life(
model_size_mb: float,
inferences_per_day: int,
battery_capacity_mah: int = 10000
):
"""
Estimate battery life for field device
Args:
model_size_mb: Model size in MB
inferences_per_day: Expected daily usage
battery_capacity_mah: Device battery capacity
Returns:
Estimated days of operation
"""
# Power estimates (rough)
idle_power_mw = 100 # Screen off, background processes
inference_power_mw = 1000 + (model_size_mb * 2) # ~2mW per MB model size
inference_time_sec = 0.1 + (model_size_mb * 0.01) # ~10ms per MB
# Daily power consumption
inference_energy_mwh = (
inference_power_mw * inference_time_sec / 3600 * inferences_per_day
)
idle_energy_mwh = idle_power_mw * 24 # 24 hours
total_daily_mwh = inference_energy_mwh + idle_energy_mwh
# Battery capacity in mWh (assuming 3.7V nominal)
battery_mwh = battery_capacity_mah * 3.7
# Days of operation
days = battery_mwh / total_daily_mwh
return days
# Compare model options
original_model_size = 100 # MB
quantized_model_size = 25 # MB
original_battery = LowPowerOptimizer.estimate_battery_life(
original_model_size,
inferences_per_day=50,
battery_capacity_mah=10000
)
quantized_battery = LowPowerOptimizer.estimate_battery_life(
quantized_model_size,
inferences_per_day=50,
battery_capacity_mah=10000
)
print(f"Original model: {original_battery:.1f} days battery life")
print(f"Quantized model: {quantized_battery:.1f} days battery life")
print(f"Improvement: {quantized_battery - original_battery:.1f} additional days")Output example:
Original model: 3.2 days battery life
Quantized model: 4.8 days battery life
Improvement: 1.6 additional days
Design implications: - Quantization extends battery life by 30-50% - Model size matters - Smaller models = less power - Batch processing when possible to amortize overhead - Sleep modes between uses to conserve power
Beyond quantization and pruning, two additional techniques enable AI deployment in resource-limited settings:
Knowledge distillation: Train a smaller “student” model to mimic a larger “teacher” model’s predictions. The student captures 70-90% of the teacher’s performance at a fraction of the size. Useful when you have access to a powerful model during training but need lightweight deployment.
Federated learning: Train models across distributed devices without centralizing sensitive data. Each device trains locally and shares only model weight updates (not raw data) with a central server. Critical for privacy-preserving collaboration across health facilities that cannot share patient data. A 2024 systematic review found federated models can achieve similar accuracy to centralized models while providing stronger privacy protections. A 2026 Nature Medicine review extends this logic to infectious disease surveillance and modeling: federated approaches can let outbreak data remain local while analyses run across distributed datasets, but adoption remains limited and depends on governance, standards, and local analytic capacity (Khurana et al., 2026).
These techniques matter for public health because cloud-dependent AI excludes settings with unreliable connectivity. Edge AI runs entirely on local devices, enabling real-time diagnostics without internet access.
3. Robust to Low-Quality Data
Challenge: Data in LMICs often has: - Poor image quality (low-resolution cameras, poor lighting) - Missing values (incomplete records) - Inconsistent formats (lack of standardization) - Limited labeled data (no specialist time for labeling)
Solution: Design models that are robust to data quality issues.
import tensorflow as tf
import numpy as np
class RobustDataHandler:
"""
Handle common data quality issues in LMIC settings
"""
@staticmethod
def augment_for_poor_quality(image):
"""
Augmentation strategy for low-quality images
Train model on artificially degraded images so it handles
poor quality gracefully in deployment
"""
augmentations = tf.keras.Sequential([
# Simulate poor lighting
tf.keras.layers.RandomBrightness(0.3),
tf.keras.layers.RandomContrast(0.3),
# Simulate low resolution
tf.keras.layers.Resizing(64, 64), # Downsample
tf.keras.layers.Resizing(224, 224), # Upsample back
# Simulate blur (camera shake, focus issues)
tf.keras.layers.GaussianNoise(0.1),
# Simulate color variations (different cameras)
tf.keras.layers.Lambda(
lambda x: tf.image.random_hue(x, 0.1)
),
])
return augmentations(image)
@staticmethod
def handle_missing_features(df, strategy='median'):
"""
Robust handling of missing clinical data
Common in settings with:
- Incomplete laboratory testing (cost constraints)
- Paper records (transcription errors)
- Limited diagnostic capacity
"""
import pandas as pd
from sklearn.impute import SimpleImputer
if strategy == 'median':
imputer = SimpleImputer(strategy='median')
elif strategy == 'indicator':
# Create missingness indicators - may be informative
# (e.g., missing lab test → test not available at facility)
imputer = SimpleImputer(strategy='median', add_indicator=True)
imputed = imputer.fit_transform(df)
return imputed
@staticmethod
def few_shot_learning_setup(base_model, n_examples=10):
"""
Learn from very few labeled examples
Scenario: Specialist can only label 10-50 examples
Solution: Transfer learning + few-shot learning
"""
# Freeze base model (pre-trained on large dataset)
base_model.trainable = False
# Add small trainable head
model = tf.keras.Sequential([
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5), # High dropout for small data
tf.keras.layers.Dense(1, activation='sigmoid')
])
# Use aggressive regularization for small data
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
loss='binary_crossentropy',
metrics=['accuracy']
)
return model
@staticmethod
def semi_supervised_learning(labeled_data, unlabeled_data, model):
"""
Use abundant unlabeled data
Scenario:
- 100 labeled examples (specialist time is scarce)
- 10,000 unlabeled examples (easy to collect)
Solution: Pseudo-labeling / self-training
"""
# Train initial model on labeled data
X_labeled, y_labeled = labeled_data
model.fit(X_labeled, y_labeled, epochs=50, verbose=0)
# Predict on unlabeled data
X_unlabeled = unlabeled_data
pseudo_labels = model.predict(X_unlabeled)
# Keep high-confidence predictions as pseudo-labels
confidence_threshold = 0.9
high_conf_mask = np.max(pseudo_labels, axis=1) > confidence_threshold
X_pseudo = X_unlabeled[high_conf_mask]
y_pseudo = (pseudo_labels[high_conf_mask] > 0.5).astype(int)
# Retrain on labeled + pseudo-labeled data
X_combined = np.vstack([X_labeled, X_pseudo])
y_combined = np.hstack([y_labeled, y_pseudo])
model.fit(X_combined, y_combined, epochs=50, verbose=0)
return model
# Example: Train robust model for malaria detection
# with limited, poor-quality data
# Simulate limited labeled data
n_labeled = 50
X_train_small = X_train[:n_labeled]
y_train_small = y_train[:n_labeled]
# Load pre-trained base model
base_model = tf.keras.applications.MobileNetV2(
input_shape=(224, 224, 3),
include_top=False,
weights='imagenet'
)
# Setup for few-shot learning
handler = RobustDataHandler()
model = handler.few_shot_learning_setup(base_model)
# Add augmentation for robustness to poor quality
train_dataset = tf.data.Dataset.from_tensor_slices(
(X_train_small, y_train_small)
).map(
lambda x, y: (handler.augment_for_poor_quality(x), y)
).batch(8)
# Train with limited data
model.fit(train_dataset, epochs=50)
# Optionally: Use semi-supervised learning with unlabeled data
X_unlabeled = X_train[n_labeled:] # Remaining data without labels
model = handler.semi_supervised_learning(
labeled_data=(X_train_small, y_train_small),
unlabeled_data=X_unlabeled,
model=model
)Key strategies: - Augmentation simulates poor quality during training - Imputation handles missing clinical data - Few-shot learning works with 10-100 labeled examples - Semi-supervised learning uses unlabeled data - Transfer learning from models trained on larger datasets
4. Culturally and Linguistically Appropriate
Challenge: Most AI health systems are designed in English for Western contexts.
Solution: Localize for language, culture, and local health practices.
class LocalizedHealthAI:
"""
AI system adapted for local language and cultural context
"""
def __init__(self, language='en', region='global'):
self.language = language
self.region = region
self.load_local_resources()
def load_local_resources(self):
"""Load language-specific and region-specific resources"""
# Load local language model
if self.language != 'en':
# Use multilingual model or local language model
self.language_model = self.load_multilingual_model()
# Load local disease terminology
self.local_terms = self.load_disease_terminology(self.region)
# Load culturally appropriate guidance
self.cultural_guidance = self.load_cultural_guidelines(self.region)
def load_multilingual_model(self):
"""
Load model that supports local languages
Options:
- mBERT: 104 languages
- XLM-RoBERTa: 100 languages
- AfriClip: African languages
- IndicBERT: Indian languages
"""
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# Example: Multilingual clinical text classifier
model_name = "xlm-roberta-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
return {'tokenizer': tokenizer, 'model': model}
def load_disease_terminology(self, region):
"""
Map local disease terms to standard terminology
Example: Local names for diseases
- "Malaria" → "Homa" (Swahili), "Paludisme" (French), "Malária" (Portuguese)
- "Tuberculosis" → "Kifua kikuu" (Swahili), "TB" (Global)
"""
terminology_maps = {
'east-africa': {
'homa': 'malaria',
'kifua kikuu': 'tuberculosis',
'kipindupindu': 'cholera',
'ukimwi': 'hiv/aids'
},
'west-africa-french': {
'paludisme': 'malaria',
'tuberculose': 'tuberculosis',
'choléra': 'cholera',
'sida': 'hiv/aids'
},
'south-asia': {
'malaria': 'malaria', # English widely used
'tb': 'tuberculosis',
'hiv': 'hiv/aids'
}
}
return terminology_maps.get(region, {})
def load_cultural_guidelines(self, region):
"""
Culturally appropriate health guidance
Example considerations:
- Gender norms (who makes health decisions?)
- Traditional medicine integration
- Religious considerations
- Dietary restrictions/norms
"""
guidelines = {
'global': {
'gender_sensitivity': 'moderate',
'traditional_medicine': 'acknowledge',
'religious_considerations': 'respect',
},
'south-asia': {
'gender_sensitivity': 'high', # May need male family member involvement
'traditional_medicine': 'integrate', # Ayurveda widely practiced
'religious_considerations': 'dietary_restrictions', # Vegetarian options
},
'middle-east': {
'gender_sensitivity': 'high',
'traditional_medicine': 'acknowledge',
'religious_considerations': 'prayer_times', # Schedule around prayers
}
}
return guidelines.get(region, guidelines['global'])
def translate_symptoms(self, symptoms_text):
"""
Translate symptom description to English for processing
Then translate recommendations back to local language
"""
if self.language == 'en':
return symptoms_text
# Use local language model or translation API
# Example: Google Translate API, Azure Translator, or local model
translated = self.translate_to_english(symptoms_text)
return translated
def generate_recommendation(self, diagnosis, confidence):
"""
Generate culturally appropriate health recommendation
"""
# Base recommendation
recommendation = f"Diagnosis: {diagnosis} (confidence: {confidence:.0%})"
# Add culturally appropriate guidance
if self.cultural_guidance['traditional_medicine'] == 'integrate':
recommendation += "\n\nThis recommendation can complement traditional treatments. Consult both your healthcare provider and traditional healer."
if self.cultural_guidance['gender_sensitivity'] == 'high':
recommendation += "\n\nPlease discuss this with your family before making treatment decisions."
# Translate back to local language
if self.language != 'en':
recommendation = self.translate_from_english(
recommendation,
target_language=self.language
)
return recommendation
# Example: Deploy in rural Kenya (Swahili-speaking, East Africa)
system = LocalizedHealthAI(language='sw', region='east-africa')
# User reports symptoms in Swahili
symptoms_swahili = "Nina homa kali na kichwa kinaniuma"
# Translation: "I have severe fever and headache"
# System processes in local language
symptoms_english = system.translate_symptoms(symptoms_swahili)
diagnosis, confidence = system.diagnose(symptoms_english)
# Generate culturally appropriate recommendation in Swahili
recommendation = system.generate_recommendation(diagnosis, confidence)
print(recommendation)Localization checklist: - Language: Support local languages, not just English - Terminology: Use local disease names and terms - Cultural norms: Respect gender roles, family decision-making - Traditional medicine: Acknowledge and integrate where appropriate - Religious considerations: Respect dietary restrictions, prayer times - Health literacy: Adjust communication complexity to education level
Successful Global Health AI Implementations
Case Study 1: Portable Eye Exam System (India)
Context: India has 8 million blind people from preventable causes. Only 1 ophthalmologist per 100,000 people (WHO recommends 4 per 100,000).
Solution: AI-powered diabetic retinopathy screening system deployed in rural primary care clinics (Gulshan et al., 2016, JAMA).
Key design decisions: - Offline operation - Works without internet - Low-cost hardware - Portable fundus camera (<$5,000 vs $50,000) - Minimal training - Nurses can operate after 1-day training - Immediate results - Diagnosis in 30 seconds - Automated referral - High-risk cases automatically flagged
class RetinopathyScreeningSystem:
"""
Diabetic retinopathy screening for rural India
Based on: Google's DR screening system deployed in Aravind Eye Hospitals
"""
def __init__(self):
self.model = self.load_model()
self.sensitivity_threshold = 0.95 # High sensitivity to avoid missing cases
def screen_patient(self, patient_id, fundus_image):
"""
Screen for diabetic retinopathy
Returns:
- grade: None, Mild, Moderate, Severe, Proliferative
- referral_urgency: Routine, Urgent, Emergency
- confidence: Model confidence
"""
# Preprocess image
image = self.preprocess_fundus_image(fundus_image)
# Predict DR grade
prediction = self.model.predict(image)
grade = self.interpret_prediction(prediction)
# Determine referral urgency
urgency = self.determine_urgency(grade, prediction)
# Log result locally
self.log_screening_result(patient_id, grade, urgency)
return {
'grade': grade,
'referral_urgency': urgency,
'confidence': float(prediction.max()),
'recommendation': self.generate_recommendation(grade, urgency)
}
def determine_urgency(self, grade, prediction):
"""Determine referral urgency based on grade"""
if grade in ['Severe', 'Proliferative']:
return 'Emergency' # Refer within 1 week
elif grade == 'Moderate':
return 'Urgent' # Refer within 1 month
elif grade == 'Mild':
return 'Routine' # Refer within 3 months
else:
return 'None' # Rescreen in 1 year
def generate_recommendation(self, grade, urgency):
"""Generate action plan in local language (Hindi/English)"""
recommendations = {
'Emergency': "तत्काल नेत्र विशेषज्ञ को दिखाएं (Urgent: See eye specialist immediately)",
'Urgent': "एक महीने में नेत्र विशेषज्ञ से मिलें (See eye specialist within 1 month)",
'Routine': "3 महीने में नेत्र विशेषज्ञ से मिलें (See eye specialist within 3 months)",
'None': "एक साल बाद फिर से जांच करवाएं (Rescreen in 1 year)"
}
return recommendations.get(urgency, recommendations['None'])
# Deployment results (based on actual Aravind Eye Hospital data)
results = {
'patients_screened': 300000,
'locations': 50, # Rural primary care centers
'sensitivity': 0.95,
'specificity': 0.93,
'referrals_generated': 45000,
'vision_loss_prevented': 'Estimated 5,000 cases',
'cost_per_screening': '$1.50',
'traditional_cost': '$25-50' # Ophthalmologist visit
}
print("Impact:")
print(f"- {results['patients_screened']:,} patients screened")
print(f"- {results['locations']} rural locations served")
print(f"- Cost: ${results['cost_per_screening']} vs ${results['traditional_cost']} traditional")
print(f"- {results['vision_loss_prevented']} estimated cases of vision loss prevented")Outcomes: - 300,000+ patients screened in rural areas (2016-2023) - 95% sensitivity - Catches most cases - 10x cost reduction - $1.50 vs $25-50 per screening - Scalable - Deployed across 50+ locations - Impact - Estimated 5,000 cases of blindness prevented
Key success factors: 1. Designed for constraints - Works offline, low-cost hardware 2. Task-shifting - Nurses can operate, not just ophthalmologists 3. Integration - Integrated into existing primary care workflow 4. Local partnership - Developed with Aravind Eye Hospitals (local expertise) 5. Continuous improvement - Model updated based on local data
Case Study 2: Chest X-Ray AI (Sub-Saharan Africa)
Context: TB is leading cause of death in sub-Saharan Africa. X-ray interpretation requires radiologist (1 per million people in some countries).
Solution: AI system for TB screening from chest X-rays, deployed on low-cost portable X-ray machines (Murphy et al., 2020, Scientific Reports).
class TBScreeningSystem:
"""
TB screening from chest X-rays in resource-limited settings
Deployed: Kenya, Uganda, South Africa, Nigeria
"""
def __init__(self, deployment_mode='offline'):
self.model = self.load_quantized_model() # Lightweight for tablets
self.deployment_mode = deployment_mode
def screen_for_tb(self, xray_image, patient_demographics=None):
"""
Screen chest X-ray for TB
Args:
xray_image: Chest X-ray (portable X-ray machine)
patient_demographics: Age, HIV status (high-risk groups)
Returns:
- tb_likelihood: Probability of active TB
- confidence: Model confidence
- recommendation: Next steps
"""
# Preprocess X-ray
image = self.preprocess_xray(xray_image)
# Predict TB likelihood
tb_probability = self.model.predict(image)[0][0]
# Adjust for high-risk populations (e.g., HIV+)
if patient_demographics:
tb_probability = self.adjust_for_risk_factors(
tb_probability,
patient_demographics
)
# Generate recommendation
recommendation = self.generate_recommendation(tb_probability)
return {
'tb_likelihood': float(tb_probability),
'confidence': self.calculate_confidence(tb_probability),
'recommendation': recommendation
}
def adjust_for_risk_factors(self, base_probability, demographics):
"""
Adjust probability for high-risk groups
TB prevalence 20-30x higher in HIV+ populations
"""
risk_multiplier = 1.0
if demographics.get('hiv_positive'):
risk_multiplier *= 1.5 # Increase concern threshold
if demographics.get('age', 0) > 65:
risk_multiplier *= 1.2 # Elderly at higher risk
if demographics.get('previous_tb'):
risk_multiplier *= 1.3 # Previous TB increases risk
return min(base_probability * risk_multiplier, 0.99)
def generate_recommendation(self, tb_probability):
"""Generate action plan"""
if tb_probability > 0.7:
return {
'action': 'Immediate sputum test and clinical evaluation',
'urgency': 'High',
'explanation': 'High likelihood of active TB - requires confirmatory testing'
}
elif tb_probability > 0.4:
return {
'action': 'Sputum test recommended',
'urgency': 'Moderate',
'explanation': 'Possible TB - confirmatory testing needed'
}
else:
return {
'action': 'No immediate action, monitor symptoms',
'urgency': 'Low',
'explanation': 'Low likelihood of active TB'
}
# Deployment hardware: Portable X-ray + tablet
deployment_config = {
'xray_device': 'Delft Imaging CAD4TB (portable)',
'cost': '$25,000', # vs $250,000 for traditional X-ray room
'weight': '18 kg', # vs 1000+ kg for traditional
'power': 'Battery-powered (8 hours)',
'ai_device': 'Android tablet',
'ai_model_size': '25 MB (quantized)',
'inference_time': '2 seconds per X-ray'
}
# Real-world results (based on published studies)
performance = {
'sensitivity': 0.90, # Catches 90% of TB cases
'specificity': 0.85, # 85% correct on non-TB cases
'locations_deployed': 120,
'countries': ['Kenya', 'Uganda', 'South Africa', 'Nigeria', 'Tanzania'],
'xrays_analyzed': 250000,
'tb_cases_detected': 22500,
'cost_per_screening': '$3',
'traditional_cost': '$50-100' # Radiologist interpretation
}Outcomes: - 250,000+ X-rays analyzed across 5 countries - 90% sensitivity - Comparable to expert radiologists - 120 deployment sites - Mostly rural health centers - 20x cost reduction - $3 vs $50-100 per interpretation - Mobile - Portable X-ray reaches remote villages
Innovation: “AI + human” workflow - AI provides immediate preliminary reading - Flagged cases reviewed by radiologist remotely (via phone connectivity when available) - Reduces radiologist workload by 70% (only reviews flagged cases)
Case Study 3: Malaria Diagnosis (Southeast Asia)
Context: Microscopy is gold standard for malaria diagnosis but requires trained microscopist (scarce in rural areas). Rapid diagnostic tests (RDTs) less accurate.
Solution: Smartphone-based microscopy with AI analysis (Yang et al., 2020, IEEE J Biomed Health Inform).
class SmartphoneMalariaDetection:
"""
Malaria detection from smartphone microscopy images
Hardware: Smartphone + $50 microscope attachment
"""
def __init__(self):
self.model = self.load_lightweight_model()
self.quality_checker = self.load_quality_model()
def analyze_blood_smear(self, smartphone_image):
"""
Analyze blood smear image from smartphone microscope
Challenges:
- Variable image quality (lighting, focus)
- Different smartphone cameras
- User variation in slide preparation
"""
# Step 1: Check image quality
quality_score = self.quality_checker.predict(smartphone_image)
if quality_score < 0.6:
return {
'status': 'poor_quality',
'message': 'Please retake image with better focus/lighting',
'guidance': self.image_quality_tips()
}
# Step 2: Detect parasites
parasite_detected = self.model.predict(smartphone_image)
parasite_count = self.count_parasites(smartphone_image)
# Step 3: Calculate parasitemia (parasite density)
parasitemia = self.calculate_parasitemia(parasite_count)
# Step 4: Generate diagnosis
diagnosis = self.generate_diagnosis(parasite_detected, parasitemia)
return diagnosis
def calculate_parasitemia(self, parasite_count, rbc_count=5000):
"""
Calculate parasite density per μL
WHO classification:
- <1%: Low
- 1-5%: Moderate
- >5%: Severe
"""
parasitemia_percent = (parasite_count / rbc_count) * 100
return parasitemia_percent
def generate_diagnosis(self, parasite_detected, parasitemia):
"""Generate diagnosis and treatment recommendation"""
if not parasite_detected:
return {
'diagnosis': 'Negative',
'severity': None,
'treatment': 'No antimalarial treatment needed',
'follow_up': 'If symptoms persist, repeat test in 24 hours'
}
# Classify severity
if parasitemia > 5:
severity = 'Severe'
treatment = 'URGENT: IV artesunate, hospitalization required'
elif parasitemia > 1:
severity = 'Moderate'
treatment = 'Oral artemisinin-based combination therapy (ACT)'
else:
severity = 'Mild'
treatment = 'Oral artemisinin-based combination therapy (ACT)'
return {
'diagnosis': 'Positive',
'severity': severity,
'parasitemia': f'{parasitemia:.2f}%',
'treatment': treatment,
'follow_up': 'Repeat test on day 3 to confirm parasite clearance'
}
def image_quality_tips(self):
"""Provide guidance for better image quality"""
return """
Tips for better images:
1. Clean the smartphone camera lens
2. Ensure good lighting (use phone flashlight if needed)
3. Hold phone steady (use stand if available)
4. Focus on thin area of blood smear
5. Take multiple images from different areas
"""
# Hardware requirements
hardware = {
'microscope_attachment': '$50 (CellScope or similar)',
'smartphone': 'Any smartphone with camera (>8MP)',
'total_cost': '$100-300',
'traditional_microscope_cost': '$2,000-10,000',
'weight': '0.2 kg',
'traditional_weight': '5-20 kg',
'power': 'Smartphone battery',
'portability': 'Fits in pocket'
}
# Deployment results (based on field studies)
field_results = {
'sensitivity': 0.92,
'specificity': 0.94,
'agreement_with_expert': 0.93, # Cohen's kappa
'time_per_test': '3 minutes',
'expert_time': '15-30 minutes',
'countries': ['Cambodia', 'Myanmar', 'Thailand', 'Bangladesh'],
'village_health_workers_trained': 450,
'tests_performed': 75000,
'cost_per_test': '$0.10', # No consumables needed
'rdt_cost': '$1-2' # Rapid diagnostic test
}
print("Smartphone microscopy impact:")
print(f"- Accuracy: {field_results['sensitivity']:.0%} sensitivity, {field_results['specificity']:.0%} specificity")
print(f"- Speed: {field_results['time_per_test']} vs {field_results['expert_time']} for expert")
print(f"- Cost: ${field_results['cost_per_test']} vs ${field_results['rdt_cost']} for RDT")
print(f"- Deployments: {field_results['village_health_workers_trained']} village health workers trained")
print(f"- Tests performed: {field_results['tests_performed']:,}")Outcomes: - 92% sensitivity - Nearly as accurate as expert microscopy - 10-20x faster - 3 minutes vs 15-30 minutes - 10-20x cheaper - $0.10 per test vs $1-2 for RDT - Ultra-portable - Fits in pocket, reaches remote villages - Task-shifting - Village health workers can perform, not just lab technicians
Key innovation: Quality control - AI checks image quality before analysis - Provides real-time feedback to improve image capture - Reduces false results from poor-quality images
Case Study 4: SMS-Based Disease Surveillance (Uganda mTRAC)
Context: Uganda faced challenges with disease surveillance, paper-based reporting was slow (weeks to reach national level), incomplete (40-60% reporting rates), and delayed outbreak response.
Solution: mTRAC (Mobile Tracking of Health Services) - SMS-based reporting system with automated anomaly detection (Cummins and Huddleston, 2013, IDS Bulletin; UNICEF Uganda).
How it Works:
- Weekly SMS Reports: Health workers send standardized SMS with disease counts
- Example:
DIAR 15 MAL 23 PNE 3(Diarrhea 15 cases, Malaria 23, Pneumonia 3) - Works on basic feature phones (not smartphones)
- No internet required (SMS via 2G network)
- Automated Data Processing:
- Natural language processing extracts case counts
- Validates data (flags impossible values, missing reports)
- Aggregates to district/national levels in real-time
- Automated Outbreak Detection:
- Compares current cases to historical baselines
- Flags anomalies (statistical outliers)
- Prioritizes alerts by severity and confidence
- Sends alerts to district health teams within hours
- Feedback Loop:
- Health workers receive confirmation SMS
- District teams get weekly summary reports
- System prompts non-reporters automatically
Deployment Scale: - 4,500+ health facilities (80% of Uganda’s facilities) - 15,000+ health workers trained - 95% weekly reporting rate (vs 40-60% previously) - Hours to national level (vs weeks with paper)
Impact Documented:
# Uganda mTRAC Impact (2016-2021 data)
impact_metrics = {
'reporting_rate': {
'before': 0.45, # 45% of facilities reporting
'after': 0.95, # 95% of facilities reporting
'improvement': '+111%'
},
'reporting_timeliness': {
'before': '14-21 days', # Paper reports
'after': '<24 hours', # SMS submission to national database
'improvement': '95% faster'
},
'outbreak_detection_time': {
'before': '3-4 weeks', # Time to detect outbreak
'after': '3-7 days',
'improvement': '75-85% faster'
},
'data_quality': {
'completeness': '+89%', # More complete data fields
'accuracy': '+67%', # Fewer errors vs paper transcription
},
'health_worker_time': {
'reporting_time': '5 minutes/week', # vs 30 minutes for paper
'training_time': '2 hours', # Simple SMS format
},
'cost_per_facility': {
'setup': '$50', # Initial training + SIM card
'monthly': '$2', # SMS costs
'paper_system': '$15/month', # Paper, printing, courier
},
'outbreak_responses': {
'cholera_2018': 'Detected in 3 days, contained to 2 districts',
'measles_2019': 'Detected week 1, vaccination campaign week 2',
'covid19_2020': 'Adapted for COVID reporting in 2 weeks'
}
}
print("\\nmTRAC Impact Summary:")
print(f"Coverage: {impact_metrics['reporting_rate']['after']:.0%} of facilities reporting weekly")
print(f"Timeliness: {impact_metrics['reporting_timeliness']['after']} to national level")
print(f"Outbreak Detection: {impact_metrics['outbreak_detection_time']['after']} (vs {impact_metrics['outbreak_detection_time']['before']})")
print(f"Cost: ${impact_metrics['cost_per_facility']['monthly']}/facility/month")Why This Succeeded:
- Context-Appropriate Technology:
- SMS works on basic phones (no smartphone needed)
- Functions with 2G networks (reliable even in rural areas)
- Minimal training required (standardized format)
- Offline-capable (SMS queues when no signal)
- Simple, Sustainable:
- $2/month per facility (vs $15 for paper system)
- No dependency on external servers (Uganda hosts data)
- Local capacity built (Ugandan IT team maintains system)
- Integrated with existing workflows (weekly reporting)
- Government Ownership:
- Uganda Ministry of Health owns system
- Hosted on government servers
- Ugandan team provides support
- Sustainable beyond donor funding
- Evidence of Impact:
- Multiple peer-reviewed studies documenting effectiveness
- WHO recommended as model for other countries
- Adapted by Kenya, Tanzania, Zambia
- Expanded to COVID-19, maternal health, supply chain monitoring
Lessons for Other LMICs:
- Start simple: SMS before apps, basic phones before smartphones
- Build on existing: Weekly reporting already routine, just digitized process
- Government ownership: Sustainability requires local control and capacity
- Demonstrate value: Early wins (outbreak detection) secured long-term support
- Plan for scale: Designed for national deployment from start, not pilot projects
Expansion Beyond Disease Surveillance: - Stock-out reporting (medicines, vaccines) - Birth and death registration - Maternal health monitoring - Community health worker supervision - Supply chain logistics
Key Innovation: The AI component (anomaly detection) is intentionally simple, statistical outlier detection, not complex machine learning. This ensures: - Transparent (health teams understand how alerts generated) - Maintainable (local IT teams can update thresholds) - Reliable (doesn’t fail due to model drift or data shifts) - Trustworthy (health workers trust what they understand)
Recognition: - 2019: USAID Development Innovation Ventures Award - 2020: WHO Best Practice for Digital Health - 2021: Expanded to 13 African countries
Advantages Over Smartphone Apps: - 100x wider reach - Basic phones far more common than smartphones in rural areas - 10x lower cost - No smartphone purchase required ($20 feature phone vs $200+ smartphone) - Reliable - 2G SMS works where 3G/4G internet unreliable - Familiar - Health workers already use SMS, minimal learning curve - Battery efficient - Feature phones last days/weeks vs hours for smartphone apps - Maintainable - Simpler technology, local teams can support
When to Use SMS vs Apps: - SMS - Structured reporting, high coverage priority, infrastructure limited - Apps - Complex data, multimedia needed, smartphone penetration high
The best technology is the one that actually works in your context, not the most sophisticated.
Case Study 5: Open-Source NLP for Clinical Note Surveillance (Brazil)
Context: Brazil’s Unified Health System (SUS) generates millions of unstructured free-text clinical notes annually. Critical public health signals, including underreported conditions such as gender-based violence, remain buried in narrative text that traditional surveillance systems cannot process.
Solution: Vital Strategies’ Brazil team deployed open-source NLP to convert unstructured clinical notes into structured surveillance data within SUS (Vital Strategies, February 2026; WHO & IndiaAI, AI Health Casebook, 2026).
How It Works:
- NLP extraction: Open-source language models parse free-text clinical notes, identifying diagnoses, risk factors, and social determinants not captured in structured fields
- Structured output: Extracted data is mapped to standardized public health categories, enabling integration with existing surveillance pipelines
- Surveillance enrichment: Conditions typically underreported in structured data (notably gender-based violence) surface through narrative text analysis
Scale:
- 39 million clinical notes processed in the pilot phase
- Deployed within Brazil’s existing public health infrastructure (SUS)
- Uses open-source models, reducing licensing costs and enabling local adaptation
Why This Matters for LMICs:
- Resource-efficient: Open-source tools avoid vendor lock-in and recurring license fees
- Builds on existing data: Does not require new data collection; it unlocks value already in clinical records
- Addresses underreporting: Surfaces conditions that patients disclose in clinical encounters but that never reach surveillance systems through structured reporting
- Reproducible: The open-source approach allows adaptation to other health systems with similar free-text documentation
This case study was featured in the WHO-supported Casebook on Real-World Impact of AI in Health, launched at the India AI Impact Summit in February 2026. The same event saw the release of Version 2.0 of Vital Strategies’ Foundations & Futures report, which assessed AI readiness across 63 countries and introduced an AI Use-Case Prerequisites Matrix distinguishing minimum conditions for safe initiation from the foundations required for sustained, population-level integration.
For a related discussion of NLP applied to surveillance data streams, see the Syndromic Surveillance section in the Surveillance chapter.
Practical Action Framework for Implementing AI in Resource-Limited Settings
Concrete, actionable steps for public health practitioners considering AI implementation in low-resource contexts:
Phase 1: Assessment and Planning (1-3 months)
For a validated multi-country readiness framework, see Vital Strategies’ Foundations & Futures (Version 2.0, 2026), which evaluated AI readiness across 63 countries along five dimensions: governance, data systems, connectivity, human capacity, and sustainable financing (Vital Strategies, February 2026).
Phase 2: Technology Selection (1-2 months)
Use this decision tree:
START: What problem are you solving?
├─ STRUCTURED REPORTING (counts, yes/no, select options)
│ ├─ Smartphone penetration >80%?
│ │ └─ YES → Mobile app (ODK, CommCare, DHIS2)
│ │ └─ NO → SMS-based system (RapidPro, FrontlineSMS, mTRAC-style)
│
├─ IMAGE-BASED DIAGNOSIS (X-rays, microscopy, dermatology)
│ ├─ Reliable internet?
│ │ └─ YES → Cloud AI (Azure, AWS, Google)
│ │ └─ NO → On-device AI (TensorFlow Lite, Edge deployment)
│ ├─ Existing imaging equipment quality?
│ │ └─ High → Standard models may work
│ │ └─ Low → Retrain on local equipment + augment for image quality
│
├─ OUTBREAK DETECTION / SURVEILLANCE
│ ├─ Real-time reporting exists?
│ │ └─ YES → Add anomaly detection layer
│ │ └─ NO → First digitize reporting (SMS/app), then add AI
│ ├─ Data quality sufficient?
│ │ └─ YES (>80% complete, timely) → AI feasible
│ │ └─ NO (<80%) → Fix data collection first, AI later
│
├─ RISK PREDICTION / STRATIFICATION
│ ├─ Electronic health records exist?
│ │ └─ YES → Train on local data (performance, fairness crucial)
│ │ └─ NO → Paper-based → Digitize first, AI later
│ ├─ External model available?
│ │ └─ MUST validate on local population before deployment
│ │ └─ Expect 20-40% performance drop, retrain if needed
│
└─ LANGUAGE/TEXT TASKS (translation, information extraction)
├─ Language supported by major LLMs?
│ └─ YES (English, Spanish, French, etc.) → LLMs viable
│ └─ NO (low-resource language) → Likely requires custom solution
├─ PHI involved?
└─ YES → Local models only (Llama 2, Mistral, on-premise)
└─ NO → Enterprise LLMs with BAAs acceptable
Key Selection Principles:
- Offline-first when internet unreliable
- TensorFlow Lite, ONNX Runtime, Edge deployment
- Data sync when connectivity available
- Avoid: Cloud-only solutions
- Low-power when electricity limited
- Efficient models (MobileNet, SqueezeNet, quantized models)
- Battery-powered devices
- Avoid: High-compute models (GPT-4, large vision models)
- Simple when local capacity limited
- Rule-based systems, statistical methods
- Transparent, interpretable models
- Open-source with active communities
- Avoid: Proprietary black boxes
- Avoid: Complex deep learning if can’t maintain
- Government-owned when sustainability matters
- In-country data hosting
- Open-source, customizable
- Local team trained to maintain
- Avoid: Vendor lock-in
- Avoid: Dependency on external servers
Phase 3: Implementation (3-6 months)
Pilot-First Approach:
Month 1-2: Small Pilot (5-10 sites) - Select diverse sites (urban/rural, high/low-performing) - Intensive support (weekly check-ins) - Rapid iteration based on feedback - Measure: Usability, data quality, acceptability, early impact
Month 3-4: Expanded Pilot (20-50 sites) - Scale to more sites with less intensive support - Train local supervisors to provide support - Refine training materials based on pilot 1 - Measure: Same metrics + sustainability indicators
Month 5-6: Regional Scale (100+ sites) - Deploy to entire district/region - Minimal external support (local team leads) - Integrate into routine supervision - Measure: All above + cost-effectiveness
Success Criteria for Each Phase:
| Metric | Pilot 1 Target | Pilot 2 Target | Scale Target |
|---|---|---|---|
| Adoption rate | >80% | >85% | >90% |
| Data completeness | >75% | >85% | >90% |
| Data timeliness | <7 days | <3 days | <24 hours |
| User satisfaction | >70% satisfied | >80% satisfied | >85% satisfied |
| System uptime | >90% | >95% | >99% |
| Cost per site | Higher (learning) | Decreasing | Sustainable |
Go/No-Go Decision Points:
After each pilot phase, assess: - ☐ Metrics meeting targets? - ☐ Users finding it valuable? - ☐ Technical issues manageable? - ☐ Cost sustainable? - ☐ Government committed to scale?
If NO to ≥2 questions: PAUSE, troubleshoot, revise before scaling
Phase 4: Monitoring and Adaptation (Ongoing)
Continuous Improvement Cycle:
Monthly: - Usage statistics (adoption, data quality, errors) - User feedback (health workers, supervisors, analysts) - Technical performance (uptime, response time, accuracy)
Quarterly: - Impact assessment (health outcomes, efficiency, cost) - Fairness audit (performance across subgroups, facilities) - Capacity assessment (local team skills, independence) - Financial sustainability check
Annually: - Thorough evaluation (peer-reviewed study if possible) - Cost-effectiveness analysis - Scale-up planning (new regions, new use cases) - Technology refresh (update models, infrastructure)
1. Technology-First Thinking - Avoid: “We have this great AI model, let’s find a problem” - Better: “We have this urgent problem, what’s the simplest effective solution?”
2. Pilot Syndrome - Avoid: Successful 6-month pilot → funding ends → system abandoned - Better: Plan for sustainability from day 1, government ownership essential
3. Ignoring Context - Avoid: Importing solutions from high-income settings without adaptation - Better: Design for local constraints (internet, power, skills, workflows)
4. Over-Engineering - Avoid: Complex deep learning when simple statistics would work - Better: Start simple, add complexity only if clearly beneficial
5. Poor Data Quality - Avoid: Building AI on incomplete, inaccurate, delayed data - Better: Fix data collection process first, then add AI
6. Capacity Neglect - Avoid: External experts build system, locals can’t maintain after departure - Better: Build local capacity from start, plan for handover
7. No Feedback Loop - Avoid: Deploy and forget - Better: Continuous monitoring, user feedback, iterative improvement
8. Fairness Blindness - Avoid: Assuming AI works equally well for all populations - Better: Test performance across subgroups, address disparities
DHIS2: The Global Standard for Health Information
DHIS2 (District Health Information Software 2) is the world’s most widely used health information management system, deployed in over 80 countries and covering 3.2 billion people. Understanding DHIS2 is essential for anyone working on AI in LMIC contexts.
Why DHIS2 Matters for AI
Scale and adoption: - Used by 80+ countries for national health information systems - Covers 3.2 billion people (40% of world population) - Free, open-source, actively maintained by University of Oslo
AI integration opportunities: - Standardized data structure enables cross-country learning - API access for automated data extraction and analysis - Built-in analytics that AI can augment - Community health worker apps (DHIS2 Capture) provide mobile data collection
Common use cases: - Disease surveillance and outbreak detection - Immunization tracking and coverage analysis - Supply chain management and stock prediction - Health facility performance monitoring
DHIS2 + AI Integration Patterns
# Example: Extract DHIS2 data for anomaly detection
import requests
def get_dhis2_disease_data(base_url, username, password, dataset_id, period):
"""
Extract disease surveillance data from DHIS2 for AI analysis
"""
# DHIS2 API endpoint for analytics
endpoint = f"{base_url}/api/analytics.json"
params = {
'dimension': f'dx:{dataset_id}',
'dimension': f'pe:{period}',
'dimension': 'ou:LEVEL-3', # District level
'skipMeta': 'true'
}
response = requests.get(
endpoint,
params=params,
auth=(username, password)
)
return response.json()
# Feed into anomaly detection pipeline
# disease_data = get_dhis2_disease_data(...)
# anomalies = detect_outbreaks(disease_data)Resources: - DHIS2 Developer Portal - DHIS2 Academy - Free training courses - DHIS2 Community - Implementation support
Implementation Evidence from Lao PDR
Lao PDR piloted a DHIS2 Tracker-based electronic immunisation registry in one district hospital in November 2022, expanded it to two provinces in 2023, and completed nationwide rollout in 2024. A mixed-methods evaluation in the two earliest implementation regions combined surveys of 26 healthcare workers, 18 stakeholder interviews, workflow observation, and a data-quality assessment of 849,055 unique vaccination events (Patel et al., 2026).
The evaluation found high user acceptance alongside material data and implementation limits. Vaccine type was missing for 15.66% of events, vaccination date was missing for 14.91%, and only 15.26% of vaccinations were recorded within three days. Workforce capacity, electricity, connectivity, governance, and sustainable financing therefore remain prerequisites for using registry data in forecasting or coverage analysis (Patel et al., 2026).
Simplified AI Vendor Evaluation for Resource-Limited Settings
Health departments with limited technical capacity need a streamlined evaluation process. Use this one-page checklist before adopting any AI tool.
Before you begin: If you cannot answer “yes” to the first three questions, do not proceed.
Must-Have Requirements (All must be YES)
| # | Question | YES/NO |
|---|---|---|
| 1 | Can the system work offline or with intermittent connectivity? | |
| 2 | Is the system validated on populations similar to yours? | |
| 3 | Can your staff operate it with less than 1 week of training? | |
| 4 | Does the vendor provide local language support? | |
| 5 | Is pricing transparent and sustainable for your budget? |
Red Flags (Any YES = Reconsider)
| # | Question | YES/NO |
|---|---|---|
| 1 | Does the vendor require your data to leave the country? | |
| 2 | Is the system “black box” with no explainability? | |
| 3 | Does the contract lock you in for >2 years? | |
| 4 | Has the vendor never deployed in a similar setting? | |
| 5 | Does the vendor dismiss questions about bias/fairness? |
Key Questions to Ask Vendors
- “Where has this been deployed in similar settings?”
- Get specific country/facility references
- Contact those sites directly
- “What happens when internet is unavailable?”
- Acceptable: “Works offline, syncs when connected”
- Unacceptable: “Requires constant connectivity”
- “How was the model validated on our population?”
- Acceptable: “Validated on [similar LMIC population]”
- Unacceptable: “Validated in US/Europe, should generalize”
- “What is the total cost of ownership for 5 years?”
- Include: licensing, training, maintenance, infrastructure
- Watch for hidden costs (API calls, storage, support)
- “What happens when the contract ends?”
- Data portability
- Transition support
- Local capacity built
Decision Matrix
| Score | Recommendation |
|---|---|
| 5/5 Must-Have + 0/5 Red Flags | Proceed with pilot |
| 4/5 Must-Have + 0-1 Red Flags | Request modifications, re-evaluate |
| <4 Must-Have OR >1 Red Flag | Do not proceed |