Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Read the DEVELOPER NOTES found in the code to understand the application
and where you will need to make adjustments/changes as well as some
suggestions for best practices.

Debugging server component
--------------------------
debugpy remote debugging enabled on port 5678 for server in docker compose, developer can attach to server from IDE e.g. vscode.

Error Responses and handling:
-----------------------------
[See ErrorResponses.md](./ErrorResponses.md)
2 changes: 1 addition & 1 deletion client/src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Badge } from '@cmsgov/design-system';
import { Link as RouterLink } from 'react-router-dom';

export default function Header({ }) {
export default function Header() {
return (
<header className="ds-u-padding--3 ds-u-sm-padding--6 ds-u-display--block ds-u-fill--primary-darkest">
<h1 className="ds-u-margin--0 ds-u-color--white ds-u-font-size--display ds-u-text-align--center">
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/patientData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import chart from '../images/who-charted.png'
import { SettingsType } from '../types/settings';
import { useState } from 'react';

export default function PatientData({ }) {
export default function PatientData() {
const [header] = useState('Add your Medicare Prescription Drug data');
const [settingsState] = useState<SettingsType>({
pkce: true,
Expand Down
122 changes: 80 additions & 42 deletions client/src/components/records.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ export type EOBRecord = {
amount: number
}

export default function Records({ }) {
export type ErrorResponse = {
type: string,
content: string,
}

export default function Records() {
const [records, setRecords] = useState<EOBRecord[]>([]);
const [message, setMessage] = useState<ErrorResponse>();
/*
* DEVELOPER NOTES:
* Here we are parsing through the different PDE Claim records
Expand All @@ -31,49 +37,81 @@ export default function Records({ }) {
.then(res => {
return res.json();
}).then(eobData => {
const records: EOBRecord[] = eobData.entry.map((resourceData: any) => {
const resource = resourceData.resource;
return {
id: resource.id,
code: resource.item[0]?.productOrService?.coding[0]?.code || 'Unknown',
display: resource.item[0]?.productOrService?.coding[0]?.display || 'Unknown Prescription Drug',
amount: resource.item[0]?.adjudication[7]?.amount?.value || '0'
if (eobData.entry) {
const records: EOBRecord[] = eobData.entry.map((resourceData: any) => {
const resource = resourceData.resource;
return {
id: resource.id,
code: resource.item[0]?.productOrService?.coding[0]?.code || 'Unknown',
display: resource.item[0]?.productOrService?.coding[0]?.display || 'Unknown Prescription Drug',
amount: resource.item[0]?.adjudication[7]?.amount?.value || '0'
}
});
setRecords(records);
}
else {
if (eobData.message) {
setMessage({"type": "error", "content": eobData.message || "Unknown"})
}
});
setRecords(records);
}
});
}, [])

return (
<div className='full-width-card'>
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
<TableCaption>Medicare Prescription Drug Claims Data</TableCaption>
<TableHead>
<TableRow>
<TableCell id="column_1">NDC Code</TableCell>
<TableCell id="column_2">Prescription Drug Name</TableCell>
<TableCell id="column_3">Cost</TableCell>
</TableRow>
</TableHead>
<TableBody>

{records.map(record => {
return (
<TableRow key={record.id}>
<TableCell stackedTitle="NDC Code" headers="column_1">
{record.code}
</TableCell>
<TableCell stackedTitle="Prescription Drug Name" headers="column_2">
{record.display}
</TableCell>
<TableCell stackedTitle="Cost" headers="column_3">
${record.amount}.00
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
);
if (message) {
return (
<div className='full-width-card'>
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
<TableCaption>Error Response</TableCaption>
<TableHead>
<TableRow>
<TableCell id="column_1">Type</TableCell>
<TableCell id="column_2">Content</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell stackedTitle="Type" headers="column_1">
{message.type}
</TableCell>
<TableCell stackedTitle="Content" headers="column_2">
{message.content}
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
);
} else {
return (
<div className='full-width-card'>
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
<TableCaption>Medicare Prescription Drug Claims Data</TableCaption>
<TableHead>
<TableRow>
<TableCell id="column_1">NDC Code</TableCell>
<TableCell id="column_2">Prescription Drug Name</TableCell>
<TableCell id="column_3">Cost</TableCell>
</TableRow>
</TableHead>
<TableBody>
{records.map(record => {
return (
<TableRow key={record.id}>
<TableCell stackedTitle="NDC Code" headers="column_1">
{record.code}
</TableCell>
<TableCell stackedTitle="Prescription Drug Name" headers="column_2">
{record.display}
</TableCell>
<TableCell stackedTitle="Cost" headers="column_3">
${record.amount}.00
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
);
}
};
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ services:
dockerfile: ./Dockerfile
ports:
- "3001:3001"
- "5678:5678"
volumes:
- ./server:/server
client:
Expand Down
3 changes: 2 additions & 1 deletion server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ COPY ["./src/prestart/env/sandbox.sample.env","./src/prestart/env/development.en
COPY . .

RUN pip install pipenv
RUN pip install debugpy
RUN pipenv install --system --deploy --ignore-pipfile

ENV ENV "development"

EXPOSE 3001

CMD ["sh", "-c", "python run.py --ENV ${ENV}"]
CMD ["sh", "-c", "python -m debugpy --listen 0.0.0.0:5678 run.py --ENV ${ENV}"]
103 changes: 51 additions & 52 deletions server/src/app/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# views.py
import json

from flask import redirect, request
import requests
from ..data.Database import *
from . import app
from ..entities.Settings import Settings
from ..utils.configUtil import getConfigSettings
from ..utils.bb2Util import generateAuthorizeUrl, getAccessToken, getBenefitData
from ..utils.userUtil import clearBB2Data, getLoggedInUser
from ..utils.config_util import get_config_settings
from ..utils.bb2_util import generate_authorize_url, get_access_token, get_benefit_data
from ..utils.user_util import clear_bb2_data, get_loggedin_user
from ..shared.LoggerFactory import LoggerFactory
import json

"""
This is the location of all the routes, via the port specified in the config, that allows the
Expand All @@ -20,67 +18,62 @@

# initialize the logger object
myLogger = LoggerFactory.get_logger(log_file=__name__,log_level='DEBUG')
loggedInUser = getLoggedInUser()
loggedInUser = get_loggedin_user()

#########################################################################################
# Test route
#########################################################################################
@app.route('/',methods=['GET'])
def verifyPortListening():
def verify_port_listening():
return 'Listening on Port 3001 for the Server!'

#########################################################################################
# Authorize routes
#########################################################################################

@app.route('/api/authorize/authurl',methods=['GET'])
def getAuthUrl():
def get_auth_url():
""" DEVELOPER NOTE:
* to utilize the latest security features/best practices
* it is recommended to utilize pkce
"""
# get configuration and settings
myEnv = request.args.get('env') or 'development'
myVersion = request.args.get('version') or 'v2'
my_env = request.args.get('env') or 'development'
my_version = request.args.get('version') or 'v2'
PKCE = request.args.get('pkce') or True

settings = Settings(myEnv,myVersion,PKCE)

configSettings = getConfigSettings(myEnv)
authUrl = generateAuthorizeUrl(settings, configSettings)
return authUrl
return generate_authorize_url(Settings(my_env, my_version, PKCE), get_config_settings(my_env))

@app.route('/api/authorize/currentAuthToken',methods=['GET'])
def getCurrentAuthToken():
def get_current_auth_token():
return loggedInUser.get('authToken')

@app.route('/api/bluebutton/callback/',methods=['GET'])
def authorizationCallback():
def authorization_callback():
try:
requestQuery = request.args
request_query = request.args

if (requestQuery.get('error') == BENE_DENIED_ACCESS):
if (request_query.get('error') == BENE_DENIED_ACCESS):
# clear all saved claims data since the bene has denied access for the application
clearBB2Data()
clear_bb2_data()
myLogger.error('Beneficiary denied application access to their data')
return redirect('http://localhost:3000')

if (requestQuery.get('code') == ''):
if (request_query.get('code') == ''):
myLogger.error('Response was missing access code!')
if (DBsettings.pkce and requestQuery.get('state')):
if (DBsettings.pkce and request_query.get('state')):
myLogger.error('State is required when using PKCE')

# get configuration and settings
myEnv = requestQuery.get('env') or 'development'
myVersion = requestQuery.get('version') or 'v2'
PKCE = requestQuery.get('pkce') or True
my_env = request_query.get('env') or 'development'
my_version = request_query.get('version') or 'v2'
PKCE = request_query.get('pkce') or True

settings = Settings(myEnv,myVersion,PKCE)
settings = Settings(my_env, my_version, PKCE)

configSettings = getConfigSettings(myEnv)
config_settings = get_config_settings(my_env)

# this gets the token from Medicare.gov once the 'user' authenticates their Medicare.gov account
authToken = getAccessToken(requestQuery.get('code'),requestQuery.get('state'),configSettings=configSettings,settings=settings)
auth_token = get_access_token(request_query.get('code'), request_query.get('state'), config_settings=config_settings, settings=settings)

"""DEVELOPER NOTES:
* This is where you would most likely place some type of
Expand All @@ -90,26 +83,34 @@ def authorizationCallback():
* Here we are however, just updating the loggedInUser we pulled from our MockDb, but we aren't persisting that change
* back into our mocked DB, normally you would want to do this
"""
#Here we are grabbing the mocked 'user' for our application

# Here we are grabbing the mocked 'user' for our application
# to be able to store the access token for that user
# thereby linking the 'user' of our sample applicaiton with their Medicare.gov account
# providing access to their Medicare data to our sample application
loggedInUser.update({'authToken':authToken})

""" DEVELOPER NOTES:
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
*
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
* using similar functionality
"""
eobData = getBenefitData(settings=settings,configsSettings=configSettings,query=requestQuery,loggedInUser=loggedInUser)

if (eobData != None and eobData != ''):
loggedInUser.update({'eobData':json.dumps(eobData)})
if auth_token and auth_token.get('expires_at') is not None:
loggedInUser.update({'authToken': auth_token})

""" DEVELOPER NOTES:
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
*
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
* using similar functionality
"""

eob_data = get_benefit_data(settings=settings,configs_settings=config_settings, query=request_query, logged_in_user=loggedInUser)

if eob_data:
if eob_data.get('entry', None) is not None:
loggedInUser['eobData'] = eob_data
else:
# error or malformed bundle, send generic error message to client
loggedInUser.update({'eobData': {'message': 'Unable to load EOB Data - fetch FHIR resource error.'}})
else:
loggedInUser.update({'eobData':json.dumps('Unable to load EOB Data!')})
clear_bb2_data()
# send generic error message to FE
loggedInUser.update({'eobData': {'message': 'Unable to load EOB Data - authorization failed.'}})

except BaseException as err:
"""DEVELOPER NOTES:
Expand All @@ -135,10 +136,8 @@ def authorizationCallback():
* DB you would choose to use
"""
@app.route('/api/data/benefit',methods=['GET'])
def getPatientEOB():
if (loggedInUser != None
and loggedInUser.get('eobData') != None
and loggedInUser.get('eobData') != ''):
return json.loads(loggedInUser.get('eobData'))
def get_patient_eob():
if loggedInUser and loggedInUser.get('eobData'):
return loggedInUser.get('eobData')
else:
return ''
return {}
2 changes: 1 addition & 1 deletion server/src/data/Database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
'userName' : '',
'pcp' : '',
'primaryFacility' : '',
'eobData' : ''
'eobData' : {}
}

DBusers = [basicUser]
Expand Down
Loading