Spaces:
Runtime error
Runtime error
| """End-to-end NER chain integration test. | |
| Gated on ``GAPGUIDE_ML_SMOKE=1`` β runs the REAL four-layer chain | |
| (Nucha + JobBERT + SkillNER + SBERT + lexical) against a fixture PDF. CI | |
| sets GAPGUIDE_PARSE_LAYERS=lexical (see conftest.py), so this opt-in test | |
| is the only place we verify the ML stack actually works end-to-end. | |
| Run manually: | |
| $env:GAPGUIDE_ML_SMOKE="1" | |
| pytest apps/accounts/tests/test_resume_parser_integration.py -q | |
| First run downloads ~1 GB of HF models + requires `en_core_web_lg` | |
| installed and pgvector extension in Postgres with SkillEmbedding populated. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| import pytest | |
| pytestmark = [ | |
| pytest.mark.django_db(transaction=True), | |
| pytest.mark.skipif( | |
| os.environ.get("GAPGUIDE_ML_SMOKE") != "1", | |
| reason="ML smoke disabled (set GAPGUIDE_ML_SMOKE=1 to enable)", | |
| ), | |
| ] | |
| FIX = Path(__file__).resolve().parent.parent.parent.parent / 'tests' / 'fixtures' / 'resumes' | |
| def seeded_catalog(): | |
| """Real catalog + SBERT embeddings. Requires: | |
| * pgvector extension on the test DB | |
| * en_core_web_lg installed | |
| * seed_initial_skills has been run (we do it here) | |
| """ | |
| from django.core.management import call_command | |
| call_command('seed_initial_skills') | |
| # Build embeddings for the seeded skills. | |
| from apps.skills.models import Skill, SkillEmbedding | |
| from sentence_transformers import SentenceTransformer | |
| model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2') | |
| skills = list(Skill.objects.all()) | |
| vectors = model.encode( | |
| [f"{s.skill_name} β {s.description or s.category}" for s in skills], | |
| normalize_embeddings=True, | |
| ) | |
| for skill, vec in zip(skills, vectors): | |
| SkillEmbedding.objects.update_or_create( | |
| skill=skill, | |
| defaults={ | |
| 'embedding': vec.tolist(), | |
| 'source_text': skill.skill_name, | |
| }, | |
| ) | |
| return skills | |
| def test_full_chain_extracts_core_skills_from_strong_fixture(seeded_catalog): | |
| """resume_ds_strong.pdf must yield at least {Python, SQL} via the full chain. | |
| We deliberately don't assert exact counts / confidences β those depend | |
| on the specific HF model versions and would flake across upgrades. | |
| """ | |
| # Force the full chain. | |
| os.environ.pop('GAPGUIDE_PARSE_LAYERS', None) | |
| from apps.accounts.resume_parser import parse_resume_envelope | |
| pdf = (FIX / 'resume_ds_strong.pdf').read_bytes() | |
| env = parse_resume_envelope(pdf) | |
| names = {s['skill_name'] for s in env['skills']} | |
| assert {'Python', 'SQL'}.issubset(names), (env['parser_version'], names) | |
| # At least one BERT/SBERT/SkillNER layer must have fired β proves the | |
| # chain isn't silently falling back to lexical-only. | |
| assert any( | |
| layer in env['parser_version'] | |
| for layer in ('nucha', 'jobbert', 'skillner', 'sbert') | |
| ), env['parser_version'] | |
| def test_minimal_fixture_exercises_sbert_fallback(seeded_catalog): | |
| """resume_minimal.pdf uses paraphrased skills ("data visualisation", | |
| "version control"). Lexical layer alone returns nothing; the SBERT | |
| fallback should resolve at least one paraphrase to a catalog skill. | |
| """ | |
| os.environ.pop('GAPGUIDE_PARSE_LAYERS', None) | |
| from apps.accounts.resume_parser import parse_resume_envelope | |
| pdf = (FIX / 'resume_minimal.pdf').read_bytes() | |
| env = parse_resume_envelope(pdf) | |
| # Either SBERT or SkillNER should salvage at least one catalog hit here β | |
| # otherwise the fallback chain isn't doing the job it was designed for. | |
| assert len(env['skills']) >= 1, (env['parser_version'], env['skills']) | |