"""Utility functions for fingerprint rules.""" from datetime import datetime, timezone from typing import Any, Dict, List from soundrecording_utils.constants import territories from soundrecording_utils.constants.ddex import validation from soundrecording_utils.metadata.types import FingerprintRule, LabelType, Track def get_fingerprint_rules_by_track_id(tracks: List[Track]) -> Dict[str, Any]: """Get fingerprint rules. Iterate over tracks (earliest release date first) and for each track extract active fingerprint rules/carveouts. Returns Dict mapping tuid to track data and rules. """ track_and_rules_by_tuid: Dict[str, Any] = {} for track in sorted(tracks, key=lambda t: t.product.release_date): vendor_rules: List[Dict[str, Any]] = [] subaccount_rules: List[Dict[str, Any]] = [] track_rules: List[Dict[str, Any]] = [] vendor_carveouts: List[Dict[str, Any]] = [] subaccount_carveouts: List[Dict[str, Any]] = [] track_carveouts: List[Dict[str, Any]] = [] label_type = track.product.label.type if label_type == LabelType.Subaccount: if track.product.label.vendor: vendor_rules = _expand_rules( list( { x for x in track.product.label.vendor.rules if x.active and x.policy != 'carveout' } ) ) vendor_carveouts = _expand_rules( list( { x for x in track.product.label.vendor.rules if x.active and x.policy == 'carveout' } ) ) subaccount_rules = _expand_rules( list( { x for x in track.product.label.rules if x.active and x.policy != 'carveout' } ) ) subaccount_carveouts = _expand_rules( list( { x for x in track.product.label.rules if x.active and x.policy == 'carveout' } ) ) else: vendor_rules = _expand_rules( list( { x for x in track.product.label.rules if x.active and x.policy != 'carveout' } ) ) vendor_carveouts = _expand_rules( list( { x for x in track.product.label.rules if x.active and x.policy == 'carveout' } ) ) track_rules = _expand_rules( list({x for x in track.rules if x.active and x.policy != 'carveout'}) ) track_carveouts = _expand_rules( list({x for x in track.rules if x.active and x.policy == 'carveout'}) ) track_and_rules_by_tuid[track.tuid] = { 'data': track, 'rules': { 'vendor_rules': [{**x, 'level': 'vendor'} for x in vendor_rules], 'subaccount_rules': [ {**x, 'level': 'subaccount'} for x in subaccount_rules ], 'track_rules': [{**x, 'level': 'track'} for x in track_rules], 'vendor_carveouts': vendor_carveouts, 'subaccount_carveouts': subaccount_carveouts, 'track_carveouts': track_carveouts, }, } return track_and_rules_by_tuid def _expand_rules(rules: List[FingerprintRule]) -> List[Dict[str, Any]]: """Expand rules if needed to handle * cases.""" expanded_rules: List[Dict[str, Any]] = [] for rule in rules: service = rule.service territory = rule.territory # copy rule for every service and territory pair if service == validation.ALL and territory == territories.ALL: for y in validation.RULE_SERVICES: expanded_rules += [ { 'policy': rule.policy, 'start_date': rule.start_date, 'end_date': rule.end_date, 'service': y, 'territory': x, } for x in territories.RULE_TERRITORIES if x != territories.ALL and y != validation.ALL ] # copy rule for every service elif service == validation.ALL: expanded_rules += [ { 'policy': rule.policy, 'start_date': rule.start_date, 'end_date': rule.end_date, 'service': x, 'territory': rule.territory, } for x in validation.RULE_SERVICES if x != validation.ALL ] # copy rule for every territory elif territory == territories.ALL: expanded_rules += [ { 'policy': rule.policy, 'service': rule.service, 'start_date': rule.start_date, 'end_date': rule.end_date, 'territory': x, } for x in territories.RULE_TERRITORIES if x != territories.ALL ] # add 'normal' rule with no need to copy else: expanded_rules.append( { 'policy': rule.policy, 'start_date': rule.start_date, 'end_date': rule.end_date, 'service': rule.service, 'territory': rule.territory, } ) return expanded_rules def get_territory_claims_by_rules( track_and_rules_by_tuid: Dict[str, Any], service: str, ) -> Dict[str, Any]: """Get claims by fingerprint rules. Args: track_and_rules_by_tuid: track and rules by tuid service: service name to filter rules by (e.g. 'tiktok', 'youtube') Returns: Dict with claimed_rules and blocked_rules for each track. """ filtered_rules_for_service = _filter_rules_by_service( track_and_rules_by_tuid, service ) for _k, v in filtered_rules_for_service.items(): v_carveouts = v['rules']['vendor_carveouts'] s_carveouts = v['rules']['subaccount_carveouts'] t_carveouts = v['rules']['track_carveouts'] vendor_carveouts_territories = [c['territory'] for c in v_carveouts] subaccount_carveouts_territories = [c['territory'] for c in s_carveouts] track_carveouts_territories = [c['territory'] for c in t_carveouts] # Check if all territories are being carved out on each level if len(vendor_carveouts_territories) == len(territories.RULE_TERRITORIES) - 1: v['claimed_rules'] = [] v['blocked_rules'] = [] continue if ( len(subaccount_carveouts_territories) == len(territories.RULE_TERRITORIES) - 1 ): v['claimed_rules'] = [] v['blocked_rules'] = [] continue if len(track_carveouts_territories) == len(territories.RULE_TERRITORIES) - 1: v['claimed_rules'] = [] v['blocked_rules'] = [] continue # Combine distinct territory carveouts from all level all_territory_carveouts = list( set( vendor_carveouts_territories + subaccount_carveouts_territories + track_carveouts_territories ) ) v_rules = v['rules']['vendor_rules'] s_rules = v['rules']['subaccount_rules'] t_rules = v['rules']['track_rules'] all_rules = v_rules + s_rules + t_rules # Format rules based on service/territory pair rules_by_service_territory_pair = _extract_service_territory_rules( service, all_rules ) # Extract active rules based on level and dates selected_rules = _extract_rules_by_level_and_date( rules_by_service_territory_pair ) monetize_rules = [r for r in selected_rules if r['policy'] == 'monetize'] block_rules = [r for r in selected_rules if r['policy'] == 'block_access'] # Filter out any active rules if carveouts are defined filtered_monetize_rules = [ r for r in monetize_rules if r['territory'] not in all_territory_carveouts ] filtered_block_rules = [ r for r in block_rules if r['territory'] not in all_territory_carveouts ] v['claimed_rules'] = sorted( filtered_monetize_rules, key=lambda d: d['territory'] ) v['blocked_rules'] = sorted(filtered_block_rules, key=lambda d: d['territory']) return _deduplicate_territories(filtered_rules_for_service) def _filter_rules_by_service( track_and_rules_by_tuid: Dict[str, Any], service: str ) -> Dict[str, Any]: """Filter rules based on given service.""" for _, v in track_and_rules_by_tuid.items(): v['rules']['vendor_carveouts'] = [ r for r in v['rules']['vendor_carveouts'] if r['service'] == service ] v['rules']['subaccount_carveouts'] = [ r for r in v['rules']['subaccount_carveouts'] if r['service'] == service ] v['rules']['track_carveouts'] = [ r for r in v['rules']['track_carveouts'] if r['service'] == service ] v['rules']['vendor_rules'] = [ r for r in v['rules']['vendor_rules'] if r['service'] == service ] v['rules']['subaccount_rules'] = [ r for r in v['rules']['subaccount_rules'] if r['service'] == service ] v['rules']['track_rules'] = [ r for r in v['rules']['track_rules'] if r['service'] == service ] return track_and_rules_by_tuid def _deduplicate_territories(tracks_and_rules: Dict[str, Any]) -> Dict[str, Any]: """Apply duplicated territories to tracks.""" claimed_territories: List[Dict[str, Any]] = [] blocked_territories: List[Dict[str, Any]] = [] # iterate over tracks (earliest release date first) for _, v in tracks_and_rules.items(): # extract rules if not already claimed/blocked by previous tracks track_claimed_territories = [ x for x in v['claimed_rules'] if x not in claimed_territories ] track_blocked_territories = [ x for x in v['blocked_rules'] if x not in blocked_territories ] # add/replace tracks with modified rules list v['claimed_rules'] = track_claimed_territories v['blocked_rules'] = track_blocked_territories # mark rules as claimed/blocked for newer tracks to compare against claimed_territories += track_claimed_territories blocked_territories += track_blocked_territories return tracks_and_rules def _extract_service_territory_rules( service: str, all_rules: List[Dict[str, Any]] ) -> Dict[str, List[Dict[str, Any]]]: """Extract service-territory pair out of all rules.""" service_territory_rules: Dict[str, List[Dict[str, Any]]] = {} for rule in all_rules: key = service + '-' + rule['territory'] if key not in service_territory_rules: service_territory_rules[key] = [] service_territory_rules[key].append(rule) return service_territory_rules def _extract_rules_by_level_and_date( service_territory_rules: Dict[str, List[Dict[str, Any]]], ) -> List[Dict[str, Any]]: """Extract rules out of service-territory pair.""" filtered_rules: List[Dict[str, Any]] = [] # Check active rules for the given service/territory pair for _, rules in service_territory_rules.items(): filtered_rules += _select_active_rules(rules) return filtered_rules def _select_active_rules(rules: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Select active rules based on level and dates.""" track_level_rules = [x for x in rules if x['level'] == 'track'] subaccount_level_rules = [x for x in rules if x['level'] == 'subaccount'] vendor_level_rules = [x for x in rules if x['level'] == 'vendor'] # Check rules following order: Track -> Subaccount -> Vendor if track_level_rules: found_active_track_rules = _find_active_rule_by_date(track_level_rules) if found_active_track_rules: return found_active_track_rules if subaccount_level_rules: found_active_subaccount_rules = _find_active_rule_by_date( subaccount_level_rules ) if found_active_subaccount_rules: return found_active_subaccount_rules if vendor_level_rules: found_active_vendor_rules = _find_active_rule_by_date(vendor_level_rules) if found_active_vendor_rules: return found_active_vendor_rules return [] def _find_active_rule_by_date(rules: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Extract active rules based on dates.""" current_datetime = datetime.now(timezone.utc) active_rules: List[Dict[str, Any]] = [] for rule in rules: policy = rule['policy'] territory = rule['territory'] start = rule['start_date'] end = rule['end_date'] if start is None: continue if end is None: if start <= current_datetime: active_rules.append( { 'policy': policy, 'territory': territory, 'start': start, 'end': end, } ) else: if start <= current_datetime <= end: active_rules.append( { 'policy': policy, 'territory': territory, 'start': start, 'end': end, } ) return active_rules