From d90f8f1068db7cf1bcff13c84a3b137ff712979a Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 4 May 2026 16:14:08 +0530 Subject: [PATCH 1/3] Allow members with 0 wins to match --- .circleci/config.yml | 2 +- src/reports/member/member-search.service.spec.ts | 3 +++ src/reports/member/member-search.service.ts | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6d288d..a10c67e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ workflows: branches: only: - develop - - PM-4949 + - PM-4931 # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index 6f662fa..7a43a60 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -227,6 +227,9 @@ describe("MemberSearchService", () => { expect(dataSql).toContain("requested_skills AS"); expect(dataSql).toContain("INNER JOIN user_match_data umd"); + expect(dataSql).toContain("THEN COUNT(DISTINCT usd.skill_id) ="); + expect(dataSql).toContain("ELSE COUNT(DISTINCT usd.skill_id) >= 1"); + expect(dataSql).not.toContain("usd.wins >= rs.min_wins"); expect(dataParams).toContainEqual([skillA, skillB]); expect(dataParams).toContainEqual([5, 0]); expect(dataParams).toContain("AND"); diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index d760b7b..421a36b 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -141,9 +141,8 @@ qualifying_users AS ( GROUP BY usd.user_id HAVING CASE WHEN ${pSearchType} = 'AND' - THEN COUNT(DISTINCT CASE WHEN usd.wins >= rs.min_wins THEN usd.skill_id END) - = ${pNumSkills}::integer - ELSE COUNT(DISTINCT CASE WHEN usd.wins >= rs.min_wins THEN usd.skill_id END) >= 1 + THEN COUNT(DISTINCT usd.skill_id) = ${pNumSkills}::integer + ELSE COUNT(DISTINCT usd.skill_id) >= 1 END ), user_match_data AS ( From 66abc009f51fcd8150dfa60e3e0399e9293cedc0 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Tue, 5 May 2026 11:27:52 +0530 Subject: [PATCH 2/3] Only verified skills need to be used --- src/reports/member/dto/member-search-response.dto.ts | 2 +- src/reports/member/member-search.service.spec.ts | 3 +++ src/reports/member/member-search.service.ts | 11 ++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/reports/member/dto/member-search-response.dto.ts b/src/reports/member/dto/member-search-response.dto.ts index 3e15d33..59209a3 100644 --- a/src/reports/member/dto/member-search-response.dto.ts +++ b/src/reports/member/dto/member-search-response.dto.ts @@ -9,7 +9,7 @@ export class MatchedSkillDto { @ApiProperty({ description: - "True when the member has at least one challenge-win event for this skill.", + "True when the member has win credit and/or at least one platform skill event for this skill (e.g. submission, review); false for self-declared only.", }) isVerified!: boolean; diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index 7a43a60..2bc1fd0 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -226,6 +226,9 @@ describe("MemberSearchService", () => { expect(validationParams).toEqual([[skillA, skillB]]); expect(dataSql).toContain("requested_skills AS"); + expect(dataSql).toContain( + "(usd.wins >= rs.min_wins OR usd.submitted > 0)", + ); expect(dataSql).toContain("INNER JOIN user_match_data umd"); expect(dataSql).toContain("THEN COUNT(DISTINCT usd.skill_id) ="); expect(dataSql).toContain("ELSE COUNT(DISTINCT usd.skill_id) >= 1"); diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 421a36b..47957bd 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -141,8 +141,13 @@ qualifying_users AS ( GROUP BY usd.user_id HAVING CASE WHEN ${pSearchType} = 'AND' - THEN COUNT(DISTINCT usd.skill_id) = ${pNumSkills}::integer - ELSE COUNT(DISTINCT usd.skill_id) >= 1 + THEN COUNT(DISTINCT CASE + WHEN (usd.wins >= rs.min_wins OR usd.submitted > 0) + THEN usd.skill_id END) + = ${pNumSkills}::integer + ELSE COUNT(DISTINCT CASE + WHEN (usd.wins >= rs.min_wins OR usd.submitted > 0) + THEN usd.skill_id END) >= 1 END ), user_match_data AS ( @@ -160,7 +165,7 @@ user_match_data AS ( jsonb_build_object( 'id', usd.skill_id::text, 'name', usd.skill_name, - 'isVerified', usd.wins > 0, + 'isVerified', (usd.wins > 0 OR usd.submitted > 0), 'wins', usd.wins, 'submitted', usd.submitted ) From 3c086130823f89ef2244512136ae963973d60b10 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Fri, 8 May 2026 10:55:15 +0530 Subject: [PATCH 3/3] Only show verified skills in results --- .../member/dto/member-search-response.dto.ts | 2 +- .../member/member-search.service.spec.ts | 11 ++---- src/reports/member/member-search.service.ts | 38 +++++++++++-------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/reports/member/dto/member-search-response.dto.ts b/src/reports/member/dto/member-search-response.dto.ts index 59209a3..0141341 100644 --- a/src/reports/member/dto/member-search-response.dto.ts +++ b/src/reports/member/dto/member-search-response.dto.ts @@ -9,7 +9,7 @@ export class MatchedSkillDto { @ApiProperty({ description: - "True when the member has win credit and/or at least one platform skill event for this skill (e.g. submission, review); false for self-declared only.", + "True for platform-backed skill activity (wins and/or skill events). Matched skills only include these, not self-attested-only skills.", }) isVerified!: boolean; diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index 2bc1fd0..661ca28 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -142,7 +142,6 @@ describe("MemberSearchService", () => { expect(dataSql).not.toContain( 'EXISTS (SELECT 1 FROM recently_active ra WHERE ra.user_id = m."userId")', ); - expect(dataSql).not.toContain("COALESCE(m.verified, false) = true"); }); it("adds profileComplete CTE/join only when enabled and keeps count params free of pagination", async () => { @@ -226,13 +225,11 @@ describe("MemberSearchService", () => { expect(validationParams).toEqual([[skillA, skillB]]); expect(dataSql).toContain("requested_skills AS"); - expect(dataSql).toContain( - "(usd.wins >= rs.min_wins OR usd.submitted > 0)", - ); + expect(dataSql).toContain("FILTER (WHERE usd.wins > 0 OR usd.submitted > 0)"); expect(dataSql).toContain("INNER JOIN user_match_data umd"); - expect(dataSql).toContain("THEN COUNT(DISTINCT usd.skill_id) ="); - expect(dataSql).toContain("ELSE COUNT(DISTINCT usd.skill_id) >= 1"); - expect(dataSql).not.toContain("usd.wins >= rs.min_wins"); + expect(dataSql).toContain("THEN COUNT(DISTINCT CASE"); + expect(dataSql).toContain("ELSE COUNT(DISTINCT CASE"); + expect(dataSql).toContain("(usd.wins >= rs.min_wins OR usd.submitted > 0)"); expect(dataParams).toContainEqual([skillA, skillB]); expect(dataParams).toContainEqual([5, 0]); expect(dataParams).toContain("AND"); diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 47957bd..92bf277 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -153,23 +153,29 @@ qualifying_users AS ( user_match_data AS ( SELECT usd.user_id, - SUM( - 1.0 - + LEAST(usd.wins::float / 100.0, 0.5) - + CASE WHEN usd.submitted > 0 - THEN (usd.wins::float / usd.submitted::float) * 0.5 - ELSE 0.0 - END + COALESCE( + SUM( + 1.0 + + LEAST(usd.wins::float / 100.0, 0.5) + + CASE WHEN usd.submitted > 0 + THEN (usd.wins::float / usd.submitted::float) * 0.5 + ELSE 0.0 + END + ) FILTER (WHERE usd.wins > 0 OR usd.submitted > 0), + 0.0 ) AS total_skill_points, - jsonb_agg( - jsonb_build_object( - 'id', usd.skill_id::text, - 'name', usd.skill_name, - 'isVerified', (usd.wins > 0 OR usd.submitted > 0), - 'wins', usd.wins, - 'submitted', usd.submitted - ) - ORDER BY usd.skill_name + COALESCE( + jsonb_agg( + jsonb_build_object( + 'id', usd.skill_id::text, + 'name', usd.skill_name, + 'isVerified', (usd.wins > 0 OR usd.submitted > 0), + 'wins', usd.wins, + 'submitted', usd.submitted + ) + ORDER BY usd.skill_name + ) FILTER (WHERE usd.wins > 0 OR usd.submitted > 0), + '[]'::jsonb ) AS matched_skills FROM user_skill_data usd WHERE usd.user_id IN (SELECT user_id FROM qualifying_users)