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
12 KiB
Scout → Engine — Exhaustive Input Inventory
Every field the new Scout UI (
dashboard-ui, branchjob-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 withENGINE_DESIGN.md§2 (signal tiers) andSIGNAL_AUDIT_V2.md.
There are three kinds of input:
- Search prefs — the
ScoutPrefsobject the UI hands the engine per search (onDone(prefs)). - 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. - 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
titleandyearsflow through both paths —ScoutConfirm.buildPrefs()and (via the carry-forwardbase)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
ScoutPrefscontract is now exactly:role · location · workMode · experience · industry · sort · title · years · targetComp · companyStage · availability · dealBreakers · priorities · stretch(+intentshapes 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.