Download app/services/leads.py from nvikou/ReplyFlow: direct link, hf CLI and curl.
- Browser
- Download file 4.18 kB
-
https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/leads.py
- Command line
-
hf download hf://spaces/nvikou/ReplyFlow/app/services/leads.py
-
curl -L -o leads.py https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/leads.py
4.18 kB
| """Conversational lead qualification.""" | |
| from __future__ import annotations | |
| import re | |
| from typing import Any, Optional | |
| from app.models import Lead | |
| EMAIL_RE = re.compile( | |
| r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+" | |
| ) | |
| PHONE_RE = re.compile(r"\+?\d[\d\s\-()]{7,}\d") | |
| BUDGET_HINTS = ( | |
| "budget", | |
| "€", | |
| "$", | |
| "eur", | |
| "usd", | |
| "rub", | |
| "₽", | |
| "тысяч", | |
| "euros", | |
| "dollars", | |
| ) | |
| NEED_HINTS = ( | |
| "need", | |
| "besoin", | |
| "нужн", | |
| "want", | |
| "looking", | |
| "cherche", | |
| "ищ", | |
| "order", | |
| "commande", | |
| "заказ", | |
| ) | |
| CONTACT_HINTS = ( | |
| "email", | |
| "mail", | |
| "phone", | |
| "téléphone", | |
| "телефон", | |
| "whatsapp", | |
| "call", | |
| "звон", | |
| ) | |
| def lead_to_dict(lead: Optional[Lead]) -> dict[str, Any]: | |
| """Serialize lead fields for prompts/UI.""" | |
| if lead is None: | |
| return { | |
| "name": None, | |
| "need": None, | |
| "budget": None, | |
| "contact_channel": None, | |
| "email": None, | |
| "phone": None, | |
| "complete": False, | |
| } | |
| return { | |
| "name": lead.name, | |
| "need": lead.need, | |
| "budget": lead.budget, | |
| "contact_channel": lead.contact_channel, | |
| "email": lead.email, | |
| "phone": lead.phone, | |
| "complete": lead.complete, | |
| } | |
| def update_lead_from_message( | |
| lead: Lead, | |
| message: str, | |
| previous_bot: Optional[str] = None, | |
| ) -> Lead: | |
| """Extract lead fields from the latest user message.""" | |
| text = message.strip() | |
| lowered = text.lower() | |
| bot = (previous_bot or "").lower() | |
| email = EMAIL_RE.search(text) | |
| if email and not lead.email: | |
| lead.email = email.group(0) | |
| if not lead.contact_channel: | |
| lead.contact_channel = "email" | |
| phone = PHONE_RE.search(text) | |
| if phone and not lead.phone: | |
| lead.phone = re.sub(r"\s+", "", phone.group(0)) | |
| if not lead.contact_channel: | |
| lead.contact_channel = "phone" | |
| if not lead.name and ( | |
| "name" in bot | |
| or "nom" in bot | |
| or "зовут" in bot | |
| or "зовут" in bot | |
| or "call me" in lowered | |
| or "je m'appelle" in lowered | |
| or "меня зовут" in lowered | |
| ): | |
| lead.name = _extract_name(text) | |
| if not lead.name and _looks_like_name(text): | |
| lead.name = text.strip()[:80] | |
| if not lead.budget and ( | |
| any(h in lowered for h in BUDGET_HINTS) | |
| or "budget" in bot | |
| or "бюджет" in bot | |
| ): | |
| lead.budget = text[:64] | |
| if not lead.need and ( | |
| any(h in lowered for h in NEED_HINTS) | |
| or "need" in bot | |
| or "besoin" in bot | |
| or "помощь" in bot | |
| ): | |
| lead.need = text[:500] | |
| if not lead.contact_channel and ( | |
| any(h in lowered for h in CONTACT_HINTS) | |
| or "reach" in bot | |
| or "contacter" in bot | |
| or "связаться" in bot | |
| ): | |
| if "whatsapp" in lowered: | |
| lead.contact_channel = "whatsapp" | |
| elif "mail" in lowered or "email" in lowered: | |
| lead.contact_channel = "email" | |
| elif "phone" in lowered or "tél" in lowered or "телефон" in lowered: | |
| lead.contact_channel = "phone" | |
| else: | |
| lead.contact_channel = text[:64] | |
| lead.complete = all( | |
| [ | |
| lead.name, | |
| lead.need, | |
| lead.budget, | |
| lead.contact_channel, | |
| ] | |
| ) | |
| return lead | |
| def _looks_like_name(text: str) -> bool: | |
| words = text.strip().split() | |
| if not (1 <= len(words) <= 3): | |
| return False | |
| if any(ch.isdigit() for ch in text): | |
| return False | |
| if len(text) > 40: | |
| return False | |
| return text[:1].isupper() or all( | |
| w[:1].isupper() for w in words if w | |
| ) | |
| def _extract_name(text: str) -> str: | |
| patterns = [ | |
| r"(?:je m'appelle|my name is|i am|i'm|меня зовут)\s+(.+)", | |
| r"(?:call me)\s+(.+)", | |
| ] | |
| lowered = text.strip() | |
| for pattern in patterns: | |
| match = re.search(pattern, lowered, flags=re.I) | |
| if match: | |
| return match.group(1).strip(" .,!")[:80] | |
| return text.strip()[:80] | |