fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: stabilize resume contact inference parsing - #133

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse
Mar 3, 2026
Merged

fix: stabilize resume contact inference parsing#133
michaelmwu merged 3 commits into
mainfrom
michaelmwu/fix-resume-parse

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

Improves Discord-side resume contact inference by extracting text from PDF/DOC/DOCX/TXT before parsing identity hints, threading filename context through inference and create-contact flows, and rejecting heading placeholders like Resume: as names.
Fixes a malformed seniority regex in shared resume extraction that could raise missing ), unterminated subpattern at position 2 and adds safeguards for heading-style names in shared normalization.
Adds regression tests for heading-name rejection, filename-aware text extraction in CRM, and seniority regex behavior, and updates bot dependencies/lockfile to include pdfminer.six and python-docx.

Related Issue

None.

How Has This Been Tested?

uv run pytest -q tests/unit/test_resume_extractor.py tests/unit/test_crm.py -k "resume or infer or upload_resume" and uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py packages/shared/src/five08/resume_extractor.py tests/unit/test_resume_extractor.py tests/unit/test_crm.py.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for processing PDF and Word documents (.docx) in resume uploads.
    • Improved resume name extraction logic to avoid mistaking resume headings for candidate names.
  • Tests

    • Added tests for filename-aware resume processing and enhanced name detection.

@coderabbitai

coderabbitaiBot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4c3cc and 7f3634b.

📒 Files selected for processing (4)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py
📝 Walkthrough

Walkthrough

This PR introduces filename-aware resume text extraction throughout the resume processing pipeline, adding support for multiple document formats (PDF, DOCX, DOC) via new dependencies (pdfminer.six and python-docx). Resume helpers in the CRM cog and shared extractor now accept an optional filename parameter, enabling format-specific parsing, improved name detection by filtering heading-like tokens, and filename-aware profile caching.

Changes

Cohort / File(s)Summary
Dependencies
apps/discord_bot/pyproject.toml
Added pdfminer.six>=20250506 and python-docx>=1.2.0 to support PDF and DOCX document parsing.
Resume Processing Core
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Threaded filename parameter through resume processing methods; introduced filename-aware text extraction helper supporting PDF, DOCX, DOC, and text formats; made profile caching filename-aware by including file extension in cache key; extended 8+ method signatures (e.g., _extract_resume_profile, _extract_resume_contact_hints, _build_resume_create_contact_payload) to accept optional filename and propagate it downstream; added resume name detection constants.
Resume Extraction Logic
packages/shared/src/five08/resume_extractor.py
Added _NAME_HEADING_TOKENS to filter non-name lines (resume, CV, profile); enhanced _normalize_name to treat heading-token lines as None; updated _extract_name to skip lines matching heading/placeholder tokens; refined seniority inference regex to use non-capturing group.
Test Coverage
tests/unit/test_crm.py, tests/unit/test_resume_extractor.py
Added 5 tests covering filename-aware resume text extraction, heading-line name filtering, fallback heuristic behavior for invalid names, and seniority inference with scale keywords.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through files with glee,
Filenames threaded, extracted with care—
PDFs, DOCXs, plain text to see,
No more "Resume:" in names we declare!
From pdfminer to python-docx so divine,
Each format now handled, by extension's design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 67.74% 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe PR title 'fix: stabilize resume contact inference parsing' directly and clearly summarizes the main purpose of the changeset, which is to improve the robustness and stability of resume contact information extraction through better name filtering, filename context threading, and regex fixes.

✏️ 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/fix-resume-parse

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/test_resume_extractor.py (1)

395-404: Consider parameterizing heading variants for stronger regression coverage.

This test currently validates Resume: only. Adding cases like Resume :, CV:, and Profile: would harden the guard behavior against formatting variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_resume_extractor.py` around lines 395 - 404, Update the
test_extract_name_skips_resume_heading_lines unit test to parameterize heading
variants so it covers formatting differences; use pytest.mark.parametrize to
pass headings like "Resume:", "Resume :", "CV:", and "Profile:" into the test,
call ResumeProfileExtractor.extract with each heading followed by the same
sample body, and assert that result.name == "Jane Doe" for every variant; keep
the test function name test_extract_name_skips_resume_heading_lines and reuse
the ResumeProfileExtractor.extract invocation and assertion logic.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

3290-3293: DOC file extraction is a best-effort fallback, not proper parsing.

The .doc format is a binary OLE compound document, not plain text. The current approach of UTF-8 decoding with non-printable character stripping may extract some readable fragments, but will miss structured content and potentially produce garbled output.

Consider adding a dedicated DOC parser (e.g., textract, antiword, or python-docx2txt) for reliable extraction. Alternatively, document this as a known limitation and consider rejecting .doc files with a message suggesting .docx or .pdf upload instead.

🤖 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 3290 -
3293, The current DOC handling (branch checking extension == ".doc" which
decodes file_content into extracted_text) treats a binary OLE .doc as plain
UTF-8 and yields unreliable output; replace this fallback with a proper parser
or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/five08/resume_extractor.py`:
- Around line 593-595: The check that creates lowered uses .rstrip(":") which
can leave trailing spaces (e.g. "resume "), causing membership tests against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS to fail; update the
normalization to strip trailing whitespace and colons (e.g. use .rstrip(" :") or
first .rstrip(":").rstrip() or otherwise ensure both spaces and ":" are removed)
for the variable lowered (and the same change at the other occurrence around
lines referencing the same logic) so comparisons against
_PLACEHOLDER_NAME_TOKENS and _NAME_HEADING_TOKENS behave correctly.
- Around line 1245-1247: The regex fragment in resume_extractor.py that uses
\b500\+? and \b1000\+? can over-match prefixes of larger numbers (e.g., "5000");
update those tokens to ensure they match whole numeric tokens only by preventing
adjacent digits—replace occurrences of \b500\+? and \b1000\+? with a pattern
that asserts no digit on either side such as (?<!\d)500\+?(?!\d) and
(?<!\d)1000\+?(?!\d) respectively (modify the same combined pattern string shown
in the diff so the impact_score/seniority logic uses the tightened tokens).
---
Nitpick comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 3290-3293: The current DOC handling (branch checking extension ==
".doc" which decodes file_content into extracted_text) treats a binary OLE .doc
as plain UTF-8 and yields unreliable output; replace this fallback with a proper
parser or explicit rejection: either integrate a DOC extraction library (e.g.,
textract, antiword, python-docx2txt) to populate extracted_text reliably in the
same code path, or reject ".doc" uploads (return an informative message asking
for .docx/.pdf) and log the limitation. Update the branch that inspects
extension and uses file_content/extracted_text to call the chosen parser or
perform the rejection and ensure any error paths log the filename/extension for
debugging.
In `@tests/unit/test_resume_extractor.py`:
- Around line 395-404: Update the test_extract_name_skips_resume_heading_lines
unit test to parameterize heading variants so it covers formatting differences;
use pytest.mark.parametrize to pass headings like "Resume:", "Resume :", "CV:",
and "Profile:" into the test, call ResumeProfileExtractor.extract with each
heading followed by the same sample body, and assert that result.name == "Jane
Doe" for every variant; keep the test function name
test_extract_name_skips_resume_heading_lines and reuse the
ResumeProfileExtractor.extract invocation and assertion logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec84c15 and 1b4c3cc.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/discord_bot/pyproject.toml
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/resume_extractor.py
  • tests/unit/test_crm.py
  • tests/unit/test_resume_extractor.py

Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR stabilizes resume contact inference for the Discord CRM bot by extracting text from common resume file formats before parsing, carrying filename context through inference/create-contact flows, and hardening name/seniority heuristics to avoid common parsing failures.

Changes:

  • Add filename-aware document text extraction in the Discord CRM cog (PDF/DOCX/TXT, plus best-effort DOC) and thread filename through inference and contact creation flows.
  • Harden shared resume extraction: skip heading-style “names” (e.g., Resume:) and fix a malformed seniority regex that could throw a regex compilation error.
  • Add regression tests covering heading-name rejection, filename-aware extraction wiring, and seniority inference behavior; update bot dependencies to include pdfminer.six and python-docx.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds filename-aware resume text extraction + name validation and threads filename through CRM inference/create flows.
packages/shared/src/five08/resume_extractor.pyRejects heading/placeholder names during normalization/extraction; fixes seniority inference regex.
tests/unit/test_crm.pyAdds tests ensuring heading-like names fall back and filename-aware extraction is used.
tests/unit/test_resume_extractor.pyAdds tests for heading-name skipping and seniority regex regression.
apps/discord_bot/pyproject.tomlAdds required dependencies for PDF/DOCX extraction.
uv.lockLocks new dependencies (pdfminer-six, python-docx) and their transitive deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadpackages/shared/src/five08/resume_extractor.py
Comment threadpackages/shared/src/five08/resume_extractor.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
@michaelmwu
michaelmwu requested a review from CopilotMarch 3, 2026 11:26
@michaelmwu
michaelmwu merged commit 2a26266 into mainMar 3, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-resume-parse branch March 3, 2026 11:28

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

elif extension == ".doc":
extracted_text = file_content.decode("utf-8", errors="ignore")
extracted_text = re.sub(r"[^\x20-\x7E\n\r\t]", " ", extracted_text)
extracted_text = re.sub(r"\s+", " ", extracted_text).strip()

CopilotAIMar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In the .doc branch, re.sub(r"\s+", " ", ...) collapses newlines into spaces, which makes downstream heuristics that rely on splitlines() (e.g., name fallback scanning the first N lines) ineffective and likely causes .doc uploads to fall back to "Unknown Contact" even when the header has a clear name/email. Consider preserving line breaks (only normalize spaces/tabs and/or collapse runs of blank lines) so the later line-based parsing continues to work for .doc content.

Suggested change
extracted_text=re.sub(r"\s+", " ", extracted_text).strip()
# Preserve line breaks for downstream line-based heuristics:
# - normalize line endings to '\n'
# - collapse runs of spaces/tabs within lines
# - optionally collapse excessive blank lines
extracted_text=extracted_text.replace("\r\n", "\n").replace("\r", "\n")
extracted_text=re.sub(r"[ \t]+", " ", extracted_text)
extracted_text=re.sub(r"\n{3,}", "\n\n", extracted_text).strip()

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@michaelmwu