|
1 | | -import os |
2 | 1 | import requests |
3 | | -from flask import Flask, request, render_template, redirect, url_for, session |
| 2 | +import os |
| 3 | +from flask import Flask, request, render_template, redirect, url_for |
4 | 4 | from dotenv import load_dotenv |
| 5 | +from datetime import datetime |
5 | 6 |
|
6 | 7 | app = Flask(__name__) |
7 | | -app.secret_key = 'supersecretkey' |
| 8 | + |
| 9 | +FHIR_SERVER_BASE_URL = "http://pwebmedcit.services.brown.edu:9091/fhir" |
| 10 | + |
8 | 11 | load_dotenv() |
9 | 12 |
|
10 | | -FHIR_SERVER_BASE_URL = os.getenv("FHIR_SERVER_BASE_URL") |
11 | 13 | username = os.getenv("FHIR_USERNAME") |
12 | 14 | password = os.getenv("FHIR_PASSWORD") |
13 | 15 |
|
14 | | -def request_patient(patient_id, credentials): |
15 | | - req = requests.get(FHIR_SERVER_BASE_URL + "/Patient/" + str(patient_id), auth=credentials) |
16 | | - if req.status_code == 200: |
17 | | - return req.json() |
18 | | - else: |
19 | | - return None |
| 16 | +observation_codes = { |
| 17 | + "Systolic Blood Pressure": "8480-6", |
| 18 | + "Diastolic Blood Pressure": "8462-4", |
| 19 | + "Total Cholesterol": "2093-3", |
| 20 | + "HDL Cholesterol": "2085-9", |
| 21 | + "LDL Cholesterol": "18262-6" |
| 22 | +} |
20 | 23 |
|
21 | | -def request_observations(patient_id, credentials): |
22 | | - req = requests.get(FHIR_SERVER_BASE_URL + f"/Observation?patient={patient_id}", auth=credentials) |
23 | | - if req.status_code == 200: |
24 | | - return req.json() |
| 24 | +def get_observation_value(entry): |
| 25 | + if 'valueQuantity' in entry['resource']: |
| 26 | + return entry['resource']['valueQuantity']['value'] |
| 27 | + elif 'valueString' in entry['resource']: |
| 28 | + return entry['resource']['valueString'] |
25 | 29 | else: |
26 | | - return None |
| 30 | + return 'Not Found' |
27 | 31 |
|
28 | | -def extract_patient_data(patient, observations): |
29 | | - data = {} |
30 | | - data['age'] = 2024 - int(patient['birthDate'][:4]) # Simplified age calculation |
31 | | - data['gender'] = patient['gender'] |
32 | | - data['race'] = 'unknown' # Default value |
33 | | - |
34 | | - for entry in observations.get('entry', []): |
35 | | - resource = entry['resource'] |
36 | | - code = resource['code']['coding'][0]['code'] |
37 | | - |
38 | | - print(f"Processing code: {code}") |
39 | | - |
40 | | - value = None |
41 | | - if 'valueString' in resource: |
42 | | - try: |
43 | | - value = float(resource['valueString']) |
44 | | - print(f"Found valueString: {value}") |
45 | | - except ValueError: |
46 | | - print(f"Invalid valueString: {resource['valueString']}") |
47 | | - continue # skip if the valueString is not a valid float |
48 | | - |
49 | | - if value is not None: |
50 | | - if code == '2093-3': # Total Cholesterol |
51 | | - data['total_cholesterol'] = value |
52 | | - elif code == '2085-9': # HDL Cholesterol |
53 | | - data['hdl_cholesterol'] = value |
54 | | - elif code == '18262-6': # LDL Cholesterol |
55 | | - data['ldl_cholesterol'] = value |
56 | | - elif code == '8480-6': # Systolic Blood Pressure |
57 | | - data['systolic_bp'] = value |
58 | | - elif code == '8462-4': # Diastolic Blood Pressure |
59 | | - data['diastolic_bp'] = value |
| 32 | +def get_latest_observation(entries): |
| 33 | + if not entries: |
| 34 | + return 'Not Found' |
| 35 | + latest_entry = max(entries, key=lambda e: e['resource']['effectiveDateTime']) |
| 36 | + return get_observation_value(latest_entry) |
60 | 37 |
|
61 | | - print(f"Extracted data: {data}") |
62 | | - return data |
| 38 | +def calculate_age(birth_date): |
| 39 | + birth_date = datetime.strptime(birth_date, '%Y-%m-%d') |
| 40 | + today = datetime.today() |
| 41 | + return today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day)) |
63 | 42 |
|
64 | | -@app.route('/', methods=['GET', 'POST']) |
65 | | -def index(): |
66 | | - if request.method == 'POST': |
67 | | - patient_id = request.form.get('patient_id') |
68 | | - session['patient_id'] = patient_id |
69 | | - credentials = (username, password) |
70 | | - patient = request_patient(patient_id, credentials) |
71 | | - observations = request_observations(patient_id, credentials) |
| 43 | +def get_patient_demographics(patient_id, credentials): |
| 44 | + response = requests.get(FHIR_SERVER_BASE_URL + f"/Patient/{patient_id}", auth=credentials) |
| 45 | + if response.status_code == 200: |
| 46 | + patient = response.json() |
| 47 | + birth_date = patient['birthDate'] |
| 48 | + age = calculate_age(birth_date) |
| 49 | + sex = patient['gender'] |
| 50 | + race = next((extension['valueCodeableConcept']['text'] |
| 51 | + for extension in patient.get('extension', []) |
| 52 | + if extension['url'] == 'http://hl7.org/fhir/StructureDefinition/us-core-race'), 'Not Found') |
| 53 | + return {'age': age, 'sex': sex, 'race': race} |
| 54 | + else: |
| 55 | + return {'age': 'Not Found', 'sex': 'Not Found', 'race': 'Not Found'} |
72 | 56 |
|
73 | | - if patient and observations: |
74 | | - patient_data = extract_patient_data(patient, observations) |
75 | | - session['patient_data'] = patient_data |
| 57 | +def get_patient_observations(patient_id, credentials): |
| 58 | + observations = {} |
| 59 | + demographics = get_patient_demographics(patient_id, credentials) |
| 60 | + for obs_name, code in observation_codes.items(): |
| 61 | + response = requests.get(FHIR_SERVER_BASE_URL + f"/Observation?patient={patient_id}&code={code}", auth=credentials) |
| 62 | + if response.status_code == 200: |
| 63 | + data = response.json() |
| 64 | + if 'entry' in data and data['entry']: |
| 65 | + observations[obs_name] = get_latest_observation(data['entry']) |
| 66 | + else: |
| 67 | + observations[obs_name] = 'Not Found' |
76 | 68 | else: |
77 | | - session['patient_data'] = {} |
78 | | - return redirect(url_for('ascvd_risk_calculator')) |
| 69 | + observations[obs_name] = 'Not Found' |
| 70 | + return observations, demographics |
79 | 71 |
|
80 | | - return render_template('index.html') |
81 | | - |
82 | | -@app.route('/ascvd', methods=['GET', 'POST']) |
83 | | -def ascvd_risk_calculator(): |
84 | | - patient_data = session.get('patient_data', {}) |
85 | | - result = None |
| 72 | +@app.route('/', methods=['GET', 'POST']) |
| 73 | +def index(): |
| 74 | + observations = None |
| 75 | + demographics = None |
| 76 | + additional_fields = {} |
86 | 77 |
|
87 | 78 | if request.method == 'POST': |
88 | | - try: |
89 | | - age = request.form.get('age', type=int) |
90 | | - gender = request.form.get('gender') |
91 | | - race = request.form.get('race') |
92 | | - systolic_bp = request.form.get('systolic_bp', type=int) |
93 | | - diastolic_bp = request.form.get('diastolic_bp', type=int) |
94 | | - total_cholesterol = request.form.get('total_cholesterol', type=int) |
95 | | - hdl_cholesterol = request.form.get('hdl_cholesterol', type=int) |
96 | | - ldl_cholesterol = request.form.get('ldl_cholesterol', type=int) |
97 | | - diabetes = request.form.get('diabetes') == 'yes' |
98 | | - smoker = request.form.get('smoker') == 'yes' |
99 | | - on_bp_meds = request.form.get('on_bp_meds') == 'yes' |
100 | | - on_statin = request.form.get('on_statin') == 'yes' |
101 | | - on_aspirin = request.form.get('on_aspirin') == 'yes' |
102 | | - refine_risk = request.form.get('refine_risk') == 'yes' |
103 | | - |
104 | | - # Use the fetched data if available, otherwise use user input |
105 | | - age = patient_data.get('age', age) |
106 | | - gender = patient_data.get('gender', gender) |
107 | | - race = patient_data.get('race', race) |
108 | | - systolic_bp = patient_data.get('systolic_bp', systolic_bp) |
109 | | - diastolic_bp = patient_data.get('diastolic_bp', diastolic_bp) |
110 | | - total_cholesterol = patient_data.get('total_cholesterol', total_cholesterol) |
111 | | - hdl_cholesterol = patient_data.get('hdl_cholesterol', hdl_cholesterol) |
112 | | - ldl_cholesterol = patient_data.get('ldl_cholesterol', ldl_cholesterol) |
113 | | - |
114 | | - if None in [age, gender, race, systolic_bp, diastolic_bp, total_cholesterol, hdl_cholesterol]: |
115 | | - result = 'Incomplete input. Please enter all required fields.' |
116 | | - else: |
117 | | - risk_score = calculate_ascvd_risk( |
118 | | - age, gender, race, systolic_bp, diastolic_bp, total_cholesterol, hdl_cholesterol, ldl_cholesterol, |
119 | | - diabetes, smoker, on_bp_meds, on_statin, on_aspirin, refine_risk |
120 | | - ) |
121 | | - result = f"Estimated 10-year ASCVD Risk: {risk_score:.2f}%" |
122 | | - except ValueError: |
123 | | - result = 'Invalid input. Please enter valid values for all fields.' |
124 | | - |
125 | | - return render_template('ascvd.html', result=result, patient_data=patient_data) |
| 79 | + patient_id = request.form['patient_id'] |
| 80 | + additional_fields['diabetes'] = request.form.get('diabetes') |
| 81 | + additional_fields['smoker'] = request.form.get('smoker') |
| 82 | + additional_fields['hypertension'] = request.form.get('hypertension') |
| 83 | + additional_fields['statin'] = request.form.get('statin') |
| 84 | + additional_fields['aspirin'] = request.form.get('aspirin') |
| 85 | + credentials = (username, password) |
| 86 | + observations, demographics = get_patient_observations(patient_id, credentials) |
| 87 | + # Capture additional inputs from the user |
| 88 | + demographics['age'] = request.form.get('age') or demographics['age'] |
| 89 | + demographics['sex'] = request.form.get('sex') or demographics['sex'] |
| 90 | + demographics['race'] = request.form.get('race') or demographics['race'] |
| 91 | + for obs_name in observation_codes.keys(): |
| 92 | + form_name = obs_name.replace(' ', '_').lower() |
| 93 | + observations[obs_name] = request.form.get(form_name) or observations[obs_name] |
126 | 94 |
|
127 | | -def calculate_ascvd_risk(age, gender, race, systolic_bp, diastolic_bp, total_cholesterol, hdl_cholesterol, ldl_cholesterol, |
128 | | - diabetes, smoker, on_bp_meds, on_statin, on_aspirin, refine_risk): |
129 | | - # Simplified calculation for demonstration purposes |
130 | | - risk_score = (age * 0.1 + total_cholesterol * 0.2 + hdl_cholesterol * -0.1 + |
131 | | - systolic_bp * 0.2 + diastolic_bp * 0.1 + |
132 | | - (10 if diabetes else 0) + (20 if smoker else 0)) |
133 | | - if on_bp_meds: |
134 | | - risk_score += 10 |
135 | | - if on_statin: |
136 | | - risk_score += 10 |
137 | | - if on_aspirin: |
138 | | - risk_score += 5 |
139 | | - if refine_risk: |
140 | | - risk_score *= 0.9 # Example refinement factor |
141 | | - return risk_score |
| 95 | + return render_template('index.html', observations=observations, demographics=demographics, additional_fields=additional_fields) |
142 | 96 |
|
143 | 97 | if __name__ == '__main__': |
144 | | - port = int(os.getenv('PORT', 5000)) |
145 | | - app.run(debug=True, port=port) |
| 98 | + port_str = os.environ['FHIR_PORT'] |
| 99 | + port_int = int(port_str) |
| 100 | + app.run(debug=True, port=port_int) |
0 commit comments