feat: add governed cross-module reporting
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Retention minimization for governed provider-report results."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_reporting.backend.db.models import ReportingProviderExecution
|
||||
|
||||
|
||||
class ReportingRetentionService:
|
||||
def apply_retention(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
dry_run: bool,
|
||||
now: datetime,
|
||||
limit: int = 500,
|
||||
) -> dict[str, int]:
|
||||
if not isinstance(session, Session):
|
||||
raise TypeError("Reporting retention requires a SQLAlchemy Session")
|
||||
observed_at = _aware(now)
|
||||
rows = (
|
||||
session.query(ReportingProviderExecution)
|
||||
.filter(
|
||||
ReportingProviderExecution.expires_at.is_not(None),
|
||||
ReportingProviderExecution.expires_at <= observed_at,
|
||||
ReportingProviderExecution.retention_redacted_at.is_(None),
|
||||
)
|
||||
.order_by(
|
||||
ReportingProviderExecution.expires_at.asc(),
|
||||
ReportingProviderExecution.id.asc(),
|
||||
)
|
||||
.limit(max(1, min(int(limit), 5_000)))
|
||||
.all()
|
||||
)
|
||||
counts = {
|
||||
"eligible": len(rows),
|
||||
"redacted": 0,
|
||||
"remaining_in_batch": 0,
|
||||
}
|
||||
if dry_run:
|
||||
return counts
|
||||
for row in rows:
|
||||
# Keep immutable request/output hashes and provenance as audit
|
||||
# evidence while removing the retained report detail itself.
|
||||
row.result_payload = {}
|
||||
row.retention_redacted_at = observed_at
|
||||
counts["redacted"] += 1
|
||||
session.flush()
|
||||
counts["remaining_in_batch"] = (
|
||||
session.query(ReportingProviderExecution.id)
|
||||
.filter(
|
||||
ReportingProviderExecution.expires_at.is_not(None),
|
||||
ReportingProviderExecution.expires_at <= observed_at,
|
||||
ReportingProviderExecution.retention_redacted_at.is_(None),
|
||||
)
|
||||
.limit(1)
|
||||
.count()
|
||||
)
|
||||
return counts
|
||||
|
||||
|
||||
def _aware(value: datetime) -> datetime:
|
||||
return value if value.tzinfo is not None else value.replace(tzinfo=UTC)
|
||||
|
||||
|
||||
__all__ = ["ReportingRetentionService"]
|
||||
Reference in New Issue
Block a user