# Job filtering with Jev

A complete Python workflow and a minimal TypeScript API example. This filters job listings for a reader; it does not screen applicants or make hiring decisions.

Download `filter_jobs.py`, `evaluate.ts` and `jobs.json` into one folder. Python needs version 3.11 or later and no extra packages. TypeScript runs with Node 24 or later.

## Prepare without an API call

```sh
python filter_jobs.py prepare jobs.json --output requests.jsonl
```

The three input records are fictional. This command validates them and writes request payloads for the two records with descriptions. The remaining record is marked `review` with `missing_description`. It makes no network requests and needs no key.

## Run the complete workflow

Set `TYPESAFE_API_KEY` in your process environment or secret manager. Never place it in browser code, a public file or Git. Obtain a key at https://console.typesafe.ai/keys.

```sh
python filter_jobs.py run jobs.json --output decisions.jsonl --target-role "Python backend engineer" --work-mode remote
```

This sends one request per eligible record to `https://api.typesafe.ai/v1/systemone` and consumes your TypeSafe account allowance. It uses `jev-1.13.0` explicitly. TrueFetch credits are not involved. TrueFetch collection is not yet open; supply a jobs JSON file you are authorized to process.

## TypeScript API call

With the same environment variable set:

```sh
node evaluate.ts requests.jsonl answer.json
```

This evaluates only the first prepared request and saves the raw answer. It demonstrates the HTTP call; use the Python workflow for batch validation and routing. It does not retry automatically.

## Input contract

Supply an array of 1–25 records, or an object with `status` and `results`. Status must be `succeeded`; `partial` requires `--allow-partial`. Failed collection results are rejected before any API call.

Each record needs a nonempty `title` and an HTTP(S) `sourceUrl` without embedded credentials. Optional string fields: `company`, `location`, `description`, `source`, `collectedAt`. Null optional fields are allowed. Other fields are discarded. Missing or blank descriptions go to review without a call. Identical source URLs are processed once; alternate URLs for the same listing are not deduplicated.

The file limit is 1 MB; each field is limited to 12,000 characters and each normalized record to 24 KB in UTF-8. Oversize input is rejected, never silently truncated. Only title, location and description are sent as model state.

## Routing rules

Four independent questions share one request: role relevance (Score), work arrangement (Choice), stated duties (Noul), and embedded evaluator instructions (Noul).

Rules run in this order:

1. Possible instruction injection (Noul at least 0.2): review.
2. Insufficient duty evidence (Noul below 0.8): review.
3. Role confidence below 0.8: review.
4. Role score at most 0.5 on the 0–3 rubric: skip.
5. Unless `--work-mode any`: unknown or low-confidence arrangement goes to review; a clear mismatch is skipped.
6. Role score at least 2.5: shortlist. Otherwise: review.

The thresholds are starting values, not a calibrated accuracy guarantee. Geographic eligibility, visas, salary, dates and seniority are not checked. A remote match does not imply worldwide eligibility. Validate on labeled examples from your own sources before acting on results. The injection check is a signal for review, not a security boundary.

## Output and failures

Each JSONL row keeps title, source URL, collection status and supplied collection timestamp. Evaluated rows also contain routing reason, resolved model, original answers, token usage, request ID when supplied, and thresholds. Reasons describe code decisions, not generated model explanations. `usage` may omit token counts; missing counts are unknown, not zero.

Existing output files are never overwritten. Exit 0 means completed processing; 1 means input, configuration or file error; 2 means at least one provider request or response failed. Inspect any output after a failed or interrupted run; it may contain only completed rows. Do not blindly rerun already evaluated records: another API call can incur usage again.

The Python client uses a 30-second socket timeout and at most two retries, only on 429 and 529. It honors numeric, HTTP-date and millisecond retry headers up to 30 seconds; a longer requested delay ends the request. Network failures, redirects and timeouts are not replayed. Provider bodies and credentials are not printed. Files contain supplied job content and should be stored accordingly.

## References

- API: https://docs.typesafe.ai/api
- Models and current prices: https://docs.typesafe.ai/models
- Confidence: https://docs.typesafe.ai/confidence
- Known limitations: https://docs.typesafe.ai/model-jaggedness/jev-1.13

Independent TrueFetch example. No affiliation with TypeSafe is implied.
