From 4b41ec15445978842553c60fd5545526d9647dda Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 23:24:34 +0000 Subject: [PATCH] Fix resume section detection on text with leading whitespace _detect_sections() and _strip_markdown() anchored their regexes to the exact start of a line, so section headers and markdown headers indented with leading spaces/tabs (common in PDF-extracted text and in-code fixtures) were never matched. Allow optional leading whitespace before the anchored patterns. Fixes #54 --- ingestion/parsers/resume_parser.py | 10 +++++----- tests/unit/test_resume_parser.py | 15 --------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/ingestion/parsers/resume_parser.py b/ingestion/parsers/resume_parser.py index bd64123..4e96695 100644 --- a/ingestion/parsers/resume_parser.py +++ b/ingestion/parsers/resume_parser.py @@ -98,7 +98,7 @@ def _parse_markdown(self, content: str) -> ParseResult: def _strip_markdown(self, content: str) -> str: """Remove markdown syntax from content.""" # Remove markdown headers - text = re.sub(r"^#+\s+", "", content, flags=re.MULTILINE) + text = re.sub(r"^[ \t]*#+\s+", "", content, flags=re.MULTILINE) # Remove markdown links [text](url) text = re.sub(r"\[([^\]]+)\]\(([^\)]+)\)", r"\1", text) @@ -131,10 +131,10 @@ def _detect_sections(self, text: str) -> list[str]: for section in SECTION_HEADERS: # Look for section header patterns patterns = [ - rf"^{re.escape(section)}\s*$", - rf"^{re.escape(section)}\s*[:|-]", - rf"\n{re.escape(section)}\s*$", - rf"\n{re.escape(section)}\s*[:|-]", + rf"^[ \t]*{re.escape(section)}\s*$", + rf"^[ \t]*{re.escape(section)}\s*[:|-]", + rf"\n[ \t]*{re.escape(section)}\s*$", + rf"\n[ \t]*{re.escape(section)}\s*[:|-]", ] for pattern in patterns: diff --git a/tests/unit/test_resume_parser.py b/tests/unit/test_resume_parser.py index aae644f..372a2d7 100644 --- a/tests/unit/test_resume_parser.py +++ b/tests/unit/test_resume_parser.py @@ -17,9 +17,6 @@ def parser(self): """Create a ResumeParser instance.""" return ResumeParser() - @pytest.mark.xfail( - strict=True, reason="issue #54: resume section detection fails on leading whitespace" - ) def test_parse_single_column_resume_text(self, parser, sample_resume_text): """Test parsing a standard single-column resume text.""" result = parser.parse(sample_resume_text) @@ -36,9 +33,6 @@ def test_parse_single_column_resume_text(self, parser, sample_resume_text): "skills" in s for s in detected_lower ) - @pytest.mark.xfail( - strict=True, reason="issue #54: resume section detection fails on leading whitespace" - ) def test_parse_resume_no_work_experience(self, parser): """Test parsing a resume with no work experience section - handles gracefully.""" resume_no_work = """ @@ -87,9 +81,6 @@ def test_parse_multipage_pdf(self, parser): assert "Page 2 Content" in result.text assert "Page 3 Content" in result.text - @pytest.mark.xfail( - strict=True, reason="issue #54: resume section detection fails on leading whitespace" - ) def test_parse_markdown_resume(self, parser): """Test parsing a Markdown resume.""" markdown_resume = """ @@ -132,9 +123,6 @@ def test_parse_invalid_list_content(self, parser): exc_info.value ) - @pytest.mark.xfail( - strict=True, reason="issue #54: resume section detection fails on leading whitespace" - ) def test_detect_sections(self, parser): """Test section detection in resume text.""" text = """ @@ -155,9 +143,6 @@ def test_detect_sections(self, parser): assert any("education" in s for s in sections_lower) assert any("skills" in s for s in sections_lower) - @pytest.mark.xfail( - strict=True, reason="issue #54: resume section detection fails on leading whitespace" - ) def test_strip_markdown_syntax(self, parser): """Test markdown syntax stripping.""" markdown_text = """