feat: add conversion-oriented traffic dashboard
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 8s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 8s
This commit is contained in:
@@ -24,7 +24,16 @@ export async function GET(request: NextRequest) {
|
||||
const minutes = Math.max(parseInt(query.get("minutes") || "1440", 10), 5);
|
||||
const since = new Date(Date.now() - minutes * 60 * 1000);
|
||||
|
||||
const [summaryRows, actorSummaryRows, externalActorRows, totalRows, latestEvents] = await Promise.all([
|
||||
const [
|
||||
summaryRows,
|
||||
actorSummaryRows,
|
||||
externalActorRows,
|
||||
totalRows,
|
||||
latestEvents,
|
||||
judgeCompleteRows,
|
||||
capturedPayoutCount,
|
||||
releasedPayoutCount,
|
||||
] = await Promise.all([
|
||||
prisma.auditEvent.groupBy({
|
||||
by: ["action"],
|
||||
where: {
|
||||
@@ -72,6 +81,29 @@ export async function GET(request: NextRequest) {
|
||||
createdAt: true,
|
||||
},
|
||||
}),
|
||||
prisma.auditEvent.findMany({
|
||||
where: {
|
||||
createdAt: { gte: since },
|
||||
action: "JUDGE_COMPLETE",
|
||||
},
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
}),
|
||||
prisma.ledgerEntry.count({
|
||||
where: {
|
||||
createdAt: { gte: since },
|
||||
phase: "CAPTURE",
|
||||
response_status: "SUCCESS",
|
||||
},
|
||||
}),
|
||||
prisma.ledgerEntry.count({
|
||||
where: {
|
||||
createdAt: { gte: since },
|
||||
phase: "RELEASE",
|
||||
response_status: "SUCCESS",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const actionSummary = Object.fromEntries(
|
||||
@@ -133,6 +165,44 @@ export async function GET(request: NextRequest) {
|
||||
{ external: 0, internal: 0 } as Record<string, number>
|
||||
);
|
||||
|
||||
const discoveryEvents =
|
||||
(actionSummary["EXTERNAL_LIST_OPEN_TASKS"] || 0) +
|
||||
(actionSummary["EXTERNAL_LIST_OPEN_TASKS_MCP"] || 0);
|
||||
|
||||
const claimEvents = actionSummary["EXTERNAL_CLAIM_TASK_SUCCESS"] || 0;
|
||||
const submitEvents = actionSummary["EXTERNAL_SUBMIT_SOLUTION_SUCCESS"] || 0;
|
||||
const judgePassEvents = judgeCompleteRows.filter((row) => {
|
||||
const metadata = asRecordJson(row.metadata);
|
||||
return metadata?.overall_result === "PASS";
|
||||
}).length;
|
||||
const judgeFailEvents = judgeCompleteRows.filter((row) => {
|
||||
const metadata = asRecordJson(row.metadata);
|
||||
return metadata?.overall_result === "FAIL";
|
||||
}).length;
|
||||
|
||||
const conversionRate = (numerator: number, denominator: number) => {
|
||||
if (!denominator) return 0;
|
||||
return Math.round((numerator / denominator) * 1000) / 10;
|
||||
};
|
||||
|
||||
const externalFunnel = {
|
||||
discovery_events: discoveryEvents,
|
||||
claim_events: claimEvents,
|
||||
submit_events: submitEvents,
|
||||
judge_pass_events: judgePassEvents,
|
||||
judge_fail_events: judgeFailEvents,
|
||||
judge_total_events: judgePassEvents + judgeFailEvents,
|
||||
payout_captured: capturedPayoutCount,
|
||||
payout_released: releasedPayoutCount,
|
||||
};
|
||||
|
||||
const conversionRates = {
|
||||
claim_rate: conversionRate(claimEvents, discoveryEvents),
|
||||
submit_rate: conversionRate(submitEvents, claimEvents),
|
||||
pass_rate: conversionRate(judgePassEvents, submitEvents),
|
||||
payout_rate: conversionRate(capturedPayoutCount, judgePassEvents),
|
||||
};
|
||||
|
||||
const externalEventTypes = Object.entries(actionSummary)
|
||||
.filter(([action]) => action.startsWith("EXTERNAL_"))
|
||||
.map(([action, count]) => ({ action, count }));
|
||||
@@ -172,6 +242,8 @@ export async function GET(request: NextRequest) {
|
||||
channel_summary: channelSummary,
|
||||
actor_summary: actorSummary,
|
||||
external_actor_summary: externalActorSummary,
|
||||
external_funnel: externalFunnel,
|
||||
conversion_rates: conversionRates,
|
||||
external_event_types: externalEventTypes,
|
||||
internal_event_types: internalEventTypes,
|
||||
recent_external_events: recentExternalEvents,
|
||||
|
||||
Reference in New Issue
Block a user