6.1 KiB
Apec Ingestion Design
This spec covers the next executable slice of the job discovery system: use the canonical Candidate Profile to fetch public Apec listings, store raw listing snapshots, and produce a first normalized YAML listing file for inspection. It stops before ranking, explanations, and multi-source support.
Scope
This slice must let the user:
- run one explicit command,
- read
data/candidate-profile.yaml, - derive deterministic Apec search queries from that profile,
- fetch up to 50 public Apec listings,
- store raw listing detail-page HTML and crawl metadata,
- produce one normalized YAML file of listings,
- tolerate partial failure by keeping successful listings and recording per-listing errors.
Out of scope:
- ranking,
- LLM explanation,
- non-Apec sources,
- cross-run or cross-source deduplication,
- database storage,
- authenticated sessions.
Architecture
The design is a fetch-and-normalize pipeline centered on one explicit command:
fetch-apec
Inputs:
data/candidate-profile.yaml
Outputs:
- raw Apec listing snapshots,
- per-run metadata,
- one normalized
listings.yamlfile.
The key boundary is that this slice acquires and structures listing data but does not score it. It should preserve enough raw evidence to debug extraction and enough normalized structure to support the future ranking slice.
Components
Profile Query Derivation
Responsibility:
- read the canonical
Candidate Profile, - derive a small deterministic set of Apec search queries from profile content.
This should use explicit profile fields such as:
- target roles,
- strengths,
- skills to emphasize,
- constraints.
It must be deterministic and reproducible, not LLM-generated.
Apec Search Adapter
Responsibility:
- execute public Apec searches,
- gather listing URLs and lightweight result metadata,
- paginate as needed,
- respect the 50-listing cap across the full run.
Listing Snapshot Fetcher
Responsibility:
- visit each public Apec listing detail page,
- store:
- listing URL,
- raw HTML,
- crawl timestamp,
- basic fetch metadata,
- per-listing error information on failure.
Normalizer
Responsibility:
- transform successful Apec detail pages into a first normalized listing shape,
- extract obvious fields without overreaching.
Within-Run Deduper
Responsibility:
- remove obvious duplicates within the current run only,
- use Apec URL or source job ID when available.
This slice does not attempt cross-run or cross-source deduplication.
Data Flow
- The user runs
fetch-apec. - The system reads
data/candidate-profile.yaml. - It derives a small deterministic set of Apec search queries.
- It fetches search results from Apec.
- It caps the full run at 50 listings.
- For each listing URL:
- fetch the public detail page,
- store raw HTML and crawl metadata,
- normalize successful pages into listing records,
- record per-listing errors when fetch or normalization fails.
- It performs minimal within-run deduplication.
- It writes:
listings.yamlrun-meta.yaml- snapshot HTML files
- It prints a concise run summary.
File Layout
data/
candidate-profile.yaml
apec/
runs/
<timestamp>/
listings.yaml
run-meta.yaml
snapshots/
<listing-id-or-slug>.html
File roles:
listings.yamlcontains normalized deduplicated listings for that runrun-meta.yamlcontains derived queries, counts, crawl metadata, and per-listing errorssnapshots/*.htmlcontains raw listing detail pages for debugging and later extraction improvements
Command Surface
This slice should expose one explicit command:
uv run job-research fetch-apec
Expected behavior:
- load the default candidate profile,
- derive queries automatically,
- fetch public Apec listings,
- write run artifacts,
- print a summary including:
- query count,
- fetched listing count,
- normalized listing count,
- deduplicated count,
- failed listing count.
Normalized Listing Shape
The first normalized listing shape should include:
sourcesource_job_idwhen availableurltitlecompanylocationcontract_typedescription_textpublished_atwhen availablefetched_atwarnings
This is intentionally minimal. Salary, remote mode, seniority inference, and richer tagging belong to later slices unless extraction proves stable enough to add them cheaply.
Validation and Error Handling
Hard Failures
These should fail the run before or during crawling:
- missing or unreadable
data/candidate-profile.yaml, - no usable derived search queries,
- Apec unreachable for the whole run,
- every listing fetch failing.
Partial-Success Behavior
These should still produce output artifacts:
- some listing detail pages fail to load,
- some listing pages load but normalize incompletely,
- some duplicates are removed during within-run deduplication.
When partial success occurs, the run should:
- keep successful normalized listings,
- record per-listing failures in
run-meta.yaml, - preserve available snapshots.
Testing Strategy
The slice should be tested around correctness of ingestion, resilience, and artifact shape.
Core test cases:
- deterministic query derivation from
candidate-profile.yaml, - 50-listing cap behavior,
- snapshot persistence for successful detail pages,
- partial-success handling with per-listing errors,
- minimal within-run deduplication,
- normalized YAML output shape,
- run summary metadata.
Acceptance Criteria
This slice is done when:
- one command can fetch public Apec listings from the canonical candidate profile,
- raw detail-page HTML snapshots are preserved,
- one normalized
listings.yamlfile is produced, - partial failures are visible rather than silent,
- the resulting listing artifact is good enough to feed the later ranking slice.
Non-Goals for This Slice
This design intentionally does not solve:
- listing ranking,
- fit explanations,
- source-agnostic ingestion,
- multi-board aggregation,
- database-backed run history,
- login-required crawling,
- long-term deduplication across runs.
Those belong to later slices after raw listing acquisition is stable.