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
155 lines
12 KiB
Markdown
155 lines
12 KiB
Markdown
# 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 | ₹10–14L … ₹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 / 60–90d / 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.
|