from dataclasses import asdict from typing import Optional, List from external_api.base.clients.delphi_client import AdsPerformanceResponse, AdPerformanceSummaryItem from performance.schemas import PerformanceMetricsListResponseSchema, PerformanceMetricsResponseSchema from performance.services.metrics_api.dimensions import Dimensions from utils.list_utils import get_nested_attr PERFORMANCE_FIELDS_MAPPINGS = { 16: "finance.spend", 26: "video.video_views", 40: "general.impressions", 6: "general.conversions", 37: "general.average_frequency", 17: "general.clicks", 51: "finance.cp_conversion", 22: "general.reach", 23: "general.ctr", 35: "general.engagements", 43: "finance.cp_engagement", 19: "finance.cp_click", 63: "video.video_watched_25", 39: "video.video_watched_50", 64: "video.video_watched_75", 41: "video.video_watched_100", 47: "finance.cpm_impressions", 30: "finance.cpm_reach", 32: "finance.cp_video_view", 18: "video.thruplays", 28: "general.unique_outbound_clicks", 42: "general.unique_outbound_ctr", 12: "general.unique_custom_conv", 27: "general.landing_page_views", 2: "general.leads", 38: "general.reactions", 20: "finance.cp_thruplay", 21: "finance.cp_unique_outbound_click", 13: "finance.cp_unique_custom_conversion", 33: "finance.cp_landing_page_view", 60: "general.comments", 61: "finance.cp_comment", 59: "general.unique_ctr", 36: "general.unique_clicks", 44: "finance.cp_unique_click", 68: "general.custom_conversions", 69: "finance.cp_custom_conversion", 70: "general.facebook_page_likes", 72: "linkfire.clicks", 73: "linkfire.conversions", 74: "linkfire.ctr", 75: "linkfire.direct_to_service", 76: "linkfire.event_rate", 77: "linkfire.events", 78: "linkfire.in_page_plays", 79: "linkfire.new_sessions", 80: "linkfire.page_views", 82: "linkfire.platform_to_linkfire_event_rate", 83: "linkfire.platform_to_linkfire_bounce_rate", 84: "linkfire.platform_to_linkfire_click_rate", 85: "linkfire.bounce_rate", } START_DATE = 49 END_DATE = 50 class MetricsMapper: u_id: int = 0 def map_metrics( self, dimension: Dimensions, metrics: AdsPerformanceResponse, metadata: dict, totals: Optional[AdsPerformanceResponse] = None, ) -> PerformanceMetricsListResponseSchema: metrics_response = PerformanceMetricsListResponseSchema( items=[self.__map_item(dimension, item, metadata) for item in metrics.items] ) if totals is not None: metrics_response.totalFields = self.__map_totals(totals) return metrics_response def __map_item( self, dimension: Dimensions, item: AdPerformanceSummaryItem, metadata: Optional[dict] = None ) -> PerformanceMetricsResponseSchema: entity_id = self.__get_entity_id(dimension, item) item_metadata = metadata.get(entity_id, None) if metadata else None if item_metadata is not None: item_metadata = asdict(item_metadata) if dimension == Dimensions.PROJECT and item_metadata is not None: entity_id = item_metadata.get("projectId") return PerformanceMetricsResponseSchema( entityId=entity_id, name=get_nested_attr(item, dimension.dimension_name_path(), None), fields=self.__map_metrics(dimension, item), metadata=item_metadata, ) def __get_entity_id(self, dimension: Dimensions, item: AdPerformanceSummaryItem): path = dimension.dimension_entity_id_path() if not path: return self.next_uid() return get_nested_attr(item.dimensions, path, self.next_uid()) def __map_totals(self, totals_response: AdsPerformanceResponse) -> dict: totals = next(iter(totals_response.items), None) # Get fist item in the list return self.__map_base_metrics(getattr(totals, "metrics", {})) def __map_metrics(self, dimension: Dimensions, metrics_item: AdsPerformanceResponse) -> dict: fields = self.__map_base_metrics(metrics_item.metrics) dates_entity = dimension.dimension_dates_path() if dates_entity: dimension_object = get_nested_attr(metrics_item, dates_entity, None) fields = {START_DATE: dimension_object.start_date, END_DATE: dimension_object.end_date, **fields} return fields def __map_base_metrics(self, metrics: List[AdPerformanceSummaryItem]): return {_id: get_nested_attr(metrics, path, None) for _id, path in PERFORMANCE_FIELDS_MAPPINGS.items()} def next_uid(self): self.u_id += 1 return self.u_id