Skip to content

Commit

Permalink
[DA-4616] AwardeeInSite Backend Job & API (#4032)
Browse files Browse the repository at this point in the history
* Add Awardee InSite API

* Add Awardee InSite API tests

* Add cloud scheduler to run awardee insite datafeed

* Add Awardee InSite Feed

* Add datafeed queries for awardee insite

* Fix get_id

* Update function name

* Add awardee insite data feed test

* Add enrollment status query

* Map enum cols from PS table to strings

* Update test

* Add test

* Update to_client_json

* Handle withdrawn participants

* Update model

* Fix queries & add update withdrawn query

* Add awardee insite migration file

* Add staticmethod

* Disable withdrawn update for testing

* Add POST to AwardeeInSiteInputFeed

* Fix

* Fix table name

* Add missing col

* Fix

* Camel case vals

* Add snake_to_camel case func

* Fixes

* Add ehr cond for testing

* Update get_id

* Test

* Fix bug

* Testing

* Add logging for debugging

* Add logging for debugging

* Add logging for debugging

* Add logging for debugging

* Rm logging/print

* Add else unset in query

* Debug patient status

* Fix

* Fixes

* Remove patientStatus

* Patient Status test

* Debug

* rdr_service/workflow_management/ppsc/ppsc_data_transfer_input_feed.py

* rdr_service/workflow_management/ppsc/ppsc_data_transfer_input_feed.py

* Add eval

* Add logging, ast

* Add patientStatus cond

* Fix tests

* Improve logging

* Fix query

* Fix alias

* Handle withdrawn participants

* Fix

* Handle withdrawn pids inside the query

* Finalize query

* Rm withdrawn col list

* Update to_client_json

* Update test

* Set site default to NULL

* Update comment

* Correct doc

* Update method to use baseclass

* Use base dao's method

* Update method name

* Add awardee insite docs
  • Loading branch information
shahwaiz14 authored Jan 9, 2025
1 parent 716777f commit 5d53823
Show file tree
Hide file tree
Showing 12 changed files with 1,800 additions and 3 deletions.
1 change: 1 addition & 0 deletions doc/api_workflows/api_resource_ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ API Resource Reference
:maxdepth: 2

Participant Summary Field List <field_reference/participant_summary_field_list>
Awardee InSite Field List <field_reference/awardee_insite_field_list>
BioBank Order Field List <field_reference/biobank_order_field_list>
Enumerated Field Options Reference <field_reference/enumerated_fields>
Patient API Reference <endpoints/patient>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
============================================================
Awardee InSite Field List
============================================================

.. autoclass:: rdr_service.model.awardee_insite.AwardeeInSite
:members:
:undoc-members:
:exclude-members: internal_fields, id, modified, created, create_surrogate_key_sql
143 changes: 143 additions & 0 deletions rdr_service/alembic/ppsc/versions/ae17f737a2cc_add_awardee_insite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""add_awardee_insite
Revision ID: ae17f737a2cc
Revises: 6e07f138ede8
Create Date: 2024-12-30 11:44:27.237681
"""
from alembic import op
import sqlalchemy as sa
import rdr_service.model.utils
from sqlalchemy.dialects import mysql


# revision identifiers, used by Alembic.
revision = "ae17f737a2cc"
down_revision = "6e07f138ede8"
branch_labels = None
depends_on = None


def upgrade(engine_name):
globals()["upgrade_%s" % engine_name]()


def downgrade(engine_name):
globals()["downgrade_%s" % engine_name]()


def upgrade_ppsc():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"awardee_insite",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("created", rdr_service.model.utils.UTCDateTime(), nullable=True),
sa.Column("modified", rdr_service.model.utils.UTCDateTime(), nullable=True),
sa.Column("participant_id", sa.BigInteger(), nullable=False),
sa.Column("first_name", sa.String(length=255), nullable=True),
sa.Column("middle_name", sa.String(length=255), nullable=True),
sa.Column("last_name", sa.String(length=255), nullable=True),
sa.Column("zip_code", sa.String(length=10), nullable=True),
sa.Column("state", sa.String(length=255), nullable=True),
sa.Column("city", sa.String(length=255), nullable=True),
sa.Column("street_address", sa.String(length=255), nullable=True),
sa.Column("street_address2", sa.String(length=255), nullable=True),
sa.Column("phone_number", sa.String(length=80), nullable=True),
sa.Column("email", sa.String(length=255), nullable=True),
sa.Column("date_of_birth", sa.Date(), nullable=True),
sa.Column("organization", sa.String(length=255), nullable=True),
sa.Column("withdrawal_status", sa.String(length=32), nullable=False),
sa.Column(
"withdrawal_time", rdr_service.model.utils.UTCDateTime(), nullable=True
),
sa.Column("deactivation_status", sa.String(length=32), nullable=False),
sa.Column(
"deactivation_time", rdr_service.model.utils.UTCDateTime(), nullable=True
),
sa.Column("deceased_status", sa.String(length=32), nullable=False),
sa.Column(
"deceased_authored", rdr_service.model.utils.UTCDateTime(), nullable=True
),
sa.Column(
"clinic_physical_measurements_status", sa.String(length=32), nullable=False
),
sa.Column(
"clinic_physical_measurements_finalized_time",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column(
"clinic_physical_measurements_finalized_site",
sa.String(length=255),
nullable=True,
),
sa.Column(
"self_reported_physical_measurements_status",
sa.String(length=32),
nullable=False,
),
sa.Column(
"self_reported_physical_measurements_authored",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column(
"consent_for_electronic_health_records",
sa.String(length=10),
nullable=False,
),
sa.Column(
"consent_for_electronic_health_records_authored",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column(
"consent_for_electronic_health_records_first_yes_authored",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column(
"first_ehr_receipt_time",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column(
"latest_ehr_receipt_time",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column("consent_for_study_enrollment", sa.String(length=10), nullable=False),
sa.Column(
"consent_for_study_enrollment_authored",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column("patient_status", mysql.JSON(), nullable=True),
sa.Column("enrollment_status", sa.String(length=32), nullable=True),
sa.Column("biospecimen_source_site", sa.String(length=255), nullable=True),
sa.Column(
"biospecimen_order_time",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.Column("biospecimen_status", sa.String(length=255), nullable=False),
sa.Column(
"sample_1sal2_collection_method", sa.String(length=255), nullable=False
),
sa.Column("sample_status_1sal2", sa.String(length=255), nullable=False),
sa.Column("sample_order_status_1sal2", sa.String(length=255), nullable=False),
sa.Column(
"sample_order_status_1sal2_time",
rdr_service.model.utils.UTCDateTime(),
nullable=True,
),
sa.PrimaryKeyConstraint("id"),
schema="ppsc",
)
# ### end Alembic commands ###


def downgrade_ppsc():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("awardee_insite", schema="ppsc")
# ### end Alembic commands ###
103 changes: 103 additions & 0 deletions rdr_service/api/awardee_insite_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from flask import request
from werkzeug.exceptions import BadRequest, InternalServerError

from rdr_service.query import Query, Results
from rdr_service.api.base_api import BaseApi, log_api_request
from rdr_service.app_util import auth_required, get_validated_user_info
from rdr_service.api_util import AWARDEE, RDR
from rdr_service.dao.awardee_insite_dao import AwardeeInSiteDao


AWARDEE_INSITE_PAGINATION_MAX_RESULTS = 1000


class AwardeeInSiteApi(BaseApi):
def __init__(self):
super().__init__(AwardeeInSiteDao())
self.awardee = None

@auth_required([RDR] + [AWARDEE])
def get(self, id_=None, participant_id=None):
log_api_request(log=request.log_record)

_, user_info = get_validated_user_info()

# Get the "awardee" linked to the user_email from the config
if AWARDEE in user_info["roles"]:
try:
self.awardee = user_info["awardee"]
except KeyError:
raise InternalServerError("Config error for awardee")

# In case RDR needs to call the API, they can pass an awardee query param with a awardee name to get the data
if RDR in user_info["roles"]:
self.awardee = request.args.get("awardee")
if not self.awardee:
raise BadRequest(
"Awardee not found. Please pass an awardee to the query"
)

return self._query()

def _make_query(self, check_invalid: bool = True) -> Query:
"""
Returns a Query object, setting properties like the max_results to be returned
in a page and field filters (awardee name, last modified).
"""
query_definition = super()._make_query(check_invalid)

field_filters = []
if self.awardee:
field_filters.append(self.dao.make_query_filter("awardee", self.awardee))

if len(request.args) > 0:
for key, value in request.args.items(multi=True):
if key == "updatedSince":
field_filters.append(self.dao.make_query_filter(key, value))

query_definition.field_filters = field_filters
query_definition.max_results = AWARDEE_INSITE_PAGINATION_MAX_RESULTS

return query_definition

def _query(self) -> dict:
"""
Called in GET function. Creates a Query object and then runs that
query and return the payload.
"""
query_definition: Query = self._make_query()
results: Results = self.dao.query(query_definition)
payload: dict = self._make_bundle(results)
return payload

def _make_bundle(self, results: Results) -> dict:
"""
Return response in a dict. If pagination token exists (meaning there is a next page), it creates
a URL so that the client can call that URL to retrieve the next page. The URL to get the
next page, and the participants results are added into the dictionary to be sent to the client.
:param results: Result object containing the results of the query in
the item attribute and pagination token in the pagination_token attribute.
:return: Payload that will be sent in the GET request.
"""

from rdr_service import main

bundle_dict = {"resourceType": "Bundle", "type": "searchset"}
if results.pagination_token:
query_params = request.args.copy()
query_params["_token"] = results.pagination_token
next_url = main.api.url_for(
self.__class__, _external=True, **query_params.to_dict(flat=False)
)
bundle_dict["link"] = [{"relation": "next", "url": next_url}]

entries = []
for item in results.items:
resource = self._make_response(item[0]) # item = [awardee_model, HPO.name]
entries.append({"resource": resource})

bundle_dict["entry"] = entries
if results.total is not None:
bundle_dict["total"] = results.total
return bundle_dict
Loading

0 comments on commit 5d53823

Please sign in to comment.