Classification and collection evidence¶
← Maintainer handbook · Architecture · Search registry · Lifecycle
Classification is deterministic and conservative. Discovery candidates are not accepted records. configs/categories.yml owns keywords; src/opportunities/pipeline/classification.py owns decisions; transport and presentation do not classify. The collection map shows where these checks sit before persistence.
Acceptance order¶
| Check | Acceptance boundary |
|---|---|
| Employment type | The normalized title explicitly identifies Internship or New Grad; internship terminology wins when both occur |
| Seniority | No configured senior/management exclusion in the title |
| Technology | No configured nontechnical role in the title; a supported category in the title, or narrowly permitted description fallback |
| Cycle / posting date | Explicit target cycle, or no conflicting cycle with posting evidence on/after May 1, 2026 |
| Geography | Explicit European evidence after conservative normalization |
Unknown types/categories or ambiguous evidence are excluded with stable reasons. A global rule must not be weakened merely to admit one ambiguous listing.
Cycle and date precedence¶
Title opportunity years have priority over description context. A conflicting title year rejects the listing; otherwise a target title year is enough. Without a title cycle, narrow opportunity-context years in the description are checked, with conflicting contextual years rejected. Without explicit cycle evidence, posting-date evidence must meet the fixed May 1, 2026 floor.
Internship graduation/eligibility years (for example, “graduating in 2027”) are not opportunity-cycle evidence. Title-explicit New Grad roles use title/contextual opportunity years as hiring-cycle evidence, so explicit 2025/2026 roles are rejected.
An explicit target-cycle listing does not require posting-age metadata. Relative posting ages are approximate: unknown, lower-bound ages such as 30+ days ago, and malformed ages such as -5 days ago are not fabricated into exact dates. Signs and punctuation are preserved when validating posting-age text. Missing posting metadata for an existing job is not closure evidence.
Changing OPPORTUNITIES_TARGET_CYCLE alone does not roll the product forward: the posting floor, search window, public copy, fixtures and policy require a coordinated review.
Category and geography¶
Categories are checked in configuration order, title before description. Only generic technology titles can use description evidence; generic research titles alone are insufficient. The non_technical_role_keywords list excludes commercial, recruitment, and nontechnical research roles regardless of incidental software mentions.
Location normalization uses src/opportunities/normalization/geography.json, shared with the website's country filters, and assesses semicolon-separated locations independently. Named European countries or terminal uppercase country codes provide country evidence. Known cities qualify only without unrecognized geographic qualifiers: London is supported, but London, South Africa is not. Workplace and city-area modifiers do not change the country. Explicit European countries can support mixed-location roles; EMEA, unspecified remote work, and global language alone cannot establish European eligibility.
Optional industries come from structured criteria, not arbitrary description keywords. Start date requires an explicit month/season plus year in the title or a narrow start-date context. Missing optional metadata is not filled by guessing.
Collection boundary¶
- Enforce source authorization and fixed guest HTTPS endpoints.
- Fetch bounded 25-card pages; stop on empty/repeated raw pages or configured caps, not merely on a page with no eligible titles.
- Apply exact normalized company allowlists and title prefilters.
- Fetch details, require independent title/company identity, normalize and classify.
- Return isolated accepted records, warnings, and explicit unavailability evidence to persistence.
Malformed detail identity is not replaced by card identity. A majority-malformed page/search fails instead of masquerading as a successful empty result. Non-404/410 recheck transport failures fail the search, preserving its lifecycle state. Ambiguous recheck HTML is never closure evidence.
Known-job rechecks select a deterministic bounded lowest-ID subset; they are not a rotating queue. The separate rotating audit checks bounded batches of due jobs. See Database lifecycle for its distinct deletion policy and Configuration for retries, pacing, cookies and stop conditions.
Change safely¶
Add nearby positive and negative tests for title/type, seniority, category fallback, conflicting/eligibility years, posting-floor boundaries, and namesake/ambiguous locations. Use fixed UTC clocks and synthetic HTML. Run classifier/parser tests first, then the full Python gate and benchmarks. No live request is necessary to develop or review a rule.