@mcptoolshop/registry-stats 2.3.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +137 -134
- package/README.fr.md +137 -134
- package/README.hi.md +137 -134
- package/README.it.md +137 -134
- package/README.ja.md +137 -134
- package/README.md +47 -5
- package/README.pt-BR.md +137 -134
- package/README.zh.md +137 -134
- package/dist/cli.js +4 -3
- package/dist/index.cjs +311 -3
- package/dist/index.d.cts +108 -1
- package/dist/index.d.ts +108 -1
- package/dist/index.js +304 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22,11 +22,18 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
RegistryError: () => RegistryError,
|
|
24
24
|
calc: () => calc,
|
|
25
|
+
computeMomentum: () => computeMomentum,
|
|
25
26
|
createCache: () => createCache,
|
|
26
27
|
createHandler: () => createHandler,
|
|
27
28
|
defaultConfig: () => defaultConfig,
|
|
29
|
+
detectAnomalies: () => detectAnomalies,
|
|
30
|
+
detectSeasonality: () => detectSeasonality,
|
|
31
|
+
forecast: () => forecast,
|
|
32
|
+
generateRecommendations: () => generateRecommendations,
|
|
33
|
+
inferPortfolio: () => inferPortfolio,
|
|
28
34
|
loadConfig: () => loadConfig,
|
|
29
35
|
registerProvider: () => registerProvider,
|
|
36
|
+
segmentTrends: () => segmentTrends,
|
|
30
37
|
serve: () => serve,
|
|
31
38
|
starterConfig: () => starterConfig,
|
|
32
39
|
stats: () => stats
|
|
@@ -69,7 +76,7 @@ async function fetchWithRetry(url, registry, init) {
|
|
|
69
76
|
let lastError;
|
|
70
77
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
71
78
|
await acquireSlot(registry);
|
|
72
|
-
const res = await fetch(url, init);
|
|
79
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(3e4), ...init });
|
|
73
80
|
if (res.status === 404) return null;
|
|
74
81
|
if (res.ok) return res.json();
|
|
75
82
|
const retryAfter = res.headers.get("retry-after");
|
|
@@ -91,7 +98,7 @@ async function fetchWithRetry(url, registry, init) {
|
|
|
91
98
|
async function fetchDirect(url, registry, init) {
|
|
92
99
|
let lastError;
|
|
93
100
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
94
|
-
const res = await fetch(url, init);
|
|
101
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(3e4), ...init });
|
|
95
102
|
if (res.status === 404) return null;
|
|
96
103
|
if (res.ok) return res.json();
|
|
97
104
|
const retryAfter = res.headers.get("retry-after");
|
|
@@ -313,7 +320,8 @@ var docker = {
|
|
|
313
320
|
if (options?.dockerToken) {
|
|
314
321
|
headers["Authorization"] = `Bearer ${options.dockerToken}`;
|
|
315
322
|
}
|
|
316
|
-
const
|
|
323
|
+
const safePkg = pkg.split("/").map((s) => encodeURIComponent(s)).join("/");
|
|
324
|
+
const json2 = await fetchWithRetry(`${API4}/${safePkg}`, "docker", { headers });
|
|
317
325
|
if (!json2 || !json2.name || !json2.namespace) return null;
|
|
318
326
|
return {
|
|
319
327
|
registry: "docker",
|
|
@@ -600,6 +608,299 @@ Endpoints:`);
|
|
|
600
608
|
return server;
|
|
601
609
|
}
|
|
602
610
|
|
|
611
|
+
// src/inference.ts
|
|
612
|
+
function mean(arr) {
|
|
613
|
+
if (arr.length === 0) return 0;
|
|
614
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
615
|
+
}
|
|
616
|
+
function stddev(arr) {
|
|
617
|
+
if (arr.length < 2) return 0;
|
|
618
|
+
const m = mean(arr);
|
|
619
|
+
const variance = arr.reduce((s, v) => s + (v - m) ** 2, 0) / arr.length;
|
|
620
|
+
return Math.sqrt(variance);
|
|
621
|
+
}
|
|
622
|
+
function linearRegression(ys) {
|
|
623
|
+
const n = ys.length;
|
|
624
|
+
if (n < 2) return { slope: 0, intercept: ys[0] ?? 0, r2: 0 };
|
|
625
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
626
|
+
for (let i = 0; i < n; i++) {
|
|
627
|
+
sumX += i;
|
|
628
|
+
sumY += ys[i];
|
|
629
|
+
sumXY += i * ys[i];
|
|
630
|
+
sumX2 += i * i;
|
|
631
|
+
}
|
|
632
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
633
|
+
if (denom === 0) return { slope: 0, intercept: sumY / n, r2: 0 };
|
|
634
|
+
const slope = (n * sumXY - sumX * sumY) / denom;
|
|
635
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
636
|
+
const meanY = sumY / n;
|
|
637
|
+
let ssTot = 0, ssRes = 0;
|
|
638
|
+
for (let i = 0; i < n; i++) {
|
|
639
|
+
ssTot += (ys[i] - meanY) ** 2;
|
|
640
|
+
ssRes += (ys[i] - (intercept + slope * i)) ** 2;
|
|
641
|
+
}
|
|
642
|
+
const r2 = ssTot > 0 ? 1 - ssRes / ssTot : 0;
|
|
643
|
+
return { slope, intercept, r2 };
|
|
644
|
+
}
|
|
645
|
+
var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
646
|
+
function forecast(series, days = 7) {
|
|
647
|
+
if (series.length < 7) return [];
|
|
648
|
+
const window = series.slice(-Math.min(14, series.length));
|
|
649
|
+
const n = window.length;
|
|
650
|
+
const weights = window.map((_, i) => Math.exp(0.1 * (i - n + 1)));
|
|
651
|
+
const totalW = weights.reduce((a, b) => a + b, 0);
|
|
652
|
+
let wSumX = 0, wSumY = 0, wSumXY = 0, wSumX2 = 0;
|
|
653
|
+
for (let i = 0; i < n; i++) {
|
|
654
|
+
const w = weights[i];
|
|
655
|
+
wSumX += w * i;
|
|
656
|
+
wSumY += w * window[i];
|
|
657
|
+
wSumXY += w * i * window[i];
|
|
658
|
+
wSumX2 += w * i * i;
|
|
659
|
+
}
|
|
660
|
+
const denom = totalW * wSumX2 - wSumX * wSumX;
|
|
661
|
+
let slope, intercept;
|
|
662
|
+
if (Math.abs(denom) < 1e-10) {
|
|
663
|
+
slope = 0;
|
|
664
|
+
intercept = wSumY / totalW;
|
|
665
|
+
} else {
|
|
666
|
+
slope = (totalW * wSumXY - wSumX * wSumY) / denom;
|
|
667
|
+
intercept = (wSumY - slope * wSumX) / totalW;
|
|
668
|
+
}
|
|
669
|
+
let ssRes = 0;
|
|
670
|
+
for (let i = 0; i < n; i++) {
|
|
671
|
+
ssRes += weights[i] * (window[i] - (intercept + slope * i)) ** 2;
|
|
672
|
+
}
|
|
673
|
+
const rse = Math.sqrt(ssRes / Math.max(1, totalW - 2));
|
|
674
|
+
const z80 = 1.28;
|
|
675
|
+
const results = [];
|
|
676
|
+
for (let d = 1; d <= days; d++) {
|
|
677
|
+
const x = n - 1 + d;
|
|
678
|
+
const predicted = Math.max(0, Math.round(intercept + slope * x));
|
|
679
|
+
const margin = Math.round(z80 * rse * Math.sqrt(1 + 1 / n + (x - n / 2) ** 2 / (n * n / 12)));
|
|
680
|
+
results.push({
|
|
681
|
+
day: d,
|
|
682
|
+
predicted,
|
|
683
|
+
lower: Math.max(0, predicted - margin),
|
|
684
|
+
upper: predicted + margin
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return results;
|
|
688
|
+
}
|
|
689
|
+
function detectAnomalies(series, threshold = 2) {
|
|
690
|
+
if (series.length < 7) return [];
|
|
691
|
+
const anomalies = [];
|
|
692
|
+
const windowSize = Math.min(14, Math.floor(series.length * 0.7));
|
|
693
|
+
for (let i = windowSize; i < series.length; i++) {
|
|
694
|
+
const window = series.slice(i - windowSize, i);
|
|
695
|
+
const m = mean(window);
|
|
696
|
+
const s = stddev(window);
|
|
697
|
+
if (s < 1) continue;
|
|
698
|
+
const zscore = (series[i] - m) / s;
|
|
699
|
+
if (Math.abs(zscore) >= threshold) {
|
|
700
|
+
anomalies.push({
|
|
701
|
+
day: i,
|
|
702
|
+
value: series[i],
|
|
703
|
+
expected: Math.round(m),
|
|
704
|
+
zscore: Math.round(zscore * 10) / 10,
|
|
705
|
+
type: zscore > 0 ? "spike" : "drop"
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return anomalies;
|
|
710
|
+
}
|
|
711
|
+
function segmentTrends(series, minSegmentLength = 5) {
|
|
712
|
+
if (series.length < minSegmentLength) return [];
|
|
713
|
+
const segments = [];
|
|
714
|
+
let segStart = 0;
|
|
715
|
+
while (segStart < series.length - minSegmentLength + 1) {
|
|
716
|
+
let bestEnd = segStart + minSegmentLength - 1;
|
|
717
|
+
const initialSlope = linearRegression(series.slice(segStart, segStart + minSegmentLength)).slope;
|
|
718
|
+
const initialDir = initialSlope > 0.5 ? "up" : initialSlope < -0.5 ? "down" : "flat";
|
|
719
|
+
for (let end = segStart + minSegmentLength; end < series.length; end++) {
|
|
720
|
+
const seg2 = series.slice(segStart, end + 1);
|
|
721
|
+
const { slope: slope2 } = linearRegression(seg2);
|
|
722
|
+
const dir = slope2 > 0.5 ? "up" : slope2 < -0.5 ? "down" : "flat";
|
|
723
|
+
if (dir !== initialDir) break;
|
|
724
|
+
bestEnd = end;
|
|
725
|
+
}
|
|
726
|
+
const seg = series.slice(segStart, bestEnd + 1);
|
|
727
|
+
const { slope } = linearRegression(seg);
|
|
728
|
+
const direction = slope > 0.5 ? "up" : slope < -0.5 ? "down" : "flat";
|
|
729
|
+
segments.push({
|
|
730
|
+
start: segStart,
|
|
731
|
+
end: bestEnd,
|
|
732
|
+
direction,
|
|
733
|
+
slope: Math.round(slope * 10) / 10,
|
|
734
|
+
magnitude: Math.round(seg[seg.length - 1] - seg[0])
|
|
735
|
+
});
|
|
736
|
+
segStart = bestEnd + 1;
|
|
737
|
+
}
|
|
738
|
+
return segments;
|
|
739
|
+
}
|
|
740
|
+
function detectSeasonality(series, startDaysAgo) {
|
|
741
|
+
if (series.length < 14) return null;
|
|
742
|
+
const buckets = [[], [], [], [], [], [], []];
|
|
743
|
+
const today = /* @__PURE__ */ new Date();
|
|
744
|
+
for (let i = 0; i < series.length; i++) {
|
|
745
|
+
const date = new Date(today);
|
|
746
|
+
date.setDate(date.getDate() - (startDaysAgo - i));
|
|
747
|
+
const dow = date.getDay();
|
|
748
|
+
buckets[dow].push(series[i]);
|
|
749
|
+
}
|
|
750
|
+
const dayAvgs = buckets.map((b) => Math.round(mean(b)));
|
|
751
|
+
const overallMean = mean(dayAvgs);
|
|
752
|
+
const maxAvg = Math.max(...dayAvgs);
|
|
753
|
+
const minAvg = Math.min(...dayAvgs);
|
|
754
|
+
if (overallMean < 1 || (maxAvg - minAvg) / overallMean < 0.15) return null;
|
|
755
|
+
const peakIdx = dayAvgs.indexOf(maxAvg);
|
|
756
|
+
return {
|
|
757
|
+
dayOfWeek: dayAvgs,
|
|
758
|
+
peakDay: DAY_NAMES[peakIdx]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function computeMomentum(series) {
|
|
762
|
+
if (series.length < 14) return 0;
|
|
763
|
+
const last7 = series.slice(-7);
|
|
764
|
+
const prev7 = series.slice(-14, -7);
|
|
765
|
+
const last7Sum = last7.reduce((a, b) => a + b, 0);
|
|
766
|
+
const prev7Sum = prev7.reduce((a, b) => a + b, 0);
|
|
767
|
+
const dampK = 10;
|
|
768
|
+
const dirScore = prev7Sum > dampK ? Math.max(-40, Math.min(40, (last7Sum - prev7Sum) / Math.sqrt(prev7Sum + dampK) * 4)) : last7Sum > 0 ? 20 : 0;
|
|
769
|
+
const { slope: recentSlope } = linearRegression(last7);
|
|
770
|
+
const { slope: prevSlope } = linearRegression(prev7);
|
|
771
|
+
const accelScore = Math.max(-20, Math.min(20, (recentSlope - prevSlope) * 2));
|
|
772
|
+
const cv = last7Sum > 0 ? stddev(last7) / mean(last7) : 1;
|
|
773
|
+
const consistencyScore = Math.max(0, 20 - cv * 20);
|
|
774
|
+
const volumeScore = last7Sum > 0 ? Math.min(20, Math.log10(last7Sum + 1) * 5) : 0;
|
|
775
|
+
return Math.round(Math.max(-100, Math.min(100, dirScore + accelScore + consistencyScore + volumeScore)));
|
|
776
|
+
}
|
|
777
|
+
function generateRecommendations(packages, opts = {}) {
|
|
778
|
+
const recs = [];
|
|
779
|
+
const declining = packages.filter((p) => p.momentum < -30);
|
|
780
|
+
if (declining.length > 0) {
|
|
781
|
+
const names = declining.slice(0, 3).map((p) => p.name).join(", ");
|
|
782
|
+
recs.push({
|
|
783
|
+
type: "attention",
|
|
784
|
+
priority: declining.some((p) => p.momentum < -60) ? "high" : "medium",
|
|
785
|
+
title: `${declining.length} package${declining.length > 1 ? "s" : ""} losing momentum`,
|
|
786
|
+
detail: `${names}${declining.length > 3 ? ` and ${declining.length - 3} more` : ""} show sustained decline. Consider: release updates, fix open issues, or update documentation.`,
|
|
787
|
+
metric: `Worst momentum: ${Math.min(...declining.map((p) => p.momentum))}`
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
if (opts.gini !== void 0 && opts.gini > 0.7) {
|
|
791
|
+
recs.push({
|
|
792
|
+
type: "risk",
|
|
793
|
+
priority: opts.gini > 0.85 ? "high" : "medium",
|
|
794
|
+
title: "High portfolio concentration",
|
|
795
|
+
detail: `Gini coefficient ${opts.gini.toFixed(2)} indicates downloads are heavily concentrated in a few packages. Diversify promotion efforts across the portfolio.`,
|
|
796
|
+
metric: `Gini: ${opts.gini.toFixed(2)}`
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (opts.npmPct !== void 0 && opts.npmPct > 75) {
|
|
800
|
+
recs.push({
|
|
801
|
+
type: "risk",
|
|
802
|
+
priority: "medium",
|
|
803
|
+
title: "Heavy npm dependency",
|
|
804
|
+
detail: `${opts.npmPct}% of downloads come from npm. Consider cross-publishing to PyPI and NuGet to reduce single-registry risk.`,
|
|
805
|
+
metric: `npm share: ${opts.npmPct}%`
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const growing = packages.filter((p) => p.momentum > 40 && p.anomalies.some((a) => a.type === "spike"));
|
|
809
|
+
if (growing.length > 0) {
|
|
810
|
+
const names = growing.slice(0, 3).map((p) => p.name).join(", ");
|
|
811
|
+
recs.push({
|
|
812
|
+
type: "opportunity",
|
|
813
|
+
priority: "medium",
|
|
814
|
+
title: `${growing.length} package${growing.length > 1 ? "s" : ""} gaining traction`,
|
|
815
|
+
detail: `${names} show organic growth with download spikes. Capitalize with blog posts, social media, or conference talks.`,
|
|
816
|
+
metric: `Best momentum: ${Math.max(...growing.map((p) => p.momentum))}`
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
const forecastGrowing = packages.filter((p) => {
|
|
820
|
+
if (p.forecast7.length < 7) return false;
|
|
821
|
+
const lastActual = p.forecast7[0]?.predicted ?? 0;
|
|
822
|
+
const lastForecast = p.forecast7[6]?.predicted ?? 0;
|
|
823
|
+
return lastForecast > lastActual * 1.2;
|
|
824
|
+
});
|
|
825
|
+
if (forecastGrowing.length > 0) {
|
|
826
|
+
recs.push({
|
|
827
|
+
type: "growth",
|
|
828
|
+
priority: "low",
|
|
829
|
+
title: `${forecastGrowing.length} package${forecastGrowing.length > 1 ? "s" : ""} predicted to grow`,
|
|
830
|
+
detail: `Statistical models predict >20% growth in the next 7 days for ${forecastGrowing.slice(0, 3).map((p) => p.name).join(", ")}.`
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
const spiked = packages.filter((p) => p.anomalies.filter((a) => a.type === "spike").length >= 2);
|
|
834
|
+
if (spiked.length > 0) {
|
|
835
|
+
recs.push({
|
|
836
|
+
type: "attention",
|
|
837
|
+
priority: "low",
|
|
838
|
+
title: `${spiked.length} package${spiked.length > 1 ? "s" : ""} with repeated spikes`,
|
|
839
|
+
detail: `Multiple download spikes detected for ${spiked.slice(0, 3).map((p) => p.name).join(", ")}. Could indicate bot activity, viral posts, or dependency adoption.`
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
843
|
+
recs.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
844
|
+
return recs;
|
|
845
|
+
}
|
|
846
|
+
function inferPortfolio(leaderboard, opts = {}) {
|
|
847
|
+
const packages = [];
|
|
848
|
+
for (const row of leaderboard) {
|
|
849
|
+
const series = row.range30;
|
|
850
|
+
if (!series || series.length < 7) {
|
|
851
|
+
packages.push({
|
|
852
|
+
name: row.name,
|
|
853
|
+
registry: row.registry,
|
|
854
|
+
forecast7: [],
|
|
855
|
+
anomalies: [],
|
|
856
|
+
trendSegments: [],
|
|
857
|
+
seasonality: null,
|
|
858
|
+
momentum: 0
|
|
859
|
+
});
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
packages.push({
|
|
863
|
+
name: row.name,
|
|
864
|
+
registry: row.registry,
|
|
865
|
+
forecast7: forecast(series, 7),
|
|
866
|
+
anomalies: detectAnomalies(series),
|
|
867
|
+
trendSegments: segmentTrends(series),
|
|
868
|
+
seasonality: detectSeasonality(series, 30),
|
|
869
|
+
momentum: computeMomentum(series)
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
const forecastTotal7 = new Array(7).fill(0);
|
|
873
|
+
for (const pkg of packages) {
|
|
874
|
+
for (const pt of pkg.forecast7) {
|
|
875
|
+
forecastTotal7[pt.day - 1] += pt.predicted;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const totalWeek = leaderboard.reduce((s, r) => s + (r.week ?? 0), 0);
|
|
879
|
+
let weightedMomentum = 0;
|
|
880
|
+
for (const pkg of packages) {
|
|
881
|
+
const row = leaderboard.find((r) => r.name === pkg.name);
|
|
882
|
+
const weight = totalWeek > 0 ? (row?.week ?? 0) / totalWeek : 1 / packages.length;
|
|
883
|
+
weightedMomentum += pkg.momentum * weight;
|
|
884
|
+
}
|
|
885
|
+
const decliningPct = packages.filter((p) => p.momentum < -20).length / Math.max(1, packages.length);
|
|
886
|
+
const totalAnomalies = packages.reduce((s, p) => s + p.anomalies.length, 0);
|
|
887
|
+
const anomalyDensity = totalAnomalies / Math.max(1, packages.length);
|
|
888
|
+
const giniRisk = (opts.gini ?? 0) * 30;
|
|
889
|
+
const declineRisk = decliningPct * 40;
|
|
890
|
+
const anomalyRisk = Math.min(30, anomalyDensity * 10);
|
|
891
|
+
const riskScore = Math.round(Math.max(0, Math.min(100, giniRisk + declineRisk + anomalyRisk)));
|
|
892
|
+
const diversityTrend = "stable";
|
|
893
|
+
const recommendations = generateRecommendations(packages, opts);
|
|
894
|
+
return {
|
|
895
|
+
packages,
|
|
896
|
+
recommendations,
|
|
897
|
+
forecastTotal7,
|
|
898
|
+
riskScore,
|
|
899
|
+
diversityTrend,
|
|
900
|
+
portfolioMomentum: Math.round(weightedMomentum)
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
603
904
|
// src/index.ts
|
|
604
905
|
function createCache() {
|
|
605
906
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -797,11 +1098,18 @@ stats.mine = async function mine(maintainer, options) {
|
|
|
797
1098
|
0 && (module.exports = {
|
|
798
1099
|
RegistryError,
|
|
799
1100
|
calc,
|
|
1101
|
+
computeMomentum,
|
|
800
1102
|
createCache,
|
|
801
1103
|
createHandler,
|
|
802
1104
|
defaultConfig,
|
|
1105
|
+
detectAnomalies,
|
|
1106
|
+
detectSeasonality,
|
|
1107
|
+
forecast,
|
|
1108
|
+
generateRecommendations,
|
|
1109
|
+
inferPortfolio,
|
|
803
1110
|
loadConfig,
|
|
804
1111
|
registerProvider,
|
|
1112
|
+
segmentTrends,
|
|
805
1113
|
serve,
|
|
806
1114
|
starterConfig,
|
|
807
1115
|
stats
|
package/dist/index.d.cts
CHANGED
|
@@ -116,6 +116,113 @@ declare function createHandler(opts?: StatsOptions): Handler;
|
|
|
116
116
|
/** Starts an HTTP server. Returns the server instance. */
|
|
117
117
|
declare function serve(opts?: ServerOptions): node_http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* AI Inference Module — statistical forecasting, anomaly detection, and recommendations.
|
|
121
|
+
*
|
|
122
|
+
* Zero dependencies. Pure math on time-series data arrays.
|
|
123
|
+
* Designed to run at build-time in the fetch-stats pipeline.
|
|
124
|
+
*/
|
|
125
|
+
interface ForecastPoint {
|
|
126
|
+
day: number;
|
|
127
|
+
predicted: number;
|
|
128
|
+
lower: number;
|
|
129
|
+
upper: number;
|
|
130
|
+
}
|
|
131
|
+
interface Anomaly {
|
|
132
|
+
day: number;
|
|
133
|
+
value: number;
|
|
134
|
+
expected: number;
|
|
135
|
+
zscore: number;
|
|
136
|
+
type: 'spike' | 'drop';
|
|
137
|
+
}
|
|
138
|
+
interface TrendSegment {
|
|
139
|
+
start: number;
|
|
140
|
+
end: number;
|
|
141
|
+
direction: 'up' | 'down' | 'flat';
|
|
142
|
+
slope: number;
|
|
143
|
+
magnitude: number;
|
|
144
|
+
}
|
|
145
|
+
interface Recommendation {
|
|
146
|
+
type: 'growth' | 'risk' | 'opportunity' | 'attention';
|
|
147
|
+
priority: 'high' | 'medium' | 'low';
|
|
148
|
+
title: string;
|
|
149
|
+
detail: string;
|
|
150
|
+
metric?: string;
|
|
151
|
+
}
|
|
152
|
+
interface PackageInference {
|
|
153
|
+
name: string;
|
|
154
|
+
registry: string;
|
|
155
|
+
forecast7: ForecastPoint[];
|
|
156
|
+
anomalies: Anomaly[];
|
|
157
|
+
trendSegments: TrendSegment[];
|
|
158
|
+
seasonality: {
|
|
159
|
+
dayOfWeek: number[];
|
|
160
|
+
peakDay: string;
|
|
161
|
+
} | null;
|
|
162
|
+
momentum: number;
|
|
163
|
+
}
|
|
164
|
+
interface PortfolioInference {
|
|
165
|
+
packages: PackageInference[];
|
|
166
|
+
recommendations: Recommendation[];
|
|
167
|
+
forecastTotal7: number[];
|
|
168
|
+
riskScore: number;
|
|
169
|
+
diversityTrend: 'improving' | 'stable' | 'declining';
|
|
170
|
+
portfolioMomentum: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Forecast next N days using weighted linear regression on recent data.
|
|
174
|
+
* Uses the last 14 days with exponential weighting (recent days matter more).
|
|
175
|
+
* Returns predictions with 80% confidence intervals.
|
|
176
|
+
*/
|
|
177
|
+
declare function forecast(series: number[], days?: number): ForecastPoint[];
|
|
178
|
+
/**
|
|
179
|
+
* Detect anomalies using adaptive z-score with rolling baseline.
|
|
180
|
+
* More sophisticated than simple global z-score — uses a 14-day rolling
|
|
181
|
+
* window so seasonal patterns don't trigger false positives.
|
|
182
|
+
*/
|
|
183
|
+
declare function detectAnomalies(series: number[], threshold?: number): Anomaly[];
|
|
184
|
+
/**
|
|
185
|
+
* Segment a time series into directional trend segments.
|
|
186
|
+
* Uses a simple piecewise linear approach with minimum segment length.
|
|
187
|
+
*/
|
|
188
|
+
declare function segmentTrends(series: number[], minSegmentLength?: number): TrendSegment[];
|
|
189
|
+
/**
|
|
190
|
+
* Detect day-of-week seasonality patterns.
|
|
191
|
+
* Requires at least 14 days of data to identify weekly cycles.
|
|
192
|
+
*/
|
|
193
|
+
declare function detectSeasonality(series: number[], startDaysAgo: number): {
|
|
194
|
+
dayOfWeek: number[];
|
|
195
|
+
peakDay: string;
|
|
196
|
+
} | null;
|
|
197
|
+
/**
|
|
198
|
+
* Compute a composite momentum score (-100 to +100).
|
|
199
|
+
* Combines: short-term trend, acceleration, volume, and consistency.
|
|
200
|
+
*/
|
|
201
|
+
declare function computeMomentum(series: number[]): number;
|
|
202
|
+
/**
|
|
203
|
+
* Generate automated recommendations based on portfolio analysis.
|
|
204
|
+
*/
|
|
205
|
+
declare function generateRecommendations(packages: PackageInference[], opts?: {
|
|
206
|
+
gini?: number;
|
|
207
|
+
npmPct?: number;
|
|
208
|
+
totalWeekly?: number;
|
|
209
|
+
}): Recommendation[];
|
|
210
|
+
/**
|
|
211
|
+
* Run full inference pipeline on a leaderboard dataset.
|
|
212
|
+
* This is the main entry point called from fetch-stats.mjs.
|
|
213
|
+
*/
|
|
214
|
+
declare function inferPortfolio(leaderboard: Array<{
|
|
215
|
+
name: string;
|
|
216
|
+
registry: string;
|
|
217
|
+
week: number;
|
|
218
|
+
range30?: number[] | null;
|
|
219
|
+
trendPct?: number | null;
|
|
220
|
+
}>, opts?: {
|
|
221
|
+
gini?: number;
|
|
222
|
+
npmPct?: number;
|
|
223
|
+
totalWeekly?: number;
|
|
224
|
+
}): PortfolioInference;
|
|
225
|
+
|
|
119
226
|
declare function createCache(): StatsCache;
|
|
120
227
|
|
|
121
228
|
declare function registerProvider(provider: RegistryProvider): void;
|
|
@@ -130,4 +237,4 @@ declare namespace stats {
|
|
|
130
237
|
}) => Promise<PackageStats[]>;
|
|
131
238
|
}
|
|
132
239
|
|
|
133
|
-
export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
|
|
240
|
+
export { type Anomaly, type ChartData, type ComparisonResult, type Config, type DailyDownloads, type ForecastPoint, type PackageConfig, type PackageInference, type PackageStats, type PortfolioInference, type RateLimitConfig, type Recommendation, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, type TrendSegment, calc, computeMomentum, createCache, createHandler, defaultConfig, detectAnomalies, detectSeasonality, forecast, generateRecommendations, inferPortfolio, loadConfig, registerProvider, segmentTrends, serve, starterConfig, stats };
|
package/dist/index.d.ts
CHANGED
|
@@ -116,6 +116,113 @@ declare function createHandler(opts?: StatsOptions): Handler;
|
|
|
116
116
|
/** Starts an HTTP server. Returns the server instance. */
|
|
117
117
|
declare function serve(opts?: ServerOptions): node_http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* AI Inference Module — statistical forecasting, anomaly detection, and recommendations.
|
|
121
|
+
*
|
|
122
|
+
* Zero dependencies. Pure math on time-series data arrays.
|
|
123
|
+
* Designed to run at build-time in the fetch-stats pipeline.
|
|
124
|
+
*/
|
|
125
|
+
interface ForecastPoint {
|
|
126
|
+
day: number;
|
|
127
|
+
predicted: number;
|
|
128
|
+
lower: number;
|
|
129
|
+
upper: number;
|
|
130
|
+
}
|
|
131
|
+
interface Anomaly {
|
|
132
|
+
day: number;
|
|
133
|
+
value: number;
|
|
134
|
+
expected: number;
|
|
135
|
+
zscore: number;
|
|
136
|
+
type: 'spike' | 'drop';
|
|
137
|
+
}
|
|
138
|
+
interface TrendSegment {
|
|
139
|
+
start: number;
|
|
140
|
+
end: number;
|
|
141
|
+
direction: 'up' | 'down' | 'flat';
|
|
142
|
+
slope: number;
|
|
143
|
+
magnitude: number;
|
|
144
|
+
}
|
|
145
|
+
interface Recommendation {
|
|
146
|
+
type: 'growth' | 'risk' | 'opportunity' | 'attention';
|
|
147
|
+
priority: 'high' | 'medium' | 'low';
|
|
148
|
+
title: string;
|
|
149
|
+
detail: string;
|
|
150
|
+
metric?: string;
|
|
151
|
+
}
|
|
152
|
+
interface PackageInference {
|
|
153
|
+
name: string;
|
|
154
|
+
registry: string;
|
|
155
|
+
forecast7: ForecastPoint[];
|
|
156
|
+
anomalies: Anomaly[];
|
|
157
|
+
trendSegments: TrendSegment[];
|
|
158
|
+
seasonality: {
|
|
159
|
+
dayOfWeek: number[];
|
|
160
|
+
peakDay: string;
|
|
161
|
+
} | null;
|
|
162
|
+
momentum: number;
|
|
163
|
+
}
|
|
164
|
+
interface PortfolioInference {
|
|
165
|
+
packages: PackageInference[];
|
|
166
|
+
recommendations: Recommendation[];
|
|
167
|
+
forecastTotal7: number[];
|
|
168
|
+
riskScore: number;
|
|
169
|
+
diversityTrend: 'improving' | 'stable' | 'declining';
|
|
170
|
+
portfolioMomentum: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Forecast next N days using weighted linear regression on recent data.
|
|
174
|
+
* Uses the last 14 days with exponential weighting (recent days matter more).
|
|
175
|
+
* Returns predictions with 80% confidence intervals.
|
|
176
|
+
*/
|
|
177
|
+
declare function forecast(series: number[], days?: number): ForecastPoint[];
|
|
178
|
+
/**
|
|
179
|
+
* Detect anomalies using adaptive z-score with rolling baseline.
|
|
180
|
+
* More sophisticated than simple global z-score — uses a 14-day rolling
|
|
181
|
+
* window so seasonal patterns don't trigger false positives.
|
|
182
|
+
*/
|
|
183
|
+
declare function detectAnomalies(series: number[], threshold?: number): Anomaly[];
|
|
184
|
+
/**
|
|
185
|
+
* Segment a time series into directional trend segments.
|
|
186
|
+
* Uses a simple piecewise linear approach with minimum segment length.
|
|
187
|
+
*/
|
|
188
|
+
declare function segmentTrends(series: number[], minSegmentLength?: number): TrendSegment[];
|
|
189
|
+
/**
|
|
190
|
+
* Detect day-of-week seasonality patterns.
|
|
191
|
+
* Requires at least 14 days of data to identify weekly cycles.
|
|
192
|
+
*/
|
|
193
|
+
declare function detectSeasonality(series: number[], startDaysAgo: number): {
|
|
194
|
+
dayOfWeek: number[];
|
|
195
|
+
peakDay: string;
|
|
196
|
+
} | null;
|
|
197
|
+
/**
|
|
198
|
+
* Compute a composite momentum score (-100 to +100).
|
|
199
|
+
* Combines: short-term trend, acceleration, volume, and consistency.
|
|
200
|
+
*/
|
|
201
|
+
declare function computeMomentum(series: number[]): number;
|
|
202
|
+
/**
|
|
203
|
+
* Generate automated recommendations based on portfolio analysis.
|
|
204
|
+
*/
|
|
205
|
+
declare function generateRecommendations(packages: PackageInference[], opts?: {
|
|
206
|
+
gini?: number;
|
|
207
|
+
npmPct?: number;
|
|
208
|
+
totalWeekly?: number;
|
|
209
|
+
}): Recommendation[];
|
|
210
|
+
/**
|
|
211
|
+
* Run full inference pipeline on a leaderboard dataset.
|
|
212
|
+
* This is the main entry point called from fetch-stats.mjs.
|
|
213
|
+
*/
|
|
214
|
+
declare function inferPortfolio(leaderboard: Array<{
|
|
215
|
+
name: string;
|
|
216
|
+
registry: string;
|
|
217
|
+
week: number;
|
|
218
|
+
range30?: number[] | null;
|
|
219
|
+
trendPct?: number | null;
|
|
220
|
+
}>, opts?: {
|
|
221
|
+
gini?: number;
|
|
222
|
+
npmPct?: number;
|
|
223
|
+
totalWeekly?: number;
|
|
224
|
+
}): PortfolioInference;
|
|
225
|
+
|
|
119
226
|
declare function createCache(): StatsCache;
|
|
120
227
|
|
|
121
228
|
declare function registerProvider(provider: RegistryProvider): void;
|
|
@@ -130,4 +237,4 @@ declare namespace stats {
|
|
|
130
237
|
}) => Promise<PackageStats[]>;
|
|
131
238
|
}
|
|
132
239
|
|
|
133
|
-
export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
|
|
240
|
+
export { type Anomaly, type ChartData, type ComparisonResult, type Config, type DailyDownloads, type ForecastPoint, type PackageConfig, type PackageInference, type PackageStats, type PortfolioInference, type RateLimitConfig, type Recommendation, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, type TrendSegment, calc, computeMomentum, createCache, createHandler, defaultConfig, detectAnomalies, detectSeasonality, forecast, generateRecommendations, inferPortfolio, loadConfig, registerProvider, segmentTrends, serve, starterConfig, stats };
|