feat: add project-level impact score breakdown rollup with percentile bands (IN-1212) - #4439
Conversation
PR SummaryMedium Risk Overview Rollup uses the same Performance: the expensive global Reviewed by Cursor Bugbot for commit e24e5f3. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds a Tinybird endpoint for project-level Impact metrics and global percentile bands.
Changes:
- Rolls package metrics up to projects.
- Computes global percentile bands with NULL handling.
- Supports filtering by project slug or ID.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ip.name AS name, | ||
| max(pk.dependentCount) AS directDependents, | ||
| max(pk.transitiveDependentCount) AS transitiveDependents, | ||
| sum(pk.downloadsLast30d) AS downloads, |
| centrality, | ||
| centralityTopPct, | ||
| centralityBand | ||
| FROM project_impact_bands |
| - `project_insights_impact_breakdown.pipe` rolls up the 4 Impact sub-metrics from package level | ||
| to project level and computes a percentile rank-band for each, for the new project overview | ||
| page's "Impact breakdown" section (IN-1212). |
| AND rep.excluded = false | ||
| LEFT JOIN repos r ON r.url = rep.url | ||
| LEFT JOIN packageRepos pr ON pr.repoId = r.id | ||
| LEFT JOIN ossPackages_enriched_ds pk ON pk.id = pr.packageId |
There was a problem hiding this comment.
Downloads sum inflated by join fan-out
Medium Severity
sum(pk.downloadsLast30d) aggregates over the joined rowset, so a package linked to multiple project repos via packageRepos, or duplicate unmerged repos / packageRepos rows (both ReplacingMergeTree, joined without FINAL), is counted more than once. That inflates project downloads and skews download percentiles/bands. The MAX metrics are resilient to this fan-out; SUM is not. Elsewhere, ossPackages_enriched.pipe uses FINAL and collapses packageRepos to one repo per package before aggregating.
Reviewed by Cursor Bugbot for commit 49e40be. Configure here.
| TYPE COPY | ||
| TARGET_DATASOURCE project_insights_impact_breakdown_ds | ||
| COPY_MODE replace | ||
| COPY_SCHEDULE 40 2 * * * |
There was a problem hiding this comment.
COPY schedule collides in dense window
High Severity
COPY_SCHEDULE 40 2 * * * lands in the dense 1–3 AM UTC window and collides minute-for-minute with leaderboards_project_active_organizations.pipe. The account’s 12-concurrent COPY quota is already under pressure there; jobs that exceed it can stick in queued permanently and empty downstream data.
Triggered by learned rule: Tinybird COPY pipe schedules must not collide — account concurrent quota is 12
Reviewed by Cursor Bugbot for commit 8b2a09e. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (4)
services/libs/tinybird/pipes/project_insights_impact_breakdown_copy.pipe:85
downloadsis summed at the joined-row grain, butpackageReposexplicitly permits one package to link to multiple repos. If two of those repos belong to the same project, the same package's 30-day downloads are counted more than once, inflating both the rollup and its percentile. Deduplicate by(projectId, packageId)before summing (do not usesumDistinct(downloadsLast30d), since different packages can legitimately have equal counts).
sum(pk.downloadsLast30d) AS downloads,
services/libs/tinybird/pipes/project_insights_impact_breakdown_copy.pipe:94
- Both
reposandpackageReposareReplacingMergeTreedatasources, but these joins omitFINAL. Until background merges complete, multiple physical versions can duplicate joined rows; that directly overcountsdownloadsand can select obsolete links. The canonical enrichment path reads both withFINAL(ossPackages_enriched.pipe:17,22).
LEFT JOIN repos r ON r.url = rep.url
LEFT JOIN packageRepos pr ON pr.repoId = r.id
services/libs/tinybird/pipes/project_insights_impact_breakdown_copy.pipe:96
- The global ranking population currently includes soft-deleted and disabled projects.
insightsProjects.enableddefines whether a project is active in analytics, and established project queries excludedeletedAt; retaining inactive projects can shift every active project's percentile. Filter the base project population before grouping.
FROM insightsProjects ip FINAL
services/libs/tinybird/pipes/project_insights_impact_breakdown_copy.pipe:92
- Soft-deleted repositories are still eligible when their
enabled/excludedflags retain their prior values, so removed repos can continue contributing package metrics. Other active-repository rollups explicitly requireisNull(deletedAt)(for example,repositories_populated_copy.pipe:8); apply the same filter here.
AND rep.enabled = true
AND rep.excluded = false
… bands (IN-1212) Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
8b2a09e to
e24e5f3
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
Reviewed by Cursor Bugbot for commit e24e5f3. Configure here.
| LEFT JOIN repos r ON r.url = rep.url | ||
| LEFT JOIN packageRepos pr ON pr.repoId = r.id | ||
| LEFT JOIN ossPackages_enriched_ds pk ON pk.id = pr.packageId | ||
| GROUP BY ip.id, ip.slug, ip.name |
There was a problem hiding this comment.
Deleted projects skew percentiles
Medium Severity
project_impact_raw reads all insightsProjects rows without isNull(deletedAt) or enabled = 1. Soft-deleted and disabled projects still enter the global rank population when they have package metrics, which displaces active projects' percentile bands. Comparable pipes already exclude them.
Reviewed by Cursor Bugbot for commit e24e5f3. Configure here.
| LEFT JOIN repos r ON r.url = rep.url | ||
| LEFT JOIN packageRepos pr ON pr.repoId = r.id | ||
| LEFT JOIN ossPackages_enriched_ds pk ON pk.id = pr.packageId | ||
| GROUP BY ip.id, ip.slug, ip.name |
There was a problem hiding this comment.
Downloads sum can double-count
Medium Severity
downloads uses sum(pk.downloadsLast30d) over the repositories → repos → packageRepos join without deduplicating by packageId. packageRepos allows one package to link to multiple repos, so the same package can appear multiple times for one project and inflate downloads and its percentile.
Reviewed by Cursor Bugbot for commit e24e5f3. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
services/libs/tinybird/pipes/project_insights_impact_breakdown_copy.pipe:90
- This population includes soft-deleted/disabled projects and soft-deleted repositories.
FINALonly selects the latest row version; it does not filterdeletedAt. As a result, the endpoint can expose deleted projects and attribute packages from deleted repos, unlike the active-project population used byinsights_projects_populated_copy.pipe:277andhealth_score_security.pipe:8-12.
FROM insightsProjects ip FINAL
LEFT JOIN
repositories rep FINAL
ON rep.insightsProjectId = ip.id
| ip.name AS name, | ||
| max(pk.dependentCount) AS directDependents, | ||
| max(pk.transitiveDependentCount) AS transitiveDependents, | ||
| sum(pk.downloadsLast30d) AS downloads, |


Summary
Rolls up the 4 Impact sub-metrics (transitive dependents, graph centrality, downloads, direct dependents) from package level to project level, and computes a global percentile rank-band for each (e.g. "Top 1%"), for the new project overview page's Impact breakdown section (IN-1212).
Deployed to production and verified — including a performance fix found after the initial deploy (see below). GitHub PR left unmerged pending review, per this ticket's convention.
Join path fix: packageRepos, not naive URL matching
Investigation confirmed naive string/URL matching between a project's repos and
ossPackages_enriched_ds(e.g.LIKEonpurl/repositoryUrl) returns false-positive matches — mirrors, forks, and unrelated same-named repos. The correct join is the onehealth_score_v2_impact.pipealready uses for its per-repoimpactScore = MAX(packages.impact):Verified live against Kubernetes: 326 enabled/non-excluded repos, all resolved through
repos, yielding 897 linked packages viapackageRepos— this exact chain, mirrored from the existing precedent.The full chain is
LEFT JOINed (notINNER JOINed) frominsightsProjectsonward so every project appears in the output, including ones with zero linked packages — this was caught during validation (see below).Aggregation per metric
Chosen per-metric rather than one rule for all 4, each verified against real data:
dependentCount):MAX— mirrors theimpactScore = MAX(packages.impact)precedent; a project's most-depended-upon package is the signal.transitiveDependentCount):MAX, notSUM. Verified live against Kubernetes: dozens of its own packages carry the same ~677,129 transitive-dependent count (these counts are graph-wide per ecosystem tooling, not package-unique), so summing would have inflated a single real signal by ~300x into a meaningless number.downloadsLast30d):SUM, notMAX. Verified live against Kubernetes: its linked packages (pypikubernetes, npm@kubernetes/client-node, nugetKubernetesClient, etc.) are independently-tracked registry packages with no shared/duplicated download counts, so summing safely reflects total adoption footprint across ecosystems.centralityScore):MAX— a PageRank-style per-package score, not additive.Percentile methodology
Projects are ranked globally against all other projects with a non-NULL value for that metric — deliberately not partitioned per-ecosystem. Verified live that only 43% of projects with linked packages (3,397 of 7,906) are single-ecosystem; the rest span 2-8 ecosystems (e.g.
opentelemetryspans all 8 tracked ecosystems), so there's no single well-defined "project's ecosystem" to partition a project-grain percentile by. A project-level rollup value (e.g. total downloads summed across a project's npm + PyPI + Go packages) is compared fairly against other projects' equally cross-ecosystem-summed values.rank() OVER (ORDER BY metric DESC)divided by the count of ranked (non-NULL) projects gives each project's position, bucketed into Top 1% / Top 10% / Top 25% / Top 50% / Bottom 50%. Projects with a NULL source metric are excluded from the ranking population entirely (not ranked as 0), and surfaceNULLpercentile +NULLband rather than a fabricated low percentile.Validation results (read-only, via Tinybird MCP against production)
tb check pipes/project_insights_impact_breakdown.pipe— clean.pygithubanddevlight-navigationtabbar(confirmed zero linked packages) correctly returnNULLacross all 4 metrics + all percentile/band fields — not a fabricated 0 or last-place band. This required fixing an initialINNER JOINversion of the pipe that silently dropped such projects entirely; the shipped version usesLEFT JOINthroughout so every one of the 13,548insightsProjectsrows is present (5,662 withdirectDependentsdata, 7,886 NULL).count(DISTINCT id)oninsightsProjectsdirectly.Data-quality caveat for reviewers
centralityScoreis confirmed 100% NULL/unpopulated in production today — 0 of ~4.2M npm packages (and 0 across every other tracked ecosystem: go, maven, nuget, pypi, packagist, cargo, rubygems) have a usable value. The rollup and percentile logic for this metric is implemented and will activate automatically once the centrality scoring worker backfills real values, but until then every project will show "no data" for the Graph centrality row in the UI. This is a pre-existing data gap, not introduced by this pipe.downloadsLast30dand other source fields also have partial NULL coverage by ecosystem (e.g. only 34/895 of Kubernetes's linked packages have a download count) — this is expected and handled gracefully (SUM over non-NULL values), not a bug.Performance issue found post-deploy, fixed
After the initial deploy, the live endpoint was measured at 3.3–3.5 seconds per single-project request (consistent across repeated calls, not a cold-cache artifact), scanning ~37M rows / ~2.2GB every time. Root cause: the original design computed the rollup and global percentile rank live, on every request —
rank() OVER (ORDER BY metric DESC)andcountIf(...) OVER ()require the full joined population in scope before anyWHERE slug = ...filter can apply, so ClickHouse couldn't push the filter down. Every single-project lookup paid the full-scan cost.Fix: moved the rollup + ranking computation into a new scheduled
TYPE COPYpipe (project_insights_impact_breakdown_copy.pipe,COPY_SCHEDULE 40 2 * * *— 10 minutes afterossPackages_enriched_ds's own 02:30 UTC refresh, the only real staleness dependency sinceinsightsProjects/repositories/repos/packageReposare all near-live Sequin CDC-replicated tables) that materializes into a new datasource,project_insights_impact_breakdown_ds. The public endpoint pipe (project_insights_impact_breakdown.pipe) is now a simple filteredSELECTagainst that pre-ranked table — same patternproject_insights.pipealready uses againstproject_insights_copy_ds. Logic is otherwise byte-identical (same join, same aggregation choices, same percentile methodology) — this is a performance refactor, not a behavior change.Result: confirmed via direct measurement, same-project values unchanged before/after:
~500x faster. Data values confirmed identical: Kubernetes
directDependents=31058/Top 10%,transitiveDependents=677129/Top 1%,downloads=179350062/Top 25%,centrality=null— matches pre-fix exactly.pygithub(zero-package project) still correctly returns all-NULL fields rather than being dropped. Full-table row count still 13,548, matchinginsightsProjectsexactly.Deploy status
Deployed to production, including the performance fix. Not yet deployed to staging (see below) — this pipe's join chain could not be pushed to staging at all:
lfx_insights_stgis missing thereposdatasource entirely (not sparse — completely absent), a pre-existing staging environment gap unrelated to this PR. Flagging for separate follow-up, not attempting to fix staging here.Test plan
health_score_v2_impact.pipeprecedentreposdatasource gap noted above