Skip to content

Commit 0a58453

Browse files
authored
feat: replace rank_packages() with cumulative-coverage impact scoring [CM-1228] (#4204)
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
1 parent 72eef8d commit 0a58453

2 files changed

Lines changed: 131 additions & 24 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
-- Two related changes bundled here:
2+
--
3+
-- 1. package_criticality_spotlight: replace text-based (ecosystem, namespace, name)
4+
-- matching with a package_id FK so the spotlight join uses an indexed integer key.
5+
--
6+
-- 2. rank_packages(): replace the weighted PERCENT_RANK formula and arbitrary
7+
-- per-ecosystem top-N caps with cumulative-coverage scoring.
8+
-- The smallest set of packages that together account for coverage_cutoff (default 90%)
9+
-- of each signal is critical. impact = average of (1 − cumulative_coverage) across
10+
-- available signals. is_critical = true if in that set for any signal.
11+
12+
-- ── 1. package_criticality_spotlight schema change ───────────────────────────
13+
14+
DROP INDEX IF EXISTS package_criticality_spotlight_ecosystem_coalesce_name_idx;
15+
16+
ALTER TABLE package_criticality_spotlight
17+
ADD COLUMN IF NOT EXISTS package_id bigint NOT NULL REFERENCES packages(id),
18+
DROP COLUMN IF EXISTS name,
19+
DROP COLUMN IF EXISTS namespace;
20+
21+
CREATE UNIQUE INDEX IF NOT EXISTS package_criticality_spotlight_package_id_idx
22+
ON package_criticality_spotlight (package_id);
23+
24+
-- ── 2. rank_packages() ───────────────────────────────────────────────────────
25+
26+
DROP FUNCTION IF EXISTS rank_packages(numeric, numeric, numeric, jsonb);
27+
28+
CREATE OR REPLACE FUNCTION rank_packages(
29+
coverage_cutoff numeric DEFAULT 0.90,
30+
ecosystems text[] DEFAULT NULL
31+
)
32+
RETURNS TABLE(processed_rows int)
33+
LANGUAGE plpgsql AS $$
34+
DECLARE
35+
processed_count int;
36+
effective_ecosystems text[];
37+
BEGIN
38+
IF ecosystems IS NULL THEN
39+
SELECT ARRAY_AGG(DISTINCT ecosystem)
40+
INTO effective_ecosystems
41+
FROM packages;
42+
ELSE
43+
effective_ecosystems := ecosystems;
44+
END IF;
45+
46+
WITH base AS (
47+
SELECT
48+
id,
49+
ecosystem,
50+
COALESCE(downloads_last_30d, 0) AS downloads,
51+
COALESCE(dependent_count, 0) AS direct_dependents,
52+
COALESCE(transitive_dependent_count, 0) AS transitive_dependents,
53+
SUM(COALESCE(downloads_last_30d, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_downloads,
54+
SUM(COALESCE(dependent_count, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_direct_dependents,
55+
SUM(COALESCE(transitive_dependent_count, 0)) OVER (PARTITION BY ecosystem) AS ecosystem_total_transitive_dependents
56+
FROM packages
57+
WHERE ecosystem = ANY(effective_ecosystems)
58+
),
59+
walked AS (
60+
-- One row per (package × signal). Signals with a zero ecosystem total are
61+
-- excluded so they don't factor into the average (e.g. downloads for maven).
62+
-- cumulative_share_exclusive for the top-ranked package equals 0 by arithmetic
63+
-- (sum of rows above it is 0), so the top package is always inside the critical set.
64+
SELECT
65+
id,
66+
ecosystem,
67+
SUM(signal_value) OVER coverage_window / ecosystem_signal_total::numeric AS cumulative_share_inclusive,
68+
(SUM(signal_value) OVER coverage_window - signal_value) / ecosystem_signal_total::numeric AS cumulative_share_exclusive
69+
FROM base
70+
CROSS JOIN LATERAL (VALUES
71+
('downloads', downloads, ecosystem_total_downloads),
72+
('direct_dependents', direct_dependents, ecosystem_total_direct_dependents),
73+
('transitive_dependents', transitive_dependents, ecosystem_total_transitive_dependents)
74+
) AS signal(signal_name, signal_value, ecosystem_signal_total)
75+
WHERE ecosystem_signal_total > 0
76+
WINDOW coverage_window AS (
77+
PARTITION BY ecosystem, signal_name
78+
ORDER BY signal_value DESC, id
79+
ROWS UNBOUNDED PRECEDING
80+
)
81+
),
82+
combined AS (
83+
SELECT
84+
id,
85+
ecosystem,
86+
AVG(1.0 - cumulative_share_inclusive)::numeric(10, 4) AS new_impact,
87+
BOOL_OR(cumulative_share_exclusive < coverage_cutoff) AS new_is_critical
88+
FROM walked
89+
GROUP BY id, ecosystem
90+
),
91+
final AS (
92+
SELECT
93+
combined.id,
94+
combined.new_impact,
95+
combined.new_is_critical OR (spotlight.package_id IS NOT NULL) AS new_is_critical,
96+
ROW_NUMBER() OVER (
97+
PARTITION BY combined.ecosystem
98+
ORDER BY combined.new_impact DESC NULLS LAST, combined.id
99+
) AS new_rank_in_ecosystem
100+
FROM combined
101+
LEFT JOIN package_criticality_spotlight spotlight ON spotlight.package_id = combined.id
102+
)
103+
UPDATE packages p
104+
SET impact = final.new_impact,
105+
is_critical = final.new_is_critical,
106+
rank_in_ecosystem = final.new_rank_in_ecosystem,
107+
last_rank_pass_at = NOW(),
108+
last_synced_at = NOW()
109+
FROM final
110+
WHERE p.id = final.id;
111+
112+
GET DIAGNOSTICS processed_count = ROW_COUNT;
113+
114+
RETURN QUERY SELECT processed_count;
115+
END;
116+
$$;

services/apps/packages_worker/src/criticality/run-impact.ts

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
*
66
* Usage (from services/apps/packages_worker):
77
* pnpm run:impact
8-
* pnpm run:impact --top-n '{"cargo":75000,"maven":150000}'
9-
* pnpm run:impact --w-downloads 0.30 --w-dep-pkgs 0.20 --w-transitive 0.50
8+
* pnpm run:impact --cutoff 0.90
9+
* pnpm run:impact --ecosystems npm,go
10+
* pnpm run:impact --cutoff 0.85 --ecosystems npm
1011
*/
1112
import { getPackagesDb } from '../db'
1213

13-
// Env defaults for local dev
1414
process.env.CROWD_PACKAGES_DB_WRITE_HOST ??= 'localhost'
1515
process.env.CROWD_PACKAGES_DB_PORT ??= '5434'
1616
process.env.CROWD_PACKAGES_DB_DATABASE ??= 'packages-db'
@@ -24,40 +24,31 @@ function parseArg(flag: string, fallback: number): number {
2424
return Number.isNaN(v) ? fallback : v
2525
}
2626

27-
function parseJsonArg(flag: string, fallback: string): string {
27+
function parseListArg(flag: string): string[] | null {
2828
const idx = process.argv.indexOf(flag)
29-
return idx !== -1 ? process.argv[idx + 1] : fallback
29+
if (idx === -1) return null
30+
return process.argv[idx + 1].split(',').map((s) => s.trim())
3031
}
3132

32-
const wDownloads = parseArg('--w-downloads', 0.25)
33-
const wDepPkgs = parseArg('--w-dep-pkgs', 0.25)
34-
const wTransitive = parseArg('--w-transitive', 0.5)
35-
const topN = parseJsonArg('--top-n', '{"cargo":75000,"maven":150000}')
33+
const cutoff = parseArg('--cutoff', 0.9)
34+
const ecosystems = parseListArg('--ecosystems')
3635

3736
async function main() {
38-
if (Math.abs(wDownloads + wDepPkgs + wTransitive - 1.0) > 0.001) {
39-
console.error(
40-
`Weights must sum to 1.0, got ${(wDownloads + wDepPkgs + wTransitive).toFixed(3)}`,
41-
)
42-
process.exit(1)
43-
}
44-
4537
console.log(`Running rank_packages()`)
46-
console.log(` weights: downloads=${wDownloads} dep_pkgs=${wDepPkgs} transitive=${wTransitive}`)
47-
console.log(` top-n : ${topN}\n`)
38+
console.log(` cutoff : ${cutoff}`)
39+
console.log(` ecosystems: ${ecosystems ? ecosystems.join(', ') : 'all'}\n`)
4840

4941
const qx = await getPackagesDb()
5042
const t = Date.now()
5143

52-
const [result] = await qx.select(
53-
`SELECT * FROM rank_packages($/wDownloads/, $/wDepPkgs/, $/wTransitive/, $/topN/::jsonb)`,
54-
{ wDownloads, wDepPkgs, wTransitive, topN },
55-
)
44+
const [result] = await qx.select(`SELECT * FROM rank_packages($/cutoff/, $/ecosystems/)`, {
45+
cutoff,
46+
ecosystems,
47+
})
5648

5749
const elapsed = ((Date.now() - t) / 1000).toFixed(1)
5850
console.log(`Done in ${elapsed}s`)
59-
console.log(` scored_rows: ${result.scored_rows?.toLocaleString()}`)
60-
console.log(` ranked_rows: ${result.ranked_rows?.toLocaleString()}`)
51+
console.log(` processed_rows: ${result.processed_rows?.toLocaleString()}`)
6152

6253
process.exit(0)
6354
}

0 commit comments

Comments
 (0)