From 9096643986d56ec0289b0eda99233301b124e075 Mon Sep 17 00:00:00 2001 From: Kaparthy Reddy Date: Thu, 28 May 2026 15:04:05 +0530 Subject: [PATCH] fix: return null deltaPercentage when previous month has zero contributions - Hardcoded +100% was misleading regardless of current month total - deltaPercentage is now number | null in MonthlyStats type - calculateMonthlyStats returns null when previousMonthTotal === 0 - generateMonthlySVG and generateAutoThemeMonthlySVG render 'N/A' for null - Updated test to expect null instead of 100 --- lib/calculate.test.ts | 2 +- lib/calculate.ts | 20 +++++++++--------- lib/svg/generator.ts | 48 +++++++++++++++++++++++++------------------ types/index.ts | 4 ++-- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/lib/calculate.test.ts b/lib/calculate.test.ts index 7f5f4fb0..2835efcc 100644 --- a/lib/calculate.test.ts +++ b/lib/calculate.test.ts @@ -348,7 +348,7 @@ describe('calculateMonthlyStats', () => { expect(result.previousMonthTotal).toBe(0); expect(result.currentMonthTotal).toBe(10); - expect(result.deltaPercentage).toBe(100); + expect(result.deltaPercentage).toBeNull(); }); it('handles zero current month contributions', () => { diff --git a/lib/calculate.ts b/lib/calculate.ts index aa7dc05e..d332add7 100644 --- a/lib/calculate.ts +++ b/lib/calculate.ts @@ -207,16 +207,16 @@ export function calculateMonthlyStats( }).format(now); const deltaAbsolute = currentMonthTotal - previousMonthTotal; - let deltaPercentage = 0; - - if (previousMonthTotal === 0) { - if (currentMonthTotal > 0) { - deltaPercentage = 100; - } - } else { - deltaPercentage = Math.round((deltaAbsolute / previousMonthTotal) * 100); - if (deltaPercentage === -0) deltaPercentage = 0; - } + // When there is no baseline (previous month = 0), the percentage change is + // mathematically undefined. Return null so the renderer can display 'N/A' + // instead of the misleading hardcoded +100%. + const deltaPercentage: number | null = + previousMonthTotal === 0 + ? null + : (() => { + const pct = Math.round((deltaAbsolute / previousMonthTotal) * 100); + return pct === -0 ? 0 : pct; + })(); return { currentMonthTotal, diff --git a/lib/svg/generator.ts b/lib/svg/generator.ts index ad7597cf..fa3a6144 100644 --- a/lib/svg/generator.ts +++ b/lib/svg/generator.ts @@ -434,18 +434,22 @@ export function generateMonthlySVG(stats: MonthlyStats, params: BadgeParams): st : `${stats.deltaAbsolute} commits`; } else if (params.delta_format === 'both') { deltaText = - stats.deltaPercentage > 0 - ? `+${stats.deltaPercentage}% (+${stats.deltaAbsolute})` - : stats.deltaPercentage < 0 - ? `${stats.deltaPercentage}% (${stats.deltaAbsolute})` - : `0% (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})`; + stats.deltaPercentage === null + ? `N/A (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})` + : stats.deltaPercentage > 0 + ? `+${stats.deltaPercentage}% (+${stats.deltaAbsolute})` + : stats.deltaPercentage < 0 + ? `${stats.deltaPercentage}% (${stats.deltaAbsolute})` + : `0% (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})`; } else { deltaText = - stats.deltaPercentage > 0 - ? `+${stats.deltaPercentage}%` - : stats.deltaPercentage < 0 - ? `${stats.deltaPercentage}%` - : `0%`; + stats.deltaPercentage === null + ? 'N/A' + : stats.deltaPercentage > 0 + ? `+${stats.deltaPercentage}%` + : stats.deltaPercentage < 0 + ? `${stats.deltaPercentage}%` + : `0%`; } const deltaColor = stats.deltaAbsolute >= 0 ? accent : '#ff4444'; @@ -514,18 +518,22 @@ function generateAutoThemeMonthlySVG(stats: MonthlyStats, params: BadgeParams): : `${stats.deltaAbsolute} commits`; } else if (params.delta_format === 'both') { deltaText = - stats.deltaPercentage > 0 - ? `+${stats.deltaPercentage}% (+${stats.deltaAbsolute})` - : stats.deltaPercentage < 0 - ? `${stats.deltaPercentage}% (${stats.deltaAbsolute})` - : `0% (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})`; + stats.deltaPercentage === null + ? `N/A (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})` + : stats.deltaPercentage > 0 + ? `+${stats.deltaPercentage}% (+${stats.deltaAbsolute})` + : stats.deltaPercentage < 0 + ? `${stats.deltaPercentage}% (${stats.deltaAbsolute})` + : `0% (${stats.deltaAbsolute > 0 ? '+' : ''}${stats.deltaAbsolute})`; } else { deltaText = - stats.deltaPercentage > 0 - ? `+${stats.deltaPercentage}%` - : stats.deltaPercentage < 0 - ? `${stats.deltaPercentage}%` - : `0%`; + stats.deltaPercentage === null + ? 'N/A' + : stats.deltaPercentage > 0 + ? `+${stats.deltaPercentage}%` + : stats.deltaPercentage < 0 + ? `${stats.deltaPercentage}%` + : `0%`; } return ` diff --git a/types/index.ts b/types/index.ts index 8d98e04e..68afb6d4 100644 --- a/types/index.ts +++ b/types/index.ts @@ -77,8 +77,8 @@ export interface MonthlyStats { /** Total number of contributions in the previous calendar month. */ previousMonthTotal: number; - /** Percentage change in contributions compared to the previous month (can be negative). */ - deltaPercentage: number; + /** Percentage change in contributions compared to the previous month (can be negative). Null when previous month has zero contributions (undefined baseline). */ + deltaPercentage: number | null; /** Absolute change in contribution count compared to the previous month (can be negative). */ deltaAbsolute: number;