Compare commits

...

2 Commits

+61 -14
View File
@@ -534,6 +534,19 @@ FB_SEARCHES = [
"who can design a website for my", "who can design a website for my",
"looking for someone to create a website", "looking for someone to create a website",
"recommendations for a web designer", "recommendations for a web designer",
"I need a website",
"I need a website built",
"need wordpress site",
"need a custom website",
"I need a new website",
"need an online store",
"need an online shop",
"need website help",
"who can help me with my website",
"looking for a website designer",
"need a shopify website",
"looking for website design",
"want to build a website for my",
] ]
VIEWPORTS = [ VIEWPORTS = [
@@ -1361,6 +1374,8 @@ async def ask_ollama(prompt: str) -> str:
async def classify_leads(results: list[dict]) -> list[dict]: async def classify_leads(results: list[dict]) -> list[dict]:
if not results: if not results:
return [] return []
# ── 1. AI classification ─────────────────────────────────────────
briefs = [r["title"][:200] for r in results] briefs = [r["title"][:200] for r in results]
prompt = f"""Classify each post as LEAD or NOT. prompt = f"""Classify each post as LEAD or NOT.
LEAD = someone REQUESTING/POSTING/WANTING a website built, designed, or created for them. LEAD = someone REQUESTING/POSTING/WANTING a website built, designed, or created for them.
@@ -1376,7 +1391,7 @@ NOT LEAD:
For each numbered post, answer ONLY "yes" (LEAD) or "no" (NOT LEAD): For each numbered post, answer ONLY "yes" (LEAD) or "no" (NOT LEAD):
{chr(10).join(f'{i+1}. {t}' for i, t in enumerate(briefs))} {chr(10).join(f'{i+1}. {t}' for i, t in enumerate(briefs))}
Return a JSON array like ["yes","no","yes"] matching the order above.""" Return a JSON array like ["yes","no","yes"] matching the order above."""
ai_succeeded = False ai_leads: list[dict] = []
try: try:
raw = await ask_ollama(prompt) raw = await ask_ollama(prompt)
raw = raw.strip() raw = raw.strip()
@@ -1389,19 +1404,14 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
raw = raw.strip() raw = raw.strip()
answers = json.loads(raw) answers = json.loads(raw)
if isinstance(answers, list) and len(answers) == len(results): if isinstance(answers, list) and len(answers) == len(results):
ai_succeeded = True
filtered = []
for i, ans in enumerate(answers): for i, ans in enumerate(answers):
if isinstance(ans, str) and ans.lower() == 'yes': if isinstance(ans, str) and ans.lower() == 'yes':
filtered.append(results[i]) ai_leads.append(results[i])
if filtered: logger.info("AI classified %d/%d as LEAD", len(ai_leads), len(results))
return filtered[:10]
logger.info("AI classified all %d items as NOT LEAD — returning empty", len(results))
return []
except Exception as e: except Exception as e:
logger.warning("AI classification failed, falling back to keyword filter: %s", e) logger.warning("AI classification failed: %s", e)
if not ai_succeeded: # ── 2. Keyword fallback (always runs) ────────────────────────────
web_terms = [ web_terms = [
"website", "web design", "web develop", "web dev", "website", "web design", "web develop", "web dev",
"web designer", "web developer", "web designer", "web developer",
@@ -1411,6 +1421,15 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
"site for my", "site for my business", "site for my", "site for my business",
"new website", "redesign my website", "new website", "redesign my website",
"help with my website", "update my website", "help with my website", "update my website",
"make a website", "make my website",
"website for my",
"online store", "online shop",
"build my site", "build a site",
"set up a website", "set up my website",
"custom website",
"shopify",
"my site",
"webpage", "web page",
] ]
request_terms = [ request_terms = [
"looking for", "need a", "need an", "looking to", "looking for", "need a", "need an", "looking to",
@@ -1423,8 +1442,13 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
"know a", "know any", "recommendation", "know a", "know any", "recommendation",
"suggest", "looking for recommendations", "suggest", "looking for recommendations",
"can anyone", "does anyone", "can anyone", "does anyone",
"dm me", "pm me", "message me",
"quote for",
"can you help",
"how much",
"price for",
] ]
filtered = [] keyword_leads: list[dict] = []
for r in results: for r in results:
t = r['title'].lower() t = r['title'].lower()
has_web = any(kw in t for kw in web_terms) has_web = any(kw in t for kw in web_terms)
@@ -1435,9 +1459,32 @@ Return a JSON array like ["yes","no","yes"] matching the order above."""
'affordable web design package', 'web hosting', 'affordable web design package', 'web hosting',
'i do web design', 'i develop websites']): 'i do web design', 'i develop websites']):
continue continue
filtered.append(r) keyword_leads.append(r)
return filtered[:10]
return [] # ── 3. Merge: prefer AI leads, supplement with keywords to reach 5 ──
seen_titles: set[int] = set()
merged: list[dict] = []
for r in ai_leads + keyword_leads:
key = hash(r.get('title', ''))
if key not in seen_titles:
seen_titles.add(key)
merged.append(r)
# Fill to 5 with loose keyword matches (at least web OR request term)
if len(merged) < 5:
for r in results:
key = hash(r.get('title', ''))
if key in seen_titles:
continue
t = r['title'].lower()
if any(kw in t for kw in web_terms) or any(kw in t for kw in request_terms):
seen_titles.add(key)
merged.append(r)
if len(merged) >= 5:
break
logger.info("classify_leads: %d merged (%d AI + %d keyword) from %d raw", len(merged), len(ai_leads), len(keyword_leads), len(results))
return merged[:10]
# ══════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════
# FastAPI Endpoints # FastAPI Endpoints