75a0a3fde7
- 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.
205 lines
6.8 KiB
Python
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()
|