pre-bemis

This commit is contained in:
Marcelo
2026-04-22 05:04:19 +00:00
parent ac1a7900c8
commit 80d27f83b6
91 changed files with 11769 additions and 820 deletions

View File

@@ -2,6 +2,16 @@ import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSession } from "@/lib/auth/requireSession";
import { logLine } from "@/lib/logger";
import { elapsedMs, formatServerTiming, nowMs, PERF_LOGS_ENABLED } from "@/lib/perf/serverTiming";
let reportsFiltersColdStart = true;
function getColdStartInfo() {
const coldStart = reportsFiltersColdStart;
reportsFiltersColdStart = false;
return { coldStart, uptimeMs: Math.round(process.uptime() * 1000) };
}
const RANGE_MS: Record<string, number> = {
"24h": 24 * 60 * 60 * 1000,
@@ -33,10 +43,19 @@ function pickRange(req: NextRequest) {
}
export async function GET(req: NextRequest) {
const perfEnabled = PERF_LOGS_ENABLED;
const totalStart = nowMs();
const timings: Record<string, number> = {};
const { coldStart, uptimeMs } = getColdStartInfo();
const authStart = nowMs();
const session = await requireSession();
if (perfEnabled) timings.auth = elapsedMs(authStart);
if (!session) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
const preQueryStart = nowMs();
const url = new URL(req.url);
const range = url.searchParams.get("range") ?? "24h";
const machineId = url.searchParams.get("machineId") ?? undefined;
const { start, end } = pickRange(req);
@@ -46,20 +65,51 @@ export async function GET(req: NextRequest) {
ts: { gte: start, lte: end },
};
if (perfEnabled) timings.preQuery = elapsedMs(preQueryStart);
const workOrdersStart = nowMs();
const workOrderRows = await prisma.machineCycle.findMany({
where: { ...baseWhere, workOrderId: { not: null } },
distinct: ["workOrderId"],
select: { workOrderId: true },
});
if (perfEnabled) timings.workOrders = elapsedMs(workOrdersStart);
const skuStart = nowMs();
const skuRows = await prisma.machineCycle.findMany({
where: { ...baseWhere, sku: { not: null } },
distinct: ["sku"],
select: { sku: true },
});
if (perfEnabled) timings.skus = elapsedMs(skuStart);
const postQueryStart = nowMs();
const workOrders = workOrderRows.map((r) => r.workOrderId).filter(Boolean) as string[];
const skus = skuRows.map((r) => r.sku).filter(Boolean) as string[];
return NextResponse.json({ ok: true, workOrders, skus });
const payload = { ok: true, workOrders, skus };
const responseHeaders = new Headers();
if (perfEnabled) {
timings.postQuery = elapsedMs(postQueryStart);
timings.total = elapsedMs(totalStart);
responseHeaders.set("Server-Timing", formatServerTiming(timings));
const payloadBytes = Buffer.byteLength(JSON.stringify(payload));
logLine("perf.reports.filters", {
orgId: session.orgId,
coldStart,
uptimeMs,
range,
machineId,
timings,
rowCounts: {
workOrderRows: workOrderRows.length,
skuRows: skuRows.length,
},
payloadBytes,
});
}
return NextResponse.json(payload, { headers: responseHeaders });
}

View File

@@ -2,6 +2,16 @@ import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSession } from "@/lib/auth/requireSession";
import { logLine } from "@/lib/logger";
import { elapsedMs, formatServerTiming, nowMs, PERF_LOGS_ENABLED } from "@/lib/perf/serverTiming";
let reportsColdStart = true;
function getColdStartInfo() {
const coldStart = reportsColdStart;
reportsColdStart = false;
return { coldStart, uptimeMs: Math.round(process.uptime() * 1000) };
}
const RANGE_MS: Record<string, number> = {
"24h": 24 * 60 * 60 * 1000,
@@ -37,10 +47,19 @@ function safeNum(v: unknown) {
}
export async function GET(req: NextRequest) {
const perfEnabled = PERF_LOGS_ENABLED;
const totalStart = nowMs();
const timings: Record<string, number> = {};
const { coldStart, uptimeMs } = getColdStartInfo();
const authStart = nowMs();
const session = await requireSession();
if (perfEnabled) timings.auth = elapsedMs(authStart);
if (!session) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
const preQueryStart = nowMs();
const url = new URL(req.url);
const range = url.searchParams.get("range") ?? "24h";
const machineId = url.searchParams.get("machineId") ?? undefined;
const { start, end } = pickRange(req);
const workOrderId = url.searchParams.get("workOrderId") ?? undefined;
@@ -52,6 +71,9 @@ export async function GET(req: NextRequest) {
...(sku ? { sku } : {}),
};
if (perfEnabled) timings.preQuery = elapsedMs(preQueryStart);
const kpiStart = nowMs();
const kpiRows = await prisma.machineKpiSnapshot.findMany({
where: { ...baseWhere, ts: { gte: start, lte: end } },
orderBy: { ts: "asc" },
@@ -67,6 +89,7 @@ export async function GET(req: NextRequest) {
machineId: true,
},
});
if (perfEnabled) timings.kpiRows = elapsedMs(kpiStart);
let oeeSum = 0;
let oeeCount = 0;
@@ -96,10 +119,12 @@ export async function GET(req: NextRequest) {
}
}
const cyclesStart = nowMs();
const cycles = await prisma.machineCycle.findMany({
where: { ...baseWhere, ts: { gte: start, lte: end } },
select: { goodDelta: true, scrapDelta: true },
});
if (perfEnabled) timings.cycles = elapsedMs(cyclesStart);
let goodTotal = 0;
let scrapTotal = 0;
@@ -109,6 +134,7 @@ export async function GET(req: NextRequest) {
if (safeNum(c.scrapDelta) != null) scrapTotal += Number(c.scrapDelta);
}
const kpiAggStart = nowMs();
const kpiAgg = await prisma.machineKpiSnapshot.groupBy({
by: ["machineId"],
where: { ...baseWhere, ts: { gte: start, lte: end } },
@@ -116,6 +142,7 @@ export async function GET(req: NextRequest) {
_min: { good: true, scrap: true },
_count: { _all: true },
});
if (perfEnabled) timings.kpiAgg = elapsedMs(kpiAggStart);
let targetTotal = 0;
if (goodTotal === 0 && scrapTotal === 0) {
@@ -151,10 +178,12 @@ export async function GET(req: NextRequest) {
if (maxTarget != null) targetTotal += maxTarget;
}
const eventsStart = nowMs();
const events = await prisma.machineEvent.findMany({
where: { ...baseWhere, ts: { gte: start, lte: end } },
select: { eventType: true, data: true },
});
if (perfEnabled) timings.events = elapsedMs(eventsStart);
let macrostopSec = 0;
let microstopSec = 0;
@@ -223,10 +252,12 @@ export async function GET(req: NextRequest) {
trend.scrapRate.push({ t, v: (scrap / (good + scrap)) * 100 });
}
}
const cycleRowsStart = nowMs();
const cycleRows = await prisma.machineCycle.findMany({
where: { ...baseWhere, ts: { gte: start, lte: end } },
select: { actualCycleTime: true },
});
if (perfEnabled) timings.cycleRows = elapsedMs(cycleRowsStart);
const values = cycleRows
.map((c) => Number(c.actualCycleTime))
@@ -310,10 +341,14 @@ export async function GET(req: NextRequest) {
const scrapBySku = new Map<string, number>();
const scrapByWo = new Map<string, number>();
const scrapRowsStart = nowMs();
const scrapRows = await prisma.machineCycle.findMany({
where: { ...baseWhere, ts: { gte: start, lte: end } },
select: { sku: true, workOrderId: true, scrapDelta: true },
});
if (perfEnabled) timings.scrapRows = elapsedMs(scrapRowsStart);
const postQueryStart = nowMs();
for (const row of scrapRows) {
const scrap = safeNum(row.scrapDelta);
@@ -340,20 +375,20 @@ export async function GET(req: NextRequest) {
return NextResponse.json({
const payload = {
ok: true,
summary: {
oeeAvg,
availabilityAvg,
performanceAvg,
qualityAvg,
goodTotal,
scrapTotal,
targetTotal,
scrapRate,
topScrapSku,
topScrapWorkOrder,
},
oeeAvg,
availabilityAvg,
performanceAvg,
qualityAvg,
goodTotal,
scrapTotal,
targetTotal,
scrapRate,
topScrapSku,
topScrapWorkOrder,
},
downtime: {
macrostopSec,
@@ -365,9 +400,36 @@ export async function GET(req: NextRequest) {
},
trend,
insights,
distribution: {
cycleTime: cycleTimeBins
distribution: {
cycleTime: cycleTimeBins,
},
};
});
const responseHeaders = new Headers();
if (perfEnabled) {
timings.postQuery = elapsedMs(postQueryStart);
timings.total = elapsedMs(totalStart);
responseHeaders.set("Server-Timing", formatServerTiming(timings));
const payloadBytes = Buffer.byteLength(JSON.stringify(payload));
logLine("perf.reports.api", {
orgId: session.orgId,
coldStart,
uptimeMs,
range,
machineId,
workOrderId,
sku,
timings,
rowCounts: {
kpiRows: kpiRows.length,
cycles: cycles.length,
events: events.length,
cycleRows: cycleRows.length,
scrapRows: scrapRows.length,
},
payloadBytes,
});
}
return NextResponse.json(payload, { headers: responseHeaders });
}