app.bettersight.io/backend/services/retention_service.py

63 lines
2.4 KiB
Python

import logging
from datetime import datetime, timedelta, timezone
from repositories.scrape_repository import scrape_repository
from repositories.price_history_repository import price_history_repository
from repositories.alert_repository import alert_repository
logger = logging.getLogger(__name__)
# CLAUDE.md §22 DATA_RETENTION — scrape_runs and alerts (read only) at
# 90 days, price_history at 365. tenant_data (0 = retained until an
# explicit deletion request) has no cron counterpart — that's
# billing_service.delete_tenant_data(). audit_logs (730 days) has no
# implementation here — see billing_service's note on why no
# audit_logs collection exists in this schema.
DATA_RETENTION_DAYS = {
'scrape_runs': 90,
'price_history': 365,
'alerts': 90,
}
def run_weekly_cleanup():
"""
Deletes data past its retention window — the Sunday 3am n8n
workflow's only job is to trigger this. All real logic lives here,
not in n8n, matching every other internal route in this codebase.
Data flow:
now → per-collection cutoff = now - retention_days →
scrape_runs older than cutoff: deleted →
alerts older than cutoff AND read=true: deleted (unread alerts
are never auto-deleted, regardless of age) →
price_history older than cutoff: deleted →
counts logged and returned
"""
now = datetime.now(timezone.utc)
scrape_cutoff = (now - timedelta(days=DATA_RETENTION_DAYS['scrape_runs'])).isoformat()
scrape_runs_deleted = 0
for run in scrape_repository.list_older_than(scrape_cutoff):
scrape_repository.delete(run['id'])
scrape_runs_deleted += 1
alert_cutoff = (now - timedelta(days=DATA_RETENTION_DAYS['alerts'])).isoformat()
alerts_deleted = 0
for alert in alert_repository.list_read_older_than(alert_cutoff):
alert_repository.delete(alert['id'])
alerts_deleted += 1
price_history_cutoff = (now - timedelta(days=DATA_RETENTION_DAYS['price_history'])).isoformat()
price_history_deleted = 0
for record in price_history_repository.list_older_than(price_history_cutoff):
price_history_repository.delete(record['id'])
price_history_deleted += 1
result = {
'scrape_runs_deleted': scrape_runs_deleted,
'alerts_deleted': alerts_deleted,
'price_history_deleted': price_history_deleted,
}
logger.info(f'Weekly data cleanup: {result}')
return result