173187dcbb
The structured model and StructuredCvProfileJson.FromSections already map
Projects/Certifications/Languages headings, but the AI normalize prompt
never emitted them, so on the benchmark CV the entire Projects section and
the in-summary languages (English Native, Norwegian B1) were silently
dropped. This closes that gap upstream — no backend schema or data change.
ai-service (tools/summarizer/app.py):
- /cv/normalize: added # Projects and # Certifications headings; a
languages-from-prose rule (pull "native English", "Norwegian at B1" out
of the summary even with no Languages section; ignore programming
languages); and skill-group prefix stripping ("Development:",
"DevOps & Infrastructure:", "Practices:" dropped, only the skills kept).
- /cv/classify-block: Projects and Certifications added to the section
enum + rules (fallback path).
Backend:
- LooksLikeNormalizedMarkdownCv now recognises # Projects / # Certifications
so those CVs still take the markdown assembly path.
Tests:
- CvExtractionCoverageTests (4) lock the C# mapping of Projects,
Certifications and Languages sections into the structured profile.
- ai-service test_classify_block_supports_projects_section (1).
426 backend tests, 17 ai-service tests pass; app.py compiles.
The LLM behaviour (prompt -> headings) needs Ollama to observe and was not
run here; the C# side that consumes the headings is proven and the prompt
change is additive. Merge-not-replace + the review screen are the next
increment (2.1-a, approved: always-review, conservative merge).
Deployment: these prompts live in the ai-service container, which
deploy.sh does not rebuild by default -- deploy with
DEPLOY_BUILD_AI_SERVICE=true or the change won't take effect.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local AI Service
This service runs a local Hugging Face summarization model and also exposes document text extraction with OCR for supported PDFs and images.
Capabilities
- job/role summarization
- PDF text extraction
- OCR fallback for scanned PDFs
- OCR for image uploads (
png,jpg,jpeg,webp) - DOCX / TXT / MD extraction
- optional Ollama-backed CV block classification for harder sectioning
Install
Windows:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python -m uvicorn app:app --host 127.0.0.1 --port 8001 --workers 1
Linux / macOS:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m uvicorn app:app --host 127.0.0.1 --port 8001 --workers 1
If the host is missing python3-venv or pip, use the bootstrap script instead:
./scripts/bootstrap-and-test.sh bootstrap
Docker
The Dockerfile installs Tesseract OCR so scanned PDFs and supported images can be processed inside the container.
Tests
Run the summarizer unit tests with:
./scripts/bootstrap-and-test.sh test
The script:
- creates
.venvwith stdlibvenvwhen available - falls back to user-space
virtualenvwhen hostvenvsupport is missing - installs
requirements-dev.txt - writes pytest cache under
tmp/pytest-cacheto avoid stale root-owned.pytest_cachedirectories
API
GET /health— health check and runtime capabilities, including lazy model state (model_loaded,model_disabled,summarize_available,model_load_error) plus Ollama version/model metadata when configuredPOST /summarize— JSON body{ "text": "...", "max_length": 150, "min_length": 30 }POST /extract-text— multipart file upload, returns extracted text and OCR metadataPOST /cv/classify-block— JSON body{ "block": "..." }, uses Ollama whenOLLAMA_MODELis configured
Ollama
Set these before starting the service if you want the hybrid CV classifier enabled:
export OLLAMA_BASE_URL=http://ollama:11434
export OLLAMA_MODEL=qwen2.5:7b
Choose the model by setting OLLAMA_MODEL and then warming it with the helper script:
OLLAMA_MODEL=qwen2.5:7b ./scripts/start-ollama-cv.sh
Equivalent manual flow:
docker compose up -d ollama
docker compose exec ollama ollama pull qwen2.5:7b
docker compose up -d ai-service
- Model weights are downloaded on first pull.
- OCR quality depends on scan quality and language support.
- Default OCR language is English (
eng).