Skip to content

Commit b4d12a1

Browse files
committed
fix: smooth npm download anomalies
1 parent f7f622a commit b4d12a1

File tree

1 file changed

+85
-1
lines changed

1 file changed

+85
-1
lines changed

src/utils/stats.server.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,88 @@ import type {
2828
OSSStatsWithDelta,
2929
} from './stats.types'
3030

31+
interface NpmDailyDownload {
32+
day: string
33+
downloads: number
34+
}
35+
36+
function toIsoDayUtc(date: Date): string {
37+
return date.toISOString().slice(0, 10)
38+
}
39+
40+
function addUtcDays(day: string, amount: number): string {
41+
const date = new Date(`${day}T00:00:00.000Z`)
42+
date.setUTCDate(date.getUTCDate() + amount)
43+
return toIsoDayUtc(date)
44+
}
45+
46+
/**
47+
* NPM sometimes reports ecosystem-wide zero days.
48+
*
49+
* For isolated zero days after a package has active traffic, backfill with the
50+
* same weekday from the previous week (day - 7), with up to 4 weeks fallback
51+
* when consecutive anomaly weeks occur.
52+
*/
53+
function backfillZeroAnomalyDays(
54+
dailyDownloads: NpmDailyDownload[],
55+
today: string,
56+
): NpmDailyDownload[] {
57+
if (dailyDownloads.length < 8) {
58+
return dailyDownloads
59+
}
60+
61+
const sorted = [...dailyDownloads].sort((a, b) => a.day.localeCompare(b.day))
62+
const originalByDay = new Map(
63+
sorted.map((d) => [d.day, d.downloads] as const),
64+
)
65+
const result = sorted.map((d) => ({ ...d }))
66+
const correctedByDay = new Map(
67+
result.map((d) => [d.day, d.downloads] as const),
68+
)
69+
70+
let seenNonZero = false
71+
72+
for (const point of result) {
73+
if (point.downloads > 0) {
74+
seenNonZero = true
75+
continue
76+
}
77+
78+
if (!seenNonZero || point.day === today) {
79+
continue
80+
}
81+
82+
let previousWeekDownloads: number | undefined
83+
for (let weeksBack = 1; weeksBack <= 4; weeksBack++) {
84+
const previousWeekDay = addUtcDays(point.day, -7 * weeksBack)
85+
const candidate = correctedByDay.get(previousWeekDay)
86+
if (candidate !== undefined && candidate > 0) {
87+
previousWeekDownloads = candidate
88+
break
89+
}
90+
}
91+
92+
if (!previousWeekDownloads) {
93+
continue
94+
}
95+
96+
// Guard against filling true inactivity periods.
97+
const hasNearbyNonZero = [-3, -2, -1, 1, 2, 3].some((offset) => {
98+
const nearbyDownloads = originalByDay.get(addUtcDays(point.day, offset))
99+
return nearbyDownloads !== undefined && nearbyDownloads > 0
100+
})
101+
102+
if (!hasNearbyNonZero) {
103+
continue
104+
}
105+
106+
point.downloads = previousWeekDownloads
107+
correctedByDay.set(point.day, previousWeekDownloads)
108+
}
109+
110+
return result
111+
}
112+
31113
/**
32114
* Fetch NPM package statistics for multiple packages
33115
* Aggregates stats from individual package cache
@@ -370,10 +452,12 @@ export const fetchNpmDownloadsBulk = createServerFn({ method: 'POST' })
370452
new Date(a.day).getTime() - new Date(b.day).getTime(),
371453
)
372454

455+
const correctedDownloads = backfillZeroAnomalyDays(allDownloads, today)
456+
373457
return {
374458
name: pkg.name,
375459
hidden: pkg.hidden,
376-
downloads: allDownloads,
460+
downloads: correctedDownloads,
377461
}
378462
})
379463

0 commit comments

Comments
 (0)