@@ -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