From 869e70f8c73970ab428154abd3e6dbc37ed1c7be Mon Sep 17 00:00:00 2001 From: kiannidev <156195510+kiannidev@users.noreply.github.com> Date: Sun, 5 Jul 2026 05:45:24 +0200 Subject: [PATCH] feat(engine): add rankOpportunitiesAtOrAboveScore helper Filter ranked discovery candidates by a minimum score threshold with finite-only input validation and stable rankOpportunities ordering. Co-authored-by: Cursor --- packages/gittensory-engine/src/index.ts | 1 + .../src/ranked-opportunity-min-score.ts | 15 ++++++ .../test/opportunity-ranker.test.ts | 18 ++++++- test/unit/opportunity-ranker.test.ts | 50 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 packages/gittensory-engine/src/ranked-opportunity-min-score.ts diff --git a/packages/gittensory-engine/src/index.ts b/packages/gittensory-engine/src/index.ts index 0bf70e93ea..2dc2c6f600 100644 --- a/packages/gittensory-engine/src/index.ts +++ b/packages/gittensory-engine/src/index.ts @@ -10,6 +10,7 @@ export { rankOpportunities, type OpportunityRankInput, } from "./opportunity-ranker.js"; +export { rankOpportunitiesAtOrAboveScore } from "./ranked-opportunity-min-score.js"; export { extractObjectiveAnchorHistory, extractObjectiveAnchorFeatures, diff --git a/packages/gittensory-engine/src/ranked-opportunity-min-score.ts b/packages/gittensory-engine/src/ranked-opportunity-min-score.ts new file mode 100644 index 0000000000..839742718b --- /dev/null +++ b/packages/gittensory-engine/src/ranked-opportunity-min-score.ts @@ -0,0 +1,15 @@ +import { rankOpportunities, type OpportunityRankInput } from "./opportunity-ranker.js"; + +/** + * Rank candidates and keep only those whose {@link rankOpportunityScore} is at or above `minScore`. + * Non-finite thresholds return an empty list. Pure — delegates ordering to {@link rankOpportunities}. + */ +export function rankOpportunitiesAtOrAboveScore( + candidates: Array, + minScore: number, +): Array & OpportunityRankInput & { rankScore: number }> { + if (!Number.isFinite(minScore)) return []; + if (candidates.length === 0) return []; + const threshold = Math.min(1, Math.max(0, minScore)); + return rankOpportunities(candidates).filter((entry) => entry.rankScore >= threshold); +} diff --git a/packages/gittensory-engine/test/opportunity-ranker.test.ts b/packages/gittensory-engine/test/opportunity-ranker.test.ts index 27ff3f3c2a..e4c351ddb3 100644 --- a/packages/gittensory-engine/test/opportunity-ranker.test.ts +++ b/packages/gittensory-engine/test/opportunity-ranker.test.ts @@ -3,12 +3,13 @@ // (dist/index.js) so the export contract itself is exercised. Pure module — no network, never flakes. import { test } from "node:test"; import assert from "node:assert/strict"; -import { rankOpportunityScore, rankOpportunities, pickTopRankedOpportunities } from "../dist/index.js"; +import { rankOpportunityScore, rankOpportunities, pickTopRankedOpportunities, rankOpportunitiesAtOrAboveScore } from "../dist/index.js"; test("barrel: the public entrypoint re-exports the ranker API", () => { assert.equal(typeof rankOpportunityScore, "function"); assert.equal(typeof rankOpportunities, "function"); assert.equal(typeof pickTopRankedOpportunities, "function"); + assert.equal(typeof rankOpportunitiesAtOrAboveScore, "function"); }); const full = { potential: 1, feasibility: 1, laneFit: 1, freshness: 1, dupRisk: 0 }; @@ -120,3 +121,18 @@ test("pickTopRankedOpportunities: rejects non-finite limits", () => { assert.deepEqual(pickTopRankedOpportunities(candidates, Number.NaN), []); assert.deepEqual(pickTopRankedOpportunities(candidates, Number.POSITIVE_INFINITY), []); }); + +test("rankOpportunitiesAtOrAboveScore: keeps ranked candidates at or above the threshold", () => { + const candidates = [ + { id: "low", potential: 0.2, feasibility: 1, laneFit: 1, freshness: 1, dupRisk: 0 }, + { id: "high", potential: 0.9, feasibility: 1, laneFit: 1, freshness: 1, dupRisk: 0 }, + { id: "mid", potential: 0.5, feasibility: 1, laneFit: 1, freshness: 1, dupRisk: 0 }, + ]; + const filtered = rankOpportunitiesAtOrAboveScore(candidates, 0.5); + assert.deepEqual(filtered.map((entry) => entry.id), ["high", "mid"]); +}); + +test("rankOpportunitiesAtOrAboveScore: rejects non-finite thresholds", () => { + const candidates = [{ id: "only", ...full }]; + assert.deepEqual(rankOpportunitiesAtOrAboveScore(candidates, Number.NaN), []); +}); diff --git a/test/unit/opportunity-ranker.test.ts b/test/unit/opportunity-ranker.test.ts index d79285f2b8..942840df56 100644 --- a/test/unit/opportunity-ranker.test.ts +++ b/test/unit/opportunity-ranker.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { pickTopRankedOpportunities, rankOpportunities, rankOpportunityScore, type OpportunityRankInput } from "../../packages/gittensory-engine/src/opportunity-ranker"; +import { rankOpportunitiesAtOrAboveScore } from "../../packages/gittensory-engine/src/ranked-opportunity-min-score"; // A neutral, all-passing candidate (every factor 1, no contention → score 1); tests override one field at a time. function input(over: Partial = {}): OpportunityRankInput { @@ -186,3 +187,52 @@ describe("pickTopRankedOpportunities", () => { expect(barrel.pickTopRankedOpportunities(candidates, 1).map((entry) => entry.id)).toEqual(["top"]); }); }); + +describe("rankOpportunitiesAtOrAboveScore", () => { + const candidates = [ + { id: "mid", ...input({ potential: 0.5 }) }, + { id: "top", ...input() }, + { id: "low", ...input({ freshness: 0.25 }) }, + ]; + + it("keeps only candidates at or above the score threshold in rank order", () => { + const filtered = rankOpportunitiesAtOrAboveScore(candidates, 0.5); + expect(filtered.map((entry) => entry.id)).toEqual(["top", "mid"]); + expect(filtered.every((entry) => entry.rankScore >= 0.5)).toBe(true); + }); + + it("returns every candidate when the threshold is zero", () => { + expect(rankOpportunitiesAtOrAboveScore(candidates, 0).map((entry) => entry.id)).toEqual([ + "top", + "mid", + "low", + ]); + }); + + it("returns only perfect scores when the threshold is one", () => { + expect(rankOpportunitiesAtOrAboveScore(candidates, 1).map((entry) => entry.id)).toEqual(["top"]); + }); + + it("returns an empty array for a non-finite threshold or no candidates", () => { + expect(rankOpportunitiesAtOrAboveScore(candidates, Number.NaN)).toEqual([]); + expect(rankOpportunitiesAtOrAboveScore(candidates, Number.POSITIVE_INFINITY)).toEqual([]); + expect(rankOpportunitiesAtOrAboveScore([], 0.5)).toEqual([]); + }); + + it("clamps out-of-range thresholds before filtering", () => { + expect(rankOpportunitiesAtOrAboveScore(candidates, -0.5).map((entry) => entry.id)).toEqual([ + "top", + "mid", + "low", + ]); + expect(rankOpportunitiesAtOrAboveScore(candidates, 1.5).map((entry) => entry.id)).toEqual(["top"]); + }); + + it("is exported from the package barrel", async () => { + const barrel = await import("../../packages/gittensory-engine/src/index"); + expect(typeof barrel.rankOpportunitiesAtOrAboveScore).toBe("function"); + expect( + barrel.rankOpportunitiesAtOrAboveScore(candidates, 0.5).map((entry: { id: string }) => entry.id), + ).toEqual(["top", "mid"]); + }); +});