"""Dataclass models shared across monitors and the consumer bot.""" from dataclasses import field from datetime import datetime from pydantic.dataclasses import dataclass @dataclass class CycleResult: """Tallied outcome of one monitor cycle. Pure counts for the caller to log or act on as it sees fit; no policy is baked in. Attributes: successes: Category tasks that completed without an expected error. failures: Category tasks that raised an expected error. skipped: True when pre_task_func failed and no category tasks ran. """ successes: int = 0 failures: int = 0 skipped: bool = False @dataclass class ConsumerConfig: """Configuration for a single retailer's consumer. Attributes: monitor_name: Retailer name (matches setup_config key). collection: MongoDB collection for this retailer's products. channel_ids: Discord webhook/channel routing config. min_percentage: Minimum discount % to trigger a ping. ping_threshold: Price floor for the secondary 40% rule (e.g. Woot pings on 40%+ discounts if the item is >= this price). None disables the secondary rule. """ monitor_name: str collection: object channel_ids: dict min_percentage: int ping_threshold: int | None = None @dataclass class PricingInfo: """Price fields for a product. Attributes: original_price: Pre-discount price. current_price: Current listed price. price_before_drop: Stored price prior to the last detected drop. edu_price: Education-store price when offered. """ original_price: float current_price: float price_before_drop: float | None = None edu_price: float | None = None @dataclass class AvailabilityInfo: """Availability and membership flags for a product. Attributes: available: Whether the product is purchasable. condition: Product condition such as new or open box. app_exclusive: Only purchasable through the retailer's app. member_pick: Flagged as a member pick offer. member_deal: Flagged as a member deal offer. availability_last_checked: When available was last verified against the retailer, letting monitors skip rechecks within an interval. """ available: bool | None = None condition: str | None = None app_exclusive: bool | None = None member_pick: bool | None = None member_deal: bool | None = None availability_last_checked: datetime | None = None @dataclass class ProductMetadata: """Presentation and routing metadata for a product. Attributes: image_url: Product image URL. channel_id: Resolved Discord channel id for the ping. customization_index: Config number when priced via customization. member_non_member_comparison: Display string comparing member and non-member pricing. overstock_text: Overstock label text when present. percentage: Computed discount percentage. affiliate_url: Affiliate link when available. promo_code: Event-level checkout code (e.g. "BIRTHDAY26") when the product's event advertises one, else None. promo_percentage: Advertised percent off from a percentage code ("extra 22% off" -> 22); None for dollar codes or no code. This is the claimed discount, not the actual price delta. promo_dollar_off: Advertised dollar amount off from a spend-threshold code ("get an extra $22 off" -> 22); None for percentage codes or no code. Pairs with promo_min_spend. promo_min_spend: Spend threshold a dollar code requires ("Spend $100" -> 100); None unless promo_dollar_off is set. """ image_url: str | None = None channel_id: str | None = None customization_index: str | None = None member_non_member_comparison: str | None = None overstock_text: str | None = None percentage: int | None = None affiliate_url: str | None = None promo_code: str | None = None promo_percentage: int | None = None promo_dollar_off: int | None = None promo_min_spend: int | None = None @dataclass class ProductTiming: """Lifecycle timestamps for a product. Attributes: discovered_date: When the product was first stored. start_date: Offer start when the retailer provides one. end_date: Offer end when the retailer provides one. """ discovered_date: datetime | None = None start_date: datetime | None = None end_date: datetime | None = None @dataclass class Product: """A monitored product assembled from retailer data. Attributes: product_id: Retailer-specific product identifier. title: Product title. url: Product page URL. pricing: Price fields. metadata: Presentation and routing metadata. timing: Lifecycle timestamps. availability: Availability and membership flags. """ product_id: str title: str url: str pricing: PricingInfo metadata: ProductMetadata = field(default_factory=ProductMetadata) timing: ProductTiming = field(default_factory=ProductTiming) availability: AvailabilityInfo = field(default_factory=AvailabilityInfo)