mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 07:19:35 +00:00
163 lines
5.1 KiB
Python
163 lines
5.1 KiB
Python
"""Follow-up Scheduler — generates follow-up drafts for unreplied outreach.
|
|
|
|
Checks sent drafts that haven't received replies after 2/5/10 days
|
|
and creates new follow-up drafts linked to the original.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Any, Dict, List
|
|
from uuid import uuid4
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select, and_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
logger = logging.getLogger("dealix.followups")
|
|
|
|
router = APIRouter(prefix="/followups", tags=["Follow-ups"])
|
|
|
|
|
|
async def _get_db():
|
|
from app.database import get_db
|
|
async for session in get_db():
|
|
yield session
|
|
|
|
|
|
@router.get("/due")
|
|
async def list_due_followups(
|
|
days_since_sent: int = Query(2, ge=1, le=30),
|
|
limit: int = Query(50, ge=1, le=200),
|
|
db: AsyncSession = Depends(_get_db),
|
|
) -> Dict[str, Any]:
|
|
from app.models.outreach_draft import OutreachDraft
|
|
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=days_since_sent)
|
|
stmt = (
|
|
select(OutreachDraft)
|
|
.where(
|
|
and_(
|
|
OutreachDraft.status == "sent",
|
|
OutreachDraft.sent_at <= cutoff,
|
|
OutreachDraft.reply_text.is_(None),
|
|
)
|
|
)
|
|
.order_by(OutreachDraft.sent_at.asc())
|
|
.limit(limit)
|
|
)
|
|
result = await db.execute(stmt)
|
|
rows = list(result.scalars().all())
|
|
|
|
due = []
|
|
for row in rows:
|
|
days_elapsed = (datetime.now(timezone.utc) - row.sent_at).days if row.sent_at else 0
|
|
followup_text = ""
|
|
followup_type = ""
|
|
if days_elapsed >= 10:
|
|
followup_text = row.followup_5d or "آخر متابعة — لو مو الوقت المناسب أفهم تماماً. شكراً."
|
|
followup_type = "day_10_breakup"
|
|
elif days_elapsed >= 5:
|
|
followup_text = row.followup_5d or row.followup_2d or ""
|
|
followup_type = "day_5_value"
|
|
elif days_elapsed >= 2:
|
|
followup_text = row.followup_2d or ""
|
|
followup_type = "day_2_reminder"
|
|
|
|
if followup_text:
|
|
due.append({
|
|
"original_draft_id": str(row.id),
|
|
"company": row.company,
|
|
"channel": row.channel,
|
|
"contact_email": row.contact_email,
|
|
"contact_phone": row.contact_phone,
|
|
"days_since_sent": days_elapsed,
|
|
"followup_type": followup_type,
|
|
"followup_text": followup_text,
|
|
"sector": row.sector,
|
|
})
|
|
|
|
return {
|
|
"due_count": len(due),
|
|
"cutoff_days": days_since_sent,
|
|
"followups": due,
|
|
}
|
|
|
|
|
|
class GenerateFollowupsRequest(BaseModel):
|
|
days_since_sent: int = 2
|
|
max_followups: int = 20
|
|
|
|
|
|
@router.post("/generate")
|
|
async def generate_followup_drafts(
|
|
req: GenerateFollowupsRequest,
|
|
db: AsyncSession = Depends(_get_db),
|
|
) -> Dict[str, Any]:
|
|
from app.models.outreach_draft import OutreachDraft
|
|
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=req.days_since_sent)
|
|
stmt = (
|
|
select(OutreachDraft)
|
|
.where(
|
|
and_(
|
|
OutreachDraft.status == "sent",
|
|
OutreachDraft.sent_at <= cutoff,
|
|
OutreachDraft.reply_text.is_(None),
|
|
)
|
|
)
|
|
.order_by(OutreachDraft.sent_at.asc())
|
|
.limit(req.max_followups)
|
|
)
|
|
result = await db.execute(stmt)
|
|
originals = list(result.scalars().all())
|
|
|
|
created = 0
|
|
batch_id = f"followup_{datetime.now(timezone.utc).strftime('%Y%m%d')}_{str(uuid4())[:6]}"
|
|
|
|
for orig in originals:
|
|
days_elapsed = (datetime.now(timezone.utc) - orig.sent_at).days if orig.sent_at else 0
|
|
if days_elapsed >= 10:
|
|
body = orig.followup_5d or "آخر متابعة — لو مناسب نتكلم، أنا موجود. لو لا، شكراً على وقتكم."
|
|
subject = f"متابعة أخيرة: {orig.company}"
|
|
elif days_elapsed >= 5:
|
|
body = orig.followup_5d or orig.followup_2d or ""
|
|
subject = f"متابعة: {orig.company}"
|
|
else:
|
|
body = orig.followup_2d or ""
|
|
subject = f"متابعة سريعة: {orig.company}"
|
|
|
|
if not body:
|
|
continue
|
|
|
|
followup = OutreachDraft(
|
|
batch_id=batch_id,
|
|
company=orig.company,
|
|
contact_name=orig.contact_name,
|
|
contact_email=orig.contact_email,
|
|
contact_phone=orig.contact_phone,
|
|
channel=orig.channel,
|
|
subject=subject,
|
|
body=body,
|
|
sector=orig.sector,
|
|
city=orig.city,
|
|
fit_score=orig.fit_score,
|
|
risk_score=orig.risk_score,
|
|
status="draft",
|
|
approval_required=True,
|
|
source=f"followup_day_{days_elapsed}_of_{str(orig.id)[:8]}",
|
|
)
|
|
db.add(followup)
|
|
created += 1
|
|
|
|
if created:
|
|
await db.commit()
|
|
|
|
return {
|
|
"batch_id": batch_id,
|
|
"followups_created": created,
|
|
"originals_checked": len(originals),
|
|
}
|