diff --git a/src/emcommon/metrics/metrics_summaries.py b/src/emcommon/metrics/metrics_summaries.py index ca34178..e7d50b0 100644 --- a/src/emcommon/metrics/metrics_summaries.py +++ b/src/emcommon/metrics/metrics_summaries.py @@ -4,20 +4,39 @@ # @memoize -def labeled_mode_for_trip(composite_trip: dict, trip_labels_map: dict[str, any]) -> str: +def label_for_trip(composite_trip: dict, label_key: str, trip_labels_map: dict[str, any] = None) -> str: """ :param composite_trip: composite trip + :param label_key: which type of label to get ('mode', 'purpose', or 'replaced_mode') :param trip_labels_map: trip labels map - :return: labeled mode for the trip, derived from the trip's user_input if available, or the trip_labels_map if available, or 'unlabeled' otherwise + :return: the label for the trip, derived from the trip's user_input if available, or the trip_labels_map if available, or 'unlabeled' otherwise """ + label_key = label_key.upper() + label_key_confirm = label_key.lower() + '_confirm' UNLABELED = 'unlabeled' if not composite_trip: return UNLABELED - if 'user_input' in composite_trip and 'mode_confirm' in composite_trip['user_input']: - return composite_trip['user_input']['mode_confirm'] + if 'user_input' in composite_trip and label_key_confirm in composite_trip['user_input']: + return composite_trip['user_input'][label_key_confirm] if trip_labels_map and composite_trip['_id']['$oid'] in trip_labels_map: - if 'MODE' in trip_labels_map[composite_trip['_id']['$oid']]: - return trip_labels_map[composite_trip['_id']['$oid']]['MODE']['data']['label'] + if label_key_upper in trip_labels_map[composite_trip['_id']['$oid']]: + return trip_labels_map[composite_trip['_id']['$oid']][label_key_upper]['data']['label'] + return UNLABELED + +def labeled_purpose_for_trip(composite_trip: dict, trip_labels_map: dict[str, any] = None) -> str: + """ + :param composite_trip: composite trip + :param trip_labels_map: trip labels map + :return: labeled purpose for the trip, derived from the trip's user_input if available, or the trip_labels_map if available, or 'unlabeled' otherwise + """ + UNLABELED = 'unlabeled' + if not composite_trip: + return UNLABELED + if 'user_input' in composite_trip and 'purpose_confirm' in composite_trip['user_input']: + return composite_trip['user_input']['purpose_confirm'] + if trip_labels_map and composite_trip['_id']['$oid'] in trip_labels_map: + if 'PURPOSE' in trip_labels_map[composite_trip['_id']['$oid']]: + return trip_labels_map[composite_trip['_id']['$oid']]['PURPOSE']['data']['label'] return UNLABELED @@ -26,7 +45,7 @@ def generate_summaries(metrics: list[str], composite_trips: list, trip_labels_ma return {metric: get_summary_for_metric(metric, composite_trips, trip_labels_map) for metric in metrics} -def value_of_metric_for_trip(metric: str, trip: dict, trip_labels_map: dict[str, any]): +def value_of_metric_for_trip(metric: str, trip: dict): if metric == 'distance': return trip['distance'] elif metric == 'count': @@ -36,7 +55,7 @@ def value_of_metric_for_trip(metric: str, trip: dict, trip_labels_map: dict[str, return None -def get_summary_for_metric(metric: str, composite_trips: list, trip_labels_map: dict[str, any]): +def get_summary_for_metric(metric: str, composite_trips: list, trip_labels_map: dict[str, any] = None): days_of_metrics_data = {} for trip in composite_trips: date = trip['start_fmt_time'].split('T')[0] @@ -54,19 +73,24 @@ def get_summary_for_metric(metric: str, composite_trips: list, trip_labels_map: days_summaries.append(summary_for_day) return days_summaries - -def metric_summary_by_mode(metric: str, composite_trips: list, trip_labels_map: dict[str, any]): +def metric_summary_by_mode(metric: str, composite_trips: list, trip_labels_map = None): """ :param composite_trips: list of composite trips :return: a dict of mode keys to the metric total for that mode """ + grouping_fields = { + 'mode_confirm': lambda trip: label_for_trip(trip, 'mode', trip_labels_map), + 'purpose_confirm': lambda trip: label_for_trip(trip, 'purpose', trip_labels_map), + 'replaced_mode_confirm': lambda trip: label_for_trip(trip, 'replaced_mode', trip_labels_map), + } + mode_to_metric_map = {} if not composite_trips: return mode_to_metric_map for trip in composite_trips: - mode_key = 'mode_' + labeled_mode_for_trip(trip, trip_labels_map) - if mode_key not in mode_to_metric_map: - mode_to_metric_map[mode_key] = 0 - mode_to_metric_map[mode_key] += value_of_metric_for_trip( - metric, trip, trip_labels_map) + for grouping_field, field_for_trip_fn in grouping_fields.items(): + grouping_key = grouping_field + '_' + field_for_trip_fn(trip) + if grouping_key not in mode_to_metric_map: + mode_to_metric_map[grouping_key] = 0 + mode_to_metric_map[grouping_key] += value_of_metric_for_trip(metric, trip) return mode_to_metric_map diff --git a/src/emcommon/util.py b/src/emcommon/util.py index d056362..6045a28 100644 --- a/src/emcommon/util.py +++ b/src/emcommon/util.py @@ -12,3 +12,18 @@ def wrapper(*args, **kwargs): return wrapper # __pragma__('nokwargs') + + +# e-mission-phone www/js/diary/timelineHelper.ts unpackServerData() +def flatten_db_entry(entry: dict) -> dict: + ''' + DB entries retrieved from the server have '_id', 'metadata', and 'data' fields. + This function returns a shallow copy of the obj, which flattens the 'data' field into the top + level, while also including '_id', 'metadata.key', and 'metadata.origin_key'. + ''' + return { + **entry['data'], + '_id': entry['_id'], + 'key': entry['metadata']['key'], + 'origin_key': entry['metadata']['origin_key'] if 'origin_key' in entry['metadata'] else entry['metadata']['key'] + }