Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions platform/lib/queries/adoption-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* list of comparable rows (stabilization, durability, cascade, revert, new-code churn).
*/

import { PP_STABLE } from "./temporal";

import type {
AdoptionConfidence,
AdoptionTimeline,
Expand All @@ -33,7 +35,7 @@ export interface AdoptionDelta {
}

export interface AdoptionSummary {
inflection: string; // YYYY-MM-DD
inflection: string; // YYYY-MM-DD
rampEnd: string | null;
confidence: AdoptionConfidence;
totalAiCommits: number;
Expand All @@ -46,9 +48,14 @@ export interface RepoAdoption extends AdoptionSummary {
headlineDeltaPp: number | null;
}

const FLAT_THRESHOLD_PP = 2;
// Was 2pp β€” well under the engine's own PP_STABLE floor (5pp) for "this
// delta isn't noise," so moves the engine's own trend narrative would call
// stable were rendering as a confident up/down arrow here.
const FLAT_THRESHOLD_PP = PP_STABLE;

function weightedDurabilitySurvival(m: ReportMetrics | undefined): number | null {
function weightedDurabilitySurvival(
m: ReportMetrics | undefined,
): number | null {
if (!m?.durability_by_origin) return null;
let surviving = 0;
let introduced = 0;
Expand Down
10 changes: 10 additions & 0 deletions platform/lib/queries/dora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ const PROVIDER = "datadog" as const;
const COMMIT_CHUNK_SIZE = 100;
const DEFAULT_WINDOW_DAYS = 30;

/**
* Below this many evaluated deploys, the headline KPI cards (CFR in
* particular β€” a ratio) are one-or-two-events noise dressed up as a
* precise percentage. Shared by the org dashboard's DORAOverview and the
* repo-detail page's DORARepoCard β€” a single repo is, if anything, MORE
* likely to sit below this floor than the org aggregate, so it needs the
* same guard, not a laxer one.
*/
export const MIN_EVALUATED_FOR_KPIS = 10;

const ORIGINS = ["HUMAN", "AI_ASSISTED", "BOT"] as const;
type Origin = (typeof ORIGINS)[number];

Expand Down
26 changes: 20 additions & 6 deletions platform/lib/queries/temporal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ import { classifyHealth } from "@/types/temporal";

const SPARKLINE_POINTS = 12;

/**
* Canonical percentage-point significance thresholds, mirrored from
* `iris/analysis/trend_delta.py` (see docs/METRICS.md): delta < PP_STABLE is
* noise, PP_STABLE <= delta < PP_NOTABLE is worth mentioning, >= PP_NOTABLE
* is significant. Shared here so every platform-side "is this delta worth
* an alert" decision (detectChanges below, the adoption timeline's
* up/down/flat classification) uses the same floor as the engine's own
* narrative instead of independently hand-picked numbers.
*/
export const PP_STABLE = 5.0;
export const PP_NOTABLE = 15.0;

/**
* Default lookback window in days. Mirrors the engine CLI's
* `iris analyze --days` default. Every read from `metrics` filters by
Expand Down Expand Up @@ -304,41 +316,43 @@ export function detectChanges(
}
}

// Stabilization drop > 10pp
// Stabilization drop >= PP_STABLE (the engine's own "no longer noise"
// floor β€” was 10pp, matching neither PP_STABLE nor PP_NOTABLE).
check(
"stabilization_ratio",
`Stabilization ${current.stabilization_ratio !== null && previous.stabilization_ratio !== null && current.stabilization_ratio < previous.stabilization_ratio ? "dropped" : "improved"} by ${Math.abs(((current.stabilization_ratio ?? 0) - (previous.stabilization_ratio ?? 0)) * 100).toFixed(0)}pp`,
current.stabilization_ratio,
previous.stabilization_ratio,
0.1,
PP_STABLE / 100,
current.stabilization_ratio !== null &&
previous.stabilization_ratio !== null &&
current.stabilization_ratio < previous.stabilization_ratio
? "warning"
: "info",
);

// Revert rate increase > 5pp
// Revert rate increase >= PP_STABLE (already matched this by coincidence).
check(
"revert_rate",
`Revert rate changed by ${Math.abs(((current.revert_rate ?? 0) - (previous.revert_rate ?? 0)) * 100).toFixed(0)}pp`,
current.revert_rate,
previous.revert_rate,
0.05,
PP_STABLE / 100,
current.revert_rate !== null &&
previous.revert_rate !== null &&
current.revert_rate > previous.revert_rate
? "critical"
: "info",
);

// AI coverage change > 15pp
// AI coverage change >= PP_STABLE (was 15pp, i.e. PP_NOTABLE β€” a much
// higher bar than the other two metrics here for no documented reason).
check(
"ai_detection_coverage_pct",
`AI adoption changed by ${Math.abs((current.ai_detection_coverage_pct ?? 0) - (previous.ai_detection_coverage_pct ?? 0)).toFixed(0)}pp`,
current.ai_detection_coverage_pct,
previous.ai_detection_coverage_pct,
15,
PP_STABLE,
"info",
);

Expand Down
14 changes: 12 additions & 2 deletions platform/lib/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ export const translations = {
"Systemic patterns that drive the most rework in this repository.",
empty:
"No systemic hotspots detected. Stabilization, coupling and fix distribution all look within healthy ranges.",
noData:
"Not enough data to evaluate yet β€” this repo has no stability map, coupling, or fix-targeting data in this window.",
severityHigh: "High",
severityMedium: "Medium",
severityLow: "Low",
Expand Down Expand Up @@ -593,6 +595,7 @@ export const translations = {
revertRate: "Revert Rate",
churnEvents: "Churn Events",
commits: "Commits",
noActivityHint: "No commits in this window",
},
mergeStrategy: {
label: "Merge",
Expand All @@ -604,7 +607,9 @@ export const translations = {
dora: {
title: "DORA",
subtitle:
"Deployment metrics scoped to this repository over the last {days} days.",
"Deployment metrics scoped to this repository over the last {days} days β€” live from Datadog as of now. The rest of this page reflects the last analysis run, which may be older.",
lowSample:
"Based on only {actual} evaluated deploys (below the {threshold} this page treats as a stable read) β€” treat these four as directional, not precise.",
incidentDisclaimer:
"MTTR by incident isn't shown per-repo β€” Datadog failure events don't carry repository attribution, so any per-repo number would be a misleading copy of the org-wide one. See the dashboard for the incident-level view.",
},
Expand Down Expand Up @@ -1862,6 +1867,8 @@ export const translations = {
"PadrΓ΅es sistΓͺmicos que geram mais retrabalho neste repositΓ³rio.",
empty:
"Nenhum hotspot sistΓͺmico detectado. EstabilizaΓ§Γ£o, acoplamento e distribuiΓ§Γ£o de fixes estΓ£o dentro de faixas saudΓ‘veis.",
noData:
"Ainda nΓ£o hΓ‘ dados suficientes pra avaliar β€” esse repositΓ³rio nΓ£o tem stability map, acoplamento ou dados de fix-targeting nessa janela.",
severityHigh: "Alto",
severityMedium: "MΓ©dio",
severityLow: "Baixo",
Expand Down Expand Up @@ -1997,6 +2004,7 @@ export const translations = {
revertRate: "Taxa de revert",
churnEvents: "Eventos de churn",
commits: "Commits",
noActivityHint: "Sem commits nessa janela",
},
mergeStrategy: {
label: "Merge",
Expand All @@ -2008,7 +2016,9 @@ export const translations = {
dora: {
title: "DORA",
subtitle:
"MΓ©tricas de deploy deste repositΓ³rio nos ΓΊltimos {days} dias.",
"MΓ©tricas de deploy deste repositΓ³rio nos ΓΊltimos {days} dias β€” direto do Datadog, a partir de agora. O resto desta pΓ‘gina reflete a ΓΊltima anΓ‘lise, que pode ser mais antiga.",
lowSample:
"Baseado em apenas {actual} deploys avaliados (abaixo dos {threshold} que esta pΓ‘gina considera uma leitura estΓ‘vel) β€” trate esses quatro nΓΊmeros como direcionais, nΓ£o precisos.",
incidentDisclaimer:
"MTTR por incidente nΓ£o aparece per-repo β€” os eventos de falha do Datadog nΓ£o carregam atribuiΓ§Γ£o de repositΓ³rio, entΓ£o qualquer nΓΊmero per-repo seria uma cΓ³pia enganosa do org-wide. Veja a visΓ£o de incidentes no dashboard.",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { useTranslation } from "@/hooks/useTranslation";
import { MIN_EVALUATED_FOR_KPIS } from "@/lib/queries/dora";
import type { OrgDORA } from "@/types/org-summary";

/**
Expand All @@ -23,14 +24,6 @@ import type { OrgDORA } from "@/types/org-summary";
*/
const MIN_FAILED_FOR_CORRELATION = 10;

/**
* Below this many evaluated deploys, the four headline KPI cards (CFR in
* particular β€” a ratio) are one-or-two-events noise dressed up as a
* precise percentage. Same order of magnitude as MIN_FAILED_FOR_CORRELATION,
* applied here to the broader "evaluated" denominator rather than "failed".
*/
const MIN_EVALUATED_FOR_KPIS = 10;

interface Props {
data: OrgDORA;
}
Expand Down
13 changes: 13 additions & 0 deletions platform/src/app/[tenant]/repos/[repoName]/dora-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MetricCard } from "@/components/charts/MetricCard";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { useTranslation } from "@/hooks/useTranslation";
import { MIN_EVALUATED_FOR_KPIS } from "@/lib/queries/dora";
import type { RepoDORA } from "@/types/org-summary";

interface Props {
Expand All @@ -15,6 +16,10 @@ interface Props {
export function DORARepoCard({ data }: Props) {
const { t } = useTranslation();

const evaluatedDeploys =
data.deploymentsTotal - data.deploymentsPendingEvaluation;
const lowSample = evaluatedDeploys < MIN_EVALUATED_FOR_KPIS;

return (
<section className="space-y-3">
<div>
Expand Down Expand Up @@ -55,6 +60,14 @@ export function DORARepoCard({ data }: Props) {
value={formatHours(data.leadTimeSecondsMedian)}
/>
</div>
{lowSample && (
<p className="text-xs text-muted-foreground">
{t("repos.detail.dora.lowSample", {
threshold: MIN_EVALUATED_FOR_KPIS,
actual: evaluatedDeploys,
})}
</p>
)}

<div className="grid gap-3 md:grid-cols-3">
<RepoFactCard
Expand Down
28 changes: 25 additions & 3 deletions platform/src/app/[tenant]/repos/[repoName]/investment-hotspots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,38 @@ export function InvestmentHotspots({ data }: InvestmentHotspotsProps) {
const { t } = useTranslation();

if (data.hotspots.length === 0) {
// Distinguish "nothing was even evaluated" from "evaluated and
// healthy" β€” both used to render the same reassuring message, which
// reads as a false-positive "all good" for a repo with no signal at
// all (e.g. no stability_map/churn_couplings/fix_target_by_origin in
// the payload yet).
const { directories, couplings, origins } = data.sourceCounts;
const hasNoData = directories + couplings + origins === 0;

return (
<Card>
<CardHeader>
<CardTitle>{t("investHere.title")}</CardTitle>
<CardDescription>{t("investHere.subtitle")}</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center gap-3 rounded-md border border-signal-purple/30 bg-signal-purple/5 p-4 text-sm text-muted-foreground">
<AlertOctagon className="size-4 shrink-0 text-signal-purple" />
<span>{t("investHere.empty")}</span>
<div
className={cn(
"flex items-center gap-3 rounded-md border p-4 text-sm text-muted-foreground",
hasNoData
? "border-border bg-muted/30"
: "border-signal-purple/30 bg-signal-purple/5",
)}
>
<AlertOctagon
className={cn(
"size-4 shrink-0",
hasNoData ? "text-muted-foreground" : "text-signal-purple",
)}
/>
<span>
{hasNoData ? t("investHere.noData") : t("investHere.empty")}
</span>
</div>
</CardContent>
</Card>
Expand Down
14 changes: 14 additions & 0 deletions platform/src/app/[tenant]/repos/[repoName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,14 @@ export default async function RepoDetailPage({
: "\u2014"
}
delta={stabDelta}
// stabilization_ratio defaults to 1.0 (100%) when there are no
// touched files \u2014 without this, an empty window reads as
// "perfectly stable" instead of "nothing to measure."
hint={
latest?.commits_total === 0
? t("repos.detail.metrics.noActivityHint")
: undefined
}
/>
<MetricCard
label={t("repos.detail.metrics.revertRate")}
Expand All @@ -290,6 +298,12 @@ export default async function RepoDetailPage({
}
delta={revertDelta}
invertDelta
// Same defaulting issue as stabilization above, but toward 0.0%.
hint={
latest?.commits_total === 0
? t("repos.detail.metrics.noActivityHint")
: undefined
}
/>
<MetricCard
label={t("repos.detail.metrics.churnEvents")}
Expand Down
62 changes: 62 additions & 0 deletions platform/tests/adoption-timeline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, it } from "vitest";

import { extractAdoptionSummary } from "@/lib/queries/adoption-timeline";
import type { ReportMetrics } from "@/types/metrics";

function metrics(over: Partial<ReportMetrics>): ReportMetrics {
return {
commits_total: 0,
commits_revert: 0,
revert_rate: 0,
churn_events: 0,
churn_lines_affected: 0,
files_touched: 0,
files_stabilized: 0,
stabilization_ratio: 0,
...over,
} as ReportMetrics;
}

describe("extractAdoptionSummary β€” flat threshold matches PP_STABLE", () => {
it("classifies a 2.5pp stabilization move as flat, not up (was flagged at 2pp before)", () => {
const summary = extractAdoptionSummary(
metrics({
adoption_timeline: {
first_ai_commit_date: "2026-01-01",
adoption_ramp_start: "2026-01-15",
adoption_ramp_end: null,
adoption_confidence: "clear",
total_ai_commits: 10,
pre_adoption: metrics({ stabilization_ratio: 0.7 }),
post_adoption: metrics({ stabilization_ratio: 0.725 }),
},
}),
);

const stab = summary?.deltas.find((d) => d.key === "stabilization");
expect(stab?.deltaPp).toBeCloseTo(2.5, 5);
// 2.5pp is under the engine's own PP_STABLE (5pp) floor for "not noise"
// β€” the old 2pp threshold here would have called this "up".
expect(stab?.direction).toBe("flat");
});

it("classifies a 6pp stabilization move as up (above PP_STABLE)", () => {
const summary = extractAdoptionSummary(
metrics({
adoption_timeline: {
first_ai_commit_date: "2026-01-01",
adoption_ramp_start: "2026-01-15",
adoption_ramp_end: null,
adoption_confidence: "clear",
total_ai_commits: 10,
pre_adoption: metrics({ stabilization_ratio: 0.7 }),
post_adoption: metrics({ stabilization_ratio: 0.76 }),
},
}),
);

const stab = summary?.deltas.find((d) => d.key === "stabilization");
expect(stab?.deltaPp).toBeCloseTo(6, 5);
expect(stab?.direction).toBe("up");
});
});
Loading
Loading