Cookbook 331: Offline Clinical Pulmonology Asthma-COPD Overlap (ACO), FeNO & Triple Inhaler Engine

This cookbook details how to deploy a localized, containerized pulmonology, respiratory medicine, and chronic obstructive airway disease decision-support engine for outpatient pulmonary clinics, respiratory diagnostic laboratories, and health-system disease management programs to ingest post-bronchodilator spirometry curves, fractional exhaled nitric oxide ($\text{FeNO}$), absolute blood eosinophil counts ($\text{AEC}$), smoking exposure histories, and exacerbation chronologies, evaluate GINA / GOLD Asthma-COPD Overlap ($\text{ACO}$) joint diagnostic criteria, phenotype Type 2 High ($\text{T2-High}$) Eosinophilic vs Neutrophilic Airway Endotypes, guide Single-Inhaler Triple Therapy ($\text{ICS / LABA / LAMA}$) optimization, and enforce the LABA / LAMA Monotherapy Contraindication Sentinel (preventing fatal asthma exacerbations from unshielded bronchodilators) according to Global Initiative for Asthma ($\text{GINA}$), Global Initiative for Chronic Obstructive Lung Disease ($\text{GOLD}$), and American Thoracic Society ($\text{ATS}$) consensus guidelines without external cloud API reliance.


1. Clinical Background & Airway Endotype Architecture

Asthma-COPD Overlap ($\text{ACO}$) represents a complex, heterogeneous clinical phenotype characterized by persistent airflow limitation alongside prominent historical, physiological, and inflammatory features of both asthma and COPD:


2. Pipeline & Workflow Architecture

[Patient Telemetry: Spirometry FEV1/FVC, FeNO, Blood Eos, Smoking, Inhaler Regimen]
                                         │
                                         ▼
      [Spirometric Obstruction & Reversibility Engine: Post-BD <0.70 + Reversibility]
                                         │
                                         ▼
      [GINA/GOLD ACO Scoring: Major Criteria (Asthma hx, >400mL, Eos>=300) + Minor]
                                         │
                                         ▼
      [T2 Biomarker Phenotyper: FeNO (>=50 vs <25 ppb) + Blood Eosinophils (>=300)]
                                         │
                                         ▼
      [LABA/LAMA Monotherapy Fatal Asthma Sentinel & Mandatory ICS Anchor Gatekeeper]
                                         │
                                         ▼
      [Single-Inhaler Triple Therapy Titrator (ICS/LABA/LAMA) + Pneumonia Sentinel]

3. Environment & Prerequisites

Install required scientific Python and respiratory modeling packages:

pip install numpy scipy pandas torch torchvision matplotlib

4. Complete Offline Python / PyTorch Implementation

"""
Cookbook 331: Offline Pulmonology Asthma-COPD Overlap (ACO), FeNO & Triple Inhaler Engine
OpenPHR Clinical AI Working Group (https://openphr.org)
"""

import math
import numpy as np
import pandas as pd
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass

@dataclass
class PulmonologyPatientTelemetry:
    patient_id: str
    age_years: float = 58.0
    smoking_pack_years: float = 24.0 # >= 10 pack-years = COPD risk
    # Spirometry Telemetry (Post-Bronchodilator)
    post_bd_fev1_liters: float = 1.62 # L (Predicted 3.10 L -> 52.3% predicted)
    predicted_fev1_liters: float = 3.10 # L
    post_bd_fvc_liters: float = 2.80 # L
    post_bd_fev1_fvc_ratio: float = 0.58 # < 0.70 = Persistent airflow limitation
    # Bronchodilator Reversibility (Post vs Pre Albuterol)
    fev1_reversibility_delta_ml: float = 430.0 # mL (> 400 mL = Major asthma criterion)
    fev1_reversibility_delta_percent: float = 18.5 # % (> 15% = Marked reversibility)
    # Inflammatory Biomarkers Telemetry
    feno_parts_per_billion: float = 62.0 # ppb (>= 50 ppb = T2-High Eosinophilic)
    blood_eosinophil_count_cells_ul: float = 420.0 # cells/uL (>= 300 = High ICS response)
    total_serum_ige_iu_ml: float = 240.0 # IU/mL (> 100 = Atopy)
    # Clinical History & Exacerbations
    childhood_asthma_or_atopy_history: bool = True
    exacerbations_past_12_months: int = 2 # Frequent exacerbator (>= 2)
    prior_severe_pneumonia_history: bool = False
    # Current Active Pharmacotherapy
    current_regimen: str = "LABA Monotherapy (Salmeterol 50mcg BID)" # 🚨 DEADLY ASTHMA PITFALL!

@dataclass
class ACOEvaluationReport:
    patient_id: str
    aco_diagnostic_status: str # "Definite Asthma-COPD Overlap (ACO)", "Pure COPD", "Pure Asthma"
    major_criteria_met: List[str]
    minor_criteria_met: List[str]
    t2_inflammatory_endotype: str # "T2-High Eosinophilic Airway Inflammation"
    pharmacotherapy_recommendation: List[str]
    safety_sentinels: List[str]
    clinical_gina_gold_directive: str

class AsthmaCOPDOverlapDecisionEngine:
    """
    Offline clinical engine for GINA/GOLD Asthma-COPD Overlap (ACO) diagnosis,
    FeNO / blood eosinophil biomarker phenotyping, and single-inhaler triple therapy titration.
    """

    def evaluate_aco_criteria(self, d: PulmonologyPatientTelemetry) -> Tuple[str, List[str], List[str]]:
        major_met = []
        minor_met = []

        # 1. Baseline Persistent Airflow Limitation (Prerequisite)
        has_persistent_obstruction = d.post_bd_fev1_fvc_ratio < 0.70 and d.age_years >= 40.0 and d.smoking_pack_years >= 10.0

        # Major Asthma Criteria
        if d.childhood_asthma_or_atopy_history:
            major_met.append("Major 1: Documented childhood/early-onset asthma or atopy history.")
        if d.fev1_reversibility_delta_ml >= 400.0 and d.fev1_reversibility_delta_percent >= 15.0:
            major_met.append(f"Major 2: Marked bronchodilator reversibility (+{d.fev1_reversibility_delta_ml:.0f} mL and +{d.fev1_reversibility_delta_percent:.1f}%).")
        if d.blood_eosinophil_count_cells_ul >= 300.0:
            major_met.append(f"Major 3: Elevated blood eosinophils ({d.blood_eosinophil_count_cells_ul:.0f} cells/uL >= 300).")

        # Minor Asthma Criteria
        if d.feno_parts_per_billion >= 50.0:
            minor_met.append(f"Minor 1: High FeNO ({d.feno_parts_per_billion:.0f} ppb >= 50 ppb).")
        elif d.feno_parts_per_billion >= 25.0:
            minor_met.append(f"Minor 1: Intermediate FeNO ({d.feno_parts_per_billion:.0f} ppb).")

        if (d.fev1_reversibility_delta_ml >= 200.0 and d.fev1_reversibility_delta_percent >= 12.0) and not (d.fev1_reversibility_delta_ml >= 400.0):
            minor_met.append(f"Minor 2: Standard bronchodilator reversibility (+{d.fev1_reversibility_delta_ml:.0f} mL and +{d.fev1_reversibility_delta_percent:.1f}%).")

        if d.total_serum_ige_iu_ml >= 100.0:
            minor_met.append(f"Minor 3: Elevated total serum IgE ({d.total_serum_ige_iu_ml:.0f} IU/mL >= 100).")

        # Classification Logic
        is_aco = has_persistent_obstruction and (len(major_met) >= 1 or len(minor_met) >= 2)

        if is_aco:
            status = "DEFINITE ASTHMA-COPD OVERLAP (ACO) (Meets GINA/GOLD Joint Criteria)"
        elif has_persistent_obstruction:
            status = "CHRONIC OBSTRUCTIVE PULMONARY DISEASE (COPD) (Without prominent asthma features)"
        else:
            status = "ASTHMA / NON-FIXED AIRFLOW LIMITATION"

        return status, major_met, minor_met

    def phenotype_t2_inflammation(self, d: PulmonologyPatientTelemetry) -> str:
        is_feno_high = d.feno_parts_per_billion >= 50.0
        is_eos_high = d.blood_eosinophil_count_cells_ul >= 300.0

        if is_feno_high and is_eos_high:
            return f"T2-HIGH EOSINOPHILIC / ALLERGIC ENDOTYPE (FeNO {d.feno_parts_per_billion:.0f} ppb, Eos {d.blood_eosinophil_count_cells_ul:.0f} cells/uL) -> Highly responsive to Inhaled Corticosteroids (ICS) and Biologics (anti-IL5/anti-IL4R)."
        elif is_feno_high or is_eos_high:
            return f"T2-HIGH PREDOMINANT ENDOTYPE (FeNO {d.feno_parts_per_billion:.0f} ppb or Eos {d.blood_eosinophil_count_cells_ul:.0f} cells/uL) -> Significant ICS responsiveness."
        else:
            return f"T2-LOW NEUTROPHILIC / PAUCIGRANULOCYTIC ENDOTYPE (FeNO {d.feno_parts_per_billion:.0f} ppb, Eos {d.blood_eosinophil_count_cells_ul:.0f} cells/uL) -> Limited ICS response; prioritize dual bronchodilation (LABA/LAMA)."

    def generate_pharmacotherapy_plan(self, aco_status: str, d: PulmonologyPatientTelemetry) -> Tuple[List[str], List[str]]:
        plan = []
        sentinels = []

        fev1_pct_pred = (d.post_bd_fev1_liters / max(d.predicted_fev1_liters, 0.1)) * 100.0

        # Safety Sentinel: LABA or LAMA Monotherapy without ICS in ACO
        has_laba_only = "LABA Monotherapy" in d.current_regimen or "LAMA Monotherapy" in d.current_regimen
        if "ACO" in aco_status and has_laba_only:
            sentinels.append(f"🚨 FATAL ASTHMA CONTRAINDICATION: Patient is on {d.current_regimen} without an Inhaled Corticosteroid (ICS). In patients with asthmatic features, LABA or LAMA monotherapy causes unopposed airway inflammation and significantly increases the risk of severe, life-threatening asthma exacerbations and death. STAT DISCONTINUE MONOTHERAPY AND INITIATE ICS-CONTAINING REGIMEN!")

        # Triple Therapy Optimization
        if "ACO" in aco_status:
            plan.append("1. FIRST-LINE ANCHOR: Mandatory Inhaled Corticosteroid (ICS) foundation. Never prescribe long-acting bronchodilators without ICS in ACO.")

            if d.exacerbations_past_12_months >= 1 or fev1_pct_pred < 60.0 or d.blood_eosinophil_count_cells_ul >= 300.0:
                plan.append("2. SINGLE-INHALER TRIPLE THERAPY (ICS / LABA / LAMA):")
                plan.append("   • Fluticasone Furoate / Umeclidinium / Vilanterol (Trelegy Ellipta 100/62.5/25 mcg or 200/62.5/25 mcg) 1 inhalation once daily.")
                plan.append("   • Alternative: Budesonide / Glycopyrrolate / Formoterol (Breztri Aerosphere 160/9/4.8 mcg) 2 puffs BID.")
                plan.append(f"   ℹ️ Indication: Frequent exacerbator ({d.exacerbations_past_12_months} in past year), FEV1 {fev1_pct_pred:.1f}% predicted (<60%), and high blood eosinophils ({d.blood_eosinophil_count_cells_ul:.0f} cells/uL).")
            else:
                plan.append("2. DUAL THERAPY: Initiate medium-to-high dose ICS / LABA (e.g. Fluticasone/Salmeterol or Budesonide/Formoterol BID).")

        # Pneumonia Risk Sentinel
        if d.prior_severe_pneumonia_history and d.blood_eosinophil_count_cells_ul < 100.0:
            sentinels.append("ICS-INDUCED PNEUMONIA RISK SENTINEL: High-dose ICS with low blood eosinophils (< 100 cells/uL) and prior pneumonia history significantly increases recurrent bacterial pneumonia risk. Maintain moderate-dose ICS and monitor closely.")

        plan.append("3. RESCUE INHALER: Albuterol/Ipratropium MDI or ICS/Formoterol SMART protocol as needed for acute bronchospasm.")

        return plan, sentinels

    def evaluate_case(self, data: PulmonologyPatientTelemetry) -> ACOEvaluationReport:
        status, majors, minors = self.evaluate_aco_criteria(data)
        t2_endotype = self.phenotype_t2_inflammation(data)
        plan, sentinels = self.generate_pharmacotherapy_plan(status, data)

        directives = []
        directives.append(f"DIAGNOSIS: {status}.")
        directives.append(f"ENDOTYPE: {t2_endotype}.")
        directives.append("THERAPY: Single-inhaler triple therapy (ICS/LABA/LAMA) with strict prohibition of LABA/LAMA monotherapy.")

        return ACOEvaluationReport(
            patient_id=data.patient_id,
            aco_diagnostic_status=status,
            major_criteria_met=majors,
            minor_criteria_met=minors,
            t2_inflammatory_endotype=t2_endotype,
            pharmacotherapy_recommendation=plan,
            safety_sentinels=sentinels,
            clinical_gina_gold_directive=" ".join(directives)
        )

# Example Execution & Verification
if __name__ == "__main__":
    engine = AsthmaCOPDOverlapDecisionEngine()

    print("=" * 80)
    print("OpenPHR Clinical Pulmonology Asthma-COPD Overlap (ACO) & Triple Inhaler Engine")
    print("=" * 80)

    # Test Case 1: 58-year-old male, 24 pack-year smoking history, history of childhood asthma.
    # Post-BD FEV1/FVC: 0.58 (< 0.70). FEV1 Reversibility: +430 mL and +18.5% (Major Criterion).
    # Biomarkers: FeNO 62 ppb (>= 50 ppb), Blood Eosinophils 420 cells/uL (>= 300 cells/uL) -> T2-High!
    # Currently on LABA Monotherapy -> 🚨 FATAL ASTHMA CONTRAINDICATION SENTINEL!
    # Triage: Single-Inhaler Triple Therapy (Trelegy/Breztri) + STAT Monotherapy Cessation!
    aco1 = PulmonologyPatientTelemetry(
        patient_id="PULM-ACO-4401",
        age_years=58.0,
        smoking_pack_years=24.0,
        post_bd_fev1_liters=1.62,
        predicted_fev1_liters=3.10,
        post_bd_fvc_liters=2.80,
        post_bd_fev1_fvc_ratio=0.58,
        fev1_reversibility_delta_ml=430.0,
        fev1_reversibility_delta_percent=18.5,
        feno_parts_per_billion=62.0,
        blood_eosinophil_count_cells_ul=420.0,
        total_serum_ige_iu_ml=240.0,
        childhood_asthma_or_atopy_history=True,
        exacerbations_past_12_months=2,
        current_regimen="LABA Monotherapy (Salmeterol 50mcg BID)"
    )

    rep1 = engine.evaluate_case(aco1)

    print(f"\n[Patient {rep1.patient_id} - Airway Phenotype Assessment]")
    print(f"ACO Status: {rep1.aco_diagnostic_status}")
    print("\nMajor Criteria Met:")
    for m in rep1.major_criteria_met:
        print(f"  • {m}")
    print("\nMinor Criteria Met:")
    for mn in rep1.minor_criteria_met:
        print(f"  • {mn}")
    print(f"\nInflammatory Endotype:\n  {rep1.t2_inflammatory_endotype}")
    print("\nPharmacotherapy Recommendations:")
    for rx in rep1.pharmacotherapy_recommendation:
        print(f"  {rx}")
    if rep1.safety_sentinels:
        print("\nSafety Sentinels:")
        for s in rep1.safety_sentinels:
            print(f"  🚨 {s}")
    print(f"\nGINA / GOLD Consensus Directive:\n{rep1.clinical_gina_gold_directive}")

5. Clinical Verification & Guideline Conformance


6. References