2 Commits

Author SHA1 Message Date
raulgupta
13faed0a06 Engine: signal-weighted India stack + cursorless pool + honest-card hardening
Boards (signal-weighted, ~150/sweep):
- Add TimesJobs (shahidirfan) — richest board: skills+salary+experience+offsite. Build/normalize +
  register; legacy city alias (New Delhi→Delhi, Bengaluru→Bangalore, Gurugram→Gurgaon) — verified live
  it returns 0 for "New Delhi", 35 for "Delhi".
- Swap LinkedIn harvestapi → curious_coder (structured industry/jobFunction/applicants); harvestapi kept
  registered as linkedin_v1 fallback.
- Drop Indeed from enabled (0% skills, redundant). Budgets: Foundit 40, TimesJobs 35, Naukri 30,
  LinkedIn 30, WorkIndia 15.

Pool — cursorless freshness + safety:
- REMOVE pagination/cursors: curious_coder only honors start=0 (start>0 → empty), proven; the rest are
  date feeds. Freshness now = boards' date-sort + the per-user seen-net + pool_save id-dedup.
- Exhaustion guard: a refill adding < POOL_MIN_NEW_PER_REFILL new jobs flags the (user,query) exhausted →
  the gate relaxes the floor instead of block-fetching dupes (makes the 100/90 floor safe on niche queries).
- Storage cap: pool_save trims beyond POOL_MAX_PER_QUERY freshest (kills DB swell). 72h TTL verified.
- Thresholds 100/90 (deep pool, pay-for-volume).

Scoring / cards:
- Evidence-based fallback prose: when Haiku's cards stage gives nothing, the card uses Opus's REAL
  dimension notes (not a generic "Strong on X") + coverage logging + generic salvage parser.
- "Skills & requirements" → "Skills fit" (consistent dimension labels).

Bug fixes:
- mark_seen dedups ids (was CardinalityViolationError on a duplicate id in one batch).

Tests: deep stack contracts + committed board-sample fixture (no cache-pollution flakiness) + offline
e2e + opt-in live e2e. 64 pass.
2026-06-26 15:56:27 +05:30
raulgupta
04e6fb64bb Engine: regression harness (Phase 0) + India non-tech actors (Phase 1)
Phase 0 — measurement gate:
- tests/test_contracts.py: BOARDS↔NORMALIZERS alignment, funnel-shape guards, overlap() None
  contract, and the frontend↔backend EXACT-STRING vocab check (scout.ts options must resolve in
  coerce.py, else silent keyword-fold).
- tests/run_regression.py + fixtures/regression_set.json (+ _seed_regression.py): a frozen 120-pair
  set (tech + non-tech) Opus-judged for recruiter-fit targets. Two metrics: score MAE and SIFT TOP-K
  MEMBERSHIP RECALL (do the best jobs reach Opus?). Baseline: MAE 24.7, recall 0.68 — i.e. ~32% of
  the genuinely-best jobs are cut before the curator ever sees them (worse for non-tech).

Phase 1 — coverage (India non-tech):
- Enable Indeed (misceres) + enrich indeed_to_scoutjob with details.description + location_mode
  (jobType is employment type, not skills → required_skills now []).
- Add WorkIndia (shahidirfan) — India blue/grey-collar non-tech: build_workindia_input +
  workindia_to_scoutjob (real skills + description; per-job apply URL from job_id since source_url
  is generic and would dedup-collapse the deck).
- Drop Wellfound from the stack (US-startup-heavy + 400s); kept registered-but-off.
- BOARDS_ENABLED = naukri,foundit,linkedin,indeed,workindia. Pool ~90 → ~140 jobs/search.
Regression scores unchanged (engine scoring untouched). 31 tests pass.
2026-06-25 15:44:28 +05:30