Spaces:
Running
Running
| """ | |
| Product-level analysis freshness rules. | |
| """ | |
| from __future__ import annotations | |
| from datetime import datetime, timezone | |
| from enum import Enum | |
| from typing import Any, cast | |
| from app.core.config import settings | |
| class FreshnessStatus(str, Enum): | |
| """Product freshness state for an existing analysis.""" | |
| FRESH = "fresh" | |
| STALE_BY_AGE = "stale_by_age" | |
| STALE_BY_PATCH = "stale_by_patch" | |
| def _as_utc_datetime(value: Any) -> datetime | None: | |
| if value is None: | |
| return None | |
| if isinstance(value, datetime): | |
| return value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc) | |
| if isinstance(value, str): | |
| parsed = datetime.fromisoformat(value) | |
| return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=timezone.utc) | |
| return None | |
| def get_analysis_reference_at(document: dict[str, Any]) -> datetime | None: | |
| """Return the best available execution timestamp for freshness checks.""" | |
| raw = document.get("results") | |
| results: dict[str, Any] = cast(dict[str, Any], raw) if isinstance(raw, dict) else {} | |
| return ( | |
| _as_utc_datetime(results.get("analysis_date")) | |
| or _as_utc_datetime(document.get("analyzed_at")) | |
| or _as_utc_datetime(document.get("cached_at")) | |
| ) | |
| def evaluate_freshness( | |
| document: dict[str, Any], | |
| current_patch_at: datetime | None, | |
| ) -> FreshnessStatus: | |
| """ | |
| Evaluate analysis freshness using product rules: | |
| patch recency first, then max age. | |
| """ | |
| analysis_at = get_analysis_reference_at(document) | |
| if analysis_at is None: | |
| return FreshnessStatus.STALE_BY_AGE | |
| if current_patch_at is not None and analysis_at < current_patch_at: | |
| return FreshnessStatus.STALE_BY_PATCH | |
| age_days = (datetime.now(timezone.utc) - analysis_at).days | |
| if age_days > settings.analysis_freshness_max_age_days: | |
| return FreshnessStatus.STALE_BY_AGE | |
| return FreshnessStatus.FRESH | |
| def get_staleness_reason(status: FreshnessStatus) -> str | None: | |
| if status == FreshnessStatus.STALE_BY_AGE: | |
| return "STALE_REASON_AGE" | |
| if status == FreshnessStatus.STALE_BY_PATCH: | |
| return "STALE_REASON_PATCH" | |
| return None | |