diff --git a/scripts/last30days.py b/scripts/last30days.py index 2a998eb..5adb89e 100644 --- a/scripts/last30days.py +++ b/scripts/last30days.py @@ -236,11 +236,13 @@ def main(): progress = ui.ProgressDisplay(args.topic, show_banner=True) if not args.refresh and not args.mock: - cached = cache.load_cache(cache_key) + cached, cache_age = cache.load_cache_with_age(cache_key) if cached: # Use cached data - progress.show_cached() + progress.show_cached(cache_age) report = schema.Report.from_dict(cached) + report.from_cache = True + report.cache_age_hours = cache_age output_result(report, args.emit) return diff --git a/scripts/lib/cache.py b/scripts/lib/cache.py index 0545ba6..0a6ac7b 100644 --- a/scripts/lib/cache.py +++ b/scripts/lib/cache.py @@ -57,6 +57,39 @@ def load_cache(cache_key: str, ttl_hours: int = DEFAULT_TTL_HOURS) -> Optional[d return None +def get_cache_age_hours(cache_path: Path) -> Optional[float]: + """Get age of cache file in hours.""" + if not cache_path.exists(): + return None + try: + stat = cache_path.stat() + mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) + now = datetime.now(timezone.utc) + return (now - mtime).total_seconds() / 3600 + except OSError: + return None + + +def load_cache_with_age(cache_key: str, ttl_hours: int = DEFAULT_TTL_HOURS) -> tuple: + """Load data from cache with age info. + + Returns: + Tuple of (data, age_hours) or (None, None) if invalid + """ + cache_path = get_cache_path(cache_key) + + if not is_cache_valid(cache_path, ttl_hours): + return None, None + + age = get_cache_age_hours(cache_path) + + try: + with open(cache_path, 'r') as f: + return json.load(f), age + except (json.JSONDecodeError, OSError): + return None, None + + def save_cache(cache_key: str, data: dict): """Save data to cache.""" ensure_cache_dir() diff --git a/scripts/lib/render.py b/scripts/lib/render.py index 14c8149..7a7a83f 100644 --- a/scripts/lib/render.py +++ b/scripts/lib/render.py @@ -29,6 +29,13 @@ def render_compact(report: schema.Report, limit: int = 15) -> str: # Header lines.append(f"## Research Results: {report.topic}") lines.append("") + + # Cache indicator + if report.from_cache: + age_str = f"{report.cache_age_hours:.1f}h old" if report.cache_age_hours else "cached" + lines.append(f"**⚡ CACHED RESULTS** ({age_str}) - use `--refresh` for fresh data") + lines.append("") + lines.append(f"**Date Range:** {report.range_from} to {report.range_to}") lines.append(f"**Mode:** {report.mode}") if report.openai_model_used: diff --git a/scripts/lib/schema.py b/scripts/lib/schema.py index 2adfc4d..9b84414 100644 --- a/scripts/lib/schema.py +++ b/scripts/lib/schema.py @@ -156,6 +156,9 @@ class Report: # Status tracking reddit_error: Optional[str] = None x_error: Optional[str] = None + # Cache info + from_cache: bool = False + cache_age_hours: Optional[float] = None def to_dict(self) -> Dict[str, Any]: d = { @@ -178,6 +181,10 @@ class Report: d['reddit_error'] = self.reddit_error if self.x_error: d['x_error'] = self.x_error + if self.from_cache: + d['from_cache'] = self.from_cache + if self.cache_age_hours is not None: + d['cache_age_hours'] = self.cache_age_hours return d @classmethod @@ -248,6 +255,8 @@ class Report: context_snippet_md=data.get('context_snippet_md', ''), reddit_error=data.get('reddit_error'), x_error=data.get('x_error'), + from_cache=data.get('from_cache', False), + cache_age_hours=data.get('cache_age_hours'), ) diff --git a/scripts/lib/ui.py b/scripts/lib/ui.py index a08f6ca..f2d93c3 100644 --- a/scripts/lib/ui.py +++ b/scripts/lib/ui.py @@ -177,8 +177,12 @@ class ProgressDisplay: sys.stderr.write(f"{Colors.CYAN}X:{Colors.RESET} {x_count} posts\n\n") sys.stderr.flush() - def show_cached(self): - sys.stderr.write(f"{Colors.GREEN}⚡{Colors.RESET} {Colors.DIM}Using cached results{Colors.RESET}\n\n") + def show_cached(self, age_hours: float = None): + if age_hours is not None: + age_str = f" ({age_hours:.1f}h old)" + else: + age_str = "" + sys.stderr.write(f"{Colors.GREEN}⚡{Colors.RESET} {Colors.DIM}Using cached results{age_str} - use --refresh for fresh data{Colors.RESET}\n\n") sys.stderr.flush() def show_error(self, message: str):