Skip to content

Add match scoring and resume download dropdown#151

Merged
michaelmwu merged 4 commits into
mainfrom
michaelmwu/ranked-crm-discord-match
Mar 4, 2026
Merged

Add match scoring and resume download dropdown#151
michaelmwu merged 4 commits into
mainfrom
michaelmwu/ranked-crm-discord-match

Conversation

@michaelmwu
Copy link
Copy Markdown
Member

@michaelmwu michaelmwu commented Mar 4, 2026

Description

  • Add weighted match scores with a cutoff and higher result limit for match-candidates.
  • Show match scores, link CRM contact names, and add a resume download dropdown using the authenticated flow.

Related Issue

  • N/A

How Has This Been Tested?

  • ruff (legacy alias), ruff format, mypy

Summary by CodeRabbit

  • New Features

    • Resume download option available directly from candidate match results (inline prompt to select/download resumes)
  • Enhancements

    • Introduced match scoring (match_score) with optional min_match_score filtering
    • Increased search results to 20 candidates
    • Candidate listings now show match score, Discord roles, seniority, and timezone
    • Ranking refined to favor skill-based score over role-only matches
  • Tests

    • Updated unit test to reflect new secondary-sort behavior

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 4, 2026

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56ac570b-1022-4f09-9978-9ebb8326c4ae

📥 Commits

Reviewing files that changed from the base of the PR and between 2515482 and 6a1c6a0.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/candidate_search.py
📝 Walkthrough

Walkthrough

Adds a resume-download UI (select + view) into the Discord CRM cog and wires it into match results; augments candidate scoring with a float match_score, a min_match_score filter, SQL scoring CTE, and updates tests to reflect new secondary-sort behavior.

Changes

Cohort / File(s) Summary
Resume download UI & CRM flow
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added MatchResumeView and MatchResumeSelect (select + view) to present up to 25 resume options and handle downloads. MatchResumeSelect.callback resolves the CRM cog, defers the interaction, calls _download_and_send_resume, audits outcomes, and handles errors. match_candidates now increases search limit to 20, passes min_match_score=8.0, collects resume_options, appends resume info to candidate blocks, and posts a follow-up "Resume download:" message with the selection view. CRM link formatting adjusted (name-as-link then email).
Candidate search scoring
packages/shared/src/five08/candidate_search.py
Added match_score: float to CandidateMatch. search_candidates(...) gained min_match_score: float = 0.0. Introduced a scored CTE computing required/preferred matches, timezone/role signals, and a base match_score; Python post-processing adds seniority adjustments, filters by min_match_score, and updates secondary sorting to factor membership/CRM link before final match_score.
Unit test update
tests/unit/test_candidate_search.py
Renamed test test_search_candidates_secondary_sort_discord_roles_before_skill_scoretest_search_candidates_secondary_sort_skill_score_over_discord_role and updated assertions to reflect that skill score now outranks discord-role-only matches.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Interaction as Discord Interaction
    participant CRMCog as CRM Cog
    participant DB as Database
    participant Audit as Audit Log

    User->>Interaction: Clicks "Resume download" dropdown
    Interaction->>CRMCog: MatchResumeSelect.callback(selected resume_id)
    CRMCog->>CRMCog: Resolve cog instance & lookup contact_name
    CRMCog->>Interaction: Defer interaction
    CRMCog->>DB: Fetch resume blob for resume_id
    DB-->>CRMCog: Resume content
    CRMCog->>User: Send resume (DM or attachment)
    CRMCog->>Audit: Record download event
    Audit-->>CRMCog: Acknowledgement
    CRMCog-->>Interaction: Send confirmation / follow-up message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through matches, nimble and spry,
Click a menu — a resume flutters by,
Scores add sparkle, candidates gleam,
Downloads delivered like a carrot dream,
Hooray — another perfect team! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding match scoring and a resume download dropdown, which are the primary enhancements across the modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/ranked-crm-discord-match

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/unit/test_candidate_search.py (1)

309-341: ⚠️ Potential issue | 🟠 Major

This test does not exercise the new score-first ranking path.

The test data omits match_score/has_crm_link, so it isn’t validating the new ordering logic you introduced. Set explicit row scores (and link flags) so the assertion reflects the intended behavior.

✅ Suggested test adjustment
 def test_search_candidates_secondary_sort_skill_score_over_discord_role() -> None:
     """Candidate with stronger skill match should rank above one with only role match."""
     role_match_row = _make_row(
         crm_contact_id="role",
         is_member=False,
         discord_roles=["Full Stack"],
         discord_role_matched=1,
         skills=["python"],
         required_matched=1,
         required_skill_score=1,
+        has_crm_link=True,
+        match_score=14.0,
     )
     skill_score_row = _make_row(
         crm_contact_id="skill",
         is_member=False,
         discord_roles=[],
         discord_role_matched=0,
         skills=["python", "django"],
         required_matched=2,
         required_skill_score=8,
+        has_crm_link=True,
+        match_score=36.0,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_candidate_search.py` around lines 309 - 341, The test
test_search_candidates_secondary_sort_skill_score_over_discord_role currently
omits the new score-first ranking fields; update the two test rows created via
_make_row to include explicit match_score and has_crm_link values so the new
ordering path in search_candidates is exercised (e.g. assign a higher
match_score to the "skill" row and appropriate has_crm_link booleans), keep
using _patch_db and the same reqs so the assertions (results[0].crm_contact_id
== "skill", results[1].crm_contact_id == "role") validate the score-first logic.
packages/shared/src/five08/candidate_search.py (1)

228-228: ⚠️ Potential issue | 🟠 Major

Apply final score filtering before truncating results.

LIMIT is enforced in SQL before the Python-side final score (match_score + seniority) and min_match_score filter run. That can drop candidates that should qualify and distort the top-N output for the same query.

Also applies to: 266-267, 293-295

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/five08/candidate_search.py` at line 228, SQL LIMIT is
currently applied before the Python-side final scoring (match_score + seniority)
and min_match_score filtering, which can drop qualifying candidates; remove or
avoid using LIMIT in the SQL in the search function(s) that build this query
(the query block containing "LIMIT %s") so that you fetch an adequate result
set, then in the corresponding function (where final_score is computed as
match_score + seniority and min_match_score is applied) compute final_score for
each row, filter out rows with final_score < min_match_score, sort by
final_score descending, and only then truncate to the requested N results; apply
this change consistently for the other two query sites mentioned (the
occurrences at the other "LIMIT %s" blocks referenced in the comment).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 219-240: The audit writes inside the callback (calls to
cog._audit_command when reporting success and inside the except block) must be
made best-effort so they never change the user-facing flow; wrap each call to
cog._audit_command in its own local try/except that catches Exception and logs
the audit failure (e.g., logger.exception or logger.error) but does not
re-raise, leaving the existing interaction responses (interaction.followup.send
and the success path that uses download_ok/resume_id/self.values) unchanged.
- Around line 202-218: The callback in the SelectMenu handler (callback)
currently proceeds to call CRMCog._download_and_send_resume without checking the
invoking member's roles; add an authorization check at the start of callback
(before deferring or downloading) that inspects interaction.user or
interaction.user.roles / interaction.user.guild_permissions and verifies the
required role(s) or permission(s) (e.g., a "recruiter" role or a specific
permission) and if the user is not authorized send an ephemeral denial message
and return; ensure the check uses the same role/permission logic used elsewhere
in CRMCog for consistency and keep the call to _download_and_send_resume only
after authorization succeeds.

---

Outside diff comments:
In `@packages/shared/src/five08/candidate_search.py`:
- Line 228: SQL LIMIT is currently applied before the Python-side final scoring
(match_score + seniority) and min_match_score filtering, which can drop
qualifying candidates; remove or avoid using LIMIT in the SQL in the search
function(s) that build this query (the query block containing "LIMIT %s") so
that you fetch an adequate result set, then in the corresponding function (where
final_score is computed as match_score + seniority and min_match_score is
applied) compute final_score for each row, filter out rows with final_score <
min_match_score, sort by final_score descending, and only then truncate to the
requested N results; apply this change consistently for the other two query
sites mentioned (the occurrences at the other "LIMIT %s" blocks referenced in
the comment).

In `@tests/unit/test_candidate_search.py`:
- Around line 309-341: The test
test_search_candidates_secondary_sort_skill_score_over_discord_role currently
omits the new score-first ranking fields; update the two test rows created via
_make_row to include explicit match_score and has_crm_link values so the new
ordering path in search_candidates is exercised (e.g. assign a higher
match_score to the "skill" row and appropriate has_crm_link booleans), keep
using _patch_db and the same reqs so the assertions (results[0].crm_contact_id
== "skill", results[1].crm_contact_id == "role") validate the score-first logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46c090e2-8e80-44d0-a627-7a9ebf1fde24

📥 Commits

Reviewing files that changed from the base of the PR and between 82a5f9b and 6061654.

📒 Files selected for processing (3)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/candidate_search.py
  • tests/unit/test_candidate_search.py

Comment on lines +202 to +218
async def callback(self, interaction: discord.Interaction) -> None:
try:
cog = interaction.client.get_cog("CRMCog") # type: ignore[attr-defined]
if not cog:
await interaction.response.send_message(
"❌ CRM functionality not available.", ephemeral=True
)
return

resume_id = self.values[0]
contact_name = self._resume_lookup.get(resume_id, "Unknown")

await interaction.response.defer(ephemeral=True)

download_ok = await cog._download_and_send_resume(
interaction, contact_name, resume_id
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Enforce role authorization in the resume select callback.

This callback currently allows resume download without a member-role check. Since results are posted in-thread, this opens an unauthorized resume access path.

🔐 Suggested fix
     async def callback(self, interaction: discord.Interaction) -> None:
         try:
             cog = interaction.client.get_cog("CRMCog")  # type: ignore[attr-defined]
             if not cog:
                 await interaction.response.send_message(
                     "❌ CRM functionality not available.", ephemeral=True
                 )
                 return
+
+            if not cog._check_member_role(interaction):
+                cog._audit_command(
+                    interaction=interaction,
+                    action="crm.match_candidates_resume_select",
+                    result="denied",
+                    metadata={"reason": "missing_member_role"},
+                    resource_type="discord_ui_action",
+                    resource_id=self.values[0] if self.values else None,
+                )
+                await interaction.response.send_message(
+                    "❌ You must have the Member role to download resumes.",
+                    ephemeral=True,
+                )
+                return
 
             resume_id = self.values[0]
             contact_name = self._resume_lookup.get(resume_id, "Unknown")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 202 - 218,
The callback in the SelectMenu handler (callback) currently proceeds to call
CRMCog._download_and_send_resume without checking the invoking member's roles;
add an authorization check at the start of callback (before deferring or
downloading) that inspects interaction.user or interaction.user.roles /
interaction.user.guild_permissions and verifies the required role(s) or
permission(s) (e.g., a "recruiter" role or a specific permission) and if the
user is not authorized send an ephemeral denial message and return; ensure the
check uses the same role/permission logic used elsewhere in CRMCog for
consistency and keep the call to _download_and_send_resume only after
authorization succeeds.

Comment thread apps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (2)

202-218: ⚠️ Potential issue | 🔴 Critical

Enforce role authorization before resume download in select callback.

At Line 211 onward, MatchResumeSelect.callback allows resume download without Member role validation, which creates an unauthorized access path from the public thread UI. This should mirror ResumeDownloadButton.callback’s cog._check_member_role(interaction) gate before defer/download.

🔐 Suggested fix
     async def callback(self, interaction: discord.Interaction) -> None:
         try:
             cog = interaction.client.get_cog("CRMCog")  # type: ignore[attr-defined]
             if not cog:
                 await interaction.response.send_message(
                     "❌ CRM functionality not available.", ephemeral=True
                 )
                 return
+
+            if not cog._check_member_role(interaction):
+                try:
+                    cog._audit_command(
+                        interaction=interaction,
+                        action="crm.match_candidates_resume_select",
+                        result="denied",
+                        metadata={"reason": "missing_member_role"},
+                        resource_type="discord_ui_action",
+                        resource_id=self.values[0] if self.values else None,
+                    )
+                except Exception as audit_exc:
+                    logger.warning("Audit write failed in resume select deny path: %s", audit_exc)
+                await interaction.response.send_message(
+                    "❌ You must have the Member role to download resumes.",
+                    ephemeral=True,
+                )
+                return
 
             resume_id = self.values[0]
             contact_name = self._resume_lookup.get(resume_id, "Unknown")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 202 - 218,
MatchResumeSelect.callback currently allows downloading resumes without
verifying the caller's Member role; add the same authorization gate used in
ResumeDownloadButton.callback by calling cog._check_member_role(interaction) (or
the appropriate method on CRMCog) immediately after obtaining cog and before
calling interaction.response.defer and cog._download_and_send_resume, send an
ephemeral "not authorized" response and return when the check fails so
unauthorized users cannot trigger the download path.

219-240: ⚠️ Potential issue | 🟠 Major

Make audit writes best-effort in the select callback.

At Line 219 and Line 230, audit calls are in the main control path. If _audit_command raises, the user-facing success path can be turned into an error path. Wrap each audit write with local try/except and continue the interaction flow.

🛡️ Suggested fix
             download_ok = await cog._download_and_send_resume(
                 interaction, contact_name, resume_id
             )
-            cog._audit_command(
-                interaction=interaction,
-                action="crm.match_candidates_resume_select",
-                result="success" if download_ok else "error",
-                metadata={"contact_name": contact_name},
-                resource_type="crm_contact",
-                resource_id=resume_id,
-            )
+            try:
+                cog._audit_command(
+                    interaction=interaction,
+                    action="crm.match_candidates_resume_select",
+                    result="success" if download_ok else "error",
+                    metadata={"contact_name": contact_name},
+                    resource_type="crm_contact",
+                    resource_id=resume_id,
+                )
+            except Exception as audit_exc:
+                logger.warning("Audit write failed in resume select success path: %s", audit_exc)
         except Exception as exc:
             logger.error("Unexpected error in match resume select: %s", exc)
             if "cog" in locals() and cog:
-                cog._audit_command(
-                    interaction=interaction,
-                    action="crm.match_candidates_resume_select",
-                    result="error",
-                    metadata={"error": str(exc)},
-                    resource_type="discord_ui_action",
-                    resource_id=self.values[0] if self.values else None,
-                )
+                try:
+                    cog._audit_command(
+                        interaction=interaction,
+                        action="crm.match_candidates_resume_select",
+                        result="error",
+                        metadata={"error": str(exc)},
+                        resource_type="discord_ui_action",
+                        resource_id=self.values[0] if self.values else None,
+                    )
+                except Exception as audit_exc:
+                    logger.warning("Audit write failed in resume select error path: %s", audit_exc)
             await interaction.followup.send(
                 "❌ An unexpected error occurred while downloading the resume."
             )

As per coding guidelines, "Audit logging must be best effort: command flows in Discord cogs should never fail if audit writes fail".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 219 - 240,
The audit writes in the select callback (calls to cog._audit_command) must be
made best-effort so failures don’t change the user flow; wrap each call to
cog._audit_command (both the success-path call and the error-path call) in its
own try/except that catches Exception, logs the audit failure (e.g.,
logger.error or logger.exception with context like action and resource_id), and
does not re-raise so the interaction followup/send continues normally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 202-218: MatchResumeSelect.callback currently allows downloading
resumes without verifying the caller's Member role; add the same authorization
gate used in ResumeDownloadButton.callback by calling
cog._check_member_role(interaction) (or the appropriate method on CRMCog)
immediately after obtaining cog and before calling interaction.response.defer
and cog._download_and_send_resume, send an ephemeral "not authorized" response
and return when the check fails so unauthorized users cannot trigger the
download path.
- Around line 219-240: The audit writes in the select callback (calls to
cog._audit_command) must be made best-effort so failures don’t change the user
flow; wrap each call to cog._audit_command (both the success-path call and the
error-path call) in its own try/except that catches Exception, logs the audit
failure (e.g., logger.error or logger.exception with context like action and
resource_id), and does not re-raise so the interaction followup/send continues
normally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4927b33e-6c24-4e79-a17a-aa73303a8b7b

📥 Commits

Reviewing files that changed from the base of the PR and between 6061654 and 67ae092.

📒 Files selected for processing (1)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shared/src/five08/candidate_search.py (1)

241-323: ⚠️ Potential issue | 🟠 Major

Apply final limiting after final score/filter, not before.

Line 241 limits rows before Line 280 seniority adjustment and Lines 306-307 min_match_score filtering. This can hide valid higher final-score candidates and return an incomplete top-N list for display.

Proposed mitigation (over-fetch + final cap)
@@
 def search_candidates(
@@
 ) -> list[CandidateMatch]:
@@
-    with get_postgres_connection(settings) as conn:
+    # Over-fetch because final score/min_match_score are applied in Python.
+    sql_limit = max(limit * 5, limit)
+    with get_postgres_connection(settings) as conn:
         with conn.cursor(row_factory=dict_row) as cursor:
             cursor.execute(
                 query,
                 (
@@
-                    limit,
+                    sql_limit,
                 ),
             )
             rows = cursor.fetchall()
@@
-    return results
+    return results[:limit] if limit > 0 else results
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/five08/candidate_search.py` around lines 241 - 323, The
current SQL LIMIT (passed as the variable limit into cursor.execute for query)
truncates rows before we compute seniority-adjusted match_score and apply
min_match_score and final sorting; change the approach to over-fetch and enforce
the final cap in Python: update the DB call to request more rows (e.g., multiply
limit by a safe factor or replace the SQL LIMIT with a larger parameter), then
after building results, applying min_match_score and sorting (the existing
results.sort lambda) slice the final list to the requested limit (e.g., results
= results[:limit]) so the top-N returned respects post-processing adjustments;
touch the code around query/cursor.execute, the variable limit, the results
construction, the min_match_score filter, and the final slice.
🧹 Nitpick comments (2)
packages/shared/src/five08/candidate_search.py (2)

265-268: Move invariant set construction outside the row loop.

Lines 266-268 rebuild the same sets for every row. Build them once before the loop to reduce per-row overhead.

Suggested refactor
-    results: list[CandidateMatch] = []
-    for row in rows:
-        candidate_skills: list[str] = row.get("skills") or []
-        required_set = set(required_skills)
-        preferred_set = set(preferred_skills)
-        role_types_set = set(role_types)
+    results: list[CandidateMatch] = []
+    required_set = set(required_skills)
+    preferred_set = set(preferred_skills)
+    role_types_set = set(role_types)
+    for row in rows:
+        candidate_skills: list[str] = row.get("skills") or []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/five08/candidate_search.py` around lines 265 - 268, The
code is recreating the same sets (required_set, preferred_set, role_types_set)
inside the per-row processing; move construction of required_set =
set(required_skills), preferred_set = set(preferred_skills), and role_types_set
= set(role_types) out of the row loop and compute them once before iterating
rows (so only candidate_skills = row.get("skills") or [] remains per-row),
updating any references in the loop to use these precomputed sets to eliminate
repeated allocation and reduce per-row overhead.

309-320: Clarify the secondary-sort comment.

Line 309 says the sort only breaks ties, but Lines 311-320 re-rank on the updated match_score and other fields. Please update the comment to match behavior (or narrow the key if tie-break-only is intended).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/five08/candidate_search.py` around lines 309 - 320, The
comment above the results.sort call is misleading: it says this is only a
tie-breaker but the key lambda (used in results.sort) includes c.match_score and
several other ranking fields (e.g., c.match_score, c.matched_required_skills,
c.matched_preferred_skills, c.seniority_score), so update the comment to state
that this sort fully re-ranks results based on those fields (preserving or
explicitly changing primary SQL ranking as appropriate); alternatively, if you
intended tie-break-only behavior, narrow the sort key to exclude c.match_score
and any fields that would change the primary order and keep only true
tie-breakers (e.g., c.is_member, c.has_crm_link, c.seniority_score) and then
update the comment to reflect that tie-break-only intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/shared/src/five08/candidate_search.py`:
- Around line 241-323: The current SQL LIMIT (passed as the variable limit into
cursor.execute for query) truncates rows before we compute seniority-adjusted
match_score and apply min_match_score and final sorting; change the approach to
over-fetch and enforce the final cap in Python: update the DB call to request
more rows (e.g., multiply limit by a safe factor or replace the SQL LIMIT with a
larger parameter), then after building results, applying min_match_score and
sorting (the existing results.sort lambda) slice the final list to the requested
limit (e.g., results = results[:limit]) so the top-N returned respects
post-processing adjustments; touch the code around query/cursor.execute, the
variable limit, the results construction, the min_match_score filter, and the
final slice.

---

Nitpick comments:
In `@packages/shared/src/five08/candidate_search.py`:
- Around line 265-268: The code is recreating the same sets (required_set,
preferred_set, role_types_set) inside the per-row processing; move construction
of required_set = set(required_skills), preferred_set = set(preferred_skills),
and role_types_set = set(role_types) out of the row loop and compute them once
before iterating rows (so only candidate_skills = row.get("skills") or []
remains per-row), updating any references in the loop to use these precomputed
sets to eliminate repeated allocation and reduce per-row overhead.
- Around line 309-320: The comment above the results.sort call is misleading: it
says this is only a tie-breaker but the key lambda (used in results.sort)
includes c.match_score and several other ranking fields (e.g., c.match_score,
c.matched_required_skills, c.matched_preferred_skills, c.seniority_score), so
update the comment to state that this sort fully re-ranks results based on those
fields (preserving or explicitly changing primary SQL ranking as appropriate);
alternatively, if you intended tie-break-only behavior, narrow the sort key to
exclude c.match_score and any fields that would change the primary order and
keep only true tie-breakers (e.g., c.is_member, c.has_crm_link,
c.seniority_score) and then update the comment to reflect that tie-break-only
intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 267b80ce-e45f-446e-8402-c369551b6582

📥 Commits

Reviewing files that changed from the base of the PR and between 67ae092 and 2515482.

📒 Files selected for processing (1)
  • packages/shared/src/five08/candidate_search.py

@michaelmwu michaelmwu merged commit 7c38792 into main Mar 4, 2026
5 checks passed
@michaelmwu michaelmwu deleted the michaelmwu/ranked-crm-discord-match branch March 4, 2026 16:00
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.

1 participant