Files
email_alerts_v2/example_workflow.py
T
bolade 75a0a3fde7 feat: Implement async AI analysis for email threads
- Added `get_latest_email_date()` function in `database.py` to retrieve the most recent email date for a given account and folder.
- Enhanced `fetch_folder_emails()` in `zoho_client.py` to intelligently determine the start date for fetching emails based on the latest email date in the database.
- Introduced `analyze_and_update_threads_async()` for asynchronous analysis of email threads, allowing concurrent processing.
- Created a synchronous wrapper `analyze_and_update_threads()` for easier integration.
- Updated `fetch_emails()` to support database session and account email parameters.
- Added comprehensive documentation in `AI_ANALYSIS_GUIDE.md` detailing the new AI analysis functionality.
- Implemented tests for the new features, including `test_fetch_with_db.py`, `test_ai_analysis.py`, and `test_single_analysis.py`.
- Added error handling and logging improvements throughout the codebase.
2025-08-11 23:20:20 +01:00

205 lines
6.8 KiB
Python

#!/usr/bin/env python3
"""
Complete workflow example: Ingest emails and then analyze them with AI.
This demonstrates the full pipeline from email ingestion to AI analysis.
"""
import os
import sys
from datetime import datetime, timedelta
# Add the src directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from database import (
Message,
SessionLocal,
Thread,
analyze_and_update_threads,
create_db_tables,
get_threads_needing_analysis,
ingest_emails,
)
def create_sample_emails(account_email: str) -> list:
"""Create some sample email data for testing."""
now = datetime.now() # Using datetime.now() instead of utcnow()
sample_emails = [
{
"messageId": "msg001",
"subject": "Meeting Request - Project Review",
"from": "colleague@company.com",
"to": account_email,
"date": now - timedelta(days=2),
"body": "Hi, could we schedule a meeting to review the project progress? I'm available this week on Tuesday or Wednesday afternoon. Please let me know what works for you.",
"folder": "INBOX",
},
{
"messageId": "msg002",
"subject": "Re: Meeting Request - Project Review",
"from": account_email,
"to": "colleague@company.com",
"date": now - timedelta(days=1, hours=8),
"body": "Sure! Wednesday afternoon works for me. How about 2 PM in the conference room?",
"folder": "Sent",
"inReplyTo": "msg001",
},
{
"messageId": "msg003",
"subject": "Re: Meeting Request - Project Review",
"from": "colleague@company.com",
"to": account_email,
"date": now - timedelta(days=1, hours=6),
"body": "Perfect! See you Wednesday at 2 PM. Should I prepare anything specific for the meeting?",
"folder": "INBOX",
"inReplyTo": "msg002",
},
{
"messageId": "msg004",
"subject": "Weekly Newsletter - Company Updates",
"from": "no-reply@company.com",
"to": account_email,
"date": now - timedelta(hours=12),
"body": "Welcome to this week's company newsletter! Here are the latest updates: New office opening, Q3 results, upcoming events...",
"folder": "INBOX",
},
{
"messageId": "msg005",
"subject": "Urgent: Server Issue in Production",
"from": "ops-team@company.com",
"to": account_email,
"date": now - timedelta(hours=2),
"body": "We're experiencing a critical server issue in production. The application is currently down. Can you please help investigate? Login credentials are attached.",
"folder": "INBOX",
},
]
return sample_emails
def main():
"""Main function demonstrating the complete workflow."""
# Create database tables if they don't exist
create_db_tables()
# Example account email
account_email = "test-user@company.com"
print(f"Starting workflow for account: {account_email}")
print("=" * 50)
# Get a database session
db = SessionLocal()
try:
# Step 1: Ingest sample emails
print("Step 1: Ingesting sample emails...")
sample_emails = create_sample_emails(account_email)
ingest_emails(db, account_email, sample_emails)
print(f"✓ Ingested {len(sample_emails)} emails")
# Show what was ingested
threads = (
db.query(Thread).filter(Thread.account_email == account_email.lower()).all()
)
print(f"✓ Created {len(threads)} threads")
for thread in threads:
messages = db.query(Message).filter(Message.thread_id == thread.id).count()
print(f" - Thread {thread.id}: '{thread.subject}' ({messages} messages)")
print()
# Step 2: Check threads needing analysis
print("Step 2: Checking threads needing analysis...")
threads_needing_analysis = get_threads_needing_analysis(db, account_email)
print(f"✓ Found {len(threads_needing_analysis)} threads needing analysis")
if not threads_needing_analysis:
print("No threads need analysis.")
return
print()
# Step 3: Run AI analysis
print("Step 3: Running AI analysis...")
print(
"This will analyze threads to determine if they're actionable and generate summaries."
)
analyze_and_update_threads(
account_email=account_email, max_concurrent=3, only_unanalyzed=True
)
print("✓ AI analysis complete!")
print()
# Step 4: Show results
print("Step 4: Analysis Results")
print("-" * 30)
# Show results
analyzed_threads = (
db.query(Thread)
.filter(
Thread.account_email == account_email.lower(),
Thread.last_analyzed_at.isnot(None),
)
.all()
)
# Refresh the threads to get the latest data
for thread in analyzed_threads:
db.refresh(thread)
actionable_count = sum(1 for t in analyzed_threads if t.actionable)
print(f"Total analyzed threads: {len(analyzed_threads)}")
print(f"Actionable threads: {actionable_count}")
print(f"Non-actionable threads: {len(analyzed_threads) - actionable_count}")
print()
for thread in analyzed_threads:
confidence = thread.ai_confidence or 0.0
print(f"Thread {thread.id}: {thread.subject}")
print(f" 📧 Actionable: {'YES' if thread.actionable else 'No'}")
print(f" 🎯 Confidence: {confidence:.2f}")
print(f" 📝 Summary: {thread.ai_summary or 'No summary available'}")
print(f" 🕐 Analyzed: {thread.last_analyzed_at}")
print()
# Step 5: Show threads requiring replies
print("Step 5: Threads Requiring Reply")
print("-" * 30)
from database import get_threads_requiring_reply
reply_threads = get_threads_requiring_reply(db, account_email)
print(f"Threads requiring reply: {len(reply_threads)}")
for thread in reply_threads:
actionable_note = (
" (AI: Actionable)" if thread.actionable else " (AI: Not actionable)"
)
print(f" - {thread.subject}{actionable_note}")
if reply_threads:
print(
"\n💡 Tip: Focus on threads marked as both 'requiring reply' and 'AI: Actionable'"
)
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
finally:
db.close()
if __name__ == "__main__":
main()