fix: Maps-Vault-Sync sofort nach Google Maps, nicht erst nach Anymailfinder

- Google Maps Ergebnisse werden direkt nach der Suche in LeadVault gespeichert
- Anymailfinder-Fehler führen nicht mehr zum Datenverlust
- Bessere Fehlermeldungen: echter Fehlertext statt hardcoded Message
- Anymailfinder-Fehlermeldung auf Deutsch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Timo Uttenweiler
2026-03-20 18:01:44 +01:00
parent 82c4244233
commit f914ab6e47
2 changed files with 40 additions and 31 deletions

View File

@@ -74,7 +74,7 @@ async function runMapsEnrich(
data: { totalLeads: places.length }, data: { totalLeads: places.length },
}); });
// 2. Store raw Google Maps results immediately // 2. Store raw Google Maps results + immediately sink to LeadVault
for (const place of places) { for (const place of places) {
await prisma.leadResult.create({ await prisma.leadResult.create({
data: { data: {
@@ -91,10 +91,24 @@ async function runMapsEnrich(
}); });
} }
// Sink Google Maps results to vault immediately (before enrichment)
// so they're available even if Anymailfinder fails
await sinkLeadsToVault(
places.map(p => ({
domain: p.domain,
companyName: p.name || null,
phone: p.phone,
address: p.address,
})),
"maps",
params.queries.join(", "),
jobId,
);
// 3. Optionally enrich with Anymailfinder // 3. Optionally enrich with Anymailfinder
if (params.enrichEmails && places.length > 0) { if (params.enrichEmails && places.length > 0) {
const anymailKey = await getApiKey("anymailfinder"); const anymailKey = await getApiKey("anymailfinder");
if (!anymailKey) throw new Error("Anymailfinder key missing"); if (!anymailKey) throw new Error("Anymailfinder API-Key fehlt — bitte in den Einstellungen eintragen");
const domains = places.filter(p => p.domain).map(p => p.domain!); const domains = places.filter(p => p.domain).map(p => p.domain!);
// Map domain → placeId for updating results // Map domain → placeId for updating results
@@ -142,35 +156,29 @@ async function runMapsEnrich(
where: { id: jobId }, where: { id: jobId },
data: { status: "complete", emailsFound, totalLeads: places.length }, data: { status: "complete", emailsFound, totalLeads: places.length },
}); });
// Update vault entries with enrichment results
await sinkLeadsToVault(
enrichResults
.filter(r => r.email)
.map(r => ({
domain: r.domain,
contactName: r.person_full_name || null,
contactTitle: r.person_job_title || null,
email: r.email || null,
linkedinUrl: r.person_linkedin_url || null,
emailConfidence: r.valid_email ? 1.0 : r.email_status === "risky" ? 0.5 : 0,
})),
"maps",
params.queries.join(", "),
jobId,
);
} else { } else {
await prisma.job.update({ await prisma.job.update({
where: { id: jobId }, where: { id: jobId },
data: { status: "complete", totalLeads: places.length }, data: { status: "complete", totalLeads: places.length },
}); });
} }
// Sync to LeadVault
const finalResults = await prisma.leadResult.findMany({ where: { jobId } });
await sinkLeadsToVault(
finalResults.map(r => {
let src: { phone?: string; address?: string } = {};
try { src = JSON.parse(r.source || "{}"); } catch { /* ignore */ }
return {
domain: r.domain,
companyName: r.companyName,
contactName: r.contactName,
contactTitle: r.contactTitle,
email: r.email,
linkedinUrl: r.linkedinUrl,
emailConfidence: r.confidence,
phone: src.phone || null,
address: src.address || null,
};
}),
"maps",
params.queries.join(", "),
jobId,
);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err); const message = err instanceof Error ? err.message : String(err);
await prisma.job.update({ await prisma.job.update({

View File

@@ -121,14 +121,14 @@ export default function MapsPage() {
try { try {
const res = await fetch(`/api/jobs/${id}/status`); const res = await fetch(`/api/jobs/${id}/status`);
const data = await res.json() as { const data = await res.json() as {
status: string; totalLeads: number; emailsFound: number; results: ResultRow[]; status: string; totalLeads: number; emailsFound: number; results: ResultRow[]; error?: string;
}; };
if (data.totalLeads > 0 && data.emailsFound === 0 && enrichEmails) { if (data.totalLeads > 0 && data.emailsFound === 0 && enrichEmails) {
phase = "Enriching with Anymailfinder..."; phase = "Anreicherung mit Anymailfinder...";
} }
if (data.emailsFound > 0) { if (data.emailsFound > 0) {
phase = `Found ${data.emailsFound} emails so far...`; phase = `${data.emailsFound} E-Mails gefunden...`;
} }
setProgress({ setProgress({
@@ -146,12 +146,13 @@ export default function MapsPage() {
if (data.status === "complete") { if (data.status === "complete") {
setStage("done"); setStage("done");
const msg = enrichEmails const msg = enrichEmails
? `Done! ${data.totalLeads} companies found, ${data.emailsFound} emails enriched` ? `Fertig! ${data.totalLeads} Unternehmen gefunden, ${data.emailsFound} E-Mails angereichert`
: `Done! ${data.totalLeads} companies found`; : `Fertig! ${data.totalLeads} Unternehmen gefunden`;
toast.success(msg); toast.success(msg);
} else { } else {
setStage("failed"); setStage("failed");
toast.error("Job failed. Check your Google Maps API key in Settings."); const errMsg = data.error || "Unbekannter Fehler";
toast.error(`Job fehlgeschlagen: ${errMsg}`);
} }
} }
} catch { } catch {