From e141ff07ea9a53d9a3f16cfabd76dc14400bb043 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Fri, 23 Jan 2026 15:46:09 -0800 Subject: [PATCH] Fix cache loading with Report.from_dict() method The Report.to_dict() serializes range as {from, to} but constructor expects range_from/range_to. Added from_dict() classmethod to properly deserialize cached data, reconstructing all nested objects (Engagement, Comment, SubScores, RedditItem, XItem). Co-Authored-By: Claude Opus 4.5 --- scripts/last30days.py | 2 +- scripts/lib/schema.py | 70 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/scripts/last30days.py b/scripts/last30days.py index 59f2f96..2ddea30 100644 --- a/scripts/last30days.py +++ b/scripts/last30days.py @@ -211,7 +211,7 @@ def main(): cached = cache.load_cache(cache_key) if cached: # Use cached data - report = schema.Report(**cached) + report = schema.Report.from_dict(cached) output_result(report, args.emit) return diff --git a/scripts/lib/schema.py b/scripts/lib/schema.py index a09f2a5..2adfc4d 100644 --- a/scripts/lib/schema.py +++ b/scripts/lib/schema.py @@ -180,6 +180,76 @@ class Report: d['x_error'] = self.x_error return d + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "Report": + """Create Report from serialized dict (handles cache format).""" + # Handle range field conversion + range_data = data.get('range', {}) + range_from = range_data.get('from', data.get('range_from', '')) + range_to = range_data.get('to', data.get('range_to', '')) + + # Reconstruct Reddit items + reddit_items = [] + for r in data.get('reddit', []): + eng = None + if r.get('engagement'): + eng = Engagement(**r['engagement']) + comments = [Comment(**c) for c in r.get('top_comments', [])] + subs = SubScores(**r.get('subs', {})) if r.get('subs') else SubScores() + reddit_items.append(RedditItem( + id=r['id'], + title=r['title'], + url=r['url'], + subreddit=r['subreddit'], + date=r.get('date'), + date_confidence=r.get('date_confidence', 'low'), + engagement=eng, + top_comments=comments, + comment_insights=r.get('comment_insights', []), + relevance=r.get('relevance', 0.5), + why_relevant=r.get('why_relevant', ''), + subs=subs, + score=r.get('score', 0), + )) + + # Reconstruct X items + x_items = [] + for x in data.get('x', []): + eng = None + if x.get('engagement'): + eng = Engagement(**x['engagement']) + subs = SubScores(**x.get('subs', {})) if x.get('subs') else SubScores() + x_items.append(XItem( + id=x['id'], + text=x['text'], + url=x['url'], + author_handle=x['author_handle'], + date=x.get('date'), + date_confidence=x.get('date_confidence', 'low'), + engagement=eng, + relevance=x.get('relevance', 0.5), + why_relevant=x.get('why_relevant', ''), + subs=subs, + score=x.get('score', 0), + )) + + return cls( + topic=data['topic'], + range_from=range_from, + range_to=range_to, + generated_at=data['generated_at'], + mode=data['mode'], + openai_model_used=data.get('openai_model_used'), + xai_model_used=data.get('xai_model_used'), + reddit=reddit_items, + x=x_items, + best_practices=data.get('best_practices', []), + prompt_pack=data.get('prompt_pack', []), + context_snippet_md=data.get('context_snippet_md', ''), + reddit_error=data.get('reddit_error'), + x_error=data.get('x_error'), + ) + def create_report( topic: str,