← 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
M apps/admin/src/app/api/ingestion-stats/route.ts
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 →