Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DESCRIPTION >
- `health_score_v2_development_ds` holds the per-repo Development Activity category score (0-25)
for Health Score v2. Populated by `health_score_v2_development.pipe`.
- `repoUrl` is the repository URL — the join key back to `repositories`.
- `developmentActivityScoreV2` — NULL when covered sub-signal weight is <40% of the 25pt max
(spec Layer 1 graceful degradation), otherwise the rescaled 0-25 score.

SCHEMA >
`repoUrl` String,
`developmentActivityScoreV2` Nullable(UInt8)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DESCRIPTION >
- `health_score_v2_impact_ds` holds the per-repo raw Impact Score (0-100 float, pre-rounding)
for Health Score v2. Populated by `health_score_v2_impact.pipe`.
- `repoUrl` is the repository URL — the join key back to `repositories`.
- `impactScoreRaw` — MAX(packages.impact) * 100 over packages published by this repo. NULL when
the repo has no linked packages with a known impact value.

SCHEMA >
`repoUrl` String,
`impactScoreRaw` Nullable(Float64)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
DESCRIPTION >
- `health_score_v2_lifecycle_ds` holds the per-repo Lifecycle state for Health Score v2.
Populated by `health_score_v2_lifecycle.pipe`.
- `repoUrl` is the repository URL — the join key back to `repositories`.
- `lifecycleLabelV2` — one of active/stable/declining/abandoned/archived, or NULL when the
repo has no usable activity signal at all (2026-07-24, IN-1196 — see
health_score_v2_lifecycle.pipe for the exact condition).

SCHEMA >
`repoUrl` String,
`lifecycleLabelV2` Nullable(String)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DESCRIPTION >
- `health_score_v2_maintainer_ds` holds the per-repo Maintainer Health category score (0-40)
for Health Score v2. Populated by `health_score_v2_maintainer.pipe`.
- `repoUrl` is the repository URL — the join key back to `repositories`.
- `maintainerHealthScoreV2` — NULL when covered sub-signal weight is <40% of the 40pt max
(spec Layer 1 graceful degradation), otherwise the rescaled 0-40 score.

SCHEMA >
`repoUrl` String,
`maintainerHealthScoreV2` Nullable(UInt8)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
DESCRIPTION >
- `health_score_v2_repo_copy_ds` contains the per-repo Akrites Health Score v2 breakdown.
- Populated by `health_score_v2.pipe` copy pipe.
- Health Score v2 is computed entirely from repo-level/CDP data (maintainer activity, security posture,
development activity) — no package data, per the Health Score v2 methodology
(https://linuxfoundation-dxwx.dsp.so/4ezMyw7n-insights-projects-new-scores). Impact Score is the
one exception — package-mediated by design, computed here as MAX(packages.impact) over the repo's
own published packages (via packageRepos), not an average over unrelated packages.
- `repoUrl` is the repository URL — the join key back to `repositories`/`repos`.
- `maintainerHealthScoreV2` (0-40) — bus factor, org diversity, maintainer responsiveness.
- `securitySupplyChainScoreV2` (0-35) — open vulnerabilities, OpenSSF Scorecard, security practices,
dependency health (checked against the repo's own published packages' dependencies, not an average
across unrelated packages), supply chain integrity (hardcoded 0 — provenance/2FA data not yet piped).
Comment on lines +11 to +13
- `developmentActivityScoreV2` (0-25) — release cadence, commit activity, issue resolution, PR merge health.
- `healthScoreV2` (0-100) is the sum of the three categories above, clamped to 100.
- `lifecycleLabelV2` — per-repo lifecycle state (active/stable/declining/abandoned/archived), computed
via the spec's decision tree (archived flag > abandoned > declining > stable > active, first match
wins), or NULL when the repo has zero commits and zero issues/PRs in every window checked (2026-07-24,
IN-1196 — no usable activity signal at all). Project-level rollup uses best-state-wins precedence in
project_insights_copy.pipe.
- `impactScore` (0-100) — MAX(packages.impact) * 100 over packages published by this repo. NULL when
the repo has no linked packages.
- No graceful-degradation/signal-coverage redistribution yet — a repo with zero signal in a category
scores 0 there rather than having points redistributed from available categories.

SCHEMA >
`repoUrl` String,
`maintainerHealthScoreV2` Nullable(UInt8),
`securitySupplyChainScoreV2` Nullable(UInt8),
`developmentActivityScoreV2` Nullable(UInt8),
`healthScoreV2` Nullable(UInt8),
`lifecycleLabelV2` Nullable(String),
`impactScore` Nullable(UInt8)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DESCRIPTION >
- `health_score_v2_security_ds` holds the per-repo Security & Supply Chain category score (0-35)
for Health Score v2. Populated by `health_score_v2_security.pipe`.
- `repoUrl` is the repository URL — the join key back to `repositories`.
- `securitySupplyChainScoreV2` — NULL when covered sub-signal weight is <40% of the 35pt max
(spec Layer 1 graceful degradation), otherwise the rescaled 0-35 score.

SCHEMA >
`repoUrl` String,
`securitySupplyChainScoreV2` Nullable(UInt8)

ENGINE MergeTree
ENGINE_SORTING_KEY repoUrl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ DESCRIPTION >
- `activeContributorsPrevious365Days` column is the unique count of active contributors in the previous 365 days (365-730 days ago).
- `activeOrganizationsPrevious365Days` column is the unique count of active organizations in the previous 365 days (365-730 days ago).
- `status` column is the current status of the project (e.g., 'active', 'archived').
- `healthScoreV2` column is the Akrites-methodology composite health score (0-100), averaged from `ossPackages_enriched_ds.healthScore` across the project's linked packages. Distinct from the legacy `healthScore` column (community/contributor-based); null when the project has no linked package data.
- `healthLabel` column buckets `healthScoreV2`: 'excellent' (85+), 'healthy' (70-84), 'fair' (50-69), 'concerning' (30-49), 'critical' (<30). Lowercase, matches `ossPackages_enriched_ds.healthLabel` convention.
- `lifecycleLabel` column is the project's aggregated maintenance state across its packages, using best-state-wins precedence (active > stable > declining > abandoned > archived) over `ossPackages_enriched_ds.lifecycleLabel`.
- `impactScore` column is the average Osprey criticality `impact` (0-100) across the project's packages. Null when the project has no linked package data.
Comment on lines +35 to +38
- `impactLabel` column buckets `impactScore`: 'foundational' (85-100), 'major' (60-84), 'moderate' (30-59), 'minor' (0-29). Null when `impactScore` is null.

TAGS "Project insights", "Metrics"

Expand Down Expand Up @@ -65,7 +70,12 @@ SCHEMA >
`starsPrevious365Days` UInt64,
`forksPrevious365Days` UInt64,
`activeContributorsPrevious365Days` UInt64,
`activeOrganizationsPrevious365Days` UInt64
`activeOrganizationsPrevious365Days` UInt64,
`healthScoreV2` Nullable(UInt8),
`healthLabel` Nullable(String),
`lifecycleLabel` Nullable(String),
`impactScore` Nullable(UInt8),
`impactLabel` Nullable(String)

ENGINE MergeTree
ENGINE_SORTING_KEY type, id
63 changes: 63 additions & 0 deletions services/libs/tinybird/pipes/health_score_v2.pipe
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
DESCRIPTION >
- Combines the 5 independently-materialized Health Score v2 category/label datasources into the
final per-repo row: Maintainer Health (40) + Security & Supply Chain (35) + Development
Activity (25) + Lifecycle label + Impact Score, per the Health Score v2 methodology
(https://linuxfoundation-dxwx.dsp.so/4ezMyw7n-insights-projects-new-scores).
- Split into 5 category pipes (health_score_v2_maintainer/security/development/lifecycle/impact.pipe)
on 2026-07-22, each with its own COPY_SCHEDULE and small materialized *_ds datasource, because
the previous single-pipe 6-node version re-inlined all upstream NODEs into one query per copy
job — Tinybird does not materialize intermediate NODEs, so `repositories` and other base tables
were being re-scanned once per category, and the 6-node version's runtime grew from ~13min to
20-28+ minutes across repeated runs even with an equivalent-cost SQL change (see git history).
This node now does a single cheap LEFT JOIN over 5 small pre-computed tables instead.
- Applies spec Layer 2 graceful degradation across categories: healthScoreV2 = SUM(available
category subtotals) * (100 / SUM(available category max weights)) — each category datasource
already carries NULL when its own coverage fell below 40% (Layer 1, done per-category pipe).
If all three categories are unavailable, healthScoreV2 itself becomes NULL, per spec, rather
than a fabricated 0. Impact stays NULL (not defaulted to 0) when the repo has no packages.
- lifecycleLabelV2 passes through health_score_v2_lifecycle_ds unmodified (2026-07-24,
IN-1196) — previously wrapped in `coalesce(l.lifecycleLabelV2, 'active')`, which silently
converted the upstream pipe's genuine "no usable signal" NULL back into a fabricated 'active'
before it ever reached health_score_v2_repo_copy_ds. Confirmed live that this LEFT JOIN has
zero actual join-misses (every repo has a matching lifecycle row), so the coalesce was never
serving its ostensible "missing row" fallback purpose in practice — it was only erasing real
NULLs.

NODE health_score_v2_results
SQL >
SELECT
repoUrl,
maintainerHealthScoreV2,
securitySupplyChainScoreV2,
developmentActivityScoreV2,
if(
coveredCategoryWeight = 0,
NULL,
toNullable(toUInt8(round(least(100.0, availableCategorySum * (100.0 / coveredCategoryWeight)))))
) AS healthScoreV2,
lifecycleLabelV2,
if(impactScoreRaw IS NULL, NULL, toNullable(toUInt8(round(least(impactScoreRaw, 100))))) AS impactScore
FROM (
SELECT
base.repoUrl AS repoUrl,
m.maintainerHealthScoreV2 AS maintainerHealthScoreV2,
s.securitySupplyChainScoreV2 AS securitySupplyChainScoreV2,
d.developmentActivityScoreV2 AS developmentActivityScoreV2,
(coalesce(m.maintainerHealthScoreV2,0) + coalesce(s.securitySupplyChainScoreV2,0) + coalesce(d.developmentActivityScoreV2,0)) AS availableCategorySum,
(if(m.maintainerHealthScoreV2 IS NOT NULL, 40, 0)
+ if(s.securitySupplyChainScoreV2 IS NOT NULL, 35, 0)
+ if(d.developmentActivityScoreV2 IS NOT NULL, 25, 0)) AS coveredCategoryWeight,
l.lifecycleLabelV2 AS lifecycleLabelV2,
i.impactScoreRaw AS impactScoreRaw
FROM (SELECT DISTINCT url AS repoUrl FROM repositories WHERE deletedAt IS NULL) AS base
LEFT JOIN health_score_v2_maintainer_ds AS m ON m.repoUrl = base.repoUrl
LEFT JOIN health_score_v2_security_ds AS s ON s.repoUrl = base.repoUrl
LEFT JOIN health_score_v2_development_ds AS d ON d.repoUrl = base.repoUrl
LEFT JOIN health_score_v2_lifecycle_ds AS l ON l.repoUrl = base.repoUrl
LEFT JOIN health_score_v2_impact_ds AS i ON i.repoUrl = base.repoUrl
)

TYPE COPY
TARGET_DATASOURCE health_score_v2_repo_copy_ds
COPY_MODE replace
COPY_SCHEDULE 30 2 * * *
108 changes: 108 additions & 0 deletions services/libs/tinybird/pipes/health_score_v2_development.pipe
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
DESCRIPTION >
Development Activity category (max 25 pts): release cadence (8), commit activity (5), issue
resolution (7), PR merge health (5). Release cadence uses distinct release DAYS (not raw
publishedAt timestamps) because monorepo projects publish many packages within seconds of each
other, which would otherwise read as near-zero days between releases. Commit activity counts
`authored-commit` across all platforms (not just platform='git') since GitHub-sourced commits are
tagged platform='github', not 'git', in activityRelations. Falls back to repos.lastCommitAt
(4 pts if <3mo, 2 if <12mo, else 0) when a repo has zero rows in activityRelations at all —
per spec, and because only ~30K of 3.3M repos have any git activity ingested, vs. ~6M rows
(most repos) having lastCommitAt from GitHub enrichment — the fallback covers the vast majority.
- Split out of health_score_v2.pipe into its own copy pipe (2026-07-22) — see
health_score_v2_maintainer.pipe description for why.

NODE health_score_v2_development_calc
SQL >
SELECT
repoUrl,
if(coveredWeight / 25.0 < 0.4, NULL, toNullable(toUInt8(round(least(25.0, rawScore * (25.0 / coveredWeight)))))) AS developmentActivityScoreV2
FROM (
SELECT
repoUrl,
(releaseCadenceAvailable * releaseCadenceScore + commitActivityScore
+ issueResolutionAvailable * issueResolutionScore + prMergeAvailable * prMergeScore) AS rawScore,
(releaseCadenceAvailable * 8 + 5 + issueResolutionAvailable * 7 + prMergeAvailable * 5) AS coveredWeight
FROM (
SELECT
rp.repoUrl AS repoUrl,
(rs.repoUrl != '') AS releaseCadenceAvailable,
multiIf(
rs.daysSinceLatest < 90 AND rs.daysBetweenRecent < 90, 8,
rs.daysSinceLatest < 180, 6,
rs.daysSinceLatest < 365, 4,
rs.daysSinceLatest < 730, 2,
0
) AS releaseCadenceScore,
-- commit activity is never `blocked`: it has its own spec-mandated fallback to
-- repos.lastCommitAt, so it always contributes its full 5pt weight to coverage
multiIf(
c.repoUrl != '',
multiIf(c.commitsLast6m >= 50, 5, c.commitsLast6m >= 20, 4, c.commitsLast6m >= 5, 3, c.commitsLast6m >= 1, 1, 0),
rp.lastCommitAt > now() - INTERVAL 3 MONTH, 4,
rp.lastCommitAt > now() - INTERVAL 12 MONTH, 2,
0
) AS commitActivityScore,
(ist.repoUrl != '') AS issueResolutionAvailable,
(multiIf(ist.opened12m = 0, 0, (ist.closed12m / ist.opened12m) >= 0.8, 4, (ist.closed12m / ist.opened12m) >= 0.5, 2, 0)
+ multiIf(ist.medianCloseS < 604800, 3, ist.medianCloseS < 2592000, 2, 0)) AS issueResolutionScore,
(prs.repoUrl != '') AS prMergeAvailable,
(multiIf((prs.merged12m + prs.closedUnmerged12m) = 0, 0, (prs.merged12m / (prs.merged12m + prs.closedUnmerged12m)) >= 0.7, 3, (prs.merged12m / (prs.merged12m + prs.closedUnmerged12m)) >= 0.4, 1, 0)
+ multiIf(prs.medianMergeS < 604800, 2, prs.medianMergeS < 2592000, 1, 0)) AS prMergeScore
FROM (
SELECT base.url AS repoUrl, rc.lastCommitAt AS lastCommitAt
FROM (SELECT DISTINCT url FROM repositories WHERE deletedAt IS NULL) AS base
LEFT JOIN (
SELECT url, argMax(lastCommitAt, updatedAt) AS lastCommitAt
FROM repos GROUP BY url
) AS rc ON rc.url = base.url
) AS rp
LEFT JOIN (
SELECT
repoUrl,
dateDiff('day', top2[1], today()) AS daysSinceLatest,
if(length(top2) >= 2, dateDiff('day', top2[2], top2[1]), 9999) AS daysBetweenRecent
FROM (
SELECT
r.url AS repoUrl,
arraySlice(arraySort(x -> -toInt64(x), arrayDistinct(groupArray(rd.releaseDay))), 1, 2) AS top2
FROM repos r
INNER JOIN packageRepos pr ON pr.repoId = r.id
INNER JOIN (
SELECT DISTINCT packageId, toDate(publishedAt) AS releaseDay
FROM versions WHERE publishedAt IS NOT NULL
) rd ON rd.packageId = pr.packageId
GROUP BY r.url
)
) AS rs ON rs.repoUrl = rp.repoUrl
LEFT JOIN (
SELECT channel AS repoUrl,
countIf(timestamp > now() - INTERVAL 6 MONTH) AS commitsLast6m
FROM activityRelations_deduplicated_cleaned_bucket_union
WHERE type = 'authored-commit'
GROUP BY channel
) AS c ON c.repoUrl = rp.repoUrl
LEFT JOIN (
SELECT channel AS repoUrl,
countIf(closedAt IS NOT NULL) AS closed12m,
count() AS opened12m,
quantileIf(0.5)(closedInSeconds, closedAt > now() - INTERVAL 12 MONTH) AS medianCloseS
FROM issues_analyzed
WHERE openedAt > now() - INTERVAL 12 MONTH
GROUP BY channel
) AS ist ON ist.repoUrl = rp.repoUrl
LEFT JOIN (
SELECT channel AS repoUrl,
countIf(mergedAt IS NOT NULL) AS merged12m,
countIf(closedAt IS NOT NULL AND mergedAt IS NULL) AS closedUnmerged12m,
quantileIf(0.5)(mergedInSeconds, mergedAt > now() - INTERVAL 12 MONTH) AS medianMergeS
FROM pull_requests_analyzed
WHERE openedAt > now() - INTERVAL 12 MONTH
GROUP BY channel
) AS prs ON prs.repoUrl = rp.repoUrl
)
)

TYPE COPY
TARGET_DATASOURCE health_score_v2_development_ds
COPY_MODE replace
COPY_SCHEDULE 10 2 * * *
25 changes: 25 additions & 0 deletions services/libs/tinybird/pipes/health_score_v2_impact.pipe
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
DESCRIPTION >
Impact Score (max 100, package-mediated — the one score meant to depend on packages) =
ROUND(100 * MAX(packages.impact)) over packages published by this repo, via
repos -> packageRepos -> ossPackages_enriched_ds. Per spec this is MAX (not AVG) and joins via
packageRepos/repos (repo's own published packages), NOT packageDownloads (a looser,
undocumented linkage that caused the same averaging bug in Health Score v2 before it was fixed
to be repo-derived). NULL when the repo has no linked packages with a known impact value.
- Split out of health_score_v2.pipe into its own copy pipe (2026-07-22) — see
health_score_v2_maintainer.pipe description for why.

NODE health_score_v2_impact_calc
SQL >
SELECT
base.url AS repoUrl,
max(toFloat64OrNull(pk.impact)) * 100 AS impactScoreRaw
FROM (SELECT DISTINCT url FROM repositories WHERE deletedAt IS NULL) AS base
INNER JOIN repos r ON r.url = base.url
INNER JOIN packageRepos pr ON pr.repoId = r.id
INNER JOIN ossPackages_enriched_ds pk ON pk.id = pr.packageId
GROUP BY base.url

TYPE COPY
TARGET_DATASOURCE health_score_v2_impact_ds
COPY_MODE replace
COPY_SCHEDULE 20 2 * * *
Loading
Loading