first commit
This commit is contained in:
153
src/main.py
Normal file
153
src/main.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Main orchestrator for News Agent"""
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
from .config import get_config
|
||||
from .logger import setup_logger, get_logger
|
||||
from .storage.database import Database
|
||||
from .storage.models import DigestEntry
|
||||
from .aggregator.rss_fetcher import RSSFetcher
|
||||
from .ai.client import OpenRouterClient
|
||||
from .ai.filter import ArticleFilter
|
||||
from .ai.summarizer import ArticleSummarizer
|
||||
from .email.generator import EmailGenerator
|
||||
from .email.sender import EmailSender
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main execution flow"""
|
||||
# Setup logging
|
||||
setup_logger()
|
||||
logger = get_logger()
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("News Agent starting...")
|
||||
logger.info("=" * 60)
|
||||
|
||||
try:
|
||||
# Load configuration
|
||||
config = get_config()
|
||||
|
||||
# Initialize database
|
||||
db = Database(config.database.path)
|
||||
await db.initialize()
|
||||
|
||||
# Clean up old articles
|
||||
await db.cleanup_old_articles(config.database.retention_days)
|
||||
|
||||
# Initialize RSS fetcher
|
||||
fetcher = RSSFetcher()
|
||||
|
||||
# Fetch articles from all sources
|
||||
logger.info(f"Fetching from {len(config.rss_sources)} RSS sources...")
|
||||
articles = await fetcher.fetch_all(config.rss_sources)
|
||||
|
||||
if not articles:
|
||||
logger.warning("No articles fetched from any source")
|
||||
await fetcher.close()
|
||||
return
|
||||
|
||||
# Save articles to database (deduplication)
|
||||
new_articles_count = await db.save_articles(articles)
|
||||
logger.info(
|
||||
f"Saved {new_articles_count} new articles (filtered {len(articles) - new_articles_count} duplicates)"
|
||||
)
|
||||
|
||||
await fetcher.close()
|
||||
|
||||
# Get unprocessed articles
|
||||
unprocessed = await db.get_unprocessed_articles()
|
||||
|
||||
if not unprocessed:
|
||||
logger.info("No new articles to process")
|
||||
return
|
||||
|
||||
logger.info(f"Processing {len(unprocessed)} new articles with AI...")
|
||||
|
||||
# Initialize AI components
|
||||
ai_client = OpenRouterClient()
|
||||
filter_ai = ArticleFilter(ai_client)
|
||||
summarizer = ArticleSummarizer(ai_client)
|
||||
|
||||
# Filter articles by relevance
|
||||
logger.info("Filtering articles by relevance...")
|
||||
filtered_articles = await filter_ai.filter_articles(
|
||||
unprocessed, max_articles=config.ai.filtering.max_articles
|
||||
)
|
||||
|
||||
if not filtered_articles:
|
||||
logger.warning("No relevant articles found after filtering")
|
||||
# Mark all as processed but not included
|
||||
for article in unprocessed:
|
||||
await db.update_article_processing(
|
||||
article.id, relevance_score=0.0, ai_summary="", included=False
|
||||
)
|
||||
return
|
||||
|
||||
logger.info(f"Selected {len(filtered_articles)} relevant articles")
|
||||
|
||||
# Summarize filtered articles
|
||||
logger.info("Generating AI summaries...")
|
||||
digest_entries = []
|
||||
|
||||
for article, score in filtered_articles:
|
||||
summary = await summarizer.summarize(article)
|
||||
|
||||
# Update database
|
||||
await db.update_article_processing(
|
||||
article.id, relevance_score=score, ai_summary=summary, included=True
|
||||
)
|
||||
|
||||
# Create digest entry
|
||||
entry = DigestEntry(
|
||||
article=article,
|
||||
relevance_score=score,
|
||||
ai_summary=summary,
|
||||
category=article.category,
|
||||
)
|
||||
digest_entries.append(entry)
|
||||
|
||||
# Mark remaining unprocessed articles as processed but not included
|
||||
processed_ids = {article.id for article, _ in filtered_articles}
|
||||
for article in unprocessed:
|
||||
if article.id not in processed_ids:
|
||||
await db.update_article_processing(
|
||||
article.id, relevance_score=0.0, ai_summary="", included=False
|
||||
)
|
||||
|
||||
# Generate email
|
||||
logger.info("Generating email digest...")
|
||||
generator = EmailGenerator()
|
||||
|
||||
date_str = datetime.now().strftime("%A, %B %d, %Y")
|
||||
subject = config.email.subject_template.format(date=date_str)
|
||||
|
||||
html_content, text_content = generator.generate_digest_email(
|
||||
digest_entries, date_str, subject
|
||||
)
|
||||
|
||||
# Send email
|
||||
logger.info("Sending email...")
|
||||
sender = EmailSender()
|
||||
success = sender.send(subject, html_content, text_content)
|
||||
|
||||
if success:
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Daily digest sent successfully with {len(digest_entries)} articles!")
|
||||
logger.info("=" * 60)
|
||||
else:
|
||||
logger.error("Failed to send email")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error in main execution: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def run():
|
||||
"""Entry point for command-line execution"""
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Reference in New Issue
Block a user