app.bettersight.io/backend/repositories/brief_repository.py

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()