# flake8: noqa: F722, F821 """ Models for Snowflake Query results. """ import re from datetime import date, datetime from decimal import Decimal from functools import lru_cache from typing import Annotated, Optional, Union from pydantic import ( BaseModel, EmailStr, Field, NonNegativeInt, PositiveInt, StrictStr, StringConstraints, field_validator, ) from ... import models _FORMAT_DATE: str = "%Y-%m-%d" # YYYY-MM-DD, as used in Snowflake _PATTERN_DATE: str = r"\d{4}-\d{2}-\d{2}" # YYYY-MM-DD, as used in Snowflake class Country(BaseModel): """Represents a country and its metadata.""" id: PositiveInt = Field(..., alias="id") name: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field(..., alias="name") ) code: ( Annotated[ str, StringConstraints(min_length=2, max_length=2, pattern=r"[A-Z]{2}") ] | None ) = Field(..., alias="code") # Regions have no code class CountriesResponse(models.BaseCollectionResponse): """Response model for a collection of countries.""" data: list[Country] = Field( ..., description="List of countries with their metadata." ) class _LabelContract(BaseModel): date_start: str | date = Field( ..., alias="date_start", description="Start date of the contract." ) date_end: str | date | None = Field( default=None, alias="date_end", description="End date of the contract, may be None.", ) closer: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field( ..., alias="closer", description="Name of the person who closed the contract, e.g. 'Jane Doe'.", ) ) term: PositiveInt | float = Field( ..., alias="term", description="Represents the effective contract duration in years " "(i.e. between contract start and lifecycle term end), " "rounded to the nearest half year", ) digital_fee: PositiveInt | float | Decimal = Field( ..., alias="digital_fee", description="Digital fee percentage for the contract, e.g. 0.15 (for 15%).", ) dms_carve_out: list[str] = Field( alias="dms_carve_out", description="List of digital music services (DMS) carved out of the contract, " "e.g. 'Spotify, Apple Music'.", default_factory=list, ) territory_carve_out: list[str] = Field( alias="territory_carve_out", description="List of countries/territories carved out of the contract, " "e.g. 'US, UK'.", default_factory=list, ) @field_validator("date_start") def validate_date_format(cls, v: str | date) -> str: result = _validate_date_format(v) if result is None: raise ValueError("Expected date_start.") return result @field_validator("date_end") def validate_date_format_optional(cls, v: str | date | None) -> Optional[str]: return _validate_date_format(v, allow_none=True) class _LabelMasterContact(BaseModel): """Represents the master contact information for a label.""" full_name: Annotated[ str, StringConstraints(min_length=2, strip_whitespace=True) ] = Field(..., alias="full_name", description="Full name of the master contact.") address: Annotated[str, StringConstraints(min_length=3, strip_whitespace=True)] = ( Field(..., alias="address", description="Address of the master contact.") ) class Label(BaseModel): """Represents a label and its metadata.""" id: PositiveInt = Field(..., alias="label_id") name: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field(..., alias="name") ) company: Annotated[ str, StringConstraints(min_length=2, strip_whitespace=True), ] = Field(..., alias="company") owner: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field(..., alias="owner") ) status: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field(..., alias="status") ) service_tier: Annotated[ str, StringConstraints(min_length=1, strip_whitespace=True) ] = Field( ..., alias="service_tier", description="Service tier of the label, e.g. 'Tier 1', 'Tier 2', etc.", ) label_manager: Annotated[ str, StringConstraints(min_length=1, strip_whitespace=True) ] = Field( ..., alias="label_manager", description="Label manager's name, e.g. 'John Doe'.", ) contract: _LabelContract = Field( ..., alias="contract", description="Label contract information" ) master_contact: _LabelMasterContact = Field( ..., alias="master_contact", description="Master contact information for the label", ) release_date_most_recent: str | date | None = Field( ..., alias="release_date_most_recent", description="Most recent release date for the label, up to today's date. " "May be None if no releases.", ) release_date_latest: str | date | None = Field( ..., alias="release_date_latest", description="Latest release date for the label, even if in the future. " "May be None if no releases.", ) release_average_yearly: float = Field( ..., alias="release_average_yearly", description="Average number of releases per year for the label. " "May be 0 if no releases.", ) @field_validator("release_date_most_recent", "release_date_latest") def validate_date_format_optional(cls, v: str | date | None) -> Optional[str]: return _validate_date_format(v, allow_none=True) class LabelMetaResponse(models.BaseCollectionResponse): """Response model for a collection of label metadata.""" data: list[Label] = Field(..., description="List of labels with their metadata.") class Client(BaseModel): """Represents a client and its metadata.""" label_id: PositiveInt = Field(..., alias="label_id") name: Annotated[str, StringConstraints(min_length=2, strip_whitespace=True)] = ( Field(..., alias="name") ) class RelationshipManagerClient(Client): """Represents a client associated with a relationship manager (or none).""" relationship_manager_email: EmailStr | None = Field( ..., alias="relationship_manager_email" ) # Some are null if not assigned to an RM relationship_manager_id: NonNegativeInt | None = Field( ..., alias="relationship_manager_id" ) # Some are null if not assigned to an RM class RelationshipManagerClientResponse(models.BaseCollectionResponse): """Response model for a collection of relationship manager clients.""" data: list[RelationshipManagerClient] = Field( ..., description="List of relationship manager clients with their metadata." ) class _TopClient(Client): """Represents a top client and its metadata.""" revenue_mom_change: float = Field(..., alias="revenue_mom_change") rank: NonNegativeInt = Field(..., alias="rank") class _TopClientLtm(_TopClient): """Represents a top client for the last twelve months (LTM) and its metadata""" ltm_gross_revenue_usd: float = Field(..., alias="ltm_gross_revenue_usd") ltm_gross_revenue_usd_mom_change: float = Field( ..., alias="ltm_gross_revenue_usd_mom_change" ) class _TopClientFiscalYear(_TopClient): """Represents a top client for the fiscal year and its metadata""" fiscal_year_ytd_gross_revenue_usd: float = Field( ..., alias="fiscal_year_ytd_gross_revenue_usd" ) class _YearlyGrowth(BaseModel): """Represents the yearly growth and its metadata.""" current_revenue_usd: float = Field(..., alias="current_revenue_usd") growth_pct: float = Field(..., alias="growth_pct") months: int = Field(..., alias="months") previous_revenue_usd: float = Field(..., alias="previous_revenue_usd") year: int = Field(..., alias="year") @field_validator("year") def year_must_be_at_least_1980(cls, v: int) -> int: if v < 1980: # Assume it's bad data if year is before that arbitrary point raise ValueError("year must be at least 1980") return v @field_validator("months") def months_must_be_between_1_and_12(cls, v: int) -> int: if v < 1 or v > 12: raise ValueError("months must be between 1 and 12") return v class RelationshipManagerStats(BaseModel): """Represents the stats for a relationship manager.""" avg_ltm_gross_revenue_usd: float = Field(..., alias="avg_ltm_gross_revenue_usd") avg_ltm_revenue_mom_change: float = Field(..., alias="avg_ltm_revenue_mom_change") avg_revenue_mom_change: float = Field(..., alias="avg_revenue_mom_change") fiscal_year_ytd_gross_revenue_usd: float = Field( ..., alias="fiscal_year_ytd_gross_revenue_usd" ) fiscal_year_ytd_gross_revenue_usd_prev_year: float = Field( ..., alias="fiscal_year_ytd_gross_revenue_usd_prev_year" ) label_count: NonNegativeInt = Field(..., alias="label_count") period_date: str | date = Field(..., alias="period_date") fiscal_year: NonNegativeInt = Field(..., alias="fiscal_year") relationship_manager: StrictStr = Field(..., alias="relationship_manager") relationship_manager_email: EmailStr = Field( ..., alias="relationship_manager_email" ) total_ltm_gross_revenue_usd: float = Field(..., alias="total_ltm_gross_revenue_usd") top_clients_ltm: list[_TopClientLtm] = Field(..., alias="top_clients_ltm") top_clients_fiscal_year: list[_TopClientFiscalYear] = Field( ..., alias="top_clients_fiscal_year" ) yearly_growth: list[_YearlyGrowth] = Field(..., alias="yearly_growth") yearly_growth_fiscal: list[_YearlyGrowth] = Field(..., alias="yearly_growth_fiscal") @field_validator("period_date") def date_must_be_in_past(cls, v: str | date) -> str: v_str = _validate_date_format(v) if v_str is None: raise ValueError("period_date cannot be None") # Check if the date is in the past if date.fromisoformat(v_str) > date.today(): raise ValueError("period_date must be in the past") return v_str class RelationshipManagerStatsResponse(models.BaseCollectionResponse): """Response model for a collection of relationship manager stats.""" data: list[RelationshipManagerStats] = Field( ..., description="List of relationship manager stats." ) class RelationshipManager(BaseModel): """Represents a relationship manager and their metadata.""" email: EmailStr | None = Field(..., alias="email") full_name: StrictStr | None = Field(..., alias="full_name") class RelationshipManagerResponse(models.BaseCollectionResponse): """Response model for a collection of relationship managers.""" data: list[RelationshipManager] = Field( ..., description="List of relationship managers with their metadata." ) class ScorecardFilterMeta(BaseModel): """Represents the filtering metadata for a specific relationship manager in the scorecard (or for the 'null' relationship manager, which is the one grouping all clients not assigned to an RM). """ relationship_manager_email: EmailStr | None = Field( ..., alias="relationship_manager_email" ) # Some are null if not assigned to an RM relationship_manager: StrictStr | None = Field(..., alias="relationship_manager") min_period_date: date = Field( ..., alias="min_period_date", description="Minimum period date for the RM." ) max_period_date: date = Field( ..., alias="max_period_date", description="Maximum period date for the RM." ) clients: list[Client] = Field( ..., alias="clients", description="List of clients associated with the RM." ) @field_validator("min_period_date", "max_period_date") def validate_period_dates(cls, v: date | str) -> str: result = _validate_date_format(v) if result is None: raise ValueError("Period date cannot be None") return result class ScorecardFilterMetaResponse(models.BaseCollectionResponse): """Response model for a collection of scorecard filter metadata.""" data: list[ScorecardFilterMeta] = Field( ..., description="List of scorecard filter metadata with their metadata." ) class ScorecardClient(Client): """Represents a scorecard client.""" relationship_manager_email: EmailStr | None = Field( ..., alias="relationship_manager_email" ) # Some are null, maybe not assigned to an RM? ltm_gross_revenue_usd: float = Field(..., alias="ltm_gross_revenue_usd") month_gross_revenue_usd: float = Field(..., alias="month_gross_revenue_usd") period_date: date = Field(..., alias="period_date") prior_ltm_gross_revenue_usd: float = Field(..., alias="prior_ltm_gross_revenue_usd") prior_month_gross_revenue_usd: float = Field( ..., alias="prior_month_gross_revenue_usd" ) @field_validator("period_date") def date_must_be_valid(cls, v: str | date) -> str: result = _validate_date_format(v) if result is None: raise ValueError("period_date cannot be None") return result class ScorecardClientsResponse(models.BaseCollectionResponse): """Response model for a collection of scorecard clients.""" data: list[ScorecardClient] = Field( ..., description="List of scorecard clients with their metadata." ) class _ReleasePriority(BaseModel): country: PositiveInt = Field(..., alias="country") # e.g., 1 for US priority: Annotated[ str, StringConstraints(min_length=1, max_length=1, pattern=r"[A-Z]") ] = Field(..., alias="priority") class Release(BaseModel): """Represents a release.""" relationship_manager_email: EmailStr = Field( ..., alias="relationship_manager_email" ) artist_name: Optional[ Annotated[str, StringConstraints(min_length=1, strip_whitespace=True)] ] = Field( ..., alias="artist_name", description="Artist name. In rare cases, this can be null if " "no artist is assigned.", ) date_added: str | date | None = Field( ..., alias="date_added" ) # Some are null, maybe backfilled? date_release: str | date | None = Field(..., alias="date_release") date_sale_start: Union[str, date, None] = Field(..., alias="date_sale_start") label_id: PositiveInt = Field(..., alias="label_id") label_name: Annotated[ str, StringConstraints(min_length=1, strip_whitespace=True) ] = Field(..., alias="label_name") product_id: PositiveInt = Field(..., alias="product_id") release_id: PositiveInt = Field(..., alias="release_id") release_name: StrictStr = Field(..., alias="release_name") priorities: list[_ReleasePriority] = Field(..., alias="priorities") @field_validator("date_added", "date_release", "date_sale_start") def date_must_be_valid_or_null(cls, v: Union[str, date, None]) -> Optional[str]: result = _validate_date_format(v, allow_none=True) if result is None: return None return result @field_validator("artist_name", mode="after") def cast_null_artist_name_to_empty_string(cls, v: Optional[str]) -> str: """Convert None artist_name to empty string for consistency.""" if v is None: return "" return v class ReleasesResponse(models.BaseCollectionResponse): """Response model for a collection of releases.""" data: list[Release] = Field( ..., description="List of releases with their metadata." ) @lru_cache(maxsize=32) def _is_valid_regex(value: str, pattern_str: str) -> bool: """Generic, cached regex validator for performance.""" return bool(re.compile(pattern_str).fullmatch(value)) def _validate_date_format( value: Union[str, datetime, date, None], allow_none: bool = False ) -> Optional[str]: """Validate that the date is in YYYY-MM-DD format and convert it to a string, if it is a date or datetime object. """ if value is None: if allow_none: return None else: raise ValueError("Date cannot be None unless allow_none is True") try: if isinstance(value, datetime): value = value.date().strftime(_FORMAT_DATE) elif isinstance(value, date): value = value.strftime(_FORMAT_DATE) elif isinstance(value, str): if not _is_valid_regex(value, _PATTERN_DATE): raise ValueError("Date must be in YYYY-MM-DD format") else: raise TypeError("Invalid date type, must be str, datetime, or date") except Exception as ex: raise type(ex)(f"Error validating date: {ex}") from ex return value