Skip to content

Commit 40aea24

Browse files
committed
Initial commit for flag import functionality
1 parent d3cd13e commit 40aea24

9 files changed

+551
-6
lines changed

importer/README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,18 @@ The coverage report `coverage.html` will be at the working directory
175175
- A separate List resource with references to all the Group and List resources generated is also created
176176
- You can pass in a `list_resource_id` to be used as the identifier for the (reference) List resource, or you can leave it empty and a random uuid will be generated
177177

178-
### 12. Import JSON resources from file
178+
### 12. Import flags from openSRP 1
179+
- Run `python3 main.py --csv_file csv/import/flags.csv --setup flags --encounter_id 123 --practitioner_id 456 --visit_location_id 789 --log_level info`
180+
- See example csv [here](/importer/csv/import/flags.csv)
181+
- `encounter_id` (Required) is the id of the visit encounter that will be linked to all the resources that are imported when the command is run
182+
- `practitioner_id` (Required) is the id of the practitioner that is linked to all the resources created during the import
183+
- `visit_location_id` (Required) is the location linked to the visit encounter, ideally should be the root location
184+
- This function creates a Visit Encounter using the provided encounter id (if one does not already exist)
185+
- It then creates a Flag resource, an Observation resource and an encounter resource for every row on the csv, depending on its type
186+
- It checks to confirm that the location provided in the csv exists, if it does not, it skips that row
187+
- If the entityType is product, it also checks to confirm that the Group id exists in the server and skips the row if it does not
188+
189+
### 13. Import JSON resources from file
179190
- Run `python3 main.py --bulk_import True --json_file tests/json/sample.json --chunk_size 500000 --sync sort --resources_count 100 --log_level info`
180191
- This takes in a file with a JSON array, reads the resources from the array in the file and posts them to the FHIR server
181192
- `bulk_import` (Required) must be set to True

importer/csv/import/flags.csv

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
event_id,event_date,id,entitytype,locationid,locationname,productid,productname,product_flag_problem,product_not_there,product_not_good,product_not_good_specify_other,product_not_there_specify_other,product_misuse,product_issue_details,product_service_point_good_order,product_service_point_not_good_order_reason,product_consult_beneficiaries_flag,product_consult_beneficiaries_issues_raised,product_required_action,product_required_action_yes
2+
100,2022-11-06 21:00:00,5f513cfd-90fa-40a9-8d84-63eed687063d,service_point,661604af-d9f9-4be2-a500-4d127c4bfbed,Nakuru,,,,,,,,,,"[""no""]","[""Lack of infrastructure""]",,,,
3+
101,2023-05-17 21:00:00,b0038380-7800-4771-b8e3-b97c413f8177,service_point,66cb98a3-6f0b-439d-95c1-7f562996f070,Isiolo,,,,,,,,,,,,"[""yes""]","[""Infrequent power""]",,
4+
102,2024-10-11 21:00:00,f9a1bd74-23e8-433b-84b3-bbec68f70036,service_point,f0b8cfc6-eea7-4a54-8eec-057a14a4cc95,Kisumu,,,,,,,,,,,,,,"[""yes""]","[""Cleaning""]"
5+
103,2024-10-13 21:00:00,128053da-639f-40ab-becc-374713fe60e2,product,1523b35a-1a23-4fc9-af8f-6fb5905236de,Malindi,46d7c50b-bfee-4715-a61b-141eb80a4a9d,Wheelbarrow,"[""Product is not there""]","[""never_received""]",,,,,,,,,,,

importer/importer/builder.py

+190-1
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ def get_valid_resource_type(resource_type):
679679

680680
# This function gets the current resource version from the API
681681
def get_resource(resource_id, resource_type):
682-
if resource_type not in ["List", "Group"]:
682+
if resource_type not in ["List", "Group", "Encounter", "Location"]:
683683
resource_type = get_valid_resource_type(resource_type)
684684
resource_url = "/".join([fhir_base_url, resource_type, resource_id])
685685
response = handle_request("GET", "", resource_url)
@@ -1150,3 +1150,192 @@ def build_report(csv_file, response, error_details, fail_count, fail_all):
11501150
logging.info(string_report)
11511151
logging.info("============================================================")
11521152
logging.info("============================================================")
1153+
1154+
1155+
def build_single_resource(
1156+
resource_type, resource_id, practitioner_id, period_start, location_id, form_encounter,
1157+
visit_encounter, subject, value_string="", note="",
1158+
):
1159+
template_map = {
1160+
"visit": "visit_encounter_payload.json",
1161+
"flag": "flag_payload.json",
1162+
"encounter": "flag_encounter_payload.json",
1163+
"observation": "flag_observation_payload.json",
1164+
}
1165+
json_template = next(
1166+
(template for key, template in template_map.items() if key in resource_type),
1167+
None,
1168+
)
1169+
1170+
boolean_code = boolean_value = ""
1171+
if "product" in resource_type:
1172+
code = "PRODCHECK"
1173+
display = text = "Product Check"
1174+
c_code = "issue_details"
1175+
c_display = c_text = value_string
1176+
elif "service_point_check" in resource_type:
1177+
code = "SPCHECK"
1178+
display = text = "Service Point Check"
1179+
c_code = "34657579"
1180+
c_display = c_text = "Service Point Good Order Check"
1181+
boolean_code = "373067005"
1182+
boolean_value = "No (qualifier value)"
1183+
elif "consult_beneficiaries" in resource_type:
1184+
code = "CNBEN"
1185+
display = text = "Consult Beneficiaries Visit"
1186+
c_code = "77223346"
1187+
c_display = c_text = "Consult Beneficiaries"
1188+
boolean_code = "373066001"
1189+
boolean_value = "Yes (qualifier value)"
1190+
elif "warehouse_check" in resource_type:
1191+
code = "WHCHECK"
1192+
display = text = "Warehouse check Visit"
1193+
c_code = "68561322"
1194+
c_display = c_text = "Required action"
1195+
boolean_code = "373066001"
1196+
boolean_value = "Yes (qualifier value)"
1197+
else:
1198+
code = display = text = c_code = c_display = c_text = ""
1199+
1200+
with open(json_path + json_template) as json_file:
1201+
resource_payload = json_file.read()
1202+
1203+
visit_encounter_vars = {
1204+
"$id": resource_id,
1205+
"$version": "1",
1206+
"$category_code": code,
1207+
"$category_display": display,
1208+
"$category_text": text,
1209+
"$code_code": c_code,
1210+
"$code_display": c_display,
1211+
"$code_text": c_text,
1212+
"$practitioner_id": practitioner_id,
1213+
"$start": period_start.replace(" ", "T"),
1214+
"$end": period_start.replace(" ", "T"),
1215+
"$subject": subject,
1216+
"$location": location_id,
1217+
"$form_encounter": form_encounter,
1218+
"$visit_encounter": visit_encounter,
1219+
"$value_string": value_string,
1220+
"$boolean_code": boolean_code,
1221+
"$boolean_value": boolean_value,
1222+
"$note": note,
1223+
}
1224+
for var, value in visit_encounter_vars.items():
1225+
resource_payload = resource_payload.replace(var, value)
1226+
1227+
obj = json.loads(resource_payload)
1228+
if "product_observation" in resource_type:
1229+
del obj["resource"]["valueCodeableConcept"]
1230+
del obj["resource"]["note"]
1231+
if (
1232+
"service_point_check_encounter" in resource_type
1233+
or "consult_beneficiaries_encounter" in resource_type
1234+
):
1235+
del obj["resource"]["subject"]
1236+
if (
1237+
"service_point_check_observation" in resource_type
1238+
or "consult_beneficiaries_observation" in resource_type
1239+
):
1240+
del obj["resource"]["focus"]
1241+
del obj["resource"]["valueString"]
1242+
resource_payload = json.dumps(obj, indent=4)
1243+
1244+
return resource_payload
1245+
1246+
1247+
def build_resources(
1248+
resource_type, encounter_id, flag_id, observation_id, practitioner_id, period, location,
1249+
visit_encounter, subject, value_string="", note="",
1250+
):
1251+
encounter = build_single_resource(
1252+
resource_type + "_encounter", encounter_id, practitioner_id, period, location, "",
1253+
visit_encounter, subject,
1254+
)
1255+
flag = build_single_resource(
1256+
resource_type + "_flag", flag_id, practitioner_id, period, location, encounter_id,
1257+
"", subject,
1258+
)
1259+
observation = build_single_resource(
1260+
resource_type + "_observation", observation_id, practitioner_id, period, location, encounter_id,
1261+
"", subject, value_string, note,
1262+
)
1263+
1264+
resources = encounter + "," + flag + "," + observation + ","
1265+
return resources
1266+
1267+
1268+
def check_location(location_id, locations_list):
1269+
if location_id in locations_list:
1270+
return locations_list[location_id]
1271+
1272+
check = get_resource(location_id, "Location")
1273+
if check != "0":
1274+
locations_list[location_id] = True
1275+
return True
1276+
else:
1277+
locations_list[location_id] = False
1278+
logging.info("-- Skipping location, it does NOT EXIST " + location_id)
1279+
return False
1280+
1281+
1282+
def build_flag_payload(resources, practitioner_id, visit_encounter):
1283+
initial_string = """{"resourceType": "Bundle","type": "transaction","entry": [ """
1284+
final_string = ""
1285+
locations = {}
1286+
for resource in resources:
1287+
flag_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, "flag" + resource[2]))
1288+
observation_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, "observation" + resource[2]))
1289+
1290+
sub_list = []
1291+
valid_location = check_location(resource[4], locations)
1292+
if valid_location:
1293+
if resource[3] == "service_point":
1294+
if "no" in resource[15]:
1295+
note = (
1296+
resource[16].replace('"', "").replace("[", "").replace("]", "")
1297+
)
1298+
sub_list = build_resources(
1299+
"service_point_check", resource[2], flag_id, observation_id, practitioner_id,
1300+
resource[1], resource[4], visit_encounter, "Location/" + resource[4], "", note,
1301+
)
1302+
if "yes" in resource[17]:
1303+
note = (
1304+
resource[18].replace('"', "").replace("[", "").replace("]", "")
1305+
)
1306+
sub_list = build_resources(
1307+
"consult_beneficiaries", resource[2], flag_id, observation_id, practitioner_id,
1308+
resource[1], resource[4], visit_encounter, "Location/" + resource[4], "", note,
1309+
)
1310+
if "yes" in resource[19]:
1311+
note = (
1312+
resource[20].replace('"', "").replace("[", "").replace("]", "")
1313+
)
1314+
sub_list = build_resources(
1315+
"warehouse", resource[2], flag_id, observation_id, practitioner_id, resource[1],
1316+
resource[4], visit_encounter, "Location/" + resource[4], "", note,
1317+
)
1318+
1319+
elif resource[3] == "product":
1320+
if resource[8]:
1321+
product_info = [
1322+
resource[8], resource[9], resource[10], resource[11], resource[12], resource[13], resource[14],
1323+
]
1324+
value_string = " | ".join(filter(None, product_info))
1325+
value_string = value_string.replace('"', "")
1326+
if resource[6]:
1327+
sub_list = build_resources(
1328+
"product", resource[2], flag_id, observation_id, practitioner_id,
1329+
resource[1], resource[4], visit_encounter, "Group/" + resource[6], value_string,
1330+
)
1331+
else:
1332+
logging.info("-- Missing Group, skipping resource: " + str(resource))
1333+
else:
1334+
logging.info("-- This entityType is not supported")
1335+
1336+
if len(sub_list) < 1:
1337+
sub_list = ""
1338+
1339+
final_string = final_string + sub_list
1340+
final_string = initial_string + final_string[:-1] + " ] } "
1341+
return final_string

importer/importer/services/fhir_keycloak_api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def request(self, **kwargs):
9999
# TODO - spread headers into kwargs.
100100
headers = {"content-type": "application/json", "accept": "application/json"}
101101
response = self.api_service.oauth.request(headers=headers, **kwargs)
102-
if response.status_code == 401 or '<html class="login-pf">' in response.text:
102+
if response.status_code == 401 or 'login' in response.text:
103103
self.api_service.refresh_token()
104104
return self.api_service.oauth.request(headers=headers, **kwargs)
105105
return response
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"request": {
3+
"method": "PUT",
4+
"url": "Encounter/$id",
5+
"ifMatch": "$version"
6+
},
7+
"resource": {
8+
"resourceType": "Encounter",
9+
"id": "$id",
10+
"identifier": [
11+
{
12+
"use": "usual",
13+
"value": "$id"
14+
}
15+
],
16+
"status": "finished",
17+
"class": {
18+
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
19+
"code": "OBSENC",
20+
"display": "Observation Encounter"
21+
},
22+
"type": [
23+
{
24+
"coding": [
25+
{
26+
"system": "http://smartregister.org/",
27+
"code": "$category_code",
28+
"display": "$category_display"
29+
}
30+
],
31+
"text": "$category_text"
32+
}
33+
],
34+
"priority": {
35+
"coding": [
36+
{
37+
"system": "http://terminology.hl7.org/ValueSet/v3-ActPriority",
38+
"code": "EL",
39+
"display": "elective"
40+
}
41+
],
42+
"text": "elective"
43+
},
44+
"subject": {
45+
"reference": "$subject"
46+
},
47+
"participant": [
48+
{
49+
"individual": {
50+
"reference": "Practitioner/$practitioner_id"
51+
}
52+
}
53+
],
54+
"period": {
55+
"start": "$start",
56+
"end": "$end"
57+
},
58+
"reasonCode": [
59+
{
60+
"coding": [
61+
{
62+
"system": "http://smartregister.org/",
63+
"code": "$category_code",
64+
"display": "$category_display"
65+
}
66+
],
67+
"text": "$category_text"
68+
}
69+
],
70+
"location": [
71+
{
72+
"location": {
73+
"reference": "Location/$location"
74+
},
75+
"status": "active"
76+
}
77+
],
78+
"partOf": {
79+
"reference": "Encounter/$visit_encounter"
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"request": {
3+
"method": "PUT",
4+
"url": "Observation/$id",
5+
"ifMatch": "$version"
6+
},
7+
"resource": {
8+
"resourceType": "Observation",
9+
"id": "$id",
10+
"identifier": [
11+
{
12+
"use": "usual",
13+
"value": "$id"
14+
}
15+
],
16+
"status": "final",
17+
"category": [
18+
{
19+
"coding": [
20+
{
21+
"system": "http://smartregister.org/",
22+
"code": "$category_code",
23+
"display": "$category_display"
24+
}
25+
],
26+
"text": "$category_text"
27+
}
28+
],
29+
"code": {
30+
"coding": [
31+
{
32+
"system": "http://snomed.info/sct",
33+
"code": "$code_code",
34+
"display": "$code_display"
35+
}
36+
],
37+
"text": "$code_text"
38+
},
39+
"subject": {
40+
"reference": "$subject"
41+
},
42+
"focus": [
43+
{
44+
"reference": "Location/$location"
45+
}
46+
],
47+
"encounter": {
48+
"reference": "Encounter/$form_encounter"
49+
},
50+
"effectivePeriod": {
51+
"start": "$start",
52+
"end": "$end"
53+
},
54+
"performer": [
55+
{
56+
"reference": "Practitioner/$practitioner_id"
57+
}
58+
],
59+
"valueCodeableConcept": {
60+
"coding": [
61+
{
62+
"system": "http://snomed.info/sct",
63+
"code": "$boolean_code",
64+
"display": "$boolean_value"
65+
}
66+
],
67+
"text": "$boolean_value"
68+
},
69+
"note": [
70+
{
71+
"time": "$start",
72+
"text": "$note"
73+
}
74+
],
75+
"valueString": "$value_string"
76+
}
77+
}

0 commit comments

Comments
 (0)