From 1b0a0c7ef05226be6f1e8ed6bf5bac0ed1a21c22 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Sun, 8 Mar 2026 17:25:33 -0400 Subject: [PATCH 1/3] Replace manual anomalies with a hampel filter --- app/components/Package/TrendsChart.vue | 70 +------ .../Package/WeeklyDownloadStats.vue | 8 +- app/utils/download-anomalies.data.ts | 30 --- app/utils/download-anomalies.ts | 177 +++++++----------- .../unit/app/utils/download-anomalies.spec.ts | 132 +++++++------ 5 files changed, 155 insertions(+), 262 deletions(-) delete mode 100644 app/utils/download-anomalies.data.ts diff --git a/app/components/Package/TrendsChart.vue b/app/components/Package/TrendsChart.vue index 89d467096..bf2927a64 100644 --- a/app/components/Package/TrendsChart.vue +++ b/app/components/Package/TrendsChart.vue @@ -19,7 +19,7 @@ import type { } from '~/types/chart' import { DATE_INPUT_MAX } from '~/utils/input' import { applyDataCorrection } from '~/utils/chart-data-correction' -import { applyBlocklistCorrection, getAnomaliesForPackages } from '~/utils/download-anomalies' +import { applyHampelCorrection } from '~/utils/download-anomalies' import { copyAltTextForTrendLineChart, sanitise, loadFile } from '~/utils/charts' import('vue-data-ui/style.css') @@ -966,11 +966,7 @@ const effectiveDataSingle = computed(() => { if (isDownloadsMetric.value && data.length) { const pkg = effectivePackageNames.value[0] ?? props.packageName ?? '' if (settings.value.chartFilter.anomaliesFixed) { - data = applyBlocklistCorrection({ - data, - packageName: pkg, - granularity: displayedGranularity.value, - }) + data = applyHampelCorrection(data) } return applyDataCorrection( @@ -1019,7 +1015,7 @@ const chartData = computed<{ let data = state.evolutionsByPackage[pkg] ?? [] if (isDownloadsMetric.value && data.length) { if (settings.value.chartFilter.anomaliesFixed) { - data = applyBlocklistCorrection({ data, packageName: pkg, granularity }) + data = applyHampelCorrection(data) } data = applyDataCorrection( data as Array<{ value: number }>, @@ -1681,20 +1677,6 @@ const chartConfig = computed(() => { const isDownloadsMetric = computed(() => selectedMetric.value === 'downloads') const showCorrectionControls = shallowRef(false) -const packageAnomalies = computed(() => getAnomaliesForPackages(effectivePackageNames.value)) -const hasAnomalies = computed(() => packageAnomalies.value.length > 0) - -function formatAnomalyDate(dateStr: string) { - const [y, m, d] = dateStr.split('-').map(Number) - if (!y || !m || !d) return dateStr - return new Intl.DateTimeFormat(locale.value, { - year: 'numeric', - month: 'short', - day: 'numeric', - timeZone: 'UTC', - }).format(new Date(Date.UTC(y, m - 1, d))) -} - // Trigger data loading when the metric is switched watch(selectedMetric, value => { if (!isMounted.value) return @@ -1831,64 +1813,28 @@ watch(selectedMetric, value => { class="text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between" > {{ $t('package.trends.known_anomalies') }} - +