[object Object]

← back to Homesonspec

auto-save: 2026-07-28T08:58:52 (1 files) — apps/admin/src/app/api/ingestion-stats/route.ts

d44d5270d77e0d5bcc47669a848795f5bc7ee525 · 2026-07-28 08:59:01 -0700 · Steve Abrams

Files touched

Diff

commit d44d5270d77e0d5bcc47669a848795f5bc7ee525
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 08:59:01 2026 -0700

    auto-save: 2026-07-28T08:58:52 (1 files) — apps/admin/src/app/api/ingestion-stats/route.ts
---
 apps/admin/src/app/api/ingestion-stats/route.ts | 32 +++++++++++++++++++++----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/apps/admin/src/app/api/ingestion-stats/route.ts b/apps/admin/src/app/api/ingestion-stats/route.ts
index ff99f0c..8ef3edd 100644
--- a/apps/admin/src/app/api/ingestion-stats/route.ts
+++ b/apps/admin/src/app/api/ingestion-stats/route.ts
@@ -6,7 +6,7 @@ export const dynamic = "force-dynamic";
 // Live ingestion telemetry for the /ingestion dashboard. All counts cast ::int so the
 // JSON has no BigInt. Read-only; safe to poll frequently.
 export async function GET() {
-  const [byStatus, throughput, runs, staged, topSources] = await Promise.all([
+  const [byStatus, throughput, newHomes, runs, health, staged, topSources] = await Promise.all([
     prisma.$queryRawUnsafe<{ status: string; n: number }[]>(
       `SELECT status, count(*)::int AS n FROM "InventoryHome" GROUP BY status ORDER BY n DESC`,
     ),
@@ -20,6 +20,13 @@ export async function GET() {
          max("updatedAt") AS newest
        FROM "InventoryHome"`,
     ),
+    // newly-DISCOVERED homes (createdAt) vs merely refreshed — the growth signal
+    prisma.$queryRawUnsafe<any[]>(
+      `SELECT
+         count(*) FILTER (WHERE "createdAt" > now() - interval '30 min')::int AS n30,
+         count(*) FILTER (WHERE "createdAt" > now() - interval '60 min')::int AS n60
+       FROM "InventoryHome"`,
+    ).catch(() => [{ n30: null, n60: null }]),
     prisma.$queryRawUnsafe<any[]>(
       `SELECT
          count(*) FILTER (WHERE "startedAt" > now() - interval '10 min')::int AS r10,
@@ -27,24 +34,39 @@ export async function GET() {
          EXTRACT(EPOCH FROM (now() - max("startedAt")))::int AS newest_age_s
        FROM "SourceRun"`,
     ),
+    // ingestion error signal over the last hour
+    prisma.$queryRawUnsafe<any[]>(
+      `SELECT
+         count(*) FILTER (WHERE "ok" = false AND "startedAt" > now() - interval '60 min')::int AS err_runs,
+         count(*) FILTER (WHERE "startedAt" > now() - interval '60 min')::int AS runs,
+         COALESCE(sum("errorCount") FILTER (WHERE "startedAt" > now() - interval '60 min'),0)::int AS err_count
+       FROM "SourceRun"`,
+    ).catch(() => [{ err_runs: 0, runs: 0, err_count: 0 }]),
     prisma.$queryRawUnsafe<any[]>(
       `SELECT count(*)::int AS total,
          count(*) FILTER (WHERE "updatedAt" > now() - interval '30 min')::int AS s30
        FROM "StagedRecord"`,
     ),
-    prisma.$queryRawUnsafe<{ key: string; runs: number; published: number }[]>(
-      `SELECT sr."key" AS key, count(*)::int AS runs, COALESCE(sum(run."published"),0)::int AS published
+    // per-source: recent runs, homes published, errors, and how long since its last run
+    prisma.$queryRawUnsafe<any[]>(
+      `SELECT sr."key" AS key,
+         count(*)::int AS runs,
+         COALESCE(sum(run."published"),0)::int AS published,
+         count(*) FILTER (WHERE run."ok" = false)::int AS errors,
+         EXTRACT(EPOCH FROM (now() - max(run."startedAt")))::int AS last_age_s
        FROM "SourceRun" run JOIN "SourceRegistry" sr ON sr."id" = run."sourceId"
        WHERE run."startedAt" > now() - interval '60 min'
-       GROUP BY sr."key" ORDER BY runs DESC LIMIT 8`,
-    ).catch(() => [] as { key: string; runs: number; published: number }[]),
+       GROUP BY sr."key" ORDER BY runs DESC LIMIT 10`,
+    ).catch(() => [] as any[]),
   ]);
 
   return NextResponse.json({
     ts: new Date().toISOString(),
     byStatus,
     throughput: throughput[0] ?? {},
+    newHomes: newHomes[0] ?? {},
     runs: runs[0] ?? {},
+    health: health[0] ?? {},
     staged: staged[0] ?? {},
     topSources,
   });

← 3cac9a3 deploy-kamatera: :80 acme block covers admin.homesonspec.com  ·  back to Homesonspec  ·  admin/ingestion: live sparkline + new-vs-refreshed + error r 557ae65 →