文件预览

freshness_report.py

查看 🩺 memory-health-check 技能包中的文件内容。

文件内容

bin/freshness_report.py

#!/usr/bin/env python3
"""Freshness report — entry age distribution."""
import argparse
import json
import logging
from pathlib import Path
from datetime import datetime, timezone, timedelta
from typing import Optional

import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))

from config_loader import load_config
from file_scanner import FileScanner
from health_models import DimResult
from log_utils import get_logger

logger = get_logger("memory-health-check.freshness_report")


def get_file_age_distribution(
    base_dir: Path,
    reference_date: datetime = None,
) -> dict:
    """Compute age distribution of memory files.
    
    Args:
        base_dir: Memory directory
        reference_date: Reference for "now" (default: now)
        
    Returns:
        dict: {
            "total": int,
            "by_category": {"<7d": n, "7-30d": n, "30-90d": n, ">90d": n},
            "by_percentage": dict[str, float],
        }
    """
    if reference_date is None:
        reference_date = datetime.now(tz=timezone.utc)
    
    week_ago = reference_date - timedelta(days=7)
    month_ago = reference_date - timedelta(days=30)
    quarter_ago = reference_date - timedelta(days=90)
    
    scanner = FileScanner(base_dir)
    
    dist = {"<7d": 0, "7-30d": 0, "30-90d": 0, ">90d": 0, "total": 0}
    
    for _, mtime in scanner.get_file_ages():
        dist["total"] += 1
        if mtime >= week_ago:
            dist["<7d"] += 1
        elif mtime >= month_ago:
            dist["7-30d"] += 1
        elif mtime >= quarter_ago:
            dist["30-90d"] += 1
        else:
            dist[">90d"] += 1
    
    # Compute percentages
    pct = {}
    for k, v in dist.items():
        if k != "total" and dist["total"] > 0:
            pct[k] = round(v / dist["total"] * 100, 2)
    
    return {
        "total": dist["total"],
        "by_category": {k: v for k, v in dist.items() if k != "total"},
        "by_percentage": pct,
    }


def freshness_report(
    base_dir: Path = None,
    reference_date: datetime = None,
) -> dict:
    """Generate freshness report for memory entries.
    
    Args:
        base_dir: Memory directory
        reference_date: Reference for "now" (default: now)
        
    Returns:
        dict: {
            "score": int,
            "status": str,
            "recent_7d": int,
            "recent_30d": int,
            "recent_90d": int,
            "stale": int,
            "total": int,
            "freshness_rate": float,
            "age_distribution": dict,
        }
    """
    if base_dir is None:
        base_dir = Path.home() / ".openclaw" / "workspace" / "memory"
    
    if not base_dir.exists():
        return {
            "score": 100,
            "status": "healthy",
            "recent_7d": 0,
            "recent_30d": 0,
            "recent_90d": 0,
            "stale": 0,
            "total": 0,
            "freshness_rate": 100.0,
            "age_distribution": {"<7d": 0, "7-30d": 0, "30-90d": 0, ">90d": 0},
        }
    
    if reference_date is None:
        reference_date = datetime.now(tz=timezone.utc)
    
    config = load_config()
    thresholds = config.get("thresholds", {}).get("freshness_rate", {})
    
    dist = get_file_age_distribution(base_dir, reference_date)
    
    recent_7d = dist["by_category"]["<7d"]
    recent_30d = dist["by_category"]["7-30d"]
    recent_90d = dist["by_category"]["30-90d"]
    stale = dist["by_category"][">90d"]
    total = dist["total"]
    
    freshness_rate = (dist["by_category"]["<7d"] + dist["by_category"]["7-30d"]) / max(total, 1)
    
    healthy_rate = thresholds.get("healthy", 0.70)
    warning_rate = thresholds.get("warning", 0.40)
    
    if freshness_rate >= healthy_rate:
        score = 100
        status = "healthy"
    elif freshness_rate >= warning_rate:
        score = 60
        status = "warning"
    else:
        score = 20
        status = "critical"
    
    return {
        "score": score,
        "status": status,
        "recent_7d": recent_7d,
        "recent_30d": recent_30d,
        "recent_90d": recent_90d,
        "stale": stale,
        "total": total,
        "freshness_rate": round(freshness_rate * 100, 2),
        "age_distribution": dist["by_category"],
    }


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Generate freshness report")
    parser.add_argument("--base-dir", type=Path, default=None)
    parser.add_argument("-v", "--verbose", action="store_true")
    args = parser.parse_args()
    
    if args.verbose:
        import logging
        get_logger().setLevel(logging.DEBUG)
    
    result = freshness_report(base_dir=args.base_dir)
    
    print(f"[freshness_report] Status: {result['status']}, Freshness: {result['freshness_rate']}%, Score: {result['score']}")
    print(json.dumps(result, indent=2, ensure_ascii=False))