from dataclasses import dataclass from itertools import groupby from anydi import singleton from fansifter_common.auth.types import Permission from pydantic import TypeAdapter from typing_extensions import TypedDict from dmp.adapters.db import ReportingDB from dmp.artists.cache import ArtistCache, cached_artist_data from dmp.artists.dtos import SegmentCategorySeries, SegmentCategoryValue from dmp.artists.requests import ArtistFanReportFilterRequest from dmp.artists.services import ArtistAccessService from dmp.fandata.enums import FanSegment, SegmentCategory @dataclass class GetArtistFansShareBySegmentActivityRequest(ArtistFanReportFilterRequest): countries: list[str] | None @dataclass class GetArtistFansShareBySegmentActivityResponse: items: list[SegmentCategorySeries] class CategoryValueRow(TypedDict): label: FanSegment category: SegmentCategory segment_fans_count: int fans_share: float ActivityBreakdownListValidator = TypeAdapter(list[CategoryValueRow]) @singleton class GetArtistFansShareBySegmentActivityHandler: permission = Permission("fan_data_list", "view") def __init__( self, db: ReportingDB, artist_access_service: ArtistAccessService, artist_cache: ArtistCache, ) -> None: self.db = db self.artist_access_service = artist_access_service self.artist_cache = artist_cache def handle( self, request: GetArtistFansShareBySegmentActivityRequest ) -> list[SegmentCategorySeries]: artist_access = self.artist_access_service.check_artist_access( request.identity_id, request.global_participant_id, request.account, permission=self.permission, ) return self._get_categories( request.global_participant_id, vendor_id=request.vendor_id, subaccount_id=request.subaccount_id, countries=request.countries, is_global=artist_access.is_global, ) @cached_artist_data("artist-segment-activity-breakdown") def _get_categories( self, global_participant_id: str, vendor_id: int | None, subaccount_id: int | None, countries: list[str] | None, is_global: bool, ) -> list[SegmentCategorySeries]: context = { "vendor_id": vendor_id, "subaccount_id": subaccount_id, "global_participant_id": global_participant_id, "category_enum": SegmentCategory, "ordered_category_enum_type": SegmentCategory.ordered(), "ordered_segment_enum_type": FanSegment.ordered(), "countries": countries, "is_global": is_global, } if countries: template_name = ( "artist/get-artist-segment-activity-breakdown-by-country.sql" ) else: template_name = "artist/get-artist-segment-activity-breakdown.sql" query = self.db.query_from_template(template_name, context=context) rows = ActivityBreakdownListValidator.validate_python( self.db.session.execute(query).mappings() ) return self._build_stacked_data(rows=rows) @staticmethod def _build_stacked_data( rows: list[CategoryValueRow], ) -> list[SegmentCategorySeries]: data_series = [] for id, items in groupby(rows, lambda row: row["label"]): result_items = [] segment_fans_count = 0 for item in items: result_items.append( SegmentCategoryValue( label=item["category"], value=item["fans_share"], ) ) segment_fans_count = item["segment_fans_count"] data_series.append( SegmentCategorySeries( id=id, count=segment_fans_count, items=result_items ) ) return data_series