99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from repositories.pocketbase_client import pb_client
|
|
from repositories.repo_config import USE_MOCK_REPOSITORIES
|
|
|
|
PRICE_HISTORY = 'price_history'
|
|
PRODUCTS = 'products'
|
|
BATTLECARDS = 'battlecards'
|
|
TENANTS = 'tenants'
|
|
DIGEST_LOGS = 'digest_logs'
|
|
|
|
|
|
class BriefRepository:
|
|
"""
|
|
Read-only aggregation across price_history/products/battlecards/tenants
|
|
for the weekly brief email, plus the digest_logs write. This is data
|
|
retrieval (multi-collection reads), not decision-making, so it stays
|
|
in the repository layer per brief_service's call pattern in CLAUDE.md §13.
|
|
"""
|
|
|
|
def get_weekly_data(self, tenant_id):
|
|
"""
|
|
Pulls everything brief_service needs to render brief.html for one
|
|
tenant's past 7 days.
|
|
|
|
Data flow:
|
|
tenant_id → tenant record (admin_email) →
|
|
price_history since 7 days ago → products where is_new since 7 days ago →
|
|
battlecards updated since 7 days ago →
|
|
{ admin_email, week_of, price_changes, new_products, battlecard_updates }
|
|
"""
|
|
tenant = pb_client.get_one(TENANTS, tenant_id)
|
|
since = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
|
|
|
price_changes = pb_client.list(
|
|
PRICE_HISTORY, filter_str=f'tenant_id = "{tenant_id}" && scraped_at >= "{since}"',
|
|
sort='-scraped_at', per_page=200
|
|
)
|
|
new_products = pb_client.list(
|
|
PRODUCTS, filter_str=f'tenant_id = "{tenant_id}" && is_new = true && first_seen >= "{since}"',
|
|
sort='-first_seen', per_page=200
|
|
)
|
|
battlecard_updates = pb_client.list(
|
|
BATTLECARDS, filter_str=f'tenant_id = "{tenant_id}" && generated_at >= "{since}"',
|
|
sort='-generated_at', per_page=200
|
|
)
|
|
|
|
return {
|
|
'admin_email': tenant.get('email') if tenant else None,
|
|
'tenant_name': tenant.get('name') if tenant else None,
|
|
'week_of': datetime.now(timezone.utc).strftime('%Y-%m-%d'),
|
|
'price_changes': price_changes,
|
|
'new_products': new_products,
|
|
'battlecard_updates': battlecard_updates,
|
|
}
|
|
|
|
def log_digest(self, tenant_id, digest_type, recipient_email=None, status='sent'):
|
|
"""Records that a brief/digest was sent (or failed to send)."""
|
|
return pb_client.create(DIGEST_LOGS, {
|
|
'tenant_id': tenant_id,
|
|
'digest_type': digest_type,
|
|
'recipient_email': recipient_email,
|
|
'status': status,
|
|
})
|
|
|
|
|
|
class MockBriefRepository:
|
|
"""In-memory stand-in for BriefRepository — returns empty/zeroed aggregates."""
|
|
|
|
def __init__(self):
|
|
self._tenants = {}
|
|
self._logs = {}
|
|
|
|
def seed_tenant(self, tenant_id, tenant):
|
|
self._tenants[tenant_id] = tenant
|
|
|
|
def get_weekly_data(self, tenant_id):
|
|
tenant = self._tenants.get(tenant_id, {})
|
|
return {
|
|
'admin_email': tenant.get('email'),
|
|
'tenant_name': tenant.get('name'),
|
|
'week_of': datetime.now(timezone.utc).strftime('%Y-%m-%d'),
|
|
'price_changes': [],
|
|
'new_products': [],
|
|
'battlecard_updates': [],
|
|
}
|
|
|
|
def log_digest(self, tenant_id, digest_type, recipient_email=None, status='sent'):
|
|
record_id = str(uuid.uuid4())
|
|
record = {
|
|
'id': record_id, 'tenant_id': tenant_id, 'digest_type': digest_type,
|
|
'recipient_email': recipient_email, 'status': status,
|
|
}
|
|
self._logs[record_id] = record
|
|
return record
|
|
|
|
|
|
brief_repository = MockBriefRepository() if USE_MOCK_REPOSITORIES else BriefRepository()
|