diff --git a/app/lib/service/download_counts/package_trends.dart b/app/lib/service/download_counts/package_trends.dart index 690306536c..b14c48b342 100644 --- a/app/lib/service/download_counts/package_trends.dart +++ b/app/lib/service/download_counts/package_trends.dart @@ -5,6 +5,7 @@ import 'dart:math'; const analysisWindowDays = 30; +const totalTrendWindowDays = 330; const minThirtyDaysDownloadThreshold = 30000; /// Calculates the relative daily growth rate of a package's downloads. @@ -22,6 +23,9 @@ const minThirtyDaysDownloadThreshold = 30000; /// downloads (10% relative growth) than for a package with 10000 average daily /// downloads (0.1% relative growth). double computeRelativeGrowthRate(List totalDownloads) { + if (totalDownloads.isEmpty) { + return 0; + } final List data; if (totalDownloads.length < analysisWindowDays) { data = [ @@ -38,7 +42,12 @@ double computeRelativeGrowthRate(List totalDownloads) { recentDownloads.reduce((prev, element) => prev + element) / recentDownloads.length; - if (averageRecentDownloads == 0) { + final m = min(totalDownloads.length, totalTrendWindowDays); + final averageTotalDownloads = + totalDownloads.sublist(0, m).reduce((prev, element) => prev + element) / + m; + + if (averageRecentDownloads == 0 || averageTotalDownloads == 0) { return 0; } @@ -51,7 +60,7 @@ double computeRelativeGrowthRate(List totalDownloads) { // Normalize slope by average downloads to represent relative growth. // This measures how much the download count is growing relative to its // current volume. - return growthRate / averageRecentDownloads; + return growthRate / averageTotalDownloads; } /// Computes the slope of the best-fit line for a given list of data points diff --git a/app/test/service/download_counts/computations_test.dart b/app/test/service/download_counts/computations_test.dart index cffc0f2366..d4d0059811 100644 --- a/app/test/service/download_counts/computations_test.dart +++ b/app/test/service/download_counts/computations_test.dart @@ -314,10 +314,16 @@ void main() { 'fake_download_counts_data_for_trend2.jsonl')); await processDownloadCounts(d); } - final neonTrend = computeTrendScore( - [...List.filled(15, 2000), ...List.filled(15, 1000)]); - final oxygenTrend = computeTrendScore( - [...List.filled(15, 5000), ...List.filled(15, 3000)]); + final neonTrend = computeTrendScore([ + ...List.filled(15, 2000), + ...List.filled(15, 1000), + ...List.filled(701, -1) + ]); + final oxygenTrend = computeTrendScore([ + ...List.filled(15, 5000), + ...List.filled(15, 3000), + ...List.filled(701, -1) + ]); expect(await computeTrend(), {'flutter_titanium': 0.0, 'neon': neonTrend, 'oxygen': oxygenTrend}); diff --git a/app/test/service/download_counts/package_trends_test.dart b/app/test/service/download_counts/package_trends_test.dart index 48f4d1a82c..f7c9323721 100644 --- a/app/test/service/download_counts/package_trends_test.dart +++ b/app/test/service/download_counts/package_trends_test.dart @@ -36,9 +36,12 @@ void main() { test('calculates positive relative growth rate for positive trend', () { // Input list (newest first): [1645, 1635, ..., 1355] (30 values) // Average = 1500 for the first 30 values. Slope: 10. - final downloads = - List.generate(analysisWindowDays * 2, (i) => 1645 - (i * 10)); - final expectedRate = 10.0 / 1500.0; + final downloads = [ + ...List.generate(analysisWindowDays * 2, (i) => 1645 - (i * 10)), + ...List.filled(300, 0) + ]; + final avg = downloads.reduce((prev, element) => prev + element) / 330; + final expectedRate = 10.0 / avg; expect(computeRelativeGrowthRate(downloads), expectedRate); }); @@ -107,10 +110,10 @@ void main() { final downloads = [100, 50]; // For relativeGrowth: // Padded data: [100, 50, 0...0] (28 zeros) - // avg = 150/30 = 5 + // avg = (100 + 50) / 2 = 75. // growthRate = 63750 / 67425 final expectedDampening = min(1.0, 150 / 30000); - final expectedRelativeGrowth = 63750 / 67425 / 5; + final expectedRelativeGrowth = (63750 / 67425) / 75; final expectedScore = expectedRelativeGrowth * expectedDampening * expectedDampening; expect(computeTrendScore(downloads), expectedScore); @@ -178,7 +181,7 @@ void main() { test('Short history, high sum meets threshold -> no dampening', () { final downloads = List.filled(15, 2000); final expectedDampening = min(1.0, 30000 / 30000); - final expectedRelativeGrowth = 6750000 / 67425 / 1000; + final expectedRelativeGrowth = (6750000 / 67425) / 2000; final expectedScore = expectedRelativeGrowth * expectedDampening * expectedDampening;