# Role Radar

Watches each target company's careers page for **newly-posted** Finance & Strategy /
BizOps-Strategy / Chief-of-Staff / Product-Strategy-GTM / AI-Operations roles, saves
the JD into this resume repo, tracks it in a local sheet, and triggers the
`resume-system` skill to draft a tailored resume — then **stops so you review and
apply yourself**. No application is ever submitted automatically.

## How it works — two tiers

**Tier 1 — Detector (`role_radar.py`, deterministic, stdlib-only).**
For each company in `companies.json` it hits the ATS **read-API** (no HTML scraping):

| ATS | Endpoint | JD text |
|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/<slug>/jobs?content=true` | inline `content` (HTML) |
| Ashby | `api.ashbyhq.com/posting-api/job-board/<slug>?includeCompensation=true` | inline `descriptionPlain` |
| Lever | `api.lever.co/v0/postings/<slug>?mode=json` | inline `descriptionPlain` |

It filters titles (`filters.json`), diffs job IDs against `seen_jobs.json`, and for each
**new match** writes a JD into `../job-descriptions/<company>_<role>/<date>/jd.md` and a row into
`tracker.csv`. Runs headless, costs zero Claude tokens.

**Tier 2 — Tailor (the `resume-system` skill, agentic, needs Claude).**
For each `new` row it drafts a tailored resume, renders DOCX/PDF, QA-checks layout, logs
it, and flips the row to `drafted`. See **Tier-2 routine** below.

## Files

```
companies.json   target companies: {name, platform, slug, protected?}   ← edit to add/remove
filters.json     role-family title regex + global excludes               ← edit to tune matching
role_radar.py    Tier-1 detector + --mark status helper
seen_jobs.json   state: every job id seen per company (the dedup memory)  ← do not hand-edit
tracker.csv      THE SHEET — source of truth                              ← set applied/skipped here
tracker.md       Obsidian-readable table, regenerated each run            ← read-only (regenerated)
INBOX.md         latest-run summary / what to do next                     ← read-only (regenerated)
../job-descriptions/<company>_<role>/<date>/jd.md   saved JD per detected role
```

## Running it

```bash
# from this folder; any Python 3.8+ (repo .venv preferred per AGENTS.md)
python role_radar.py                 # full run: seed on first run, else detect new
python role_radar.py --dry-run       # show what would match, write nothing
python role_radar.py --company ramp,tabs --dry-run   # scope to companies

# flip a row's status safely (re-renders tracker.md + INBOX.md):
python role_radar.py --mark "ramp::Senior Associate, Strategic Finance" --status drafted --resume "resumes/.../x.md"
python role_radar.py --mark "scale::Chief of Staff" --status applied      # match by title substring
python role_radar.py --mark "tabs::<job_id>"        --status skipped
```

### Status lifecycle
`backlog` (open match found on the seeding run) → `new` (posted *after* seeding;
auto-draft candidate) → `drafted` (resume ready to review) → you apply → `applied`.
Also `needs-input` (Tier 2 paused — JD needs evidence not in your repo) and `skipped`.
The detector never re-surfaces a job whose id is already in `seen_jobs.json`, so
`applied`/`skipped` stick.

> **First-run guard:** the very first run records *all* currently-open matches as
> `backlog` (not auto-drafted) so day one doesn't dump 36 resume drafts. Only postings
> that appear *after* seeding become `new`.

## Adding / removing a company

Edit `companies.json`. To find a slug, try the company's careers page or probe:
```bash
curl -s "https://api.ashbyhq.com/posting-api/job-board/<guess>" | head -c 200          # Ashby
curl -s "https://boards-api.greenhouse.io/v1/boards/<guess>/jobs" | head -c 200        # Greenhouse
```
A 200 with a `jobs` array = correct slug+platform. A wrong slug just logs an HTTP error
and the run continues. `"protected": true` (Anthropic) = Tier 2 always **holds for your
review**, never auto-drafts.

## Tuning the filter

`filters.json` → `families` are case-insensitive regex matched against the job title;
`exclude` drops a title even if a family matches; `enabled_families` toggles which run.
Examples already tuned in: bare `applied ai` was removed (it matched 18 Anthropic
solutions-architect dupes); `usage data`/`pricing`/`consumption` are kept narrow so they
catch your monetization-edge PM roles without matching every Product Manager.

## Tier-2 routine (what the loop does for each `new` role)

Driven by the `resume-system` skill (`jobs-resume-studio/skills/resume-system`). For a row:

1. Read its `jd.md` from `job-descriptions/`. Pick the closest existing tailored resume as the base
   (`references/source-map.md` rules); else `resumes/resume_master_2026.md`.
2. Tailor **truth-preservingly** — reframe/select/order only what the repo supports.
   Honor the skill's stop-rules.
3. Render + QA:
   ```bash
   node scripts/generate_resume_docx.js --style=kellogg-template-tight <jd-folder>/<draft>.md "<out>.docx"
   .venv/Scripts/python scripts/check_page_fill.py "<out>.pdf"        # ideal band
   .venv/Scripts/python scripts/check_bullet_wrapping.py <draft>.md   # orphan-tail exit 0
   ```
4. Append a `resumes/resume_completion_log.md` entry.
5. `python role_radar.py --mark "<company>::<job_id>" --status drafted --resume "<path>"`.

**Guardrails (non-negotiable):**
- **Never fabricate.** If the JD needs evidence not in the repo → set `needs-input`,
  record the discovery questions in the row's `notes`, do **not** invent bullets.
- **Protected companies (Anthropic)** are never auto-drafted — left for you to trigger.
- **Never auto-apply.** The loop stops at a reviewable resume.

## Scheduling

**Now — session cron (chosen).** A recurring job in this Claude session runs the detector
each morning, drafts any `new` non-protected roles via Tier 2, and posts the INBOX
summary. Caveats: it only fires while a Claude session is **alive/idle**, and recurring
session jobs **auto-expire after 7 days** — re-arm by asking Claude to recreate it.

**Durable — Windows Task Scheduler (graduate to this for an always-on watch).**
Tier 1 runs fully headless; Tier 2 needs Claude. Example daily detector at 8:37am:
```powershell
# one-time setup (PowerShell). Adjust the python path to the repo .venv.
$py  = "C:\Users\darsh.shah\Documents\Darsh's Vault\jobs-resume-studio\.venv\Scripts\python.exe"
$arg = "`"C:\Users\darsh.shah\Documents\Darsh's Vault\jobs-resume-studio\role-radar\role_radar.py`""
$act = New-ScheduledTaskAction -Execute $py -Argument $arg
$trg = New-ScheduledTaskTrigger -Daily -At 8:37am
Register-ScheduledTask -TaskName "RoleRadar" -Action $act -Trigger $trg -Description "Daily new-role detector"
```
This detects + saves JDs + updates the tracker even with no session open; you then run
Tier 2 (resume drafting) next time you open Claude. To also draft headlessly, have the
task additionally invoke `claude -p "<Tier-2 prompt>"` (consumes tokens unattended).

## Out of scope (by design)
No automated application submission. No Google Sheet. No browser automation / answer-bank.
