Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: bcbi/ASCVD-Risk-Calculator
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6acaaba1806f2aeaa4e083c2aab201eac078e2f9
Choose a base ref
..
head repository: bcbi/ASCVD-Risk-Calculator
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: fda3a60edad3f6f68837a7685b9c1dbb2ea06046
Choose a head ref
Showing with 7,952 additions and 108 deletions.
  1. +5 −2 .env
  2. +36 −25 README.md
  3. +1 −0 requirements.txt
  4. +11 −0 setup_environment.sh
  5. +147 −47 src/app.py
  6. +160 −34 src/templates/index.html
  7. +7,592 −0 test.ipynb
7 changes: 5 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
FHIR_USERNAME=
FHIR_PASSWORD=
FHIR_SERVER_BASE_URL=http://pwebmedcit.services.brown.edu:9091/fhir
FHIR_USERNAME=bcbifhir
FHIR_PASSWORD=fancy-company-attraction-p3rson
FHIR_PORT=5000
R_HOME=/Library/Frameworks/R.framework/Resources # Adjust this path based on your findings
R_USER=/Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library # Adjust this path based on your R version and user library path
61 changes: 36 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,62 @@
# fhir-condition-finder
# ASCVD Risk Calculator

This is a FHIR web application written in Python and using Flask as the backend. It can only be used for synthea datasets.
This ASCVD Risk calculator is used to study the risk prediction for cardiovascular disease (CVD).

Following are the server port number and their respective datasets
This calculator uses the Pooled Cohort risk prediction equations to predict 10-year atherosclerotic cardiovascular disease risk. More information about the predictor equation can be found here https://github.com/bcjaeger/PooledCohort/tree/master

# Steps to run the ASCVD Risk Calculator

## 1. Check if R and python are installed where you are running this app.

```
9090 -> synthea10 (demo dataset)
9091 -> synthea_ri_adult
9092 -> synthea_ri_peds
python3 --version
R --version
```

## 1. Dependencies
This app depends on Python 3 and a few Python packages outlined in the `requirements.txt` file.

## 2. Before running the app

Change these parameters in the .env file
Change these parameters in the .env file. Use the port number assigned to you to run the app for FHIR_PORT.

```
FHIR_SERVER_BASE_URL= http://pwebmedcit.services.brown.edu:????/fhir
FHIR_USERNAME = ???
FHIR_PASSWORD = ???
FHIR_PORT=5000
FHIR_PORT=????
R_HOME=???
R_USER=????
```

and change the port number according to the dataset in the app.py file
FHIR_SERVER_BASE_URL= "http://pwebmedcit.services.brown.edu:????/fhir"

## 3. Running the App

We begin by creating a Python virtual environment using the `venv` module. This is done by running the command below from the "root" of this repo.
Start R and run the R.home() and .libPaths() commands to get the directory names and update them in .env file

```
python3 -m venv venv # create a virtual environment called venv
source venv/bin/activate # activate our virtual environment
pip3 install -r requirements.txt # install all our dependencies into the virtual environment
> R.home()
[1] "/Library/Frameworks/R.framework/Resources"
> .libPaths()
[1] "/Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library"
```

## 3. Clone the repo
```
git clone https://github.com/bcbi/ASCVD-Risk-Calculator.git
After the above steps, we should be able to launch our app using the following command.
cd ASCVD-Risk-Calculator
```
## 4. Run the setup script
```
./setup_environment.sh
```

## 5. Activate the virtual environment
```
python3 src/app.py
source venv/bin/activate
```

## 6. Run the flask app
```
python src/app.py
```

This will start the app on port 5000. You can open your preferred browser and see the app running on `http://localhost:5000`
This will start the app on port "FHIR_PORT". You can open your preferred browser and see the app running on `http://localhost:FHIR_PORT` replace the FHIR_PORT with the actual port number assigned to you.
The exact URL to the app can also be found on the terminal output after running the app.


1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -13,3 +13,4 @@ requests==2.32.2
urllib3==2.0.5
Werkzeug==2.3.7
zipp==3.17.0
rpy2==3.4.5
11 changes: 11 additions & 0 deletions setup_environment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

python3 -m venv venv
source venv/bin/activate

# install python packages
pip install -r requirements.txt

# install R pacakges
Rscript -e "install.packages('devtools', repos='http://cran.rstudio.com/')"
Rscript -e "devtools::install_github('bcjaeger/PooledCohort')"
194 changes: 147 additions & 47 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,167 @@
import requests
import os
from flask import Flask, request, render_template
from dotenv import load_dotenv



import requests
import rpy2.robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects.vectors import FloatVector, StrVector
from flask import Flask, request, render_template, redirect, url_for
from dotenv import load_dotenv
from datetime import datetime

app = Flask(__name__)

FHIR_SERVER_BASE_URL="http://pwebmedcit.services.brown.edu:9091/fhir"
# Import the PooledCohort package in R
pooled_cohort = importr("PooledCohort")

# Load environment variables
load_dotenv()

FHIR_SERVER_BASE_URL = os.getenv("FHIR_SERVER_BASE_URL")
username = os.getenv("FHIR_USERNAME")
password = os.getenv("FHIR_PASSWORD")


def request_patient(patient_id, credentials):

req = requests.get(FHIR_SERVER_BASE_URL + "/Patient/" + str(patient_id), auth = credentials)

print(f"Requests status: {req.status_code}")

response = req.json()
print(response.keys())

return response

def search_patients_by_condition(condition_id, credentials):
# Search for patients with a specific condition
search_url = f"{FHIR_SERVER_BASE_URL}/Condition?code={condition_id}"
req = requests.get(search_url, auth=credentials)

if req.status_code == 200:
conditions = req.json()['entry']
patient_ids = [entry['resource']['subject']['reference'].split('/')[-1] for entry in conditions]
patients = [request_patient(patient_id, credentials) for patient_id in patient_ids]
# Convert patient details into set of tuples to ensure uniqueness
unique_patients = set((patient['id'], patient['name'][0]['given'][0], patient['name'][0]['family'], patient['gender'], patient['birthDate']) for patient in patients)

total_patients = len(unique_patients)
return {'unique_patients': unique_patients, 'total_patients': total_patients}
# Set environment variables for R from .env
os.environ['R_HOME'] = os.getenv('R_HOME')
os.environ['R_USER'] = os.getenv('R_USER')

observation_codes = {
"Systolic Blood Pressure": "8480-6",
"Diastolic Blood Pressure": "8462-4",
"Total Cholesterol": "2093-3",
"HDL Cholesterol": "2085-9",
"LDL Cholesterol": "18262-6"
}

race_mapping = {
"American Indian or Alaska Native": "white",
"Asian": "white",
"Black or African American": "black",
"Native Hawaiian or Other Pacific Islander": "white",
"White": "white"
}

def get_observation_value(entry):
if 'valueQuantity' in entry['resource']:
return entry['resource']['valueQuantity']['value']
elif 'valueString' in entry['resource']:
return entry['resource']['valueString']
else:
return None




return 'Not Found'

def get_latest_observation(entries):
if not entries:
return 'Not Found'
latest_entry = max(entries, key=lambda e: e['resource']['effectiveDateTime'])
return get_observation_value(latest_entry)

def calculate_age(birth_date):
birth_date = datetime.strptime(birth_date, '%Y-%m-%d')
today = datetime.today()
return today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))

def get_patient_demographics(patient_id, credentials):
response = requests.get(FHIR_SERVER_BASE_URL + f"/Patient/{patient_id}", auth=credentials)
if response.status_code == 200:
patient = response.json()
birth_date = patient['birthDate']
age = calculate_age(birth_date)
sex = patient['gender']
race = 'Not Found'
for extension in patient.get('extension', []):
if extension['url'] == 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race':
for ext in extension.get('extension', []):
if ext['url'] == 'ombCategory':
race = race_mapping.get(ext['valueCoding']['display'], 'white')
break
return {'age': age, 'sex': sex, 'race': race}
else:
return {'age': 'Not Found', 'sex': 'Not Found', 'race': 'Not Found'}

def get_patient_observations(patient_id, credentials):
observations = {}
demographics = get_patient_demographics(patient_id, credentials)
for obs_name, code in observation_codes.items():
response = requests.get(FHIR_SERVER_BASE_URL + f"/Observation?patient={patient_id}&code={code}", auth=credentials)
if response.status_code == 200:
data = response.json()
if 'entry' in data and data['entry']:
observations[obs_name] = get_latest_observation(data['entry'])
else:
observations[obs_name] = 'Not Found'
else:
observations[obs_name] = 'Not Found'
return observations, demographics

@app.route('/', methods=['GET', 'POST'])
def index():
result = None
credentials = (username, password)
observations = None
demographics = None
ascvd_risk = None

if request.method == 'POST':
try:
condition_id = request.form['condition_id']
result = search_patients_by_condition(condition_id, credentials)
except ValueError:
result = 'Invalid input. Please enter a valid condition ID.'

return render_template('index.html', result=result)
return render_template('index.html', observations=observations, demographics=demographics, ascvd_risk=ascvd_risk)

@app.route('/fetch_patient_data', methods=['POST'])
def fetch_patient_data():
patient_id = request.form['patient_id']
credentials = (username, password)
observations, demographics = get_patient_observations(patient_id, credentials)
return render_template('index.html', observations=observations, demographics=demographics, patient_id=patient_id)

@app.route('/calculate_risk', methods=['POST'])
def calculate_risk():
patient_id = request.form.get('patient_id')
age = int(request.form['age'])
sex = request.form['sex']
race = request.form['race']
total_cholesterol = float(request.form['total_cholesterol'])
hdl_cholesterol = float(request.form['hdl_cholesterol'])
systolic_bp = float(request.form['systolic_blood_pressure'])
diabetes = request.form['diabetes']
smoker = request.form['smoker']
hypertension = request.form['hypertension']

# Convert sex and race to R compatible formats
sex_r = StrVector([sex])
race_r = StrVector([race_mapping.get(race, 'white')])

# Convert other inputs to R compatible formats
age_r = FloatVector([age])
total_cholesterol_r = FloatVector([total_cholesterol])
hdl_cholesterol_r = FloatVector([hdl_cholesterol])
systolic_bp_r = FloatVector([systolic_bp])
diabetes_r = StrVector(['yes' if diabetes == 'yes' else 'no'])
smoker_r = StrVector(['yes' if smoker == 'yes' else 'no'])
hypertension_r = StrVector(['yes' if hypertension == 'yes' else 'no'])

# Call the ASCVD risk calculation function from PooledCohort
ascvd_risk_r = pooled_cohort.predict_10yr_ascvd_risk(
sex=sex_r,
race=race_r,
age_years=age_r,
chol_total_mgdl=total_cholesterol_r,
chol_hdl_mgdl=hdl_cholesterol_r,
bp_sys_mmhg=systolic_bp_r,
bp_meds=hypertension_r,
smoke_current=smoker_r,
diabetes=diabetes_r
)

# Convert the result back to Python
ascvd_risk = list(ascvd_risk_r)[0]*100

demographics = {'age': age, 'sex': sex, 'race': race}
observations = {
'Total Cholesterol': total_cholesterol,
'HDL Cholesterol': hdl_cholesterol,
'Systolic Blood Pressure': systolic_bp,
'Diastolic Blood Pressure': request.form['diastolic_blood_pressure'] if 'diastolic_blood_pressure' in request.form else 'Not Found'
}

return render_template('index.html', ascvd_risk=ascvd_risk, demographics=demographics, observations=observations, patient_id=patient_id)

if __name__ == '__main__':
port_str = os.environ['FHIR_PORT']
port_str = os.getenv('FHIR_PORT', '5000')
port_int = int(port_str)
app.run(debug=True, port=port_int)
app.run(debug=True, port=port_int)
Loading