feat: richer B2B assessment — legal page scraping, full contacts, summary

beauty_ai.py:
- Add _scrape_legal_pages(): fetches /aviso-legal, /politica-de-privacidad,
  /privacidad, /quienes-somos, /legal in parallel — Spanish aviso legal pages
  legally contain razón social, CIF/NIF, address and a contact email; legal
  snippet passed to AI so it can identify the registered company name
- Rewrite _build_beauty_prompt(): full technical profile (SSL, analytics, CMS,
  load time, word count, GDPR, mobile), all contact channels merged from both
  site_analyzer and legal pages, updated assessment rules with clearer HOT/WARM
  criteria, 700-char search results, richer portfolio portfolio context
- New JSON schema fields: summary (executive description), pitch_angle (one
  Spanish hook sentence), all_contacts dict (emails/phones/whatsapp/social
  full lists), best_contact_channel, best_contact_value, partnership_signals,
  revenue_estimate; outreach_email is now a complete ready-to-send email
- max_output_tokens raised from 2000 → 4000
- Contact merge: all_contacts populated from both site_analyzer and legal pages;
  top-level contact_* fields filled from merged data as fallback
- Run DDG search and legal page scraping in parallel (no extra wall-clock cost)

index.html (Pipeline):
- Business Summary panel with pitch_angle as accent subtitle
- Full all_contacts display: all emails (mailto links), all phones, all
  WhatsApp (green links), all social profiles (shortened display)
- partnership_signals chips alongside brand detection
- outreach_notes shown in amber at bottom of contact panel
- best_contact_channel chip in contact header
- Table contact column now shows best_contact_value if available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 08:33:14 +02:00
parent e7e39bed1f
commit e426922544
2 changed files with 336 additions and 106 deletions

View File

@@ -340,7 +340,7 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
<span x-show="!((row._beauty||{}).dist_matches||[]).length" style="color:var(--muted)"></span>
</td>
<td style="font-size:11px;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
x-text="(row._beauty||{}).contact_email||row.emails||'—'"></td>
x-text="(row._beauty||{}).best_contact_value||(row._beauty||{}).contact_email||row.emails||'—'"></td>
<td @click.stop style="white-space:nowrap;display:flex;gap:4px">
<button class="btn-secondary btn-sm" @click="copyOutreach(row)">Copy email</button>
<button class="btn-secondary btn-sm" @click="reassessOne(row.domain)" title="Re-run B2B assessment"></button>
@@ -350,14 +350,18 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
<tr class="detail-row" x-show="expandedLead===row.domain" @click="expandedLead=null" style="cursor:pointer">
<td colspan="8">
<div class="detail-grid" @click.stop>
<div class="detail-box">
<h4>B2B Proposal</h4>
<p x-text="(row._beauty||{}).b2b_proposal||'—'"></p>
</div>
<div class="detail-box">
<h4>Lead Reasoning</h4>
<p x-text="(row._beauty||{}).lead_reasoning||'—'"></p>
<!-- Summary + pitch -->
<div class="detail-box" style="grid-column:1/-1;background:rgba(232,121,160,.06);border-color:rgba(232,121,160,.2)">
<h4 style="display:flex;align-items:center;gap:8px">
Business Summary
<span x-show="(row._beauty||{}).pitch_angle" style="color:var(--accent);font-size:11px;font-weight:400;font-style:italic" x-text="'→ '+((row._beauty||{}).pitch_angle||'')"></span>
</h4>
<p x-text="(row._beauty||{}).summary||(row._beauty||{}).b2b_proposal||'—'"></p>
<p x-show="(row._beauty||{}).lead_reasoning" style="margin-top:6px;font-size:11px;color:var(--muted)" x-text="(row._beauty||{}).lead_reasoning"></p>
</div>
<!-- Outreach email -->
<div class="detail-box" style="grid-column:1/-1">
<h4 style="display:flex;align-items:center;gap:8px">
Outreach Email
@@ -366,35 +370,81 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
</h4>
<p style="white-space:pre-wrap;font-size:11px;color:var(--text);margin-top:6px;line-height:1.6" x-text="(row._beauty||{}).outreach_email||'—'"></p>
</div>
<!-- Brands detected -->
<div class="detail-box">
<h4>Brands Detected on Site</h4>
<p style="font-size:11px">
<template x-for="b in ((row._beauty||{}).detected_brands||[]).slice(0,30)" :key="b">
<template x-for="b in ((row._beauty||{}).detected_brands||[]).slice(0,40)" :key="b">
<span class="chip" x-text="b"></span>
</template>
<span x-show="!((row._beauty||{}).detected_brands||[]).length" style="color:var(--muted)">None detected in scraped text</span>
<span x-show="!((row._beauty||{}).detected_brands||[]).length" style="color:var(--muted)">None detected</span>
</p>
<template x-if="((row._beauty||{}).partnership_signals||[]).length>0">
<p style="margin-top:8px;font-size:11px">
<span style="color:var(--muted)">Signals: </span>
<template x-for="s in ((row._beauty||{}).partnership_signals||[])" :key="s">
<span class="chip chip-match" x-text="s"></span>
</template>
</p>
</template>
</div>
<!-- Full contact details -->
<div class="detail-box">
<h4>Contact Details</h4>
<p style="font-size:12px;line-height:1.8">
<template x-if="(row._beauty||{}).contact_email">
<span>Email: <a :href="'mailto:'+(row._beauty||{}).contact_email" x-text="(row._beauty||{}).contact_email"></a><br></span>
<h4 style="display:flex;align-items:center;gap:8px">
Contact Details
<template x-if="(row._beauty||{}).best_contact_channel">
<span class="chip chip-match" x-text="'best: '+((row._beauty||{}).best_contact_channel||'')"></span>
</template>
<template x-if="(row._beauty||{}).contact_phone">
<span>Phone: <span x-text="(row._beauty||{}).contact_phone"></span><br></span>
</h4>
<div style="font-size:12px;line-height:1.9">
<!-- All emails -->
<template x-if="((row._beauty||{}).all_contacts||{}).emails?.length">
<div>
<span style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em">Emails</span><br>
<template x-for="em in ((row._beauty||{}).all_contacts||{}).emails||[]" :key="em">
<span><a :href="'mailto:'+em" x-text="em" style="display:inline-block;margin-right:8px"></a></span>
</template>
</div>
</template>
<template x-if="(row._beauty||{}).contact_whatsapp">
<span>WhatsApp: <a :href="(row._beauty||{}).contact_whatsapp" target="_blank" x-text="(row._beauty||{}).contact_whatsapp"></a><br></span>
<!-- All phones -->
<template x-if="((row._beauty||{}).all_contacts||{}).phones?.length">
<div style="margin-top:4px">
<span style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em">Phones</span><br>
<template x-for="ph in ((row._beauty||{}).all_contacts||{}).phones||[]" :key="ph">
<span x-text="ph" style="display:inline-block;margin-right:8px"></span>
</template>
</div>
</template>
<template x-if="(row._beauty||{}).contact_social">
<span style="color:var(--muted)">Social: <span x-text="(row._beauty||{}).contact_social"></span><br></span>
<!-- WhatsApp -->
<template x-if="((row._beauty||{}).all_contacts||{}).whatsapp?.length">
<div style="margin-top:4px">
<span style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em">WhatsApp</span><br>
<template x-for="wa in ((row._beauty||{}).all_contacts||{}).whatsapp||[]" :key="wa">
<a :href="wa" target="_blank" x-text="wa" style="display:inline-block;margin-right:8px;color:var(--success)"></a>
</template>
</div>
</template>
<template x-if="row.emails && !(row._beauty||{}).contact_email">
<span style="color:var(--muted);font-size:11px">On-site: <span x-text="row.emails"></span></span>
<!-- Social -->
<template x-if="((row._beauty||{}).all_contacts||{}).social?.length">
<div style="margin-top:4px">
<span style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em">Social</span><br>
<template x-for="soc in ((row._beauty||{}).all_contacts||{}).social||[]" :key="soc">
<a :href="soc" target="_blank" x-text="soc.replace('https://','').replace('www.','').split('/').slice(0,2).join('/')" style="display:inline-block;margin-right:8px;color:var(--info)"></a>
</template>
</div>
</template>
</p>
<!-- Fallback if all_contacts not set (older assessments) -->
<template x-if="!(row._beauty||{}).all_contacts && (row._beauty||{}).contact_email">
<div>Email: <a :href="'mailto:'+(row._beauty||{}).contact_email" x-text="(row._beauty||{}).contact_email"></a></div>
</template>
</div>
<template x-if="(row._beauty||{}).outreach_notes">
<p style="margin-top:8px;font-size:11px;color:var(--warn);border-top:1px solid var(--border);padding-top:6px" x-text="(row._beauty||{}).outreach_notes"></p>
</template>
</div>
</div>
</td>
</tr>