"""Stale coverage detection — marks techniques whose last validated test is older than a configurable threshold. This is the simple version. The full Decay Engine (Fase 8) will replace this with a multi-factor, configurable decay model with confidence scores. """ import logging from datetime import datetime, timedelta, timezone from sqlalchemy import func from sqlalchemy.orm import Session from app.config import settings from app.models.enums import TechniqueStatus, TestState from app.models.technique import Technique from app.models.test import Test logger = logging.getLogger(__name__) STALE_THRESHOLD_DAYS = settings.STALE_THRESHOLD_DAYS def detect_stale_coverage(db: Session) -> int: """Scan all techniques and flag those with stale coverage. A technique is considered stale when: - It has a status other than ``not_evaluated``, AND - Its most recent *validated* test is older than *STALE_THRESHOLD_DAYS*, OR - It has never had a validated test (but has been manually marked as covered/partial). Returns the number of newly-flagged techniques. """ cutoff = datetime.now(timezone.utc) - timedelta(days=STALE_THRESHOLD_DAYS) last_validated = func.coalesce( Test.blue_validated_at, Test.red_validated_at, Test.created_at, ) # Subquery: latest validated test date per technique latest_test = ( db.query( Test.technique_id, func.max(last_validated).label("last_tested"), ) .filter(Test.state == TestState.validated) .group_by(Test.technique_id) .subquery() ) # Find techniques that are stale stale_techniques = ( db.query(Technique) .outerjoin(latest_test, Technique.id == latest_test.c.technique_id) .filter( # Either tested before cutoff, or never tested at all (latest_test.c.last_tested < cutoff) | (latest_test.c.last_tested.is_(None)) ) .filter( # Only flag techniques that have a real status (not never-evaluated ones) Technique.status_global != TechniqueStatus.not_evaluated ) .all() ) count = 0 for tech in stale_techniques: if not tech.review_required: tech.review_required = True count += 1 logger.info("Marked %s as stale coverage", tech.mitre_id) if count > 0: db.commit() logger.info( "Stale coverage detection complete — %d techniques flagged", count ) else: logger.info("Stale coverage detection complete — no new stale techniques") return count