Files
matchmaking-v2/research/docs/ENGINE_INPUTS.md
raulgupta 89ca9ad647 Initial commit: matchmaking-v2 on-demand multi-board job-search agent
On-demand Scout service (replaces the nightly aggregator):
- FastAPI agent-mesh service; card name "matchmaking-service" (HTTP /a2a/tasks
  + Redis-stream worker matching the sibling-service pattern)
- run_search: parallel multi-board sweep (Naukri/blackfalcondata + Foundit +
  LinkedIn), city-filtered at the board, cheapest-per-result first
- per-board adapters + normalizers -> ScoutJob with rich read-only details and
  offsite apply links; recall mode + MVQ guard
- result cache for dev replay ($0); per-board cost knobs; retry-on-5xx
- research/: engine design, board cost economics, India-actor shortlist, POC
2026-06-18 19:22:03 +05:30

155 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Scout → Engine — Exhaustive Input Inventory
> Every field the **new Scout UI** (`dashboard-ui`, branch `job-ui-final`) produces that is (or should
> be) an input to matchmaking-v2. Derived field-by-field from the code, cross-checked against what the
> handlers actually pass (`ScoutConfirm.go()`, `ScoutBubbleJourney.toPrefs()`). Written 2026-06-17.
> Pair with `ENGINE_DESIGN.md` §2 (signal tiers) and `SIGNAL_AUDIT_V2.md`.
There are **three kinds of input**:
1. **Search prefs** — the `ScoutPrefs` object the UI hands the engine per search (`onDone(prefs)`).
2. **Profile/context** — assembled by the orchestrator `context_resolver` (resume + LinkedIn + QScore +
course-behavior); NOT picked in Scout, but the Confirm card *displays/edits* some of it.
3. **Behavioral events** — view/save/pass emitted while swiping (learning loop + activity rank).
---
## 1. SEARCH PREFS — the `ScoutPrefs` contract (what `onDone` sends)
| # | Field | Type | UI surface(s) | Options / shape | Engine role (per ENGINE_DESIGN §2/§3) |
|---|---|---|---|---|---|
| 1 | `role` | `string[]` | Bubble journey role stage (like/love); Confirm via intent | Product/Design/Engineering/Data/Marketing/Sales/Ops/Open-to-any **+ add-your-own** | Tier-A `target_roles`**filter, not score** (want, not is) |
| 2 | `industry` | `string[]` | Confirm "Industry" select; Bubble journey industry stage | Fintech/SaaS/E-commerce/EdTech/HealthTech/Dev tools/Consulting **+ Any + add-your-own** | Tier-A `target_industries` — filter |
| 3 | `location` | `string[]` | Confirm "Where" (LocationPicker); Bubble constraints (LocationPicker) | `"City · Country"` strings (+ Remote/Anywhere) — cascading Country→City **+ custom** | Tier-A `preferred_locations`**hard filter + location-fit** (city AND country substring; Remote→100; Anywhere→open) |
| 4 | `workMode` | `string[]` | Confirm "Work mode"; Bubble constraints | Remote/Hybrid/On-site (Any = none) | Tier-A work mode — hard filter |
| 5 | `experience` | `string[]` | Confirm "Seniority" select; Bubble constraints | Fresher/Junior/Mid-level/Senior/Lead **+ Any** | Tier-A seniority floor — hard filter |
| 6 | `targetComp` | `string?` | Confirm "Target comp" select | ₹1014L … ₹30L+ **+ Any** | preference signal (NOT self-declared salary, which §2 discards; this is target band) |
| 7 | `companyStage` | `string?` | Confirm "Company stage" select | Seed/Growth/Late-pre-IPO/Enterprise **+ Any** | company-fit enricher |
| 8 | `availability` | `string?` | Confirm "Availability" select | Open now / 30d / 6090d / Just exploring **+ Any** | timing context |
| 9 | `dealBreakers` | `string[]?` | Confirm "Deal-breakers" chips | Remote-first/No on-call/Equity/Visa/4-day/No relocation | hard exclusions |
| 10 | `priorities` | `string[]?` | Confirm "What matters most? · Pick 2" | up to 2 of PRIORITY_OPTIONS | **ranking weights** (soft — orders results) |
| 11 | `stretch` | `Stretch?` | Confirm "How far should I reach?" | safe / balanced / reach | FIT↔STRETCH reach band (§3.5) → match-score floor |
| 12 | `sort` | `"match"\|"salary"\|"closing"` | default "match"; Shortlist has a local sort | — | output ordering (not a match signal) |
| 13 | **`intent`** *(not stored on prefs; shapes 1/2/5)* | `"same"\|"levelup"\|"new"` | Confirm "What are you after?" | More like this / A step up / Something new | **FIT vs STRETCH selector** (§3.5) — current-state vs aspiration |
| 14 | **`title`** ✅ wired 2026-06-17 | `string?` | Confirm card "Title" input | free text | Tier-A `current_role` / title (filter + weight). Sole title source when no resume |
| 15 | **`years`** ✅ wired 2026-06-17 | `number?` | Confirm card "Experience (yrs)" input | number | Tier-A `years_experience` (weight). Sole years source when no resume |
| 16 | **`targetTitles`** ✅ added 2026-06-17 | `string[]` (single-select → ≤1) | Bubble journey **stage 3 "Roles that fit you"** — AI/resume+LinkedIn-generated specific titles, **pick one** | e.g. "Payments PM", "Risk & Fraud Product" | **High-signal Tier-A target** — the precise role the engine should prefer to **seed the on-demand board query** (specific > broad `role`). The "true fine-tuning" pick. *Generation is a STUB for the GPT-5.4 /suggest call, mirroring the courses AI flow.* |
> Both `title` and `years` flow through **both** paths — `ScoutConfirm.buildPrefs()` and (via the
> carry-forward `base`) `ScoutBubbleJourney.toPrefs()`, so Fine-tune no longer drops them.
---
## 2. ⚠️ Remaining gap — captured but not fully used
| Field | Where it's collected | Status | Why it matters |
|---|---|---|---|
| **bubble `like` vs `love`** | Bubble journey (3-state) | **Flattened**`toPrefs()` maps both → "selected" | "love" = top priority — a free **weight** signal still collapsed to binary. Optional: feed a per-pick weight |
**Resolved 2026-06-17:** the bubble-journey path (`toPrefs`) used to drop everything except
role/industry/workMode/experience/location. It now starts from the **carry-forward `base`** the Confirm
card passes in, so title/years/comp/stage/availability/deal-breakers/priorities/stretch survive Fine-tune.
---
## 3. ✅ RESOLVED — orphaned prefs DROPPED from the contract (2026-06-17)
These had no UI surface left, so they were removed from `ScoutPrefs` entirely (interface, `emptyPrefs`,
`resumePrefs`, `filterSortScoutJobs`, `go()`), and the orphaned `ScoutFilterSheet.tsx` + its option
constants (`JOB_TYPE_OPTIONS`, `SALARY_BANDS`, `JOB_TYPE_TO_VALUE`, `DREAM_COMPANY_OPTIONS`) were deleted.
| Field | Was | Now |
|---|---|---|
| `jobType` | Action filter sheet (removed) | **dropped** |
| `salaryBand` | Action filter sheet (removed) | **dropped** (`targetComp` is the surviving comp input) |
| `dreamCompanies` | Confirm card edit (cut) | **dropped** |
| `hideEmployer` | Confirm card toggle (cut) | **dropped** |
---
## 4. BEHAVIORAL EVENTS — emitted while swiping (feed the learning loop, not the per-search query)
| Event | UI trigger | Engine role |
|---|---|---|
| **VIEW** | open a card / tap info (expand QX breakdown) | funnel "Viewed"; activity rank; weak relevance label |
| **SAVE** | swipe right / Save (heart) | funnel "Shortlisted"; **strong positive label** (§3.1 ⑥ accrue save/apply → learned ranker) |
| **PASS** | swipe left / X | negative label |
| **UNDO** | undo button | corrects the last save/pass |
| **APPLY (hand-off)** | DeckEnd "approve once" → auto/manual apply | **un-gameable ground truth** (§4) — the real save/apply rate |
---
## 5. PROFILE / CONTEXT — assembled by `context_resolver`, surfaced (not picked) in Scout
The Confirm card *displays and lets you edit* the resume-derived defaults; the rest is read server-side.
A **no-resume user** sends all of §1 as "Any"/open → engine §3.3 coverage-renormalizes (never fabricates).
| Source | Fields | Tier |
|---|---|---|
| **Resume (resume-builder parse, "Mira")** | title, seniority, industry, city, years, + pre-filled guesses for comp/stage/availability/deal-breakers/priorities/stretch (editable on the card) | A / B |
| **LinkedIn (social-branding, live fetch)** | verified title / company / industry; education; certifications | B |
| **QScore** | 10-D quotient vector (already digests interview + roleplay) | B |
| **Assessment** | passed / percentage (user-service) | B |
| **Course watch-behavior** (`growqr_course`) | revealed-preference booster (e.g. completes React → frontend signal) | C (additive) |
| **Interview / roleplay rubrics** | 8-D / 6-D (low coverage) | C (additive) — read later only if lift beyond QScore |
---
## 6. DEEP AUDIT — every engine signal × frontend coverage (2026-06-17)
Walks the **entire** `SIGNAL_AUDIT_V2.md` inventory (Tier A/B/C + labels) and assigns each signal an
owner. **Rule:** the frontend owns only what the *user states* (request prefs + editable overrides +
feedback events). Everything resume/identity/competence-derived is **server-assembled by
`context_resolver` / qscore / Perplexity / `growqr_course`** — by design NOT a Scout UI field.
| Engine signal (SIGNAL_AUDIT_V2) | Owner | Frontend field / event | Status |
|---|---|---|---|
| **Tier A** | | | |
| `years_experience` | Frontend override + resume | `years` (Confirm) | ✅ WIRED (was the gap) |
| `current_role` / title | Frontend override + resume/LinkedIn | `title` (Confirm) | ✅ WIRED (was the gap) |
| `skills` → ESCO | **Server** (resume parse) | — (not a Scout pref) | ✅ correctly server-side |
| `preferred_locations` + work mode | Frontend (request prefs) | `location` (City·Country), `workMode` | ✅ WIRED |
| `target_roles` / `target_industries` | Frontend (request prefs) | `role`, `industry` | ✅ WIRED |
| **Tier B** | | | |
| verified title / company / industry | **Server** (social-branding LinkedIn) | industry/title *overridable* via Confirm | ✅ server; overrides wired |
| degree / field / institution | **Server** (resume/LinkedIn) | — | ✅ server-side |
| certifications | **Server** (resume/LinkedIn) | — | ✅ server-side |
| assessment scores | **Server** (user-service) | — | ✅ server-side |
| QScore quotient vector (10-D) | **Server** (qscore-service) | — | ✅ server-side |
| market context (demand/freshness/comp) | **Server** (Perplexity, cached) | — | ✅ server-side |
| **Tier C (additive boosters)** | | | |
| course watch-behavior | **Server** (`growqr_course`) | — | ✅ server-side |
| improvement velocity / coachability | **Server** (interview/roleplay) | — | ✅ server-side |
| video on-camera presence | **Server** (interview/roleplay) | — | ✅ server-side |
| **Soft ranking / FIT-STRETCH (engine §3.4/§3.5)** | | | |
| ranking weights | Frontend | `priorities` (Pick 2) | ✅ WIRED |
| reach band | Frontend | `stretch` | ✅ WIRED |
| FIT vs STRETCH selector | Frontend | `intent` | ✅ WIRED |
| comp / stage / availability / deal-breakers | Frontend | `targetComp`/`companyStage`/`availability`/`dealBreakers` | ✅ WIRED |
| **Labels (training targets)** | | | |
| SAVE / DISMISS / APPLY feedback | Frontend events | swipe deck + apply hand-off | ✅ emitted (§4) |
### Verdict
**Every engine input the frontend is responsible for is now wired.** After this pass there are **no
dropped frontend fields** — `title` + `years` were the last gaps and are now sent on both paths.
Everything still "unwired" in the UI is **server-assembled context** (skills, LinkedIn identity,
QScore, assessment, course-behavior, market context) and *must not* be a Scout form field — capturing
it client-side would duplicate or fake what `context_resolver` already assembles.
**Only non-blocking residual:** the bubble `like` vs `love` 3-state is flattened to binary "selected"
(`toPrefs`); wiring it as a per-pick weight is an optional enhancement, not a missing engine input.
---
## Summary — counts
- **15 search-pref fields** actively sent (incl. intent). The `ScoutPrefs` contract is now exactly:
`role · location · workMode · experience · industry · sort · title · years · targetComp ·
companyStage · availability · dealBreakers · priorities · stretch` (+ `intent` shapes role/industry/experience).
- **title + years WIRED** (2026-06-17) on both the Confirm and Fine-tune paths — were the last gaps.
- **1 non-blocking residual** (bubble like/love → binary) — optional weight enhancement, not a missing input.
- **4 orphaned prefs DROPPED** (jobType, salaryBand, dreamCompanies, hideEmployer) — contract trimmed
to only what the UI renders.
- **5 behavioral events** for the learning loop.
- **6 server-assembled context sources** (skills/LinkedIn/QScore/assessment/course-behavior/market) —
correctly NOT Scout fields.
- **Deep-audit verdict (§6):** every frontend-owned engine input is wired; no dropped fields remain.