JD Ingest (Stage 0 — LinkedIn Ingestion)
Inputs
| Input |
Description |
url |
LinkedIn jobs page URL to extract jobs from |
file |
Path to a JSON file exported from Atlas browser (or similar logged-in browser agent) containing pre-extracted job data |
Input validation & priority:
- If
file is provided (e.g. @linkedin-ingest-data.json) → use the JSON file as the data source. Skip HTML fetching entirely. The file should contain an array of job objects with fields: job_id, title, company, location, job_link, work_type, employment_type, salary, easy_apply, linkedin_matching, duplicate_check_key, comptitle, company_url, promoted.
- If
url is provided (no file) → fetch the listing page HTML via curl (guest/unauthenticated).
- If neither is provided → ask the user.
Atlas browser workflow (recommended for logged-in data):
- Open the LinkedIn search page in Atlas browser (already logged in).
- Paste the prompt from
ATLAS_BROWSER_PROMPT.md (located next to this file).
- Atlas outputs a JSON array + summary report.
- Save the JSON to a file in the project, then pass it here via
file.
Execution
Step 1 — Load Stage 0 Framework
Fetch the Stage 0 framework from Notion:
Read and follow the instructions in that page for the full extraction logic, duplicate handling, field mappings, and output format.
Step 2 — Acquire Job Data
Choose one path based on the input provided:
Path A — Atlas JSON file (preferred for logged-in data)
- Read the JSON file specified by
file.
- Each element already contains
job_id, title, company, location, job_link, duplicate_check_key, comptitle, linkedin_matching, work_type, etc.
- Proceed directly to Step 3 (duplicate check + create).
Path B — URL fetch (guest HTML, fallback)
- If
url is provided → fetch the listing page HTML via curl.
- Parse job cards from the HTML (regex / HTML parsing).
- Extract fields: job_id, title, company, location, job_link, work_type, etc.
- Note: guest HTML may not contain all jobs visible when logged in, and will not contain LinkedIn match indicators.
Step 3 — Run the Stage 0 Pipeline
Execute the Stage 0 framework loaded in Step 1:
- Read job data (from Atlas JSON or parsed HTML)
- Normalize job fields
- Generate Duplicate Check Keys (if not already present in JSON)
- Check Job Applications DB for duplicates
- Skip duplicates
- Capture
Linkedin-matching (from JSON field or HTML when available)
- Create new records with
Status = Saved
Step 3A — Strict Duplicate Validation (mandatory)
To avoid false duplicate reports, use this strict dedupe protocol for every job:
- Search by exact
job_id in the target data source.
- If search returns no result, treat as candidate new.
- If search returns one or more pages, fetch candidate page(s) and verify duplicate only when:
Job ID exactly equals the incoming job_id, or
Duplicate Check Key exactly equals incoming duplicate_check_key.
- If neither exact match is present after fetch verification, treat as new (do not mark duplicate based on semantic similarity, title similarity, or company similarity).
Hard rule: never conclude "all duplicates" from search results alone; fetch-level exact validation is required.
Step 3B — Post-run Reconciliation (mandatory)
After create/skip decisions complete, run a verification pass before reporting:
- For each input
job_id, re-check existence in target data source.
- Produce and verify these counts:
input_total
confirmed_existing_total
confirmed_created_this_run
confirmed_duplicates_skipped
confirmed_missing_total
- Validation equation must hold:
confirmed_created_this_run + confirmed_duplicates_skipped == input_total
- If equation fails, report run as incomplete/inconsistent (not successful), include missing IDs, and do not claim final duplicate/create totals.
Step 3C — Reliability and Timeout Handling
If Notion MCP calls time out or rate limit:
- Retry with smaller batches.
- Persist progress checkpoints (processed IDs, created IDs, skipped IDs).
- Resume from last checkpoint.
- Mark final status as partial if unresolved; never present partial outcomes as final.
Step 3D — Target Scope Confirmation
Before dedupe and reporting, explicitly confirm the target destination:
- Data source ID (e.g.
collection://...)
- Database/view context if user references a specific view (e.g. "drop DB")
If ambiguous, ask once before mutating data.
Step 4 — Mandatory Ingest Log Prepend (top-of-page incremental update)
After each /JD-ingest run, you MUST prepend an incremental run summary to:
https://www.notion.so/JD-ingest-log-3368d41a5cff80268c89c3bb4ab4056f
Rules:
- Always prepend at top (newest entry first). Do not append.
- Use
notion-fetch first to get current page content and the exact old_str anchor.
- Use
notion-update-page with command: "update_content" to replace:
old_str = <current full content> (or a stable top prefix),
new_str = <new incremental block> + "\\n\\n" + <old content>.
- Incremental block must include:
- timestamp (
YYYY-MM-DD HH:MM, local),
- intake summary (
scanned, duplicates, created),
- grouped newly created links by
Linkedin-matching (or No match).
- If no new jobs were created, still prepend a short block stating
created: 0.
- If update times out/rate-limits:
- retry in smaller payloads,
- re-fetch before each retry to avoid stale
old_str,
- do not claim success unless fetch confirms the new top block exists.
- Final response for
/JD-ingest must explicitly state whether log prepend succeeded.
Expected Output
Follow the output format defined in the Stage 0 framework:
- Execution Summary — browser read, jobs extracted, duplicates checked, records created
- Intake Summary — total scanned, total duplicates skipped, total new records created
- New jobs grouped by Linkedin-matching — only newly created job links, grouped by match level
- Verification Block (required) —
input_total, confirmed_created_this_run, confirmed_duplicates_skipped, confirmed_missing_total, and whether reconciliation equation passed
1---2name: jd-ingest3description: Extract jobs from a LinkedIn jobs page, deduplicate against Notion Job Applications DB, and insert new records as Saved. Use when the user mentions JD ingest, LinkedIn job search, job ingestion, Stage 0, or bulk importing jobs from LinkedIn.4---56# JD Ingest (Stage 0 — LinkedIn Ingestion)78## Inputs910| Input | Description |11|-------|-------------|12| `url` | LinkedIn jobs page URL to extract jobs from |13| `file` | Path to a JSON file exported from Atlas browser (or similar logged-in browser agent) containing pre-extracted job data |1415**Input validation & priority:**16171. If `file` is provided (e.g. `@linkedin-ingest-data.json`) → **use the JSON file** as the data source. Skip HTML fetching entirely. The file should contain an array of job objects with fields: `job_id`, `title`, `company`, `location`, `job_link`, `work_type`, `employment_type`, `salary`, `easy_apply`, `linkedin_matching`, `duplicate_check_key`, `comptitle`, `company_url`, `promoted`.182. If `url` is provided (no file) → fetch the listing page HTML via curl (guest/unauthenticated).193. If neither is provided → ask the user.2021**Atlas browser workflow (recommended for logged-in data):**22- Open the LinkedIn search page in Atlas browser (already logged in).23- Paste the prompt from `ATLAS_BROWSER_PROMPT.md` (located next to this file).24- Atlas outputs a JSON array + summary report.25- Save the JSON to a file in the project, then pass it here via `file`.2627## Execution2829### Step 1 — Load Stage 0 Framework3031Fetch the Stage 0 framework from Notion:3233- **Stage 0 — LinkedIn ingest**34 https://www.notion.so/Stage-0-LinkedIn-ingest-3358d41a5cff80d696dfcf3b9ac748d33536Read and follow the instructions in that page for the full extraction logic, duplicate handling, field mappings, and output format.3738### Step 2 — Acquire Job Data3940Choose one path based on the input provided:4142**Path A — Atlas JSON file (preferred for logged-in data)**43- Read the JSON file specified by `file`.44- Each element already contains `job_id`, `title`, `company`, `location`, `job_link`, `duplicate_check_key`, `comptitle`, `linkedin_matching`, `work_type`, etc.45- Proceed directly to Step 3 (duplicate check + create).4647**Path B — URL fetch (guest HTML, fallback)**48- If `url` is provided → fetch the listing page HTML via curl.49- Parse job cards from the HTML (regex / HTML parsing).50- Extract fields: job_id, title, company, location, job_link, work_type, etc.51- Note: guest HTML may not contain all jobs visible when logged in, and will not contain LinkedIn match indicators.5253### Step 3 — Run the Stage 0 Pipeline5455Execute the Stage 0 framework loaded in Step 1:56571. Read job data (from Atlas JSON **or** parsed HTML)582. Normalize job fields593. Generate Duplicate Check Keys (if not already present in JSON)604. Check Job Applications DB for duplicates615. Skip duplicates626. Capture `Linkedin-matching` (from JSON field or HTML when available)637. Create new records with `Status = Saved`6465### Step 3A — Strict Duplicate Validation (mandatory)6667To avoid false duplicate reports, use this strict dedupe protocol for **every** job:68691. Search by exact `job_id` in the target data source.702. If search returns no result, treat as **candidate new**.713. If search returns one or more pages, fetch candidate page(s) and verify duplicate only when:72 - `Job ID` exactly equals the incoming `job_id`, **or**73 - `Duplicate Check Key` exactly equals incoming `duplicate_check_key`.744. If neither exact match is present after fetch verification, treat as **new** (do not mark duplicate based on semantic similarity, title similarity, or company similarity).7576**Hard rule:** never conclude "all duplicates" from search results alone; fetch-level exact validation is required.7778### Step 3B — Post-run Reconciliation (mandatory)7980After create/skip decisions complete, run a verification pass before reporting:81821. For each input `job_id`, re-check existence in target data source.832. Produce and verify these counts:84 - `input_total`85 - `confirmed_existing_total`86 - `confirmed_created_this_run`87 - `confirmed_duplicates_skipped`88 - `confirmed_missing_total`893. Validation equation must hold:90 - `confirmed_created_this_run + confirmed_duplicates_skipped == input_total`914. If equation fails, report run as **incomplete/inconsistent** (not successful), include missing IDs, and do not claim final duplicate/create totals.9293### Step 3C — Reliability and Timeout Handling9495If Notion MCP calls time out or rate limit:9697- Retry with smaller batches.98- Persist progress checkpoints (processed IDs, created IDs, skipped IDs).99- Resume from last checkpoint.100- Mark final status as partial if unresolved; never present partial outcomes as final.101102### Step 3D — Target Scope Confirmation103104Before dedupe and reporting, explicitly confirm the target destination:105106- Data source ID (e.g. `collection://...`)107- Database/view context if user references a specific view (e.g. "drop DB")108109If ambiguous, ask once before mutating data.110111### Step 4 — Mandatory Ingest Log Prepend (top-of-page incremental update)112113After each `/JD-ingest` run, you MUST prepend an incremental run summary to:114115- `https://www.notion.so/JD-ingest-log-3368d41a5cff80268c89c3bb4ab4056f`116117Rules:1181191. **Always prepend at top** (newest entry first). Do not append.1202. Use `notion-fetch` first to get current page content and the exact `old_str` anchor.1213. Use `notion-update-page` with `command: "update_content"` to replace:122 - `old_str = <current full content>` (or a stable top prefix),123 - `new_str = <new incremental block> + "\\n\\n" + <old content>`.1244. Incremental block must include:125 - timestamp (`YYYY-MM-DD HH:MM`, local),126 - intake summary (`scanned`, `duplicates`, `created`),127 - grouped newly created links by `Linkedin-matching` (or `No match`).1285. If no new jobs were created, still prepend a short block stating `created: 0`.1296. If update times out/rate-limits:130 - retry in smaller payloads,131 - re-fetch before each retry to avoid stale `old_str`,132 - do not claim success unless fetch confirms the new top block exists.1337. Final response for `/JD-ingest` must explicitly state whether log prepend succeeded.134135## Expected Output136137Follow the output format defined in the Stage 0 framework:138139- **Execution Summary** — browser read, jobs extracted, duplicates checked, records created140- **Intake Summary** — total scanned, total duplicates skipped, total new records created141- **New jobs grouped by Linkedin-matching** — only newly created job links, grouped by match level142- **Verification Block (required)** — `input_total`, `confirmed_created_this_run`, `confirmed_duplicates_skipped`, `confirmed_missing_total`, and whether reconciliation equation passed