diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index d32a9b3..cbb5aba 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -50,15 +50,15 @@ export default async function Home() { {/* At-a-glance health */}
= 0.9 + health.decodeHealth24h >= 0.9 ? "ok" - : health.successRate24h >= 0.75 + : health.decodeHealth24h >= 0.75 ? "warn" : "bad" } diff --git a/apps/web/src/lib/sase/daily-overview.ts b/apps/web/src/lib/sase/daily-overview.ts index e05e6ec..451c074 100644 --- a/apps/web/src/lib/sase/daily-overview.ts +++ b/apps/web/src/lib/sase/daily-overview.ts @@ -3,13 +3,24 @@ import { saseDb } from "@/lib/db-sase"; import { listSaseDeploys, analyzeDeployRegressions } from "./deploy-timeline"; // ─── Sase 24h snapshot (current vs prior 24h) ──────────────────────────── +// Raw success/total is dominated by coverage gaps (no-catalog / unknown VIN) +// and retry bursts — 2026-07-08: 4 Tofaş-Fiat VINs retried 5-17× each dragged +// the raw 24h rate to 48.5% on a pipeline with ZERO real failures. Decode +// health (succeeded / (succeeded + real infra failures), same partition as +// getOperationalHealth) is the headline; raw stays for context. export type SaseHealthSnapshot = { total24h: number; - successRate24h: number; + successRate24h: number; // raw succeeded/total — context only + decodable24h: number; // succeeded + real failures (coverage gaps excluded) + decodeHealth24h: number; // succeeded / decodable — the headline metric + coverageGapCount24h: number; // no-catalog + unknown rows totalPrior24h: number; successRatePrior24h: number; + decodablePrior24h: number; + decodeHealthPrior24h: number; volumeDeltaPct: number; // null-safe: 0 if prior is 0 - successDeltaPp: number; + successDeltaPp: number; // raw delta — context only + decodeHealthDeltaPp: number; }; export async function getSaseHealthSnapshot(): Promise { @@ -21,41 +32,74 @@ export async function getSaseHealthSnapshot(): Promise { Array<{ cur_total: bigint; cur_succ: bigint; + cur_real_failed: bigint; prior_total: bigint; prior_succ: bigint; + prior_real_failed: bigint; }> >` + WITH q AS ( + SELECT created_at, success, + -- real infra failure = failed AND not a coverage gap (same predicate + -- family as getOperationalHealth / vin-anomaly windowStats) + (success = false AND NOT ( + error_message ILIKE 'no catalog%' + OR (timings->>'identified_no_catalog') = '1' + OR error_message ILIKE '%unknown vin%' + OR error_message ILIKE '%tanınamad%' + OR error_message ILIKE '%destekl%' + OR (timings->>'result_kind') = 'unknown' + )) AS is_real_failure + FROM query_logs + WHERE created_at >= ${start48} + ) SELECT count(*) FILTER (WHERE created_at >= ${start24}) AS cur_total, count(*) FILTER (WHERE created_at >= ${start24} AND success = true) AS cur_succ, - count(*) FILTER (WHERE created_at >= ${start48} AND created_at < ${start24}) AS prior_total, - count(*) FILTER (WHERE created_at >= ${start48} AND created_at < ${start24} AND success = true) AS prior_succ - FROM query_logs - WHERE created_at >= ${start48} + count(*) FILTER (WHERE created_at >= ${start24} AND is_real_failure) AS cur_real_failed, + count(*) FILTER (WHERE created_at < ${start24}) AS prior_total, + count(*) FILTER (WHERE created_at < ${start24} AND success = true) AS prior_succ, + count(*) FILTER (WHERE created_at < ${start24} AND is_real_failure) AS prior_real_failed + FROM q `; const r = rows[0] ?? { cur_total: 0n, cur_succ: 0n, + cur_real_failed: 0n, prior_total: 0n, prior_succ: 0n, + prior_real_failed: 0n, }; const curTotal = Number(r.cur_total); const curSucc = Number(r.cur_succ); + const curRealFailed = Number(r.cur_real_failed); const priorTotal = Number(r.prior_total); const priorSucc = Number(r.prior_succ); + const priorRealFailed = Number(r.prior_real_failed); const curRate = curTotal > 0 ? curSucc / curTotal : 0; const priorRate = priorTotal > 0 ? priorSucc / priorTotal : 0; + const curDecodable = curSucc + curRealFailed; + const priorDecodable = priorSucc + priorRealFailed; + // Empty window = nothing broken; default healthy so a quiet day isn't a red KPI. + const curHealth = curDecodable > 0 ? curSucc / curDecodable : 1; + const priorHealth = priorDecodable > 0 ? priorSucc / priorDecodable : 1; return { total24h: curTotal, successRate24h: curRate, + decodable24h: curDecodable, + decodeHealth24h: curHealth, + coverageGapCount24h: curTotal - curSucc - curRealFailed, totalPrior24h: priorTotal, successRatePrior24h: priorRate, + decodablePrior24h: priorDecodable, + decodeHealthPrior24h: priorHealth, volumeDeltaPct: priorTotal > 0 ? (curTotal - priorTotal) / priorTotal : 0, successDeltaPp: (curRate - priorRate) * 100, + decodeHealthDeltaPp: (curHealth - priorHealth) * 100, }; } @@ -314,16 +358,17 @@ export function buildActionItems(input: { }); } - // Big success rate drop (24h vs prior) + // Big decode-health drop (24h vs prior) — REAL failures only; a raw-rate + // drop from coverage-gap retry bursts is not an incident. if ( - input.health.successDeltaPp <= -3 && - input.health.total24h >= 20 && - input.health.totalPrior24h >= 20 + input.health.decodeHealthDeltaPp <= -3 && + input.health.decodable24h >= 10 && + input.health.decodablePrior24h >= 10 ) { items.push({ severity: - input.health.successDeltaPp <= -10 ? "critical" : "high", - text: `Sase success rate düşüşü: ${(input.health.successRatePrior24h * 100).toFixed(1)}% → ${(input.health.successRate24h * 100).toFixed(1)}% (${input.health.successDeltaPp.toFixed(1)}pp)`, + input.health.decodeHealthDeltaPp <= -10 ? "critical" : "high", + text: `Sase decode health düşüşü: ${(input.health.decodeHealthPrior24h * 100).toFixed(1)}% → ${(input.health.decodeHealth24h * 100).toFixed(1)}% (${input.health.decodeHealthDeltaPp.toFixed(1)}pp, gerçek hata · katalog-boşluğu hariç)`, href: "/projects/sase/vin-decode", }); }