Skip to content

feat: expose health score v2 sub-signal detail via new pipe chain (CM-IN-1212) - #4448

Merged
gaspergrom merged 4 commits into
mainfrom
feat/CM-IN-1212-healthscore-v2-thresholds
Aug 7, 2026
Merged

feat: expose health score v2 sub-signal detail via new pipe chain (CM-IN-1212)#4448
gaspergrom merged 4 commits into
mainfrom
feat/CM-IN-1212-healthscore-v2-thresholds

Conversation

@gaspergrom

Copy link
Copy Markdown
Contributor

Summary

Part of IN-1212 (Health Score v2 content spec implementation) — the crowd.dev/Tinybird half.

  • Promotes previously-computed-but-discarded sub-signal columns (per-signal scores, *Available coverage 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.
  • Adds a new pipe chain rolling repo-level signal detail up to project level for the insights frontend's health-breakdown UI, mirroring the existing project_insights_impact_breakdown pattern:
    • health_score_v2_signal_detail (COPY) — LEFT JOINs the 3 widened category _ds tables on repoUrl
    • project_insights_health_breakdown_copy (COPY) — rolls repo-level rows up to project level
    • project_insights_health_breakdown (endpoint) — slug-filtered public endpoint, consumed by the insights server route

Deploy status

Already deployed to production — validated end-to-end via crowd-tinybird-manager (all 6 files pushed, all 5 copy backfill jobs confirmed done, project_insights_health_breakdown confirmed queryable by slug with real data + correct NULL-degradation behavior). Two real issues were found and fixed during the production push (not glossed over):

  • UInt8/UInt16 schema type mismatches on 5 expressions across the category pipes required explicit toUInt8(...) casts.
  • health_score_v2_maintainer_ds was missing a coveredWeight column the pipe needed.
  • The insights-app-token also needed a PIPES:READ grant added on the new endpoint pipe (a recurring gap worth flagging: tb push doesn'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/insights PR (IN-1212) reads this endpoint via a new server/api/project/[slug]/overview/health-score-breakdown.get.ts route — that PR depends on this one already being deployed, which it is.

Test plan

  • tb check clean on all 11 touched files
  • All 5 copy backfill jobs confirmed done
  • project_insights_health_breakdown confirmed queryable by slug with real data in production
  • No scoring/business logic touched — verified via direct SQL read of the 3 category pipes' existing signal_coverage degradation logic (Layer 1 redistribution, Layer 2 category-drop) — already implemented, this PR only exposes it

…-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>
Copilot AI balanced review requested due to automatic review settings August 6, 2026 12:41
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large schema and aggregation surface for production health metrics; rollup SQL mistakes could misstate project scores, though category scoring formulas are not changed.

Overview
IN-1212 adds Tinybird data and endpoints so the insights project overview can show Health Score v2 per-signal breakdowns (scores, raw metrics, and *Available “blocked” flags).

The three category scoring pipes (health_score_v2_maintainer, health_score_v2_security, health_score_v2_development) now select through sub-signal scores, coverage flags, and underlying counts/medians into their _ds tables (with explicit toUInt8 where needed); category totals and rescaling logic are unchanged. New health_score_v2_signal_detail COPY joins those three datasources per repo into health_score_v2_signal_detail_ds.

A new project_insights_health_breakdown_copy pipe rolls repo signal detail up to project level into project_insights_health_breakdown_ds, using coverage-aware sumIf/countIf averages for scored signals (not plain avg on placeholder zeros), min for release-cadence day fields, and typed max/min/avg rules elsewhere. project_insights_health_breakdown is a slug-filtered read endpoint over that materialized table. Copy jobs are scheduled at 50 2 and 55 2 to avoid hour-2 copy congestion before the 0 3 project_insights_copy deadline.

Reviewed by Cursor Bugbot for commit 23532d1. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • orgDiversityScore is 0 for blocked repos, so averaging every row dilutes available scores with missing data while orgDiversityAvailable still 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 0 with responsivenessAvailable = false. Including that row in avg converts 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 = 0 when Scorecard data is unavailable. Averaging those blocked rows lowers projects that have valid Scorecard data on only some repos; gate the average with scorecardAvailable.
        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 though releaseCadenceAvailable reports 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 issueResolutionAvailable may 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.

Comment thread services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe Outdated
Comment thread services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe Outdated
Comment thread services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe Outdated
Comment thread services/libs/tinybird/pipes/project_insights_health_breakdown.pipe Outdated
@gaspergrom
gaspergrom requested a review from epipav August 6, 2026 15:17
Comment thread services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe Outdated
Comment thread services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe Outdated
Copilot AI review requested due to automatic review settings August 7, 2026 09:33

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ 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 732f160. Configure here.

Comment thread services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Suppressed comments (9)

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:59

  • These scores are 0 when their availability flag is false, so plain avg turns blocked repos into scored-zero repos while the project is marked available if any repo has data. Average only available rows and return NULL when none qualify; the same gating is needed for the other availability-backed score averages below.
        avg(sd.busFactorScore) AS busFactorScore,
        max(sd.busFactorAvailable) AS busFactorAvailable,
        max(sd.busFactorCount) AS busFactorCount,
        avg(sd.orgDiversityScore) AS orgDiversityScore,
        max(sd.orgDiversityAvailable) AS orgDiversityAvailable,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:87

  • These are inverse recency metrics: larger values mean an older or slower release cadence. max therefore selects the stalest repository, while the consumer describes this as the project's last release; use min to select the most recent project release consistently with max(lastCommitAt).
        max(sd.daysSinceLatest) AS daysSinceLatest,
        max(sd.daysBetweenRecent) AS daysBetweenRecent,

services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe:67

  • repositories is a ReplacingMergeTree, so filtering mutable deletedAt before resolving the latest version can retain a superseded non-deleted row after soft deletion. Apply FINAL before the filter, as established by repositories_populated_copy.pipe:133 and repos_to_channels_excluded.pipe:15.
    FROM (SELECT DISTINCT url AS repoUrl FROM repositories WHERE deletedAt IS NULL) AS base

services/libs/tinybird/pipes/project_insights_health_breakdown.pipe:13

  • The production token grant described in the PR is not declared here, so a fresh workspace or redeployment will lose the manual permission and the Insights route will receive authorization errors. Persist the consuming token grant in the endpoint definition.
TAGS "Insights, Widget", "Project", "Health"

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:89

  • The consumer labels this as the project's commit count, but max returns only the busiest repository's count and under-reports every project with commits in multiple repos. Sum the per-repository activity counts while preserving NULL when no repository has commit history.
        max(sd.commitsLast6m) AS commitsLast6m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:94

  • These independent maxima can come from different repositories, so the consumer's project-level closed / opened comparison can describe a ratio that no aggregate dataset supports. Sum both event counts over repositories with issue-resolution data instead.
        max(sd.closed12m) AS closed12m,
        max(sd.opened12m) AS opened12m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:99

  • Taking separate maxima can combine merged12m from one repo with closedUnmerged12m from another, producing an invalid project merge rate in the consumer. Sum both disjoint PR outcome counts over repositories where this signal is available.
        max(sd.merged12m) AS merged12m,
        max(sd.closedUnmerged12m) AS closedUnmerged12m,

services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe:75

  • This job remains in the already saturated 02:00 COPY window, where the account's concurrent-copy limit can leave overflow jobs queued. Move this schedule—and its downstream project rollup—into a quieter window while preserving dependency order.
COPY_SCHEDULE 25 2 * * *

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:113

  • This downstream job also remains in the saturated 02:00 COPY window. Reschedule it together with health_score_v2_signal_detail so both avoid the concurrency cap and this job still runs after its source refresh.
COPY_SCHEDULE 35 2 * * *

Comment on lines +68 to +70
max(sd.openCriticals) AS openCriticals,
max(sd.openHighs) AS openHighs,
max(sd.openModerates) AS openModerates,
- Fix blocked-score dilution: score columns with a matching *Available
  flag now use sumIf/countIf coverage-filtered averaging instead of
  plain avg(), excluding blocked repos and preserving NULL when no
  repo in the project has the signal (Cursor HIGH, Copilot).
- Fix min/max inversion: daysSinceLatest/daysBetweenRecent are
  inverse-activity durations (smaller = more recent/frequent releases
  per health_score_v2_development.pipe's own scoring), now min() not
  max() (Copilot).
- Add FINAL to the repositories base-repo subquery in
  health_score_v2_signal_detail.pipe to avoid retaining a stale
  pre-soft-delete row version (Copilot).
- Declare the insights-app-token READ grant directly in
  project_insights_health_breakdown.pipe so it survives a fresh
  workspace/redeploy instead of relying on a manual dashboard fix
  (Copilot).
- Move the two new COPY pipes off the crowded 2:20-2:40am window to
  50/55 2 * * *, based on real 7-day tinybird.jobs_log analysis (not
  a guess) showing that window hosts several long-running neighbors;
  the new slot lands in hour 2's emptiest observed stretch with a
  comfortable buffer before project_insights_copy.pipe's hard 0 3
  deadline (epipav).

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 7, 2026 09:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (6)

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:85

  • These are additive vulnerability counts, but max() returns only the worst single repository. The consumer presents them as the project's number of open vulnerabilities, so a project with vulnerabilities in multiple repos is undercounted. Sum the nullable per-repo counts while retaining NULL when no repo has data.
        max(sd.openCriticals) AS openCriticals,
        max(sd.openHighs) AS openHighs,
        max(sd.openModerates) AS openModerates,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:104

  • commitsLast6m counts repo-specific events, so taking the maximum underreports projects with activity across multiple repositories. The consumer displays this as “N commits in the past six months” for the project; aggregate the counts instead.
        max(sd.commitsLast6m) AS commitsLast6m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:109

  • Taking independent maxima can combine closed12m from one repo with opened12m from another, producing a project backlog ratio that never existed and omitting events from other repos. The consumer compares these as project-wide annual totals, so both counts should be additive.
        max(sd.closed12m) AS closed12m,
        max(sd.opened12m) AS opened12m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:114

  • Independent maxima undercount project PR volume and can combine merged and unmerged counts from different repositories, yielding a misleading merge rate. The consumer computes and describes a project-wide ratio from these fields, so aggregate the per-repo event totals.
        max(sd.merged12m) AS merged12m,
        max(sd.closedUnmerged12m) AS closedUnmerged12m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:75

  • max() only returns the count from one repository, not the project's distinct maintainer/organization count. The Insights consumer renders these values as project-wide counts (for example, “N active maintainers”), so projects with disjoint maintainers or organizations across repositories are underreported. This needs a project-grain distinct aggregation from the underlying identities; summing repo counts would double-count identities shared across repos.

This issue also appears in the following locations of the same file:

  • line 104
  • line 108
  • line 113
        max(sd.busFactorCount) AS busFactorCount,
        sumIf(sd.orgDiversityScore, sd.orgDiversityAvailable) / nullIf(countIf(sd.orgDiversityAvailable), 0) AS orgDiversityScore,
        max(sd.orgDiversityAvailable) AS orgDiversityAvailable,
        max(sd.orgCount) AS orgCount,

services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource:23

  • This datasource documentation says both release-duration fields use max, but the copy pipe now intentionally uses min (project_insights_health_breakdown_copy.pipe:101-102). Update the documented contract so consumers do not interpret these values as the stalest repository.
    `daysSinceLatest`/`daysBetweenRecent` (max), `commitActivityScore` (avg), `commitsLast6m` (max),

@gaspergrom
gaspergrom requested a review from epipav August 7, 2026 09:57
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 7, 2026 10:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Suppressed comments (9)

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:77

  • orgCount is distinct only within each repository. max() undercounts a multi-repository project's organizational diversity when different repositories have contributors from different organizations, while the consumer presents this value as the number of organizations spanning the project. Compute a project-grain distinct organization count instead.
        max(sd.orgCount) AS orgCount,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:105

  • vulnerableDeps is distinct only per repository, but max() is exposed as the project's vulnerable-dependency count. Repositories with different vulnerable dependencies are therefore undercounted. Compute the distinct dependency union at project grain so shared dependencies are deduplicated without discarding dependencies unique to another repository.
        max(sd.vulnerableDeps) AS vulnerableDeps,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:88

  • These fields are displayed as the project's number of open vulnerabilities, but independent maxima return only the largest per-repository count. For two repositories with one critical vulnerability each, the endpoint reports 1 instead of 2; the three maxima can also originate from different repositories. Sum the per-repository counts while retaining NULL when no count data exists, and update the aggregation descriptions accordingly.
        max(sd.openCriticals) AS openCriticals,
        max(sd.openHighs) AS openHighs,
        max(sd.openModerates) AS openModerates,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:112

  • The consumer renders this as “N commits in the past six months” for the project, but max() reports only its busiest repository. Multi-repository projects will be systematically undercounted; sum the repository counts while preserving NULL for projects without commit history, and update the aggregation descriptions.
        max(sd.commitsLast6m) AS commitsLast6m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:118

  • The UI calculates and describes the project-wide issue close ratio from these two fields. Taking each maximum independently both undercounts totals and can combine values from different repositories into a ratio that no repository or project has. Sum both additive counts so the displayed project ratio is accurate, preserving NULL for missing data and updating the aggregation descriptions.
        max(sd.closed12m) AS closed12m,
        max(sd.opened12m) AS opened12m,

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:124

  • The consumer derives a project-wide PR merge rate and states “N of M pull requests” from these fields. Independent maxima can come from different repositories and do not represent the project's merged or closed-unmerged totals, producing an incorrect rate and status. Sum these additive counts across repositories, preserve NULL for missing data, and update the aggregation descriptions.
        max(sd.merged12m) AS merged12m,
        max(sd.closedUnmerged12m) AS closedUnmerged12m,

services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource:23

  • This datasource documents both release-duration rollups as max, but the copy pipe now uses min for each. Update the description so readers do not implement or debug against the obsolete aggregation contract.
    `daysSinceLatest`/`daysBetweenRecent` (max), `commitActivityScore` (avg), `commitsLast6m` (max),

services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe:19

  • project_insights_copy.pipe has no reference to this datasource or pipe; it reads health_score_v2_repo_copy_ds instead. The claimed transitive dependency is therefore false and can mislead future schedule changes. Describe only the actual five-minute dependency on project_insights_health_breakdown_copy.pipe.
    `project_insights_health_breakdown_copy.pipe`'s `55 2` and `project_insights_copy.pipe`'s hard
    `0 3 * * *` deadline (which reads from this pipe's output transitively).

services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe:56

  • project_insights_copy.pipe does not read this pipe's output; it reads health_score_v2_repo_copy_ds. Keeping a nonexistent deadline dependency in the schedule rationale may cause maintainers to preserve ordering that is not required. Remove that claim while retaining the real upstream timing rationale.
    runtimes in production so the buffer is very conservative, and completes 5 minutes before
    `project_insights_copy.pipe`'s hard `0 3 * * *` deadline (which reads this pipe's output).

sumIf(sd.busFactorScore, sd.busFactorAvailable)
/ nullIf(countIf(sd.busFactorAvailable), 0) AS busFactorScore,
max(sd.busFactorAvailable) AS busFactorAvailable,
max(sd.busFactorCount) AS busFactorCount,
@gaspergrom
gaspergrom merged commit 44b9790 into main Aug 7, 2026
16 checks passed
@gaspergrom
gaspergrom deleted the feat/CM-IN-1212-healthscore-v2-thresholds branch August 7, 2026 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants