Where: src/lib/analytics.ts, rankPerformers() (lines ~100-111),
used by GET /v1/dashboard/performers (default n=5, or
?limit= up to 50 per routes/dashboard.ts).
What's wrong:
export function rankPerformers(scores: ProjectScore[], n = 5) {
const sorted = [...mapped].sort((a, b) => b.combined_score - a.combined_score);
const top = sorted.slice(0, n);
const bottom = sorted.slice(-n).reverse();
return { top, bottom };
}
top and bottom are sliced independently from opposite ends of the same
sorted array with no check that n and n don't overlap. Whenever
scores.length < 2 * n, the two slices share entries — e.g. with 8 total
projects and the default n = 5: top covers ranks 1–5, bottom
(reversed) covers ranks 4–8 — ranks 4 and 5 appear in both lists
simultaneously. This triggers for any deployment with 9 or fewer total
projects at the default n=5 (a realistic count for early-stage/staging/demo
data, exactly when a "top and bottom performers" dashboard widget is most
likely to be looked at closely), and for larger project counts whenever a
caller requests a limit close to or above half the total.
Impact: GET /v1/dashboard/performers can report the same project as
both a top performer and a bottom performer in one response — confusing
for any dashboard rendering "best" and "worst" project lists side by side.
Suggested fix: cap n at Math.floor(scores.length / 2) before
slicing (or explicitly exclude whatever top already selected from the
bottom slice) so the two lists never share a project when there aren't
enough distinct projects to fill both non-overlapping.
Where:
src/lib/analytics.ts,rankPerformers()(lines ~100-111),used by
GET /v1/dashboard/performers(defaultn=5, or?limit=up to 50 perroutes/dashboard.ts).What's wrong:
topandbottomare sliced independently from opposite ends of the samesorted array with no check that
nandndon't overlap. Wheneverscores.length < 2 * n, the two slices share entries — e.g. with 8 totalprojects and the default
n = 5:topcovers ranks 1–5,bottom(reversed) covers ranks 4–8 — ranks 4 and 5 appear in both lists
simultaneously. This triggers for any deployment with 9 or fewer total
projects at the default
n=5(a realistic count for early-stage/staging/demodata, exactly when a "top and bottom performers" dashboard widget is most
likely to be looked at closely), and for larger project counts whenever a
caller requests a
limitclose to or above half the total.Impact:
GET /v1/dashboard/performerscan report the same project asboth a top performer and a bottom performer in one response — confusing
for any dashboard rendering "best" and "worst" project lists side by side.
Suggested fix: cap
natMath.floor(scores.length / 2)beforeslicing (or explicitly exclude whatever
topalready selected from thebottomslice) so the two lists never share a project when there aren'tenough distinct projects to fill both non-overlapping.