/// An AI generated digest of the day's news, grouped into topics. class DailyBriefing { const DailyBriefing({ required this.createdAt, required this.intro, required this.topics, required this.generatedBy, }); final DateTime createdAt; final String intro; final List topics; /// Human readable name of the summarizer, e.g. "Claude (claude-opus-5-5)". final String generatedBy; Map toJson() => { 'createdAt': createdAt.toIso8601String(), 'intro': intro, 'generatedBy': generatedBy, 'topics': topics.map((t) => t.toJson()).toList(), }; factory DailyBriefing.fromJson(Map json) => DailyBriefing( createdAt: DateTime.parse(json['createdAt'] as String), intro: json['intro'] as String? ?? '', generatedBy: json['generatedBy'] as String? ?? '', topics: (json['topics'] as List? ?? []) .map((t) => BriefingTopic.fromJson(t as Map)) .toList(), ); } class BriefingTopic { const BriefingTopic({ required this.title, required this.summary, required this.sources, }); final String title; final String summary; final List sources; Map toJson() => { 'title': title, 'summary': summary, 'sources': sources.map((s) => s.toJson()).toList(), }; factory BriefingTopic.fromJson(Map json) => BriefingTopic( title: json['title'] as String? ?? '', summary: json['summary'] as String? ?? '', sources: (json['sources'] as List? ?? []) .map((s) => ArticleRef.fromJson(s as Map)) .toList(), ); } /// A lightweight pointer to an article, stored with the briefing so links /// still work after the feed has been refreshed. class ArticleRef { const ArticleRef({ required this.title, required this.url, required this.sourceName, }); final String title; final String url; final String sourceName; Map toJson() => {'title': title, 'url': url, 'sourceName': sourceName}; factory ArticleRef.fromJson(Map json) => ArticleRef( title: json['title'] as String? ?? '', url: json['url'] as String? ?? '', sourceName: json['sourceName'] as String? ?? '', ); }