From 833b82feea4c7f47e0097e89a692cfb924fc9576 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Sun, 26 Jul 2026 12:40:08 +0200 Subject: [PATCH] Exclude null compatibility scores when ordering profiles by compatibility. Update `compute-scores` logic to delete invalid score rows instead of setting them to null. Add corresponding test coverage. --- backend/api/src/get-profiles.ts | 4 ++++ backend/api/tests/unit/get-profiles.unit.test.ts | 16 ++++++++++++++++ .../shared/src/compatibility/compute-scores.ts | 7 ++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index ecad2b79..ab6e69e3 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -659,6 +659,10 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC : '90 days', }), + // Precomputed scores can be nulled out (e.g. after the user deletes their last shared + // prompt) instead of the row being removed — don't surface those as matches. + orderByParam === 'compatibility_score' && where('cs.score IS NOT NULL'), + // Exclude profiles that the requester has chosen to hide userId && where( diff --git a/backend/api/tests/unit/get-profiles.unit.test.ts b/backend/api/tests/unit/get-profiles.unit.test.ts index e885cfc0..f9127e2e 100644 --- a/backend/api/tests/unit/get-profiles.unit.test.ts +++ b/backend/api/tests/unit/get-profiles.unit.test.ts @@ -319,4 +319,20 @@ describe('loadProfiles', () => { expect(profilesModule.loadProfiles(props)).rejects.toThrowError('Incompatible with user ID') }) }) + + describe('when ordering by compatibility score', () => { + it('excludes profiles whose precomputed score was nulled out', async () => { + ;(mockPg.map as jest.Mock).mockResolvedValue([]) + ;(mockPg.one as jest.Mock).mockResolvedValue(0) + + await profilesModule.loadProfiles({ + orderBy: 'compatibility_score', + compatibleWithUserId: 'user-1', + }) + + const [query] = mockPg.map.mock.calls[0] + + expect(query).toContain('cs.score IS NOT NULL') + }) + }) }) diff --git a/backend/shared/src/compatibility/compute-scores.ts b/backend/shared/src/compatibility/compute-scores.ts index 27bf0b82..45be30e4 100644 --- a/backend/shared/src/compatibility/compute-scores.ts +++ b/backend/shared/src/compatibility/compute-scores.ts @@ -29,11 +29,12 @@ export async function recomputeCompatibilityScoresForUser( // Load all answers for the target user const answersSelf = await getAnswersForUser(userId) - // If the user has no answered questions, set the score to null + // If the user has no answered questions, there's no valid score to keep for any pair + // involving them — remove the rows rather than nulling them out, so a row's existence + // always means "we have a valid score for this pair". if (!hasAnsweredQuestions(answersSelf)) { await pg.none( - `update compatibility_scores - set score = null + `delete from compatibility_scores where user_id_1 = $1 or user_id_2 = $1`, [userId],