147 lines
4.4 KiB
Python
147 lines
4.4 KiB
Python
"""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.
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
@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)
|