Skip to content

Commit

Permalink
app, prokit
Browse files Browse the repository at this point in the history
  • Loading branch information
Raheel Sayeed authored and Raheel Sayeed committed Jan 23, 2020
1 parent 42ef3c5 commit aa64cda
Show file tree
Hide file tree
Showing 24 changed files with 2,154 additions and 0 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Clinic PGHD Requester App
===========================


A [SMART on FHIR][sf] app to dispatch patient generated health data (PGHD) requests to the `patient`. An array of [instruments][ilist] is compiled from FHIR `ValueSets` stored in the clinic's FHIR server.

This is a companion app for the [SMART Markers][sm] framework and its apps. As the FHIR `ServiceRequest` is generated, the downstream SMART Markers apps can fulfill those requests by generated or aggregating data and submitting back to the health system's FHIR server.

This app is fully SMART on FHIR compliant and represents the concept of sending PGHD requests with emphasis on interoperability.


Installation
-----------

1. Clone this repository:
2. Install modules
3. Edit `app.py` with the settings for the SMART on FHIR endpoints and SMART credentails
4. run app.py


```bash
$ git clone https://github.com/SMARTMarkers/practitioner-ehr-app.git
$ cd practitioner-ehr-app
$ pip3 install fhirclient
$ chmod +x app.py

//Edit app.py with settings for the SMART on FHIR endpoints
$ ./app.py
```

Notice
----------
This work is under further development and eventually will become a framework to support multiple SMART EHR apps.

[sf]: https://docs.smarthealthit.org
[ilist]: https://github.com/SMARTMarkers/smartmarkers-ios/tree/master/Sources/Instruments
[sm]: https://github.com/SMARTMarkers/smartmarkers-ios




5 changes: 5 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys
import os.path
abspath = os.path.abspath(os.path.dirname(__file__))
if abspath not in sys.path:
sys.path.insert(0, abspath)
146 changes: 146 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env python3

import jwt
import logging
from prokit import smartclient
from flask import Flask, redirect, session, request, render_template, jsonify
from fhirclient import client
from fhirclient.models import practitioner


app = Flask(__name__, template_folder='app/templates')
app_secret = "applicaitonsecret123"
app.secret_key = app_secret
app.debug = True
app.user_practitioner = None


smart_settings = {
'app_id' : 'pro_app2',
'redirect_uri' : 'http://127.0.0.1:8000/callback',
'scope' : 'openid fhirUser launch launch/patient',
'app_secret' : app_secret,
'patient_id' : '39234650-0229-4aee-975b-c8ee68bab40b'
}



def _save_state(state):
session['state'] = state
session.modified = True

def _smart_client():

state = session.get('state')
if state:
return smartclient.PROClient(state=state, save_func=_save_state)
else:
return smartclient.PROClient(settings=smart_settings, save_func=_save_state)

@app.route('/launch.html')
def app_launch():
smart_settings.update({'api_base': request.args.get('iss', '')})
smart = _smart_client()
if smart.ready and smart.patient is not None:
return redirect('/index.html')
else:
auth_url = smart.authorize_url
if auth_url:
return redirect(auth_url)
else:
return msg('error authorizing')


@app.route('/requests/new')
def request_pgd():
smart = _smart_client()

instrument_type = request.args.get('instrumenttype')
instrument_id = request.args.get('instrumentid')
if instrument_id is None or instrument_type is None:
return msg('No')

instr = None
if instrument_type == 'webrepository':
instr = [instr for instr in smart.instrumentlist_devices if instr.identifier == instrument_id][0]
elif instrument_type == 'activity':
instr = [instr for instr in smart.instrumentList_activity if instr.identifier == instrument_id][0]
elif instrument_type == 'survey':
instr = [instr for instr in smart.instrumentlist_questionnaires if instr.identifier == instrument_id][0]
elif instrument_type == 'activetask':
instr = [instr for instr in smart.instrumentlist_activetasks if instr.identifier == instrument_id][0]

print(f'selected instrument is {instr.identifier}')

if instr is None:
return msg('No')

success = smart.dispatchRequest(selected_instrument=instr, practitioner_resource=app.user_practitioner, selected_schedule=None)
print(success)
if success:
return jsonify(result={'result':'success', 'request_id': success.identifier, 'request_title': success.title})
else:
return jsonify(result={'result': 'fail'})

@app.route('/index.html')
def app_main():

smart = _smart_client()
print(session['state'])
if app.user_practitioner is None:
idtoken = session['state']['launch_context']['id_token']
if idtoken is None:
return msg('id-token not found')
jwtDecoded = jwt.decode(idtoken, verify=False)
practitioner_id = jwtDecoded['fhirUser'].split('/')[1]
app.user_practitioner = smart.user_practitioner(practitioner_id)

return render_template('index.html',
page='request',
proclient=smart,
observations=smart.getobservations(),
requests=smart.getrequests(),
questionnaires=smart.instrumentlist_questionnaires,
promis=smart.instrumentlist_promis,
activetasks=smart.instrumentlist_activetasks,
activityInstruments=smart.instrumentList_activity,
healthkit=smart.instrumentlist_healthkit,
devices=smart.instrumentlist_devices
)

@app.route('/reset')
def reset():
_reset()
return msg('Deleted session')


def _reset():
if 'state' in session:
del session['state']



@app.route('/callback')
def auth_callback():
smart = _smart_client()
try:
smart.handle_callback(request.url)
except Exception as e:
return """<h1>Authorization Error</h1>{0}""".format(e)
return redirect('/index.html')


def msg(test):
return f'{test}'

def log(msg):
logging.debug(msg)






if '__main__' == __name__:
logging.basicConfig(level=logging.DEBUG)
app.run(debug=True, port=8000)
4 changes: 4 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env python3


from prokit import request
4 changes: 4 additions & 0 deletions app/templates/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<h1>About</h1>

<p>Patient-Reported Outcomes</p>

5 changes: 5 additions & 0 deletions app/templates/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<footer class="footer fixed-bottom bg-dark">
<div class="footer-copyright text-center py-3 text-light">
<span class="small" >© 2019 <a class="text-light" target="_blank" href="http://chip.org">Computational Health Informatics Program</a><br> Boston Children's Hospital</span>
</div>
</footer>
43 changes: 43 additions & 0 deletions app/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<!-- Popper JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>

<script type=text/javascript>
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>
<title>Patient Generated Data</title>

</head>
<body>
{% include 'navbar.html' %}
<div class="container" id="content" style="margin-top:30px">
{% if page == 'main' %}
{% include 'observations.html' %}
{% else %}
{% include 'request.html' %}
{% endif %}

</div>
{% include 'footer.html' %}




</body>

</html>
23 changes: 23 additions & 0 deletions app/templates/navbar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<span class="navbar-brand mb-0 h1">SMARTMarkers: Patient Data Request</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>

<!--
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">PROMIS<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/requests">Requests</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li>
</ul>
</div> -->
</nav>
25 changes: 25 additions & 0 deletions app/templates/observations.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div id="observations-page">
<div class="row">
{% for result in observations %}
<div class="col-sm-6">
<div class="card">
<div class="card-header">
<b class="card-title">{{result.identifier}} <span class="badge badge-secondary">3</span>
</b>
<br>
<span class="badge badge-primary">Active</span><span class="text-muted small">Code: {{result.code}}
<br>Recent:</span>
<br>
</div>
<div class="card-body">
<div class="container">
<div id="identifier-chart"></div>
</div>
</div>

</div>
</div>
{% endfor %}
</div>

</div>
Loading

0 comments on commit aa64cda

Please sign in to comment.