Improve demo seed
This commit is contained in:
210
shared/utils/demo_dates.py
Normal file
210
shared/utils/demo_dates.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
Demo Date Offset Utilities
|
||||
Provides functions for adjusting dates during demo session cloning
|
||||
to ensure all temporal data is relative to the demo session creation time
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# Base reference date for all demo seed data
|
||||
# All seed scripts should use this as the "logical seed date"
|
||||
BASE_REFERENCE_DATE = datetime(2025, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def adjust_date_for_demo(
|
||||
original_date: Optional[datetime],
|
||||
session_created_at: datetime,
|
||||
base_reference_date: datetime = BASE_REFERENCE_DATE
|
||||
) -> Optional[datetime]:
|
||||
"""
|
||||
Adjust a date from seed data to be relative to demo session creation time
|
||||
|
||||
This ensures that demo data appears fresh and relevant regardless of when
|
||||
the demo session is created. For example, expiration dates that were "15 days
|
||||
from seed date" will become "15 days from session creation date".
|
||||
|
||||
Args:
|
||||
original_date: The original date from the seed data (or None)
|
||||
session_created_at: When the demo session was created
|
||||
base_reference_date: The logical date when seed data was created (default: 2025-01-15)
|
||||
|
||||
Returns:
|
||||
Adjusted date relative to session creation, or None if original_date was None
|
||||
|
||||
Example:
|
||||
# Seed data created on 2025-01-15
|
||||
# Stock expiration: 2025-01-30 (15 days from seed date)
|
||||
# Demo session created: 2025-10-16
|
||||
# Result: 2025-10-31 (15 days from session date)
|
||||
|
||||
>>> original = datetime(2025, 1, 30, 12, 0, tzinfo=timezone.utc)
|
||||
>>> session = datetime(2025, 10, 16, 10, 0, tzinfo=timezone.utc)
|
||||
>>> adjusted = adjust_date_for_demo(original, session)
|
||||
>>> print(adjusted)
|
||||
2025-10-31 10:00:00+00:00
|
||||
"""
|
||||
if original_date is None:
|
||||
return None
|
||||
|
||||
# Ensure timezone-aware datetimes
|
||||
if original_date.tzinfo is None:
|
||||
original_date = original_date.replace(tzinfo=timezone.utc)
|
||||
if session_created_at.tzinfo is None:
|
||||
session_created_at = session_created_at.replace(tzinfo=timezone.utc)
|
||||
if base_reference_date.tzinfo is None:
|
||||
base_reference_date = base_reference_date.replace(tzinfo=timezone.utc)
|
||||
|
||||
# Calculate offset from base reference
|
||||
offset = original_date - base_reference_date
|
||||
|
||||
# Apply offset to session creation date
|
||||
return session_created_at + offset
|
||||
|
||||
|
||||
def adjust_date_relative_to_now(
|
||||
days_offset: int,
|
||||
hours_offset: int = 0,
|
||||
reference_time: Optional[datetime] = None
|
||||
) -> datetime:
|
||||
"""
|
||||
Create a date relative to now (or a reference time) with specified offset
|
||||
|
||||
Useful for creating dates during cloning without needing to store seed dates.
|
||||
|
||||
Args:
|
||||
days_offset: Number of days to add (negative for past dates)
|
||||
hours_offset: Number of hours to add (negative for past times)
|
||||
reference_time: Reference datetime (defaults to now)
|
||||
|
||||
Returns:
|
||||
Calculated datetime
|
||||
|
||||
Example:
|
||||
>>> # Create a date 7 days in the future
|
||||
>>> future = adjust_date_relative_to_now(days_offset=7)
|
||||
>>> # Create a date 3 days in the past
|
||||
>>> past = adjust_date_relative_to_now(days_offset=-3)
|
||||
"""
|
||||
if reference_time is None:
|
||||
reference_time = datetime.now(timezone.utc)
|
||||
elif reference_time.tzinfo is None:
|
||||
reference_time = reference_time.replace(tzinfo=timezone.utc)
|
||||
|
||||
return reference_time + timedelta(days=days_offset, hours=hours_offset)
|
||||
|
||||
|
||||
def calculate_expiration_date(
|
||||
received_date: datetime,
|
||||
shelf_life_days: int
|
||||
) -> datetime:
|
||||
"""
|
||||
Calculate expiration date based on received date and shelf life
|
||||
|
||||
Args:
|
||||
received_date: When the product was received
|
||||
shelf_life_days: Number of days until expiration
|
||||
|
||||
Returns:
|
||||
Calculated expiration datetime
|
||||
"""
|
||||
if received_date.tzinfo is None:
|
||||
received_date = received_date.replace(tzinfo=timezone.utc)
|
||||
|
||||
return received_date + timedelta(days=shelf_life_days)
|
||||
|
||||
|
||||
def get_days_until_expiration(
|
||||
expiration_date: datetime,
|
||||
reference_date: Optional[datetime] = None
|
||||
) -> int:
|
||||
"""
|
||||
Calculate number of days until expiration
|
||||
|
||||
Args:
|
||||
expiration_date: The expiration datetime
|
||||
reference_date: Reference datetime (defaults to now)
|
||||
|
||||
Returns:
|
||||
Number of days until expiration (negative if already expired)
|
||||
"""
|
||||
if reference_date is None:
|
||||
reference_date = datetime.now(timezone.utc)
|
||||
elif reference_date.tzinfo is None:
|
||||
reference_date = reference_date.replace(tzinfo=timezone.utc)
|
||||
|
||||
if expiration_date.tzinfo is None:
|
||||
expiration_date = expiration_date.replace(tzinfo=timezone.utc)
|
||||
|
||||
delta = expiration_date - reference_date
|
||||
return delta.days
|
||||
|
||||
|
||||
def is_expiring_soon(
|
||||
expiration_date: datetime,
|
||||
threshold_days: int = 3,
|
||||
reference_date: Optional[datetime] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a product is expiring soon
|
||||
|
||||
Args:
|
||||
expiration_date: The expiration datetime
|
||||
threshold_days: Number of days to consider as "soon" (default: 3)
|
||||
reference_date: Reference datetime (defaults to now)
|
||||
|
||||
Returns:
|
||||
True if expiring within threshold_days, False otherwise
|
||||
"""
|
||||
days_until = get_days_until_expiration(expiration_date, reference_date)
|
||||
return 0 <= days_until <= threshold_days
|
||||
|
||||
|
||||
def is_expired(
|
||||
expiration_date: datetime,
|
||||
reference_date: Optional[datetime] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a product is expired
|
||||
|
||||
Args:
|
||||
expiration_date: The expiration datetime
|
||||
reference_date: Reference datetime (defaults to now)
|
||||
|
||||
Returns:
|
||||
True if expired, False otherwise
|
||||
"""
|
||||
days_until = get_days_until_expiration(expiration_date, reference_date)
|
||||
return days_until < 0
|
||||
|
||||
|
||||
def adjust_multiple_dates(
|
||||
dates_dict: dict,
|
||||
session_created_at: datetime,
|
||||
base_reference_date: datetime = BASE_REFERENCE_DATE
|
||||
) -> dict:
|
||||
"""
|
||||
Adjust multiple dates in a dictionary
|
||||
|
||||
Args:
|
||||
dates_dict: Dictionary with datetime values to adjust
|
||||
session_created_at: When the demo session was created
|
||||
base_reference_date: The logical date when seed data was created
|
||||
|
||||
Returns:
|
||||
Dictionary with adjusted dates (preserves None values)
|
||||
|
||||
Example:
|
||||
>>> dates = {
|
||||
... 'expiration_date': datetime(2025, 1, 30, tzinfo=timezone.utc),
|
||||
... 'received_date': datetime(2025, 1, 15, tzinfo=timezone.utc),
|
||||
... 'optional_date': None
|
||||
... }
|
||||
>>> session = datetime(2025, 10, 16, tzinfo=timezone.utc)
|
||||
>>> adjusted = adjust_multiple_dates(dates, session)
|
||||
"""
|
||||
return {
|
||||
key: adjust_date_for_demo(value, session_created_at, base_reference_date)
|
||||
for key, value in dates_dict.items()
|
||||
}
|
||||
Reference in New Issue
Block a user