Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PRMP-640 #11

Merged
merged 8 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
177 changes: 131 additions & 46 deletions lambda/event-enrichment/event_enrichment_main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import json
import os
from datetime import date, timedelta, datetime
from typing import Optional

import boto3
import urllib3

from services.models.ods_models import PracticeOds, IcbOds

ODS_PORTAL_URL = "https://directory.spineservices.nhs.uk/ORD/2-0-0/organisations/"
ICB_ROLE_ID = "RO98"
EMPTY_ORGANISATION = {"Name": None}
Expand Down Expand Up @@ -65,55 +68,131 @@ def _enrich_events(sqs_messages: dict) -> list:
f"Skipping enrichment for degrades event with eventId: {event['eventId']}."
)
continue

# set requesting practice info
requesting_practice_organisation = _fetch_organisation(
event["requestingPracticeOdsCode"]
event.update(
_requesting_practice_info(
ods_code=event["requestingPracticeOdsCode"],
practice_name_key="requestingPracticeName",
icb_name_key="requestingPracticeIcbName",
icb_ods_code_key="requestingPracticeIcbOdsCode",
supplier_key="requestingSupplierName",
)
)
event["requestingPracticeName"] = requesting_practice_organisation["Name"]
event["requestingPracticeIcbOdsCode"] = _find_icb_ods_code(
requesting_practice_organisation
# set sending practice info
event.update(
_requesting_practice_info(
ods_code=event["sendingPracticeOdsCode"],
practice_name_key="sendingPracticeName",
icb_name_key="sendingPracticeIcbName",
icb_ods_code_key="sendingPracticeIcbOdsCode",
supplier_key="sendingSupplierName",
)
)
event["requestingPracticeIcbName"] = _fetch_organisation(
event["requestingPracticeIcbOdsCode"]
)["Name"]

# set sending practice info
sending_practice_organisation = _fetch_organisation(
event["sendingPracticeOdsCode"]
# temporary fix for EMIS wrong reportingSystemSupplier data
reporting_system_supplier = event["reportingSystemSupplier"]
if reporting_system_supplier.isnumeric():
print(
f"TEMP FIX. Reporting system supplier received: {reporting_system_supplier}. Changed to 'EMIS'."
)
event["reportingSystemSupplier"] = "EMIS"

return events


def _requesting_practice_info(
ods_code: str, practice_name_key, icb_name_key, icb_ods_code_key, supplier_key
abbas-khan10 marked this conversation as resolved.
Show resolved Hide resolved
) -> dict:
enrichment_info = {}
print("requesting data for: " + ods_code)
gp_dynamo_item = arrange_gp_data_from_dynamo(ods_code) or get_gp_data_from_api(
ods_code
)
if gp_dynamo_item:
enrichment_info.update(
{
practice_name_key: gp_dynamo_item.practice_name,
icb_ods_code_key: gp_dynamo_item.icb_ods_code,
}
)
event["sendingPracticeName"] = sending_practice_organisation["Name"]
event["sendingPracticeIcbOdsCode"] = _find_icb_ods_code(
sending_practice_organisation
enrichment_info[supplier_key] = (
get_supplier_data(ods_code, gp_dynamo_item) or "UNKNOWN"
)
event["sendingPracticeIcbName"] = _fetch_organisation(
event["sendingPracticeIcbOdsCode"]
)["Name"]

# set requesting supplier info
requesting_supplier_name = get_supplier_name(event["requestingPracticeOdsCode"])
event["requestingSupplierName"] = (
requesting_supplier_name
if requesting_supplier_name is not None
else "UNKNOWN"
enrichment_info[icb_name_key] = (
get_icb_name(gp_dynamo_item.icb_ods_code) or "UNKNOWN"
)
return enrichment_info

# set sending supplier info
sending_supplier_name = get_supplier_name(event["sendingPracticeOdsCode"])
event["sendingSupplierName"] = (
sending_supplier_name
if sending_supplier_name is not None
else "UNKNOWN"
)

# temporary fix for EMIS wrong reportingSystemSupplier data
reporting_system_supplier = event["reportingSystemSupplier"]
if reporting_system_supplier.isnumeric():
print(f"TEMP FIX. Reporting system supplier received: {reporting_system_supplier}. Changed to 'EMIS'.")
event["reportingSystemSupplier"] = "EMIS"
def arrange_gp_data_from_dynamo(ods_code: str):
try:
gp_dynamo_data = get_gp_data_from_dynamo_request(ods_code)
print("Successfully query dynamo for GP data")
return gp_dynamo_data
except PracticeOds.DoesNotExist:
print("Failed to find GP data in dynamo table")
return None


return events
def get_icb_name(ods_code: str):
if ods_code is None:
return None
else:
return get_icb_name_from_dynamo(ods_code) or get_icb_name_from_api(ods_code)


def get_icb_name_from_dynamo(ods_code: str):
try:
icb_dynamo_item = get_icb_data_from_dynamo_request(ods_code)
print("Successfully query dynamo for ICB data")
return icb_dynamo_item.icb_name
except IcbOds.DoesNotExist:
print("Failed to find ICB data in dynamo table")
return None


def get_gp_data_from_api(ods_code: str):
requesting_practice_organisation = _fetch_organisation(ods_code)

practice_name = requesting_practice_organisation["Name"]
if practice_name is None:
return None
icb_ods_code = _find_icb_ods_code(requesting_practice_organisation)
gp_api_item = PracticeOds(ods_code, practice_name, icb_ods_code=icb_ods_code)
gp_api_item.save()
return gp_api_item


def get_icb_name_from_api(ods_code: str):
icb_name = _fetch_organisation(ods_code)["Name"]
icb_api_item = IcbOds(ods_code, icb_name)
icb_api_item.save()
return icb_name


def get_supplier_data(ods_code: str, gp_dynamo_item: PracticeOds):
date_today = date.today()
date_one_month_ago = date_today - timedelta(days=30)
supplier_last_update_date = (
gp_dynamo_item.supplier_last_updated.date()
if gp_dynamo_item.supplier_last_updated
else None
)
is_out_of_date = (
supplier_last_update_date < date_one_month_ago
if supplier_last_update_date
else True
)
if not gp_dynamo_item.supplier_name and is_out_of_date:
requesting_supplier_name = get_supplier_name_from_sds_api(ods_code)
gp_dynamo_item.supplier_name = requesting_supplier_name
gp_dynamo_item.update(
actions=[
PracticeOds.supplier_name.set(requesting_supplier_name),
PracticeOds.supplier_last_updated.set(datetime.now()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we check whether the supplier_last_updated should be of date type or datetime type?
I see that we use date.today() above (which output date type), and we have datetime.now() here (which output datetime type) .
When I tried compare date and datetime in local terminal it gave an error:

>>> datetime.now() < date.today()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare datetime.datetime to datetime.date

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you try datetime.now().date() < date.today() it should be ok.
We convert datetime to date in line 179 😄

]
)
return gp_dynamo_item.supplier_name


def _find_icb_ods_code(practice_organisation: dict) -> Optional[str]:
Expand Down Expand Up @@ -260,9 +339,9 @@ def _find_supplier_ods_codes_from_supplier_details(supplier_details: dict) -> li
return supplier_ods_codes


def get_supplier_name(practice_ods_code: str) -> Optional[str]:
def get_supplier_name_from_sds_api(practice_ods_code: str) -> Optional[str]:
"""uses the SDS FHIR API to get the system supplier from an ODS code"""

print("Requesting supplier info from SDS")
if not practice_ods_code or practice_ods_code.isspace():
return None

Expand All @@ -278,21 +357,27 @@ def get_supplier_name(practice_ods_code: str) -> Optional[str]:
}

supplier_name = None

for supplier_ods_code in supplier_ods_codes:
try:
supplier_name = supplier_name_mapping[supplier_ods_code]
if supplier_name is not None:
break
except KeyError:
continue
if supplier_name is None:

if supplier_name is None:
print(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to add logger here instead of print?

f"Unable to map supplier ODS code(s) found from SDS FHI API: {str(supplier_ods_codes)}"
+ " to a known supplier name. Practice ODS code from event: {practice_ods_code}."
)
)

return supplier_name




def get_gp_data_from_dynamo_request(ods_code: str):
return PracticeOds.get(ods_code)


def get_icb_data_from_dynamo_request(ods_code: str):
return IcbOds.get(ods_code)
1 change: 1 addition & 0 deletions lambda/event-enrichment/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pynamodb==6.0.1
8 changes: 4 additions & 4 deletions lambda/event-enrichment/test_event_enrichment_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
_find_supplier_ods_codes_from_supplier_details,
_has_supplier_ods_code,
UnableToFetchSupplierDetailsFromSDSFHIRException,
get_supplier_name,
get_supplier_name_from_sds_api,
UnableToMapSupplierOdsCodeToSupplierNameException,
)

Expand Down Expand Up @@ -626,7 +626,7 @@ def test_returns_supplier_name_given_a_practice_ods_code(
{"Parameter": {"Value": "some_url.net?"}},
]

supplier_name = get_supplier_name("test_supplier_ods_code")
supplier_name = get_supplier_name_from_sds_api("test_supplier_ods_code")

expected_supplier_name = "EMIS"
assert supplier_name == expected_supplier_name
Expand All @@ -646,7 +646,7 @@ def test_supplier_name_returns_none_when_supplier_ods_code_is_not_found_from_sds
{"Parameter": {"Value": "some_url.net?"}},
]

supplier_name = get_supplier_name("PRACTICE_ODS_123")
supplier_name = get_supplier_name_from_sds_api("PRACTICE_ODS_123")

assert supplier_name is None

Expand All @@ -665,6 +665,6 @@ def test_supplier_name_returns_none_when_supplier_ods_code_is_none(
{"Parameter": {"Value": "some_url.net?"}},
]

supplier_name = get_supplier_name(None)
supplier_name = get_supplier_name_from_sds_api(None)

assert supplier_name is None
22 changes: 22 additions & 0 deletions services/models/ods_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os

from pynamodb.attributes import UnicodeAttribute, UTCDateTimeAttribute
from pynamodb.models import Model

class PracticeOds(Model):
class Meta:
table_name = os.getenv("GP_ODS_DYNAMO_TABLE_NAME")

practice_ods_code = UnicodeAttribute(hash_key=True, attr_name='PracticeOdsCode')
practice_name = UnicodeAttribute(attr_name='PracticeName')
icb_ods_code = UnicodeAttribute(null=True, attr_name='IcbOdsCode')
supplier_name = UnicodeAttribute(null=True, attr_name='SupplierName')
supplier_last_updated = UTCDateTimeAttribute(null=True, attr_name='SupplierLastUpdated')

class IcbOds(Model):
class Meta:
table_name = os.getenv("ICB_ODS_DYNAMO_TABLE_NAME")

icb_ods_code = UnicodeAttribute(hash_key=True, attr_name='IcbOdsCode')
icb_name = UnicodeAttribute(attr_name='IcbName')

1 change: 0 additions & 1 deletion tasks
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ function tf_init() {

function build_lambda {
lambda_name=$1

build_dir=lambda/build/$lambda_name
rm -rf $build_dir
mkdir -p $build_dir
Expand Down
Loading