# 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.