FHIR Genomics Analysis for Clinical Decision Support
Learning objectives
Learn how to retrieve and analyze genomic data from FHIR servers using the Synthea Coherent Data Set
Understand how to process genetic variants for clinical decision support
Implement filtering and analysis of clinically significant variants
Create CDS-ready outputs from FHIR genomic data
Introduction
For the best learning experience, run this tutorial interactively, via one of the environment setup options. Use the above button depending on your chosen setup option.
This tutorial explores how to work with genomic data in FHIR for clinical decision support (CDS) applications. We’ll use the Synthea Coherent Data Set to retrieve and analyze genomic reports and observations, ultimately implementing a CDS service that provides actionable recommendations based on a patient’s genetic profile.
In this tutorial, we’re using a FHIR server located at http://localhost:8080/fhir, but any FHIR server loaded with appropriate data can be used. For instructions on setting up your own test server, see Standing up a FHIR Testing Server.
Learning Paths
This tutorial offers three difficulty levels to accommodate different experience levels:
Experienced developers implementing genomic CDS systems
Prerequisites: Basic knowledge of Python, FHIR resources, and genomics terminology is recommended.
Basic Setup and Connection (Beginner Level)
In this section, you’ll learn how to:
Connect to a FHIR server containing genomic data
Retrieve basic genetic diagnostic reports
Display genomic information in a structured format
First, let’s set up our connection to the FHIR server:
# Load dependencyimport requests, osfhir_server = os.environ.get('FHIR_SERVER')print(f"Using FHIR server: {fhir_server}")# Check if the server is running and connection is successfulresponse = requests.get(f"{fhir_server}/metadata")print(f"Server status: {response.status_code}")
Using FHIR server: http://localhost:8080/fhir
Server status: 200
Understanding the FHIR Metadata Endpoint
The metadata endpoint (/metadata) is a special FHIR endpoint that returns the server’s capability statement - a structured document that describes what the server can do. When we query this endpoint:
We’re checking if the server is responsive (status code 200)
We’re verifying it’s a valid FHIR server
The response contains details about supported resources, operations, and search parameters
This is a lightweight way to validate connectivity before attempting more complex queries.
If the server is responsive (code 200), proceed with the next code block.
Now let’s retrieve basic genetic diagnostic reports. The LOINC code 55232-3 refers to Genetic analysis summary panel, which is commonly used to identify genomic reports. To learn more about FHIRPath and mapping columns, refer to the intermediate level FHIR analysis exercise.
# Fetch genomics reportsprint("Retrieving basic genetic diagnostic reports...")reports_df = search.steal_bundles_to_dataframe( resource_type="DiagnosticReport", request_params={"code": "http://loinc.org|55232-3", "_count": 10}, num_pages=1, fhir_paths=[ ("id", "id"), ("patient", "subject.reference"), ("status", "status"), ("code_display", "code.coding[0].display"), ("issued", "issued"), ],)# Display the first few genomics reportsprint("First few genomics reports:")display(reports_df.head())
Now, let’s extract genetic variant information from the observations:
# Extract genetic variant information from observationsprint("\nExtracting genetic variant information...")# Create a list to store variant informationvariants = []# Process each observation to extract key informationfor _, row in observations_df.iterrows():# Check if text content is availableif'code_text'in row andnot pd.isna(row['code_text']): display_text =str(row['code_text'])# Extract genetic information using regular expressions gene_match = re.search(r'The (\S+) gene', display_text) significance_match = re.search(r"variation of '([^']+)'", display_text) variant_match = re.search(r'index (\S+) is', display_text) risks_match = re.search(r'risk of: (.+)\.', display_text)# Create a record with the extracted information variant_info = {'patient': row['subject_reference'],'gene': gene_match.group(1) if gene_match else'Unknown','clinical_significance': significance_match.group(1) if significance_match else'Unknown','variant_id': variant_match.group(1) if variant_match else'Unknown','associated_risks': risks_match.group(1) if risks_match else'Unknown' } variants.append(variant_info)# Convert to DataFrame for easier analysisvariants_df = pd.DataFrame(variants)# Display the extracted variantsprint("\nExtracted genetic variants:")display(variants_df.head(5))
Implementing Clinical Decision Support (Advanced Level)
In this section, we’ll:
Filter for clinically significant variants
Generate clinical recommendations
Implement a CDS service to deliver these recommendations
First, let’s filter for variants with significant clinical impact:
# Filter for variants with significant clinical impactprint("Filtering for clinically significant variants...")pathogenic_variants = variants_df[ variants_df['clinical_significance'].str.contains('Pathogenic|Risk Factor', na=False)]print(f"Found {len(pathogenic_variants)} variants with clinical significance")display(pathogenic_variants.head())
Filtering for clinically significant variants...
Found 25 variants with clinical significance
patient
gene
clinical_significance
variant_id
associated_risks
3
Patient/837e80f6-a7a5-77f8-36aa-c7b8ff002c4b
ADD1
Risk Factor
rs4961_FS1
Cardiovascular Issues, High LDL, Hemorrhagic S...
4
Patient/837e80f6-a7a5-77f8-36aa-c7b8ff002c4b
ADD1
Risk Factor
rs4961_FS2
Cardiovascular Issues, High LDL, Hemorrhagic S...
9
Patient/837e80f6-a7a5-77f8-36aa-c7b8ff002c4b
PON1
Risk Factor
rs662_FS1
Cardiovascular Issues, Mitral valve issues, Ds...
10
Patient/837e80f6-a7a5-77f8-36aa-c7b8ff002c4b
PON1
Risk Factor
rs662_FS2
Cardiovascular Issues, Mitral valve issues, Ds...
11
Patient/837e80f6-a7a5-77f8-36aa-c7b8ff002c4b
PON1
Risk Factor
rs854560_FS2
Cardiovascular Issues, Insulin Resistance, Low...
Now that we’ve analyzed genetic variants and generated recommendations, let’s explore how to deploy these as a clinical decision support service using CDS Hooks.
What Are CDS Hooks?
CDS Hooks is a standard for integrating clinical decision support into electronic health record (EHR) systems at the point of care. It allows our genomic analysis recommendations to be delivered to clinicians at the right time and in the right context.
For a comprehensive introduction to CDS Hooks, please refer to CDS Hooks Introduction, which covers the fundamental concepts, workflow, and implementation details.
CDS Hooks at a Glance
CDS Hooks provides a framework for:
Event-Driven Integration: Hooks trigger at specific points in clinical workflow
Standard API: Consistent way for EHRs to communicate with CDS services
Card-Based Interface: Recommendations delivered as “cards” within EHR UI
Context-Aware: Services receive patient context to provide relevant advice
Common Hook Points:
patient-view: When a patient’s record is opened
order-select: When a medication or procedure is selected
order-sign: When orders are about to be signed
The CDS Hooks Workflow
When integrated into a real clinical environment, the CDS Hooks workflow would look like this:
Trigger: A clinician opens a patient’s record, triggering the patient-view hook
Request: The EHR sends a request to a CDS service with the patient context
Analysis: CDS service queries the FHIR server for relevant genomic data and analyzes variants
Response: CDS service returns CDS Hooks cards with clinical recommendations
Display: The EHR displays these recommendations to the clinician within their workflow
Implementing a CDS Hooks Service for Genomic Analysis
Now, let’s create a CDS Hooks service to serve our genomic recommendations. This service will:
Analyze a patient’s genetic data in real-time
Generate appropriate clinical recommendations
Return these recommendations as CDS Hooks cards
The service exposes two endpoints:
A discovery endpoint that tells EHRs what services are available
A service endpoint that processes requests and returns recommendations
We will use Flask, a lightweight application framework for writing web applications and services using Python.
# Import necessary librariesfrom flask import Flask, jsonify, requestimport threadingimport timeimport reimport pandas as pd# Choose a port for the Flask serverflask_port =3299flask_server_thread =None# Create a Flask applicationapp = Flask(__name__)# CDS Hooks discovery endpoint - this tells EHRs what services we provide@app.route('/cds-services', methods=['GET'])def discovery():return jsonify({'services': [ {'id': 'genomics-advisor','hook': 'patient-view','title': 'Genomics Clinical Advisor','description': 'Provides recommendations based on genetic findings',# Note: In a production environment, prefetch could be used for optimization# but is omitted here for simplicity } ] })# CDS Hooks service endpoint - this returns recommendations for a specific patient@app.route('/cds-services/genomics-advisor', methods=['POST'])def genomics_advisor():# Get data from the request request_data = request.json context = request_data.get('context', {}) patient_id = context.get('patientId')print(f"Processing request for patient: {patient_id}")try:# Query for this specific patient's genetic reports diagnostic_reports = search.steal_bundles_to_dataframe( resource_type="DiagnosticReport", request_params={"code": "http://loinc.org|55232-3", # Genetic analysis summary panel"subject": patient_id,"_count": 100 }, num_pages=1 )# Check if we got resultsifisinstance(diagnostic_reports, dict) and"DiagnosticReport"in diagnostic_reports: reports_df = diagnostic_reports["DiagnosticReport"]else:# The data might be directly in the DataFrame reports_df = diagnostic_reportsif reports_df.empty:print("No genetic reports found for this patient") pathogenic_variants = pd.DataFrame()else:print(f"Found {len(reports_df)} genetic reports for patient {patient_id}")# Extract genetic information from flattened result columns variants = []import re# Identify all result display columns display_columns = [col for col in reports_df.columns if'result'in col and'display'in col]print(f"Found {len(display_columns)} result display columns")# Process each result display columnfor col in display_columns:for _, row in reports_df.iterrows():if pd.notna(row[col]): display_text =str(row[col])# Extract information using regex gene_match = re.search(r'The (\S+) gene', display_text) significance_match = re.search(r"variation of '([^']+)'", display_text) variant_match = re.search(r'index (\S+) is', display_text) risks_match = re.search(r'risk of: (.+)\.', display_text)if gene_match or significance_match: variant_info = {'gene': gene_match.group(1) if gene_match else'Unknown','clinical_significance': significance_match.group(1) if significance_match else'Unknown','variant_id': variant_match.group(1) if variant_match else'Unknown','associated_risks': risks_match.group(1) if risks_match else'Unknown' } variants.append(variant_info)# Convert to DataFrame and filter for pathogenic variants variants_df = pd.DataFrame(variants)print(f"Extracted {len(variants_df)} variants")ifnot variants_df.empty: pathogenic_variants = variants_df[ variants_df['clinical_significance'].str.contains('Pathogenic|Risk Factor', case=False) ]print(f"Found {len(pathogenic_variants)} pathogenic variants")else:print("No variants could be extracted") pathogenic_variants = pd.DataFrame()exceptExceptionas e:import tracebackprint(f"Error querying FHIR server: {e}")print("Traceback:") traceback.print_exc() pathogenic_variants = pd.DataFrame()# Generate recommendations based on pathogenic variants recommendations = []ifnot pathogenic_variants.empty:# Check for specific gene variants genes = pathogenic_variants['gene'].unique()print(f"Found genes for recommendations: {genes}")# Known genes with clinical recommendations gene_recommendations = {'APOE': {'title': 'APOE Pathogenic Variant Detected','detail': 'Consider lipid panel and cardiovascular risk assessment','source': 'Clinical Practice Guidelines','urgency': 'high' },'BRCA1': {'title': 'BRCA1 Pathogenic Variant Detected','detail': 'Consider cancer risk assessment and screening','source': 'NCCN Guidelines','urgency': 'high' },'BRCA2': {'title': 'BRCA2 Pathogenic Variant Detected','detail': 'Consider cancer risk assessment and screening','source': 'NCCN Guidelines','urgency': 'high' },'PON1': {'title': 'PON1 Risk Variant Detected','detail': 'Consider cardiovascular risk assessment and lipid-lowering therapy','source': 'Cardiovascular Guidelines','urgency': 'medium' },'ADRB3': {'title': 'ADRB3 Risk Variant Detected','detail': 'Associated with metabolic disorders and cardiovascular risk','source': 'Metabolic Risk Guidelines','urgency': 'medium' },'CCL2': {'title': 'CCL2 Pathogenic Variant Detected','detail': 'Associated with inflammatory processes and stroke risk','source': 'Stroke Risk Guidelines','urgency': 'high' },'FTO': {'title': 'FTO Risk Variant Detected','detail': 'Associated with obesity and insulin resistance','source': 'Metabolic Guidelines','urgency': 'medium' } }# Add recommendations for each significant genefor gene in genes:if gene in gene_recommendations: recommendations.append(gene_recommendations[gene])print(f"Added recommendation for gene: {gene}")# Check for risk categories in all variants all_risks =' '.join(pathogenic_variants['associated_risks'].dropna())# Add general variant recommendations if no specific onesifnot recommendations andlen(pathogenic_variants) >0: unique_genes =', '.join(genes) recommendations.append({'title': 'Genetic Variants Detected','detail': f'Clinically significant variants found in genes: {unique_genes}','source': 'Genetic Analysis','urgency': 'medium' })print(f"Added general recommendation for genes: {unique_genes}")else:print("No pathogenic variants found, no recommendations generated")# Convert recommendations to CDS cards cards = []for rec in recommendations: cards.append({'summary': f"{rec['title']}",'indicator': 'warning'if rec['urgency'] =='high'else'info','detail': rec['detail'],'source': {'label': rec['source'] } })# If no recommendations were found, provide a default cardifnot cards: cards = [{'summary': f'No Significant Genetic Findings - {test_patient_id}','indicator': 'info','detail': 'No pathogenic or likely pathogenic variants detected in this patient\'s genetic analysis.','source': {'label': 'Genomics Service' } }]# Return the CDS Hooks responsereturn jsonify({'cards': cards})# Start Flask in a background threaddef run_flask_in_thread():from werkzeug.serving import run_simple run_simple('localhost', flask_port, app, use_reloader=False, use_debugger=False)# Start the Flask serverflask_server_thread = threading.Thread(target=run_flask_in_thread)flask_server_thread.daemon =True# Thread will exit when the notebook exitsflask_server_thread.start()# Give the server a moment to starttime.sleep(1)print(f"CDS Hooks service running at http://localhost:{flask_port}/cds-services")
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://localhost:3299
INFO:werkzeug:Press CTRL+C to quit
CDS Hooks service running at http://localhost:3299/cds-services
Testing Our CDS Hooks Service
Now that our CDS Hooks service is running, let’s test it by simulating how an EHR would interact with it. When a clinician opens a patient’s record in an EHR system, the EHR sends a request to all registered CDS services, allowing them to provide relevant recommendations.
# Let's directly query for genetic reports and work with a patient that definitely has genetic dataprint("Querying for genetic diagnostic reports...")genetic_reports_dict = search.steal_bundles_to_dataframe( resource_type="DiagnosticReport", request_params={"code": "http://loinc.org|55232-3", # Genetic analysis summary panel"_count": 5,"_include": "DiagnosticReport:result", # Include the Observation resources }, num_pages=1, fhir_paths=[ ("id", "id"), ("subject", "subject.reference"), ("status", "status"), ("issued", "issued"), ])# Display diagnostic reportsgenetic_reports_df = genetic_reports_dict["DiagnosticReport"]print(f"Found {len(genetic_reports_df)} genetic reports")display(genetic_reports_df.head())# Display observations count if availableif"Observation"in genetic_reports_dict: observations_df = genetic_reports_dict["Observation"]print(f"Found {len(observations_df)} genetic observations")# Select a patient of interest, as if a clinician selected a patient from the EHRtest_patient_id = genetic_reports_df.loc[genetic_reports_df["subject"] =="Patient/df860bc2-1943-237f-7445-ed960a1ef069", "subject"].values[0]print(f"Selected patient with genetic data: {test_patient_id}")print(f"Testing with patient ID: {test_patient_id}")
Querying for genetic diagnostic reports...
http://localhost:8080/fhir/DiagnosticReport?_count=5&_include=DiagnosticReport:result&code=http://loinc.org|55232-3
Found 159 genetic observations
Selected patient with genetic data: Patient/df860bc2-1943-237f-7445-ed960a1ef069
Testing with patient ID: Patient/df860bc2-1943-237f-7445-ed960a1ef069
Testing Locally vs. Production
In production, EHR systems would send requests directly to your CDS Hooks service. Since we’re developing locally, we need to manually mock these requests to test our service without connecting to a real EHR system.
# Create a request with the selected patientmock_request = {"hook": "patient-view","hookInstance": "d1577c69-dfbe-44ad-ba6d-3e05e953b2ea","context": {"patientId": test_patient_id }}# Send the request to our CDS serviceprint("Sending request to CDS service...")service_response = requests.post(f"http://localhost:{flask_port}/cds-services/genomics-advisor", json=mock_request)# Display the JSON response from our serviceprint("\nCDS Service Response:")print(json.dumps(service_response.json(), indent=2))
The response contains CDS cards with our recommendations. These cards would typically be displayed directly in the EHR interface to help clinicians make informed decisions. Let’s create a visual representation of how these cards might appear in an EHR system:
# Extract the CDS cards from the responsecards = service_response.json().get('cards', [])# Create a simple HTML visualization to show how these cards would appear in an EHRhtml_output =f"""<div style="font-family: Arial, sans-serif; border: 1px solid #ccc; border-radius: 5px; padding: 15px; max-width: 700px; margin: 0 auto;"> <div style="border-bottom: 1px solid #ddd; padding-bottom: 10px; margin-bottom: 15px;"> <h3 style="margin-top: 0;">EHR Patient View: {test_patient_id}</h3> <div style="color: #666; font-size: 0.9em;">Genomic Decision Support</div> </div> <div style="background-color: #f8f9fa; border: 1px solid #e9ecef; padding: 10px; margin-bottom: 15px; border-radius: 3px;"> <p style="margin: 0; font-size: 0.9em; color: #6c757d;"> <strong>Demo Note:</strong> This is a simulation of how CDS Hooks would appear in an EHR. The buttons shown below are not functional in this demo. In a real implementation, clinicians would be able to click these buttons to take recommended actions. </p> </div>"""# Add each card to the visualizationifnot cards: html_output +="<p>No clinical decision support recommendations available for this patient.</p>"else:for card in cards:# Set card color based on importance border_color ="#007bff"# Default blueif card.get('indicator') =="warning": border_color ="#ff9800"# Orange for warningselif card.get('indicator') =="critical": border_color ="#dc3545"# Red for critical alerts# Create the card HTML html_output +=f""" <div style="border-left: 4px solid {border_color}; padding: 10px 15px; margin-bottom: 10px; background-color: #f8f9fa;"> <h4 style="margin-top: 0;">{card.get('summary', '')}</h4> <p>{card.get('detail', '')}</p> """# Add suggestion buttonsif'suggestions'in card: html_output +='<div>'for suggestion in card.get('suggestions', []): html_output +=f'<span style="display: inline-block; background: #e9ecef; border: 1px solid #ced4da; padding: 5px 10px; border-radius: 3px; margin-top: 8px; margin-right: 5px; cursor: pointer;">{suggestion.get("label", "")}</span>' html_output +='</div>'# Add source informationif'source'in card: html_output +=f'<div style="color: #6c757d; font-size: 0.85em; margin-top: 5px;">Source: {card.get("source", {}).get("label", "")}</div>' html_output +='</div>'html_output +="</div>"# Display the HTMLprint("\nVisualization of CDS Cards in EHR Interface:")display(HTML(html_output))
Visualization of CDS Cards in EHR Interface:
EHR Patient View: Patient/df860bc2-1943-237f-7445-ed960a1ef069
Genomic Decision Support
Demo Note: This is a simulation of how CDS Hooks would appear in an EHR.
The buttons shown below are not functional in this demo. In a real implementation,
clinicians would be able to click these buttons to take recommended actions.
PON1 Risk Variant Detected
Consider cardiovascular risk assessment and lipid-lowering therapy
Source: Cardiovascular Guidelines
FTO Risk Variant Detected
Associated with obesity and insulin resistance
Source: Metabolic Guidelines
CCL2 Pathogenic Variant Detected
Associated with inflammatory processes and stroke risk
Source: Stroke Risk Guidelines
Summary and Next Steps
Congratulations on completing this tutorial on genomic clinical decision support! We’ve explored how to build a CDS service that can identify clinically relevant genomic variants and deliver actionable recommendations to clinicians at the point of care.
In this tutorial, we’ve covered:
Basic genomic data retrieval from FHIR servers
Variant analysis and extraction of clinically relevant information
Implementation of a CDS Hooks service to deliver genomic recommendations
Integration simulation showing how the service would appear in an EHR
Critical Implementation Considerations for Production Deployment
Moving a prototype CDS service to production involves significant security, compliance, and safety concerns. Improper implementation could pose risks to patient care and data security.
To extend this work in a production environment, you would need to:
Host the Service:
Deploy on a secure, HIPAA-compliant infrastructure with HTTPS and appropriate security measures
Implement Authentication:
Use industry-standard authentication protocols supported by your EHR
Update Clinical Knowledge:
Store clinical recommendations in a database rather than hardcoded values
Establish a regular review cycle with clinical experts
Add Robust Error Handling:
Implement comprehensive exception handling, logging, and monitoring
Develop a comprehensive test suite
Test with Users:
Conduct usability testing with actual clinicians
Start with a limited pilot before full deployment
Implement feedback mechanisms
Remember that clinical decision support systems directly impact patient care—making robust implementation both an ethical and legal obligation.