system-prompts-and-models-o.../dealix/auto_client_acquisition/agents/proposal.py
2026-05-01 14:03:52 +03:00

146 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Proposal Agent — generates tailored proposals using Claude.
وكيل العروض — يُعدّ عروضاً مخصصة باستخدام Claude.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any
from auto_client_acquisition.agents.icp_matcher import FitScore
from auto_client_acquisition.agents.intake import Lead
from core.agents.base import BaseAgent
from core.config.models import Task
from core.config.settings import get_settings
from core.llm.base import Message
from core.prompts import get_prompt
from core.utils import generate_id, utcnow
@dataclass
class Proposal:
id: str
lead_id: str
company_name: str
sector: str | None
locale: str
body_markdown: str
budget_min: float
budget_max: float
currency: str
valid_until: datetime
created_at: datetime = field(default_factory=utcnow)
def to_dict(self) -> dict[str, Any]:
return {
"id": self.id,
"lead_id": self.lead_id,
"company_name": self.company_name,
"sector": self.sector,
"locale": self.locale,
"body_markdown": self.body_markdown,
"budget_min": self.budget_min,
"budget_max": self.budget_max,
"currency": self.currency,
"valid_until": self.valid_until.isoformat(),
"created_at": self.created_at.isoformat(),
}
class ProposalAgent(BaseAgent):
"""Generates an LLM-authored proposal tailored to the lead."""
name = "proposal"
def __init__(self) -> None:
super().__init__()
self.settings = get_settings()
async def run(
self,
*,
lead: Lead,
fit_score: FitScore | None = None,
outcomes: list[str] | None = None,
start_date: datetime | None = None,
**_: Any,
) -> Proposal:
"""Generate a proposal tailored to the lead context."""
outcomes = outcomes or [
"Reduce manual work by 50%+",
"Increase qualified pipeline by 23x",
"Cut response time from hours to minutes",
]
start_date = start_date or (utcnow() + timedelta(days=14))
# Determine pricing tier based on region
budget_min, budget_max, currency = self._pricing_for_region(lead.region)
prompt = get_prompt(
"proposal_generation",
locale=lead.locale,
company_name=lead.company_name or "Your Company",
sector=lead.sector or "General",
pain_points="; ".join(lead.pain_points) or lead.message or "To be confirmed",
outcomes="; ".join(outcomes),
budget_min=f"{budget_min:,.0f}",
budget_max=f"{budget_max:,.0f}",
start_date=start_date.strftime("%Y-%m-%d"),
)
response = await self.router.run(
task=Task.PROPOSAL,
messages=[Message(role="user", content=prompt)],
max_tokens=3000,
temperature=0.5,
)
proposal = Proposal(
id=generate_id("prop"),
lead_id=lead.id,
company_name=lead.company_name,
sector=lead.sector,
locale=lead.locale,
body_markdown=response.content,
budget_min=budget_min,
budget_max=budget_max,
currency=currency,
valid_until=utcnow() + timedelta(days=30),
)
self.log.info(
"proposal_generated",
lead_id=lead.id,
proposal_id=proposal.id,
locale=lead.locale,
budget_range=f"{budget_min:,.0f}-{budget_max:,.0f} {currency}",
)
return proposal
# ── Pricing logic ───────────────────────────────────────────
def _pricing_for_region(self, region: str | None) -> tuple[float, float, str]:
"""Return (setup_min, setup_max, currency) for the lead's region."""
s = self.settings
if not region:
return float(s.pricing_sa_setup_min), float(s.pricing_sa_setup_max), "SAR"
region_lower = region.lower()
gcc_tokens = {"uae", "kuwait", "bahrain", "qatar", "oman", "الإمارات", "الكويت"}
if any(t in region_lower for t in gcc_tokens):
return (
float(s.pricing_gcc_setup_min),
float(s.pricing_gcc_setup_max),
"SAR-equivalent",
)
sa_tokens = {"saudi", "ksa", "sa", "riyadh", "jeddah", "السعودية"}
if any(t in region_lower for t in sa_tokens):
return (float(s.pricing_sa_setup_min), float(s.pricing_sa_setup_max), "SAR")
# Global
return (
float(s.pricing_global_setup_min_usd),
float(s.pricing_global_setup_max_usd),
"USD",
)