"""Pydantic schemas used in models layer.""" from datetime import datetime from enum import Enum from pydantic import computed_field from vectororder.models import Model class Priority(Enum): """Priority.""" HIGH = 1 NORMAL = 2 LOW = 3 class DeliveryType(Enum): """Delivery type.""" COMPLETE_ALBUM = "complete_album" METADATA_UPDATE = "metadata_update" class EncodingOrderType(Enum): """Encoding order type.""" RELEASE = "release" RINGTONE = "ringtone" class DownloadStreamRights(Enum): """Download stream rights.""" DOWNLOAD = "download" STREAM = "stream" def __lt__(self, other) -> bool: # type: ignore[no-untyped-def] """Less than.""" values = list(self.__class__) return values.index(self) < values.index(other) class TrackOfferType(Enum): """Track offer type.""" ALBUM_DOWNLOAD_ONLY = "album_download_only" ALBUM_DOWNLOAD_STREAM = "album_download_stream" ALBUM_TRACK_DOWNLOAD = "album_track_download" ALL = "all" STREAM_ONLY = "stream_only" TRACK_DOWNLOAD_ONLY = "track_download_only" TRACK_DOWNLOAD_STREAM = "track_download_stream" NONE = "none" class CommercialModelType(Enum): """Commercial model type.""" AD_SUPPORTED = "ad_supported" CONTRACT = "contract" PAY = "pay" RIGHTS = "rights" SUBSCRIPTION = "subscription" class UseType(Enum): """Use type.""" CONTRACT = "contract" CONDITIONAL_DOWNLOAD = "cond_download" LABEL = "label" NON_INTERACTIVE = "non-interactive" ON_DEMAND = "on-demand" PERMANENT_DOWNLOAD = "permanent_download" RINGBACK = "ringback" RINGTONE = "ringtone" STREAM = "stream" USER = "user" class DistributionFeatureId(Enum): """Distribution feature id.""" A_LA_CARTE_DOWNLOAD = 1 SUBSCRIPTION_DOWNLOAD = 2 SUBSCRIPTION_STREAMING = 3 SUBSCRIPTION_PORTABLE_TETHERED = 4 OTA_INCLUDING_DUAL_DELIVERY = 5 OTA_WITH_RINGTONE_USE = 6 STREAMING_SUBSCRIPTION_TO_MOBILE = 7 STREAMING_ON_DEMAND_TO_MOBILE = 8 RINGTONES = 9 RINGBACK_TONES = 10 AD_SUPPORTED_STREAMING = 18 class JobStatus(Enum): AUDIO_ENCODING_ERROR = "audio_encoding_error" AUDIO_MISSING = "audio_missing" AUDIO_MISSING_FOR_DELIVERY = "audio_missing_for_delivery" CANCELLED = "cancelled" DELIVERED = "delivered" DELIVERING = "delivering" DELIVERY_FAILURE = "delivery_failure" ENCODED = "encoded" ENCODING = "encoding" IMAGE_ENCODING_ERROR = "image_encoding_error" IMAGE_MISSING = "image_missing" IMAGE_MISSING_FOR_DELIVERY = "image_missing_for_delivery" INCORRECT_AUDIO_COUNT = "incorrect_audio_count" INCORRECT_AUDIO_COUNT_FOR_DELIVERY = "incorrect_audio_count_for_delivery" INELIGIBLE = "ineligible" NEW = "new" QUEUED_FOR_DELIVERY = "queued_for_delivery" METADATA_ERROR = "metadata_error" METADATA_MISSING = "metadata_missing" READY_TO_DELIVER = "ready_to_deliver" READY_TO_ENCODE = "ready_to_encode" SYSTEM_CANCELLED = "system_cancelled" SYSTEM_ERROR = "system_error" VIDEO_ENCODING_ERROR = "video_encoding_error" VIDEO_MISSING = "video_missing" VIDEO_MISSING_FOR_DELIVERY = "video_missing_for_delivery" # don't like having these 2 artificial statuses but only to avoid mypy nightmare ENCODING_STUCK = "encoding_stuck" DELIVERING_STUCK = "delivering_stuck" class JobStatusCategory(Enum): CANCELLED = "Cancelled" COMPLETED = "Completed" ERROR = "Error" PROCESSING = "Processing" STUCK = "Stuck" WAITING = "Waiting" class TrackType(Enum): """Track type.""" MUSIC = "music" VIDEO = "video" class Job(Model): """Job.""" job_id: int upc: int store_id: int delivery_type: DeliveryType encoding_order_type: EncodingOrderType status: JobStatus encoding_started: datetime | None = None delivery_started: datetime | None = None class DmsDeliverySpec(Model): """DMS Delivery Specification.""" dms_delivery_spec_id: int dms_master_master_id: int order_type: EncodingOrderType encoding: str delivery: str metadata_update: bool class Track(Model): """Track.""" track_id: int track_type: TrackType offer_type: TrackOfferType distribution_rights: set[DownloadStreamRights] = set() class Product(Model): """Product.""" product_id: int distribution_format_id: int not_for_distribution: str context_type: str tracks: list[Track] @property def is_digital_audio(self) -> bool: """Check if product is a digital audio product.""" return self.distribution_format_id == 1 and all( track.track_type == TrackType.MUSIC for track in self.tracks ) @property def has_track_with_offer_type(self) -> bool: """Check if product has a track with non-none offer type.""" return any(track.offer_type != TrackOfferType.NONE for track in self.tracks) class Vendor(Model): """Vendor.""" vendor_id: int name: str | None label_identifier: str | None api_vendor_id: int | None class Store(Model): """Store.""" store_id: int distribution_feature_ids: set[DistributionFeatureId] ddex_commercial_model_to_use_type_map_overrides: dict[ DownloadStreamRights, dict[CommercialModelType, set[UseType]] ] = {} is_physical: bool = False @computed_field # type: ignore[prop-decorator] @property def ddex_commercial_model_to_use_type_map( self, ) -> dict[DownloadStreamRights, dict[CommercialModelType, set[UseType]]]: """DDEX commercial model type.""" commercial_model_type_to_use_types_map: dict[ DownloadStreamRights, dict[CommercialModelType, set[UseType]] ] = { DownloadStreamRights.DOWNLOAD: {}, DownloadStreamRights.STREAM: {}, } def add_commercial_model_type_and_use_types( *, distribution_rights: DownloadStreamRights, commercial_model_type: CommercialModelType, use_types: set[UseType], ) -> None: use_type_overrides = set() if ( distribution_rights in self.ddex_commercial_model_to_use_type_map_overrides and commercial_model_type in self.ddex_commercial_model_to_use_type_map_overrides[ distribution_rights ] ): use_type_overrides = ( self.ddex_commercial_model_to_use_type_map_overrides[ distribution_rights ][commercial_model_type] ) commercial_model_type_to_use_types_map[distribution_rights].setdefault( commercial_model_type, set(), ).update(use_type_overrides if use_type_overrides else use_types) for distribution_feature_id in sorted( self.distribution_feature_ids, key=lambda x: x.value, ): match distribution_feature_id: case ( DistributionFeatureId.A_LA_CARTE_DOWNLOAD | DistributionFeatureId.OTA_INCLUDING_DUAL_DELIVERY ): add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.DOWNLOAD, commercial_model_type=CommercialModelType.PAY, use_types={UseType.PERMANENT_DOWNLOAD}, ) case ( DistributionFeatureId.SUBSCRIPTION_DOWNLOAD | DistributionFeatureId.SUBSCRIPTION_PORTABLE_TETHERED ): add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.DOWNLOAD, commercial_model_type=CommercialModelType.SUBSCRIPTION, use_types={UseType.CONDITIONAL_DOWNLOAD}, ) case DistributionFeatureId.SUBSCRIPTION_STREAMING: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.STREAM, commercial_model_type=CommercialModelType.SUBSCRIPTION, use_types={UseType.STREAM}, ) case DistributionFeatureId.OTA_WITH_RINGTONE_USE: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.DOWNLOAD, commercial_model_type=CommercialModelType.PAY, use_types={UseType.PERMANENT_DOWNLOAD, UseType.RINGTONE}, ) case DistributionFeatureId.STREAMING_SUBSCRIPTION_TO_MOBILE: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.STREAM, commercial_model_type=CommercialModelType.SUBSCRIPTION, use_types={UseType.NON_INTERACTIVE}, ) case DistributionFeatureId.STREAMING_ON_DEMAND_TO_MOBILE: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.STREAM, commercial_model_type=CommercialModelType.SUBSCRIPTION, use_types={UseType.ON_DEMAND}, ) case DistributionFeatureId.RINGTONES: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.DOWNLOAD, commercial_model_type=CommercialModelType.PAY, use_types={UseType.RINGTONE}, ) case DistributionFeatureId.RINGBACK_TONES: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.DOWNLOAD, commercial_model_type=CommercialModelType.PAY, use_types={UseType.RINGBACK}, ) case DistributionFeatureId.AD_SUPPORTED_STREAMING: add_commercial_model_type_and_use_types( distribution_rights=DownloadStreamRights.STREAM, commercial_model_type=CommercialModelType.AD_SUPPORTED, use_types={UseType.STREAM}, ) return commercial_model_type_to_use_types_map @computed_field # type: ignore[prop-decorator] @property def is_download_supported(self) -> bool: """Boolean value for whether this store supports downloading.""" return bool( self.distribution_feature_ids.intersection( [ DistributionFeatureId.A_LA_CARTE_DOWNLOAD, DistributionFeatureId.OTA_INCLUDING_DUAL_DELIVERY, DistributionFeatureId.SUBSCRIPTION_DOWNLOAD, DistributionFeatureId.SUBSCRIPTION_PORTABLE_TETHERED, DistributionFeatureId.OTA_WITH_RINGTONE_USE, DistributionFeatureId.RINGTONES, DistributionFeatureId.RINGBACK_TONES, ] ) ) @computed_field # type: ignore[prop-decorator] @property def is_streaming_supported(self) -> bool: """Boolean value for whether this store supports streaming.""" return bool( self.distribution_feature_ids.intersection( [ DistributionFeatureId.SUBSCRIPTION_STREAMING, DistributionFeatureId.STREAMING_SUBSCRIPTION_TO_MOBILE, DistributionFeatureId.STREAMING_ON_DEMAND_TO_MOBILE, DistributionFeatureId.AD_SUPPORTED_STREAMING, ] ) ) class StatusCategory(Model): title: JobStatusCategory statuses: set[JobStatus] class StatusByCategory(Model): waiting: StatusCategory processing: StatusCategory stuck: StatusCategory cancelled: StatusCategory completed: StatusCategory error: StatusCategory class OrderStatus(Enum): OPEN = "open" CLOSED = "close" # not a typo class OrderData(Model): encoder_id: int is_meta_update: bool order_status: OrderStatus priority: int store_ids: set[int] upcs: set[int] user_id: int class EncodingOrder(Model): automated_order: bool created_by: int created_on: datetime delivery_type: DeliveryType dms_list: list[int] # better name TBD encoder_id: int encoding_order_id: int order_closed_on: datetime order_priority: int order_status: OrderStatus upcs: list[int] | None