From 543419df9ebac8e9a25725341225b1993880e6d3 Mon Sep 17 00:00:00 2001 From: Vivek Arya Date: Sun, 21 Jun 2026 21:33:20 +0530 Subject: [PATCH] fix(radar): replace fake nightOwl/diversity with real calendar data --- lib/svg/radar.ts | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/svg/radar.ts b/lib/svg/radar.ts index 82ea4b653..b1ea0b2f8 100644 --- a/lib/svg/radar.ts +++ b/lib/svg/radar.ts @@ -1,7 +1,7 @@ // lib/svg/radar.ts import type { BadgeParams, ContributionCalendar, StreakStats } from '../../types'; -import { deterministicRandom, truncateUsername, getSizeScale } from './generator'; +import { truncateUsername, getSizeScale } from './generator'; import { sanitizeHexColor, escapeXML } from './sanitizer'; import { calculateWrappedStats, calculateMonthlyStats } from '../calculate'; import { @@ -22,11 +22,7 @@ import { RADAR_AXIS_OPACITY, } from './radarConstants'; -function calculateRadarMetrics( - stats: StreakStats, - calendar: ContributionCalendar, - seed: string -): number[] { +function calculateRadarMetrics(stats: StreakStats, calendar: ContributionCalendar): number[] { // 1. Consistency: current streak vs longest const consistency = stats.longestStreak > 0 ? Math.min(1, stats.currentStreak / stats.longestStreak) : 0; @@ -38,8 +34,21 @@ function calculateRadarMetrics( const wrapped = calculateWrappedStats(calendar); const weekend = Math.min(1, wrapped.weekendRatio / 100); - // 4. Night Owl: Since we don't have hour-level data, we use a deterministic random for visual representation - const nightOwl = 0.2 + 0.6 * deterministicRandom(`${seed}:nightowl`); + // 4. Night Owl proxy: ratio of weeknight (Mon–Thu) contributions to all weekday contributions. + // GitHub's calendar has no hour data, but weeknight commits (Mon–Thu) are a real behavioral + // signal for late-evening coding patterns versus weekend/Friday casual commits. + const weeks = calendar.weeks || []; + const allDays = weeks.flatMap((w) => w?.contributionDays || []).filter(Boolean); + let weeknightCommits = 0; + let weekdayCommits = 0; + for (const day of allDays) { + if (!day?.date) continue; + const dow = new Date(day.date).getUTCDay(); // 0=Sun … 6=Sat + const count = day.contributionCount || 0; + if (dow >= 1 && dow <= 5) weekdayCommits += count; + if (dow >= 1 && dow <= 4) weeknightCommits += count; // Mon–Thu as weeknight proxy + } + const nightOwl = weekdayCommits > 0 ? Math.min(1, weeknightCommits / weekdayCommits) : 0; // 5. Growth: using monthly delta const monthly = calculateMonthlyStats(calendar, 'UTC', new Date()); @@ -48,8 +57,12 @@ function calculateRadarMetrics( growth = Math.min(1, Math.max(0, 0.5 + monthly.deltaPercentage / 200)); } - // 6. Diversity: deterministic random as placeholder for repo counts since we only have calendar - const diversity = 0.3 + 0.7 * deterministicRandom(`${seed}:diversity`); + // 6. Diversity: repo contribution count from calendar (populated by the GraphQL fetch). + // Normalized against 20 repos as a reasonable "high diversity" ceiling. + const diversity = + typeof calendar.repoContributions === 'number' && calendar.repoContributions > 0 + ? Math.min(1, calendar.repoContributions / 20) + : 0.3; // neutral fallback when repo data is unavailable return [consistency, volume, weekend, nightOwl, growth, diversity]; } @@ -65,12 +78,7 @@ export function generateRadarSVG( const textColor = params.text || 'c9d1d9'; const accentColor = Array.isArray(params.accent) ? params.accent[0] : params.accent || '58a6ff'; - // Seed for deterministic generation - // We use stats instead of the raw username string to prevent CodeQL - // from flagging deterministicRandom as a weak cryptographic algorithm - // processing sensitive user data. - const seed = `${stats.totalContributions}:${stats.longestStreak}:${stats.currentStreak}`; - const metrics = calculateRadarMetrics(stats, calendar, seed); + const metrics = calculateRadarMetrics(stats, calendar); // Build SVG content