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: 4 additions & 9 deletions platform/lib/queries/org-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,9 @@ function simpleAvg(values: Array<number | null>): number | null {
return valid.reduce((s, v) => s + v, 0) / valid.length;
}

/** Build the org-wide sparkline by summing or averaging per-position across repos. */
/** Build the org-wide sparkline by averaging per-position across repos. */
function buildOrgSparkline(
repos: RepoSummary[],
mode: "sum" | "avg",
accessor: (r: RepoSummary) => number[],
): number[] {
const maxLen = Math.max(...repos.map((r) => accessor(r).length), 0);
Expand All @@ -146,7 +145,7 @@ function buildOrgSparkline(
count++;
}
}
result.push(mode === "sum" ? sum : count > 0 ? sum / count : 0);
result.push(count > 0 ? sum / count : 0);
}
return result;
}
Expand Down Expand Up @@ -207,10 +206,8 @@ export function computeOrgPulse(
? aiAdoptionPct - previousTotals.aiPct!
: null;

// Sparklines
const commitSparkline = buildOrgSparkline(repos, "sum", () => []);
// Use stabilization sparklines from repos
const stabSparkline = buildOrgSparkline(repos, "avg", (r) => r.sparkline);
// Sparkline — from each repo's own stabilization history.
const stabSparkline = buildOrgSparkline(repos, (r) => r.sparkline);

return {
totalCommits,
Expand All @@ -224,9 +221,7 @@ export function computeOrgPulse(
aiAdoptionPct,
aiAdoptionDelta,
sparklines: {
commits: commitSparkline,
stabilization: stabSparkline,
aiAdoption: [],
},
};
}
Expand Down
6 changes: 1 addition & 5 deletions platform/lib/queries/temporal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function getRepoTimeSeries(
const { data } = await supabase
.from("metrics")
.select(
"created_at, stabilization_ratio, revert_rate, churn_events, commits_total, ai_detection_coverage_pct, pr_merged_count, pr_single_pass_rate, fix_latency_median_hours, cascade_rate",
"created_at, stabilization_ratio, revert_rate, churn_events, commits_total, ai_detection_coverage_pct",
)
.eq("repository_id", repositoryId)
.eq("window_days", windowDays)
Expand All @@ -106,10 +106,6 @@ export async function getRepoTimeSeries(
churn_events: row.churn_events,
commits_total: row.commits_total,
ai_detection_coverage_pct: row.ai_detection_coverage_pct,
pr_merged_count: row.pr_merged_count,
pr_single_pass_rate: row.pr_single_pass_rate,
fix_latency_median_hours: row.fix_latency_median_hours,
cascade_rate: row.cascade_rate,
}));
}

Expand Down
4 changes: 2 additions & 2 deletions platform/lib/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ export const translations = {
aiExposure: {
title: "Shadow AI Exposure",
subtitle:
"How much AI-assisted code is being produced — and how much is attributed.",
"How much AI-assisted code is being produced — and how much is attributed. The per-repo breakdown behind the dashboard's Attribution Gap card.",
empty:
"No origin data yet. Run the CLI on a repository to start measuring.",
summary: {
Expand Down Expand Up @@ -2161,7 +2161,7 @@ export const translations = {
aiExposure: {
title: "Exposição a Shadow AI",
subtitle:
"Quanto código assistido por IA está sendo produzido — e quanto está atribuído.",
'Quanto código assistido por IA está sendo produzido — e quanto está atribuído. O detalhamento por repositório por trás do card "Lacuna de Atribuição" do dashboard.',
empty:
"Ainda não há dados de origem. Rode a CLI em um repositório para começar a medir.",
summary: {
Expand Down
2 changes: 0 additions & 2 deletions platform/src/app/[tenant]/repos/[repoName]/charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -788,11 +788,9 @@ export function RepoCharts({
const chartData = timeSeries.map((p) => ({
date: p.date,
stabilization_ratio: p.stabilization_ratio,
revert_rate: p.revert_rate,
churn_events: p.churn_events,
commits_total: p.commits_total,
ai_detection_coverage_pct: p.ai_detection_coverage_pct,
cascade_rate: p.cascade_rate,
}));

const hasAI = timeSeries.some(
Expand Down
72 changes: 41 additions & 31 deletions platform/src/app/[tenant]/repos/[repoName]/investment-hotspots.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
'use client';
"use client";

import { AlertOctagon, Bug, FolderOpen, Link2 } from 'lucide-react';
import { AlertOctagon, Bug, FolderOpen, Link2 } from "lucide-react";

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { useTranslation } from '@/hooks/useTranslation';
import { cn } from '@/lib/utils';
} from "@/components/ui/card";
import { useTranslation } from "@/hooks/useTranslation";
import { cn } from "@/lib/utils";
import type {
HotspotSeverity,
InvestmentHotspot,
InvestmentHotspots as InvestmentHotspotsData,
} from '@/types/invest-here';
} from "@/types/invest-here";

interface InvestmentHotspotsProps {
data: InvestmentHotspotsData;
}

const severityColor: Record<HotspotSeverity, string> = {
high: 'bg-signal-red/10 text-signal-red border-signal-red/30',
medium: 'bg-signal-yellow/10 text-signal-yellow border-signal-yellow/30',
low: 'bg-muted text-muted-foreground border-border',
high: "bg-signal-red/10 text-signal-red border-signal-red/30",
medium: "bg-signal-yellow/10 text-signal-yellow border-signal-yellow/30",
low: "bg-muted text-muted-foreground border-border",
};

function SeverityBadge({ severity }: { severity: HotspotSeverity }) {
const { t } = useTranslation();
const label =
severity === 'high'
? t('investHere.severityHigh')
: severity === 'medium'
? t('investHere.severityMedium')
: t('investHere.severityLow');
severity === "high"
? t("investHere.severityHigh")
: severity === "medium"
? t("investHere.severityMedium")
: t("investHere.severityLow");

return (
<span
className={cn(
'inline-flex shrink-0 items-center rounded-full border px-2 py-0.5 text-xs font-medium',
"inline-flex shrink-0 items-center rounded-full border px-2 py-0.5 text-xs font-medium",
severityColor[severity],
)}
>
Expand All @@ -51,19 +51,21 @@ function SeverityBadge({ severity }: { severity: HotspotSeverity }) {
function HotspotRow({ hotspot }: { hotspot: InvestmentHotspot }) {
const { t } = useTranslation();

if (hotspot.kind === 'weak_directory') {
if (hotspot.kind === "weak_directory") {
return (
<div className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0">
<FolderOpen className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-start justify-between gap-3">
<p className="truncate font-mono text-sm font-medium">
{t('investHere.weakDirectoryTitle', { directory: hotspot.directory })}
{t("investHere.weakDirectoryTitle", {
directory: hotspot.directory,
})}
</p>
<SeverityBadge severity={hotspot.severity} />
</div>
<p className="text-xs text-muted-foreground">
{t('investHere.weakDirectoryReason', {
{t("investHere.weakDirectoryReason", {
ratio: (hotspot.stabilizationRatio * 100).toFixed(0),
files: hotspot.filesTouched,
churn: hotspot.churnEvents,
Expand All @@ -74,19 +76,19 @@ function HotspotRow({ hotspot }: { hotspot: InvestmentHotspot }) {
);
}

if (hotspot.kind === 'tight_coupling') {
if (hotspot.kind === "tight_coupling") {
return (
<div className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0">
<Link2 className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-start justify-between gap-3">
<p className="font-mono text-sm font-medium">
{t('investHere.tightCouplingTitle')}
{t("investHere.tightCouplingTitle")}
</p>
<SeverityBadge severity={hotspot.severity} />
</div>
<p className="text-xs text-muted-foreground">
{t('investHere.tightCouplingReason', {
{t("investHere.tightCouplingReason", {
fileA: hotspot.fileA,
fileB: hotspot.fileB,
rate: (hotspot.couplingRate * 100).toFixed(0),
Expand All @@ -98,20 +100,28 @@ function HotspotRow({ hotspot }: { hotspot: InvestmentHotspot }) {
);
}

// fix_magnet
// fix_magnet — translate the raw origin enum, matching charts.tsx's
// originLabels convention, instead of interpolating "AI_ASSISTED" as-is.
const originLabel =
hotspot.origin === "HUMAN"
? t("repoCharts.origin.labels.human")
: hotspot.origin === "AI_ASSISTED"
? t("repoCharts.origin.labels.ai")
: t("repoCharts.origin.labels.bot");

return (
<div className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0">
<Bug className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-start justify-between gap-3">
<p className="text-sm font-medium">
{t('investHere.fixMagnetTitle', { origin: hotspot.origin })}
{t("investHere.fixMagnetTitle", { origin: originLabel })}
</p>
<SeverityBadge severity={hotspot.severity} />
</div>
<p className="text-xs text-muted-foreground">
{t('investHere.fixMagnetReason', {
origin: hotspot.origin,
{t("investHere.fixMagnetReason", {
origin: originLabel,
codeShare: hotspot.codeSharePct.toFixed(0),
fixShare: hotspot.fixSharePct.toFixed(0),
disp: hotspot.disproportionality.toFixed(1),
Expand All @@ -130,13 +140,13 @@ export function InvestmentHotspots({ data }: InvestmentHotspotsProps) {
return (
<Card>
<CardHeader>
<CardTitle>{t('investHere.title')}</CardTitle>
<CardDescription>{t('investHere.subtitle')}</CardDescription>
<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>
<span>{t("investHere.empty")}</span>
</div>
</CardContent>
</Card>
Expand All @@ -146,8 +156,8 @@ export function InvestmentHotspots({ data }: InvestmentHotspotsProps) {
return (
<Card>
<CardHeader>
<CardTitle>{t('investHere.title')}</CardTitle>
<CardDescription>{t('investHere.subtitle')}</CardDescription>
<CardTitle>{t("investHere.title")}</CardTitle>
<CardDescription>{t("investHere.subtitle")}</CardDescription>
</CardHeader>
<CardContent className="p-0">
<div>
Expand All @@ -156,7 +166,7 @@ export function InvestmentHotspots({ data }: InvestmentHotspotsProps) {
))}
</div>
<p className="border-t border-border px-4 py-3 text-xs text-muted-foreground">
{t('investHere.hypothesisNote')}
{t("investHere.hypothesisNote")}
</p>
</CardContent>
</Card>
Expand Down
2 changes: 0 additions & 2 deletions platform/src/types/org-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ export interface OrgPulse {
aiAdoptionPct: number | null;
aiAdoptionDelta: number | null;
sparklines: {
commits: number[];
stabilization: number[];
aiAdoption: number[];
};
}

Expand Down
4 changes: 0 additions & 4 deletions platform/src/types/temporal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ export interface TimeSeriesPoint {
churn_events: number | null;
commits_total: number | null;
ai_detection_coverage_pct: number | null;
pr_merged_count: number | null;
pr_single_pass_rate: number | null;
fix_latency_median_hours: number | null;
cascade_rate: number | null;
}

/** Summary of a repo's current state + trend. */
Expand Down
Loading