66from datetime import datetime
77
88CSV_HEADER_ROW = [
9- 'Item Type ' ,
9+ 'ItemType ' ,
1010 'Label' ,
1111 'Response' ,
1212 'Comment' ,
13- 'Media Hypertext Reference' ,
14- 'Location Coordinates' ,
15- 'Item Score' ,
16- 'Item Max Score' ,
17- 'Item Score Percentage' ,
13+ 'MediaHypertextReference' ,
14+ 'Latitude' ,
15+ 'Longitude' ,
16+ 'ItemScore' ,
17+ 'ItemMaxScore' ,
18+ 'ItemScorePercentage' ,
1819 'Mandatory' ,
19- 'Failed Response ' ,
20+ 'FailedResponse ' ,
2021 'Inactive' ,
21- 'Item ID' ,
22- 'Response ID' ,
23- 'Parent ID' ,
24- 'Audit Owner' ,
25- 'Audit Author' ,
26- 'Audit Name' ,
27- 'Audit Score' ,
28- 'Audit Max Score' ,
29- 'Audit Score Percentage' ,
30- 'Audit Duration (seconds)' ,
31- 'Date Started' ,
32- 'Time Started' ,
33- 'Date Completed' ,
34- 'Time Completed' ,
35- 'Audit ID' ,
36- 'Template ID' ,
37- 'Template Name' ,
38- 'Template Author'
22+ 'ItemID' ,
23+ 'ResponseID' ,
24+ 'ParentID' ,
25+ 'AuditOwner' ,
26+ 'AuditAuthor' ,
27+ 'AuditName' ,
28+ 'AuditScore' ,
29+ 'AuditMaxScore' ,
30+ 'AuditScorePercentage' ,
31+ 'AuditDuration' ,
32+ 'DateStarted' ,
33+ 'DateCompleted' ,
34+ 'DateModified' ,
35+ 'AuditID' ,
36+ 'TemplateID' ,
37+ 'TemplateName' ,
38+ 'TemplateAuthor' ,
39+ 'ItemCategory' ,
40+ 'DocumentNo' ,
41+ 'ConductedOn' ,
42+ 'PreparedBy' ,
43+ 'Location' ,
44+ 'Personnel' ,
45+ 'ClientSite'
3946]
4047
4148# audit item empty response
119126 'b5c92352-e11b-11e1-9b23-0800200c9a66' : 'N/A'
120127}
121128
129+ # maps header fields to their static IDs
130+ header_field_id = {
131+ 'DocumentNo' : 'f3245d46-ea77-11e1-aff1-0800200c9a66' ,
132+ 'ConductedOn' : 'f3245d42-ea77-11e1-aff1-0800200c9a66' ,
133+ 'PreparedBy' : 'f3245d43-ea77-11e1-aff1-0800200c9a66' ,
134+ 'Location' : 'f3245d44-ea77-11e1-aff1-0800200c9a66' ,
135+ 'Personnel' : 'f3245d45-ea77-11e1-aff1-0800200c9a66' ,
136+ 'ClientSite' : 'f3245d41-ea77-11e1-aff1-0800200c9a66'
137+ }
138+
122139
123140def get_json_property (obj , * args ):
124141 """
@@ -156,8 +173,10 @@ def __init__(self, audit_json, export_inactive_items=True):
156173 """
157174 self .audit_json = audit_json
158175 self .export_inactive_items = export_inactive_items
176+ self .map_items ()
159177 self .audit_table = self .convert_audit_to_table ()
160178
179+
161180 def audit_id (self ):
162181 """
163182 :return: The audit ID
@@ -170,6 +189,35 @@ def audit_items(self):
170189 """
171190 return self .audit_json ['header_items' ] + self .audit_json ['items' ]
172191
192+ def map_items (self ):
193+ """
194+ Creates a dictionary which maps each item to it's parent ID, Label, and Type.
195+ This tree can then be traversed recursively to find the Category or Section of a given item.
196+ """
197+ self .item_category = EMPTY_RESPONSE
198+ self .item_map = {}
199+ for item in self .audit_items ():
200+ if item .get ('item_id' ):
201+ self .item_map [item ['item_id' ]] = {
202+ 'parent_id' : item .get ('parent_id' ) or EMPTY_RESPONSE ,
203+ 'label' : item .get ('label' ) or EMPTY_RESPONSE ,
204+ 'type' : item .get ('type' ) or EMPTY_RESPONSE
205+ }
206+
207+ def get_item_category (self , item_id ):
208+ """
209+ Recursively traverses the item Map, following parent IDs until it gets to a Section or Category.
210+ When a Section or Category is found, the item Category is set to the label of that Section or Category.
211+ :param item_id: item ID to find Category for
212+ :return: Category or Section label
213+ """
214+ if not item_id :
215+ return EMPTY_RESPONSE
216+ elif self .item_map [item_id ]['type' ] == 'section' or self .item_map [item_id ]['type' ] == 'category' :
217+ return self .item_map [item_id ]['label' ] or EMPTY_RESPONSE
218+ else :
219+ return self .get_item_category (self .item_map [item_id ]['parent_id' ])
220+
173221 def audit_custom_response_id_to_label_map (self ):
174222 """
175223 :return: dictionary mapping custom response_id's to their label
@@ -188,6 +236,7 @@ def common_audit_data(self):
188236 audit_data_property = self .audit_json ['audit_data' ]
189237 template_data_property = self .audit_json ['template_data' ]
190238 audit_date_completed = audit_data_property ['date_completed' ]
239+ header_data = self .audit_json ['header_items' ]
191240 audit_data_as_list = list ()
192241 audit_data_as_list .append (audit_data_property ['authorship' ]['owner' ])
193242 audit_data_as_list .append (audit_data_property ['authorship' ]['author' ])
@@ -196,41 +245,54 @@ def common_audit_data(self):
196245 audit_data_as_list .append (audit_data_property ['total_score' ])
197246 audit_data_as_list .append (audit_data_property [SCORE_PERCENTAGE ])
198247 audit_data_as_list .append (audit_data_property ['duration' ])
199- audit_data_as_list .append (self .format_date (audit_data_property ['date_started' ]))
200- audit_data_as_list .append (self .format_time (audit_data_property ['date_started' ]))
201- audit_data_as_list .append (self .format_date (audit_date_completed ))
202- audit_data_as_list .append (self .format_time (audit_date_completed ))
248+ audit_data_as_list .append (self .format_date_time (audit_data_property ['date_started' ]))
249+ audit_data_as_list .append (self .format_date_time (audit_date_completed ))
250+ audit_data_as_list .append (self .format_date_time (audit_data_property ['date_modified' ]))
203251 audit_data_as_list .append (self .audit_id ())
204252 audit_data_as_list .append (self .audit_json ['template_id' ])
205253 audit_data_as_list .append (template_data_property ['metadata' ]['name' ])
206254 audit_data_as_list .append (template_data_property ['authorship' ]['author' ])
255+ audit_data_as_list .append (self .item_category )
256+ audit_data_as_list .append (self .get_header_item (header_data , 'DocumentNo' ))
257+ audit_data_as_list .append (self .get_header_item (header_data , 'ConductedOn' ))
258+ audit_data_as_list .append (self .get_header_item (header_data , 'PreparedBy' ))
259+ audit_data_as_list .append (self .get_header_item (header_data , 'Location' ))
260+ audit_data_as_list .append (self .get_header_item (header_data , 'Personnel' ))
261+ audit_data_as_list .append (self .get_header_item (header_data , 'ClientSite' ))
207262 return audit_data_as_list
208263
209- @staticmethod
210- def format_date (date ):
211- """
212- :param date: date in the format: 2017-03-03T03:45:58.090Z
213- :return: date in the format: '03 March 2017',
214- """
215- if date :
216- date_object = datetime .strptime (date , '%Y-%m-%dT%H:%M:%S.%fZ' )
217- formatted_date = date_object .strftime ('%d %B %Y' )
218- return formatted_date
219- else :
220- return ''
264+ def get_header_item (self , header_data , header_item_type ):
265+ """
266+
267+ :param header_data:
268+ :param header_item_type:
269+ :return:
270+ """
271+ for item in header_data :
272+ if item .get ('item_id' ) == header_field_id .get (header_item_type ):
273+ if 'responses' not in item .keys ():
274+ return EMPTY_RESPONSE
275+ if 'text' in item ['responses' ].keys ():
276+ return get_json_property (item , 'responses' , 'text' )
277+ if 'datetime' in item ['responses' ].keys ():
278+ return get_json_property (item , 'responses' , 'datetime' )
279+ if 'location_text' in item ['responses' ].keys ():
280+ return get_json_property (item , 'responses' , 'location_text' )
281+ return EMPTY_RESPONSE
221282
222283 @staticmethod
223- def format_time (date ):
284+ def format_date_time (date ):
224285 """
225286 :param date: date in the format: 2017-03-03T03:45:58.090Z
226- :return: time in the format '03:45AM'
287+ :return: date and time in the format: '03 March 2017 03:45 AM',
227288 """
228289 if date :
229290 date_object = datetime .strptime (date , '%Y-%m-%dT%H:%M:%S.%fZ' )
230- formatted_time = date_object .strftime ('%I:%M%p' )
231- return formatted_time
291+ formatted_date = date_object .strftime ('%d %B %Y' )
292+ formatted_time = date_object .strftime ('%I:%M %p' )
293+ return formatted_date + ' ' + formatted_time
232294 else :
233- return ''
295+ return EMPTY_RESPONSE
234296
235297 def convert_audit_to_table (self ):
236298 """
@@ -240,6 +302,10 @@ def convert_audit_to_table(self):
240302 """
241303 self .audit_table = []
242304 for item in self .audit_items ():
305+ if item .get ('parent_id' ):
306+ self .item_category = self .get_item_category (item ['parent_id' ])
307+ else :
308+ self .item_category = EMPTY_RESPONSE
243309 row_array = self .item_properties_as_list (item ) + self .common_audit_data ()
244310 if get_json_property (item , INACTIVE ) and not self .export_inactive_items :
245311 continue
@@ -323,8 +389,7 @@ def get_item_response(self, item):
323389 elif item_type == 'smartfield' :
324390 response = get_json_property (item , 'evaluation' )
325391 elif item_type == 'datetime' :
326- response = self .format_date (get_json_property (item , RESPONSES , 'datetime' ))
327- response = response + ' at ' + self .format_time (get_json_property (item , RESPONSES , 'datetime' ))
392+ response = self .format_date_time (get_json_property (item , RESPONSES , 'datetime' ))
328393 elif item_type == 'text' or item_type == 'textsingle' :
329394 response = get_json_property (item , RESPONSES , 'text' )
330395 elif item_type == INFORMATION and get_json_property (item , 'options' , TYPE ) == 'link' :
@@ -454,29 +519,33 @@ def get_item_location_coordinates(self, item):
454519 item_type = get_json_property (item , TYPE )
455520 if item_type == 'address' :
456521 location_coordinates = get_json_property (item , 'responses' , 'location' , 'geometry' , 'coordinates' )
457- if isinstance (location_coordinates , list ):
458- return str (location_coordinates ).strip ('[]' )
459- return EMPTY_RESPONSE
522+ if isinstance (location_coordinates , list ) and len ( location_coordinates ) :
523+ return str (location_coordinates ).strip ('[]' ). split ( ',' )
524+ return [ EMPTY_RESPONSE , EMPTY_RESPONSE ]
460525
461526 def item_properties_as_list (self , item ):
462527 """
463528 Returns selected properties of the audit item JSON as a list
464529 :param item: single item in JSON format
465530 :return: array of item data, in format that CSV writer can handle
466531 """
532+ location_coordinates = self .get_item_location_coordinates (item )
533+ latitude = location_coordinates [1 ]
534+ longitude = location_coordinates [0 ]
467535 return [
468536 self .get_item_type (item ),
469537 self .get_item_label (item ),
470538 self .get_item_response (item ),
471539 get_json_property (item , RESPONSES , 'text' ) if item .get (TYPE ) not in ['text' , 'textsingle' ] else EMPTY_RESPONSE ,
472540 self .get_item_media (item ),
473- self .get_item_location_coordinates (item ),
541+ latitude ,
542+ longitude ,
474543 self .get_item_score (item ),
475544 self .get_item_max_score (item ),
476545 self .get_item_score_percentage (item ),
477- get_json_property (item , 'options' , 'is_mandatory' ),
478- get_json_property (item , RESPONSES , FAILED ),
479- get_json_property (item , INACTIVE ),
546+ get_json_property (item , 'options' , 'is_mandatory' ) or False ,
547+ get_json_property (item , RESPONSES , FAILED ) or False ,
548+ get_json_property (item , INACTIVE ) or False ,
480549 get_json_property (item , ID ),
481550 self .get_item_response_id (item ),
482551 get_json_property (item , PARENT_ID )
0 commit comments