Skip to content
// Final record: rules alone, or 50/50 with the AI fit score when AI succeeded.
const s = $('Settings (edit me)').first().json;
const lead = $('Rule Score').first().json;
const aiItem = $('AI Profile').isExecuted ? $input.first().json : null;
const ai = aiItem && !aiItem.error && aiItem.output ? aiItem.output : null;
const aiScore = ai ? Math.max(0, Math.min(100, Math.round(Number(ai.fit_score) || 0))) : null;
const score = ai ? Math.round(0.5 * lead.rule_score + 0.5 * aiScore) : lead.rule_score;
const tier = score >= s.hot_threshold ? 'hot' : score >= s.warm_threshold ? 'warm' : 'cold';
const sg = lead.signals ?? {};

return [{
  json: {
    email: lead.email,
    name: lead.name,
    // Given name → site title → business email domain → site domain
    company: lead.company || (lead.site_ok && sg.title ? sg.title.split(/[|–-]/)[0].trim() : '') || (!lead.free_email && lead.email ? lead.email.split('@')[1] : '') || lead.domain,
    domain: lead.domain,
    website: lead.website,
    industry: ai?.industry || lead.industry,
    score,
    tier,
    rule_score: lead.rule_score,
    ai_score: aiScore,
    method: ai ? 'rules+ai' : 'rules',
    ai_error: aiItem && !ai ? (typeof aiItem.error === 'string' ? aiItem.error : aiItem.error?.message ?? 'AI step returned no output (check the OpenAI credential)') : null,
    reasons: lead.rule_reasons,
    summary: ai?.summary ?? (sg.description || null),
    pitch_angle: ai?.pitch_angle ?? null,
    signals: {
      tech: sg.tech ?? [], has_booking: sg.has_booking ?? false, hiring: sg.hiring ?? false,
      emails: sg.emails ?? [], phones: sg.phones ?? [], socials: sg.socials ?? [], location: sg.location ?? '',
    },
    site_ok: lead.site_ok,
    site_error: lead.site_error,
    dry_run: lead.dry_run,
    enriched_at: DateTime.now().toISO(),
  },
}];