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",
});
}