docs: add candidate profile ingestion design

This commit is contained in:
Antoine 2026-05-26 23:10:04 +02:00
commit 08df32da53

View File

@ -0,0 +1,258 @@
# Candidate Profile Ingestion Design
This spec covers the first executable slice of the job discovery system: producing a trustworthy local `Candidate Profile` and saved default `Search Preferences` from a PDF CV, a structured markdown profile, and explicit YAML overrides. It stops before scraping, ranking, or OpenCode-driven re-ranking.
## Scope
This slice must let the user:
- provide a PDF CV,
- provide a structured markdown profile with some freeform notes,
- generate deterministic local profile state,
- review and resolve factual conflicts explicitly,
- materialize a final normalized `Candidate Profile`,
- save default `Search Preferences` separately from career facts,
- inspect the resulting local state confidently.
Out of scope:
- job scraping,
- ranking,
- LLM explanations of job fit,
- session-level chat overrides,
- browser or web UI flows.
## Architecture
The design uses a layered state pipeline.
Inputs:
- PDF CV for raw career history
- structured markdown profile for curated interpretation and goals
- YAML overrides for explicit user corrections
Outputs:
- generated state from deterministic extraction and merge
- override state authored by the user
- final merged normalized state for downstream consumers
- saved default `Search Preferences` stored separately
The key trust rule is that factual conflicts must never be resolved silently. When the PDF CV and markdown profile disagree on factual history, the system writes the disagreement into a review-required section and stops short of claiming a trustworthy final profile until explicit override data resolves it.
## Components
### CV Extractor
Responsibility:
- read the PDF CV,
- extract text deterministically,
- derive structured career-history candidates.
Expected fields include:
- identity basics,
- experience entries,
- education entries,
- skill mentions,
- language mentions.
Behavior:
- prefer explicit extraction over inference,
- leave uncertainty visible instead of guessing,
- avoid LLM use unless deterministic extraction is incomplete or ambiguous enough to block useful structuring.
### Markdown Profile Parser
Responsibility:
- read a required structured markdown template,
- parse known sections into normalized authored signals,
- preserve some freeform notes for nuance.
Expected content includes:
- strengths,
- target roles,
- tool and stack emphasis,
- interpretation of prior experience,
- narrative clarifications not obvious from the CV.
### Merge and Conflict Analyzer
Responsibility:
- combine CV-derived and markdown-derived state into generated YAML,
- apply source authority rules,
- identify factual conflicts and unresolved ambiguities.
Authority rules:
- PDF CV is the default source of truth for raw career history,
- markdown profile is the source of truth for curated interpretation and goals,
- factual disagreements are surfaced and require explicit resolution,
- missing non-critical data may remain unresolved if it is clearly marked.
### Override Applier
Responsibility:
- read user-authored YAML overrides,
- resolve review-required conflicts,
- materialize the final canonical `Candidate Profile`.
This unit is the only place where explicit user correction can replace generated factual values.
### Search Preferences Manager
Responsibility:
- validate and persist saved default `Search Preferences` in a separate YAML file,
- keep them separate from the durable `Candidate Profile`.
This slice stores only saved defaults, not temporary session overrides.
## Data Flow
1. The user provides a PDF CV.
2. The user provides a structured markdown profile.
3. The system extracts CV-derived structured candidates.
4. The system parses markdown-derived authored signals.
5. The merge and conflict analyzer writes generated YAML.
6. If factual conflicts or blocking ambiguities exist, the system writes them into a review-required section and exits with review-required status.
7. The user edits an override YAML file.
8. The override applier materializes the final normalized `Candidate Profile`.
9. The user saves validated default `Search Preferences` separately.
The system should support rerunning generation without losing authored overrides.
## File Layout
```text
data/
profiles/
cv/
raw-text.txt
authored/
profile.md
generated/
candidate-profile.generated.yaml
overrides/
candidate-profile.override.yaml
final/
candidate-profile.yaml
preferences/
search-preferences.yaml
```
File semantics:
- `candidate-profile.generated.yaml` is machine-produced and replaceable
- `candidate-profile.override.yaml` is user-owned
- `candidate-profile.yaml` is the canonical merged state for downstream consumers
- `search-preferences.yaml` is a separate document because `Search Preferences` are not durable career facts
JSON export may be supported for debugging or automation, but YAML is the primary local format.
## Validation and Error Handling
### Hard Failures
These should fail immediately and avoid producing a claimed final profile:
- unreadable or missing PDF input,
- malformed structured markdown profile,
- invalid YAML override format,
- invalid `Search Preferences` schema.
### Review-Required State
These should produce generated state plus a clear review-required status:
- factual conflicts between CV and markdown profile,
- blocking ambiguity that would make the final profile untrustworthy if auto-resolved.
### Allowed Ambiguity
These may remain visible without blocking final state if clearly recorded:
- incomplete optional fields,
- non-critical uncertainty that does not alter durable career facts,
- missing interpretation details that can be added later.
The system must prefer explicit unresolved ambiguity over silent incorrect certainty.
## Command Surface
Proposed CLI commands for this slice:
- `ingest-cv <cv.pdf>`
- `ingest-profile <profile.md>`
- `build-profile --cv <cv.pdf> --profile <profile.md>`
- `apply-overrides <override.yaml>`
- `show-profile --final`
- `set-preferences <preferences.yaml>`
- `show-preferences`
Expected behavior:
- `build-profile` generates deterministic state and signals review-required conditions
- `apply-overrides` resolves explicit user corrections into final canonical state
- `set-preferences` validates and persists saved default `Search Preferences`
## LLM Usage Boundaries
This slice is deterministic-first.
Allowed LLM use:
- fallback interpretation when deterministic extraction is too ambiguous to structure usefully,
- optional structuring help where the result is still auditable in generated YAML.
Disallowed LLM use:
- replacing deterministic extraction as the default path,
- silently deciding factual conflicts,
- acting as the source of truth for final persisted profile state.
## Testing Strategy
The first slice should be tested around trust and repeatability.
Core test cases:
- PDF-only ingestion produces stable generated state where possible
- structured markdown parsing maps required sections correctly
- CV and markdown merge cleanly when there is no factual conflict
- factual conflict produces review-required output and no false success signal
- overrides resolve conflicts into the final canonical profile correctly
- final YAML serialization is stable and readable
- invalid overrides fail with actionable validation feedback
- `Search Preferences` validation and persistence behave independently from the profile pipeline
## Acceptance Criteria
This slice is done when:
- a real PDF CV and structured markdown profile can produce generated YAML locally,
- factual conflicts are surfaced explicitly and never resolved silently,
- override YAML can resolve those conflicts into a final canonical `Candidate Profile`,
- the final profile is trustworthy enough to become the source for later ranking work,
- saved default `Search Preferences` are validated and stored separately from the `Candidate Profile`,
- the workflow is repeatable from the CLI without requiring scraping, ranking, or UI work.
## Non-Goals for This Slice
This design intentionally does not solve:
- job board integration,
- listing normalization,
- score computation,
- `Stretch Opportunity` classification,
- dismissed listing behavior,
- OpenCode-driven session override flows.
Those belong to later slices after profile state is trustworthy.