Finalize metrics verification and backlog updates

This commit is contained in:
2026-06-05 21:49:57 +02:00
parent d3928d61c4
commit f069b74b08
15 changed files with 240 additions and 36 deletions

View File

@@ -86,6 +86,7 @@ export type LeadFunnelOutreach = {
sendStatus?: OutreachSendStatus | null;
responseStatus?: OutreachResponseStatus | null;
salesStatus?: OutreachSalesStatus | null;
doNotContactUntil?: number | null;
};
export type LeadFunnelInput = {
@@ -103,6 +104,7 @@ export type LeadFunnelInput = {
contactPerson?: string | null;
websiteDomain?: string | null;
outreach?: LeadFunnelOutreach | null;
now?: number;
};
export type LeadFunnelCard = {
@@ -303,6 +305,14 @@ function getLeadNextAction(lead: LeadFunnelInput): string {
const stageId = getLeadFunnelStageId(lead);
if (stageId === "deferred") {
if (
lead.outreach?.salesStatus === "do_not_pursue" &&
typeof lead.outreach.doNotContactUntil === "number" &&
(lead.now ?? Date.now()) >= lead.outreach.doNotContactUntil
) {
return "Erneut prüfen";
}
return "Zurückstellung prüfen";
}

View File

@@ -18,6 +18,12 @@ export type AuditRybbitSummary = {
deviceTypes: string[];
};
export type CampaignRybbitSummary = {
auditOpens: number;
ctaClicks: number;
outboundClicks: number;
};
type FetchLike = (
input: string | URL,
init?: RequestInit,
@@ -117,6 +123,23 @@ export function summarizeAuditRybbitEvents(
};
}
export function summarizeCampaignRybbitEvents(
events: RybbitEvent[],
): CampaignRybbitSummary {
const auditEvents = events.filter((event) => eventPath(event).startsWith("/audit/"));
return {
auditOpens: auditEvents.filter((event) => event.type === "pageview").length,
ctaClicks: auditEvents.filter((event) => {
return event.type === "custom_event" && eventName(event) === "audit_cta_click";
}).length,
outboundClicks: auditEvents.filter((event) => {
return event.type === "outbound_link" ||
eventName(event) === "audit_website_link_click";
}).length,
};
}
function normalizeEventsPayload(payload: unknown): RybbitEvent[] {
if (Array.isArray(payload)) {
return payload.filter((event): event is RybbitEvent => typeof event === "object" && event !== null);
@@ -193,3 +216,58 @@ export async function fetchRybbitAuditAnalytics(input: {
};
}
}
export async function fetchRybbitCampaignAnalytics(input: {
apiUrl?: string;
apiKey?: string;
siteId?: string;
startDate?: string;
endDate?: string;
fetchImpl?: FetchLike;
}) {
if (!input.apiUrl || !input.apiKey || !input.siteId) {
return {
ok: false as const,
error: "Rybbit ist nicht vollständig konfiguriert.",
data: summarizeCampaignRybbitEvents([]),
};
}
try {
const response = await (input.fetchImpl ?? fetch)(
buildRybbitEventsUrl({
apiUrl: input.apiUrl,
siteId: input.siteId,
startDate: input.startDate,
endDate: input.endDate,
}),
{
headers: {
Authorization: `Bearer ${input.apiKey}`,
},
},
);
if (!response.ok) {
const body = await response.text();
return {
ok: false as const,
error: `Rybbit API Fehler ${response.status}: ${body.slice(0, 160)}`,
data: summarizeCampaignRybbitEvents([]),
};
}
return {
ok: true as const,
data: summarizeCampaignRybbitEvents(
normalizeEventsPayload(await response.json()),
),
};
} catch (error) {
return {
ok: false as const,
error: error instanceof Error ? error.message : String(error),
data: summarizeCampaignRybbitEvents([]),
};
}
}