Skip to content

chore: remove health score v1 fields and routes - #2375

Closed
gaspergrom wants to merge 15 commits into
mainfrom
chore/IN-1222-remove-health-v1-api
Closed

gaspergrom wants to merge 15 commits into
mainfrom
chore/IN-1222-remove-health-v1-api

Conversation

@gaspergrom

@gaspergrom gaspergrom commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Do not merge until Joana Maia confirms consumer sign-off (LFX One, Org Lens, OD, Self Serve, CNCF) and a deprecation date.

Summary

  • Removes the v1 health score from the insights API and frontend: the four sub-scores, the v1 category maths, the healthScore=true flag on /api/project, and the health-score-overview route. Dead v1 types go with them.
  • Public contract change for CNCF and other API consumers. GET /api/project/{slug}/insights drops contributorHealthScore, popularityHealthScore, developmentHealthScore and securityHealthScore. healthScore already carries the v2 value in raw points. This PR documents it that way: out of 100 with all three categories, out of 60, 65 or 75 when one category is missing, and null for unscored or archived projects. CNCF needs notice before this merges.
  • GET /api/project/{slug}, GET /api/project (the list), GET /api/collection/{slug}/projects and GET /api/collection/{slug}/project-repos return healthScore as each row's raw healthScoreV2, or null, the same mapping as the insights route. The field survives the v1 column removal in crowd.dev PR 3 (#4922). On the list and both collection routes, sort=healthScore, healthScore_desc and healthScore_asc sort by raw v2. The Open Source Index sorts by healthScoreV2Normalized and does not change.
  • Removes GET /api/project/{slug}/overview/health-score-overview. Its Tinybird pipe served about 3,500 external requests in the last 30 days.
  • GET /api/project/{slug}/overview/health-score returns only the 11 benchmark keys. It stops calling two signal pipes that no benchmark uses: health_score_security and health_score_contributions_outside_work_hours. The Contribution Time Distribution widget reads a different pipe and is unaffected.
  • After this PR, insights exposes no v1 health score anywhere: no route and no public API endpoint returns the v1 healthScore value or a v1 sub-score, whether or not crowd.dev PR 3 (#4922) has deployed. Widget benchmark callouts stay.
  • The four v1 sub-scores leave the insights response and both collection routes on this deploy. The Tinybird pipes still select them until crowd.dev PR 3, and the handlers filter them out with one shared list (legacyScoreFields in health-score.helpers.ts).
  • The Open Source Index tuple reads count from the end of each row, so they map correctly before and after the upstream v1 column is removed.
  • Does not change Tinybird pipes or columns. Those removals ship in later crowd.dev PRs under their own gates.

Contract change and sign-off gate

Joana's sign-off has to cover each of these.

  • org-dashboard (OD, live, linuxfoundation/org-dashboard). OD calls GET /api/project?healthScore=true in batches of 50 slugs (51 calls in 7 days) and reads the v1 breakdown that flag adds. This PR removes the flag, and OD's Key Projects page goes empty. Because list rows now carry healthScore = raw v2, which is null for about 812 unscored projects, OD's unguarded unpacking of project.healthScore throws on any batch that contains an unscored project. OD's project detail header also reads healthScore from GET /api/project/{slug} and grades it on v1 bands (80/60/40/20); it now gets raw v2 points, so a partial project (max 60, 65 or 75) can never reach Excellent and unscored projects lose the badge. OD must migrate both the list and the detail reads before this merges, as an explicit sign-off item.
  • AAIF website. AAIF reads healthScore from GET /api/project/{slug}. After this PR it gets raw v2 points, and AAIF's v1 bands (80/60/40) no longer fit. Notify AAIF.
  • List and collection consumers. On GET /api/project, GET /api/collection/{slug}/projects and GET /api/collection/{slug}/project-repos, each row's healthScore changes from the v1 score to raw v2 points, null when unscored (k8s goes from 86 to 71; about a third of repo rows have no v2 score). On all three routes, sort=healthScore, with or without _asc/_desc, orders by raw v2. The collection routes also stop returning the four v1 sub-scores. OD reads list rows, and AAIF's v1 bands misread raw v2 here as on /{slug}, so both notices must cover these routes too.
  • CNCF API. On IN-1212 (2026-08-25) Joana asked to keep backward compatibility for other metrics on the CNCF API. This PR removes the four v1 sub-scores from that API, so the sign-off must cover the removal explicitly.
  • Unidentified external caller. An unknown client calls /overview/health-score-overview (829 calls in 7 days). That route returns 404 after this PR.

Interim state (until the Tinybird removal)

GET /api/project, GET /api/project/{slug}, GET /api/project/{slug}/insights and the two collection routes return the raw v2 score under healthScore. Tinybird keeps selecting the v1 columns until crowd.dev PR 3 deploys; the insights route and the collection routes drop the four v1 sub-scores before responding, so no response carries a v1 value in the meantime.

Changes

File What changed
api/src/lib/collections.ts Comment only: drops "V1 health scores"
frontend/docs/openapi.json Removes the four sub-scores from the example, required and properties; describes healthScore as nullable raw v2 points (max 100, or 60/65/75 with a missing category)
frontend/server/api/project/[slug]/insights.get.ts Filters the four v1 sub-scores out of the Tinybird row with the shared legacyScoreFields; healthScore stays the v2 value
frontend/server/api/project/[slug]/insights.get.test.ts New. Covers the v2 mapping, sub-score removal and null for unscored projects
frontend/server/api/project/[slug]/overview/health-score-overview.get.ts Deleted
frontend/server/api/project/[slug]/index.ts Returns healthScore as the row's raw healthScoreV2
frontend/server/api/project/[slug]/index.test.ts New. Covers the raw v2 mapping and null for unscored projects
frontend/server/api/project/index.ts Removes the healthScore=true flag and the enrichment branch that called the removed pipe; returns each row's healthScore as the raw healthScoreV2 or null; maps sort=healthScore and healthScore_* to healthScoreV2
frontend/server/api/project/index.test.ts New. Covers the raw v2 healthScore, the healthScore and healthScore_* sort mapping, and unchanged healthScoreV2Normalized sorts
frontend/server/api/collection/[slug]/projects.ts Returns each row's healthScore as the raw healthScoreV2 or null; drops the four v1 sub-scores; maps sort=healthScore and healthScore_* to healthScoreV2
frontend/server/api/collection/[slug]/project-repos.ts Returns each project and repo row's healthScore as the raw healthScoreV2 or null; drops the four v1 sub-scores; maps sort=healthScore and healthScore_* to healthScoreV2
frontend/server/api/collection/[slug]/project-routes.test.ts New. Covers both collection routes: raw v2 for a project row, null for a repo row without a v2 score, the dropped v1 sub-scores, and the healthScore sort mapping with other sorts passing through
frontend/server/helpers/health-score.helpers.ts Fetches the 11 benchmark pipes only; removes the v1 category maths and the two signal pipes no benchmark uses; exports legacyScoreFields, the four v1 sub-score names the routes drop
frontend/server/helpers/health-score.helpers.test.ts New. Covers the 11 pipes and the 11 response keys
frontend/server/api/ossindex/categories.ts Reads each field by its position from the end of the row
frontend/server/api/ossindex/collections.ts Reads each field by its position from the end of the row
frontend/server/api/ossindex/groups.ts Reads each field by its position from the end of the row
frontend/server/api/ossindex/ossindex.test.ts Runs each tuple test with and without the v1 slot
frontend/types/overview/responses.types.ts Renames HealthScoreBase to HealthScoreTinybird; removes v1 fields and SecurityScore; trims HealthScoreResults to the 11 benchmark keys
frontend/types/project.ts Removes the four sub-scores and the v1 healthScore from ProjectInsightsTinybird and ProjectInsights; adds healthScoreV2 to ProjectTinybird

JIRA

IN-1222: Deprecate v1 health score sinks and tables

Related PRs

Deploy order

  1. After the Open Source Index PR. This branch is stacked on it. Retarget the base to main once that PR merges.
  2. Before the crowd.dev PR that removes the v1 computation, the v1 columns and the two signal pipes from Tinybird. If that PR deploys first, /overview/health-score-overview and /api/project?healthScore=true return 500, and every project page loses its benchmark callouts through /overview/health-score.
  3. Before the Tinybird removal runs, confirm that neither health_score_security nor health_score_contributions_outside_work_hours has served a request since this PR deployed.

DB migrations

No DB migrations.

Test plan

Checked on the QA build of this branch (round 1, then round 2 after the partial-marker commit, both PASS):

  • GET /api/project/k8s/insights has none of the four sub-scores. healthScore equals healthScoreV2, and healthLabel is present. An unknown slug returns 404.
  • GET /api/project/k8s/overview/health-score returns 11 keys, and the benchmark values equal production.
  • GET /api/project/k8s/overview/health-score-overview returns 404.
  • GET /api/project?healthScore=true returns a plain list with the same rows as without the flag.
  • Unit test: GET /api/project/{slug} returns healthScore equal to the raw healthScoreV2 (52 for a 52/65 partial) and null for an unscored project.
  • Unit test: GET /api/project returns each row's healthScore as the raw healthScoreV2 or null, sends sort=healthScore, healthScore_desc and healthScore_asc as healthScoreV2, and leaves healthScoreV2Normalized sorts as they are.
  • Local run against production Tinybird: sort=healthScore, healthScore_desc and healthScore_asc order by raw v2, every row's healthScore equals healthScoreV2, and healthScoreV2Normalized_desc still puts the 65/65 partials first.
  • Unit test: both collection routes return healthScore equal to the raw healthScoreV2 (71 for k8s) and null for a repo row without a v2 score. They send sort=healthScore, healthScore_desc and healthScore_asc as healthScoreV2 and leave healthScoreV2Normalized_desc and contributorCount_asc as they are.
  • Local run against production Tinybird: on /api/collection/cncf/projects and /project-repos, k8s returns healthScore 71, equal to healthScoreV2 (it was the v1 86), with no mismatches across 100 rows. sort=healthScore_desc on both routes returns the same order as healthScoreV2_desc (88, 87, 87, 87, 86). No row on either route carries a v1 sub-score (0 of 100). The in-app callers of /project-repos read only identity and dependency fields, and nothing in the app calls /projects.
  • Checked every frontend/server route and api/ handler for v1 health fields (the v1 healthScore value, the four sub-scores, overallScore, health *Percentage fields). Only the two collection routes passed any through, and this PR drops them there. The public API (api/) builds its responses field by field and returns v2 only.
  • Benchmark callouts on the Popularity, Contributors and Development tabs match production in text and severity. No "Error fetching benchmarks" toast on any tab.
  • Contribution Time Distribution widget renders on Development.
  • Project overview health card and breakdown match production.
  • Collection detail dependency columns show no unexpected zeros.
  • Test-case matrix and persona run (shared with the Open Source Index PR): the OpenAPI drift case fails on production too. The persona run passed. Its one interim finding (v1 healthScore on list endpoints) no longer applies: the list now returns raw v2.

To check after deploy and before the Tinybird removal:

  • /docs renders the updated OpenAPI file. (The local /docs returned 502, so this needs a deployed check.)
  • Tinybird pipe stats show no requests to health_score_security or health_score_contributions_outside_work_hours since this PR deployed.
  • Consumer sign-off and deprecation date recorded (see the gate at the top).
  • CNCF notified of the four removed fields.
  • OD migrated off GET /api/project?healthScore=true.
  • AAIF notified that healthScore on GET /api/project/{slug}, GET /api/project and the collection routes is raw v2 points.

Pre-existing and not changed here: the OpenAPI document lists type, repoUrl and maturity for the insights response, which the live response omits.

Checklist

  • git commit --signoff -S on every commit
  • MIT license header on the five new test files
  • Consumer sign-off and deprecation date (gate)
  • PR diff: about 690 lines on top of the Open Source Index PR (about 1,470 against main until that PR merges)
  • No unrelated changes bundled
  • Cross-repo impact documented (see Deploy order)

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…N-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 20:46

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.

🔵 Needs a closer look

It removes externally consumed contracts while consumer sign-off remains pending, and the OpenAPI 3.1 null schema needs correction.

1 open finding
What changed in this PR

Removes legacy Health Score v1 API surfaces while retaining v2 scoring and benchmark functionality.

Changes:

  • Removes v1 fields, route, query flag, calculations, and unused Tinybird calls.
  • Preserves OSS Index tuple compatibility across upstream schema changes.
  • Adds focused endpoint, benchmark, and tuple-mapping tests.
File Description
api/​src/​lib/​collections.ts Updates collection mapping comment.
frontend/​docs/​openapi.json Removes v1 fields and documents nullable v2 score.
frontend/​server/​api/​project/​[slug]/​insights.get.ts Filters legacy fields and maps v2 score.
frontend/​server/​api/​project/​[slug]/​insights.get.test.ts Tests v2 and null score responses.
frontend/​server/​api/​project/​[slug]/​overview/​health-score-overview.get.ts Removes the legacy route.
frontend/​server/​api/​project/​index.ts Removes health-score enrichment flag.
frontend/​server/​helpers/​health-score.helpers.ts Retains only benchmark signals and response keys.
frontend/​server/​helpers/​health-score.helpers.test.ts Tests benchmark pipes and schema.
frontend/​server/​api/​ossindex/​categories.ts Maps tuple fields relative to the tail.
frontend/​server/​api/​ossindex/​collections.ts Supports both upstream tuple layouts.
frontend/​server/​api/​ossindex/​groups.ts Preserves group tuple mapping.
frontend/​server/​api/​ossindex/​ossindex.test.ts Tests both tuple layouts.
frontend/​types/​overview/​responses.types.ts Removes v1 health-score types.
frontend/​types/​project.ts Removes legacy insight fields.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 244 to +245
"type": "number",
"nullable": true,
…scores IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 22:42
@gaspergrom
gaspergrom force-pushed the chore/IN-1222-remove-health-v1-api branch from dd4a4d0 to 973b6fe Compare October 12, 2026 22:42

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.

🟡 Changes recommended

The single-project endpoint switches to v2 despite the documented interim contract, and required consumer sign-off remains outstanding.

2 open findings

🧠 Review effort: Balanced

return {
...project,
// External callers read healthScore, so it carries the raw v2 score.
healthScore: project.healthScoreV2,
@gaspergrom gaspergrom changed the title chore: remove health score v1 fields and routes IN-1222 chore: remove health score v1 fields and routes Oct 12, 2026
…s once IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 08:41
@gaspergrom
gaspergrom force-pushed the chore/IN-1222-remove-health-v1-api branch from 973b6fe to d347c1e Compare October 13, 2026 08:41

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.

🔵 Needs a closer look

The OpenAPI 3.1 nullability schema is invalid, and required consumer sign-off remains outstanding.

2 open findings

🧠 Review effort: Balanced

…N-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…ject list IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
… IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 09:18
@gaspergrom
gaspergrom force-pushed the chore/IN-1222-remove-health-v1-api branch from d347c1e to 1f8e782 Compare October 13, 2026 09:18

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.

🟡 Changes recommended

OpenAPI nullability and route response typings need correction, while required consumer sign-off remains outstanding.

4 open findings

🧠 Review effort: Balanced

return {
...project,
// External callers read healthScore, so it carries the raw v2 score.
healthScore: project.healthScoreV2,
let projects = res.data.map((p: ProjectTinybird) => ({
const projects = res.data.map((p: ProjectTinybird) => ({
...p,
healthScore: p.healthScoreV2 ?? null,
…1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 10:29

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.

🟡 Changes recommended

Response typings and OpenAPI nullability remain inaccurate, and required consumer sign-off is still pending.

5 open findings

🧠 Review effort: Balanced

Comment thread frontend/types/project.ts
popularityHealthScore: number;
developmentHealthScore: number;
securityHealthScore: number;
healthScoreV2: number | null;
… routes IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 10:35

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.

🔵 Needs a closer look

It changes actively consumed public contracts while consumer sign-off and several schema and response-typing corrections remain outstanding.

5 open findings

🧠 Review effort: Balanced

…s IN-1222

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 11:00

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.

Comment on lines +124 to +126
...Object.fromEntries(
Object.entries(item).filter(([key]) => !legacyScoreFields.includes(key)),
),
Comment on lines +95 to +97
...Object.fromEntries(
Object.entries(project).filter(([key]) => !legacyScoreFields.includes(key)),
),
Base automatically changed from feat/IN-1222-osi-health-v2 to main October 13, 2026 13:15
@gaspergrom

Copy link
Copy Markdown
Collaborator Author

Closing for now: org-dashboard still reads the v1 health score (/api/project?healthScore=true) and will keep running until it is retired, expected early November 2026. Reopen and rebase this once org-dashboard is shut down. The branch is kept.

@gaspergrom gaspergrom closed this Oct 15, 2026
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.

2 participants