feat: expose health score v2 sub-signal detail via new pipe chain (CM-IN-1212) - #4448
feat: expose health score v2 sub-signal detail via new pipe chain (CM-IN-1212)#4448gaspergrom wants to merge 1 commit into
Conversation
…-IN-1212) Promotes previously computed-but-discarded sub-signal scores and availability flags in the 3 category pipes (health_score_v2_maintainer, health_score_v2_security, health_score_v2_development), and adds a new pipe chain (health_score_v2_signal_detail -> project_insights_health_breakdown_copy -> project_insights_health_breakdown) rolling repo-level signal detail up to project level for the insights frontend's health-breakdown UI. No scoring/business logic changed, only column exposure and rollup. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
PR SummaryMedium Risk Overview The three category copy pipes ( A new scheduled chain mirrors Reviewed by Cursor Bugbot for commit 82c1e49. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 82c1e49. Configure here.
| max(sd.opened12m) AS opened12m, | ||
| avg(sd.medianCloseS) AS medianCloseS, | ||
| avg(sd.prMergeScore) AS prMergeScore, | ||
| max(sd.prMergeAvailable) AS prMergeAvailable, |
There was a problem hiding this comment.
Blocked scores dilute project averages
High Severity
Project-level score columns use plain avg() over every repo row, while *Available uses max(). Blocked signals still carry placeholder scores (0, or 5 for dependencyHealthScore when the repo has no packages), so those placeholders are folded into the mean whenever any sibling repo is available. Raw nullable columns already skip gaps correctly via avg/max on NULLs; the UInt8 scores do not, so the breakdown UI can show Available while the averaged score is wrong.
Reviewed by Cursor Bugbot for commit 82c1e49. Configure here.
| TYPE COPY | ||
| TARGET_DATASOURCE health_score_v2_signal_detail_ds | ||
| COPY_MODE replace | ||
| COPY_SCHEDULE 25 2 * * * |
There was a problem hiding this comment.
COPY schedules hit busy 2 AM window
Medium Severity
The new COPY pipes are scheduled at 25 2 and 35 2, inside the already dense 1–3 AM UTC window (leaderboards, health-score v2 chain, agentic/ai trackers, ossPackages_enriched, impact breakdown). That violates the Tinybird concurrent-copy quota rule: the account caps at 12 concurrent copy jobs, and overflow leaves pipes permanently queued, which can empty downstream data.
Additional Locations (1)
Triggered by learned rule: Tinybird COPY pipe schedules must not collide — account concurrent quota is 12
Reviewed by Cursor Bugbot for commit 82c1e49. Configure here.
There was a problem hiding this comment.
Pull request overview
Adds a Tinybird pipeline exposing Health Score v2 sub-signal details for the Insights health-breakdown UI.
Changes:
- Exposes existing category scores, availability flags, and raw metrics.
- Materializes repository-level details and project-level rollups.
- Adds a slug-filtered endpoint for Insights.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
pipes/project_insights_health_breakdown.pipe |
Adds the public breakdown endpoint. |
pipes/project_insights_health_breakdown_copy.pipe |
Aggregates repository signals by project. |
pipes/health_score_v2_signal_detail.pipe |
Joins category details by repository. |
pipes/health_score_v2_security.pipe |
Exposes security sub-signals. |
pipes/health_score_v2_maintainer.pipe |
Exposes maintainer sub-signals. |
pipes/health_score_v2_development.pipe |
Exposes development sub-signals. |
datasources/project_insights_health_breakdown_ds.datasource |
Defines project-level breakdown storage. |
datasources/health_score_v2_signal_detail_ds.datasource |
Defines repository-level detail storage. |
datasources/health_score_v2_security_ds.datasource |
Extends the security schema. |
datasources/health_score_v2_maintainer_ds.datasource |
Extends the maintainer schema. |
datasources/health_score_v2_development_ds.datasource |
Extends the development schema. |
Suppressed comments (8)
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:58
orgDiversityScoreis0for blocked repos, so averaging every row dilutes available scores with missing data whileorgDiversityAvailablestill becomes true. Average only rows whose availability flag is set.
avg(sd.orgDiversityScore) AS orgDiversityScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:61
- For unavailable Gerrit responsiveness data, the source emits score
0withresponsivenessAvailable = false. Including that row inavgconverts a blocked signal into a penalty for mixed-coverage projects; filter the average by availability.
avg(sd.responsivenessScore) AS responsivenessScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:71
- The source emits
scorecardScorePts = 0when Scorecard data is unavailable. Averaging those blocked rows lowers projects that have valid Scorecard data on only some repos; gate the average withscorecardAvailable.
avg(sd.scorecardScorePts) AS scorecardScorePts,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:74
- Missing repo-details data produces a zero security-practices score with
securityPracticesAvailable = false. This average therefore treats blocked repos as failures; exclude unavailable rows so the project score matches the exposed coverage state.
avg(sd.securityPracticesScore) AS securityPracticesScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:81
- When dependency data is unavailable, the source still computes a non-null fallback score while setting
dependencyHealthAvailable = false. Averaging that value makes blocked repos affect the project score; only average available rows.
avg(sd.dependencyHealthScore) AS dependencyHealthScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:84
- Unavailable release data yields score
0, so this average penalizes projects for blocked repos even thoughreleaseCadenceAvailablereports the signal as available when any repo has data. Gate the average on the availability flag.
avg(sd.releaseCadenceScore) AS releaseCadenceScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:91
- The source score is zero when issue-resolution data is blocked, so averaging all repos turns unavailable data into a negative score while
issueResolutionAvailablemay still be true. Average only available rows.
avg(sd.issueResolutionScore) AS issueResolutionScore,
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:96
- The source score is zero when PR-merge data is blocked. Including those rows in the average penalizes mixed-coverage projects despite the availability flag; exclude unavailable rows from this aggregate.
avg(sd.prMergeScore) AS prMergeScore,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SELECT | ||
| ip.id AS projectId, | ||
| ip.slug AS slug, | ||
| avg(sd.busFactorScore) AS busFactorScore, |
| max(sd.daysSinceLatest) AS daysSinceLatest, | ||
| max(sd.daysBetweenRecent) AS daysBetweenRecent, |
| d.merged12m AS merged12m, | ||
| d.closedUnmerged12m AS closedUnmerged12m, | ||
| d.medianMergeS AS medianMergeS | ||
| FROM (SELECT DISTINCT url AS repoUrl FROM repositories WHERE deletedAt IS NULL) AS base |
| `project_insights_impact_breakdown_ds`. | ||
| - Parameters: `slug` (single project). | ||
|
|
||
| TAGS "Insights, Widget", "Project", "Health" |


Summary
Part of IN-1212 (Health Score v2 content spec implementation) — the crowd.dev/Tinybird half.
*Availablecoverage flags, raw counts) in the 3 category pipes (health_score_v2_maintainer,health_score_v2_security,health_score_v2_development) — no scoring/business logic changed, only column exposure.project_insights_impact_breakdownpattern:health_score_v2_signal_detail(COPY) — LEFT JOINs the 3 widened category_dstables onrepoUrlproject_insights_health_breakdown_copy(COPY) — rolls repo-level rows up to project levelproject_insights_health_breakdown(endpoint) —slug-filtered public endpoint, consumed by the insights server routeDeploy status
Already deployed to production — validated end-to-end via
crowd-tinybird-manager(all 6 files pushed, all 5 copy backfill jobs confirmeddone,project_insights_health_breakdownconfirmed queryable by slug with real data + correct NULL-degradation behavior). Two real issues were found and fixed during the production push (not glossed over):toUInt8(...)casts.health_score_v2_maintainer_dswas missing acoveredWeightcolumn the pipe needed.insights-app-tokenalso needed aPIPES:READgrant added on the new endpoint pipe (a recurring gap worth flagging:tb pushdoesn't grant the consuming app token automatically).Staging could not be meaningfully exercised (workspace was at datasource/copy-pipe quota caps, unrelated to this change) — production deploy and validation are solid regardless.
Consumer
linuxfoundation/insightsPR (IN-1212) reads this endpoint via a newserver/api/project/[slug]/overview/health-score-breakdown.get.tsroute — that PR depends on this one already being deployed, which it is.Test plan
tb checkclean on all 11 touched filesdoneproject_insights_health_breakdownconfirmed queryable by slug with real data in productionsignal_coveragedegradation logic (Layer 1 redistribution, Layer 2 category-drop) — already implemented, this PR only exposes it