Files
receiptwitness/common/src/cartsnitch_common/seed/generators/coupons.py
T

108 lines
3.6 KiB
Python

"""Generate Coupon seed data."""
import random
import uuid
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from faker import Faker
from cartsnitch_common.constants import DiscountType
from cartsnitch_common.seed.config import (
COUPON_EXPIRED_PCT,
NUM_COUPONS,
SEED_END_DATE,
SEED_START_DATE,
)
def _decimal(val: float) -> Decimal:
return Decimal(str(round(val, 2)))
_COUPON_TITLES: list[str] = [
"Save {val} on {product}",
"{val} off your next {product} purchase",
"Get {val} off {product}",
"Buy {product}, save {val}",
"Weekend special: {val} off {product}",
"Member exclusive: {val} off {product}",
"Digital coupon: {val} off {product}",
]
def generate_coupons(
fake: Faker,
products: list[dict],
stores: list[dict],
) -> list[dict]:
"""Return NUM_COUPONS coupon records with realistic mix of active/expired."""
now = datetime.now(tz=UTC)
today = SEED_END_DATE
coupons = []
num_expired = int(NUM_COUPONS * COUPON_EXPIRED_PCT)
num_active = NUM_COUPONS - num_expired
def make_coupon(is_active: bool) -> dict:
store = random.choice(stores)
product = random.choice(products) if random.random() > 0.1 else None
product_name = product["canonical_name"].split(" ", 2)[-1] if product else "any item"
discount_type = random.choice(list(DiscountType))
if discount_type == DiscountType.PERCENT:
discount_value = _decimal(random.choice([5, 10, 15, 20, 25, 30]))
title = f"Save {int(discount_value)}% on {product_name}"
elif discount_type == DiscountType.FIXED:
discount_value = _decimal(random.choice([0.50, 1.00, 1.50, 2.00, 2.50, 3.00, 5.00]))
title = f"Save ${discount_value} on {product_name}"
elif discount_type == DiscountType.BOGO:
discount_value = None
title = f"BOGO: Buy one {product_name}, get one free"
else: # BUY_X_GET_Y
discount_value = None
title = f"Buy 2 {product_name}, get 1 free"
if is_active:
valid_from = today - timedelta(days=random.randint(1, 30))
valid_to = today + timedelta(days=random.randint(1, 60))
else:
valid_to = today - timedelta(days=random.randint(1, 180))
valid_from = valid_to - timedelta(days=random.randint(7, 30))
requires_clip = random.random() > 0.5
coupon_code = fake.bothify(text="??##-??##").upper() if not requires_clip else None
min_purchase = _decimal(random.choice([0, 0, 0, 5.00, 10.00, 15.00])) or None
scraped_at = datetime(
SEED_START_DATE.year, SEED_START_DATE.month, SEED_START_DATE.day, tzinfo=UTC
) + timedelta(days=random.randint(0, 180))
return {
"id": uuid.uuid4(),
"store_id": store["id"],
"normalized_product_id": product["id"] if product else None,
"title": title,
"description": fake.sentence(nb_words=10),
"discount_type": discount_type,
"discount_value": discount_value,
"min_purchase": min_purchase,
"valid_from": valid_from,
"valid_to": valid_to,
"requires_clip": requires_clip,
"coupon_code": coupon_code,
"source_url": None,
"scraped_at": scraped_at,
"created_at": now,
"updated_at": now,
}
for _ in range(num_expired):
coupons.append(make_coupon(is_active=False))
for _ in range(num_active):
coupons.append(make_coupon(is_active=True))
random.shuffle(coupons)
return coupons