-
Notifications
You must be signed in to change notification settings - Fork 0
Fit Scoring Engine
Relevant source files
- src-tauri/src/experience.rs
- src-tauri/src/fit.rs
- src/lib/fit.test.ts
- src/lib/fit.ts
- src/lib/level.test.ts
- src/lib/level.ts
- src/lib/rolesView.test.ts
- src/lib/rolesView.ts
The Fit Scoring Engine is responsible for evaluating the compatibility between a Job and the user's Target Criteria. It implements a two-layer rubric consisting of a Hard Layer (strict filters that generate flags) and a Soft Layer (weighted scoring across five dimensions).
The engine operates on a "Recall-Safe" principle: flags and penalties are only applied when a conflict is explicitly known. If data is missing from either the Job or the Profile, the engine defaults to a neutral stance to avoid false negatives src-tauri/src/fit.rs#3-5
The primary entry point is score_fit, which consumes a Job, TargetCriteria, and CompetencyIndex to produce a FitBreakdownsrc-tauri/src/fit.rs#335-338
The following diagram illustrates how raw job descriptions and user profile data are transformed into structured entities and finally processed by the scoring engine.
Scoring Pipeline Data Flow
flowchart TD
subgraph subGraph2 ["Scoring Engine (src-tauri/src/fit.rs)"]
HF["hard_filters()"]
SS["score_fit()"]
FB["FitBreakdown Struct"]
end
subgraph subGraph1 ["Code Entity Space"]
JobEntity["Job Struct"]
ExpEntity["Experience Struct"]
TC["TargetCriteria Struct"]
CI["CompetencyIndex"]
end
subgraph subGraph0 ["Natural Language Space"]
JD["Job Description (Markdown)"]
EXP["Experience Notes (Markdown)"]
end
JD --> JobEntity
EXP --> ExpEntity
ExpEntity --> TC
JobEntity --> HF
TC --> HF
HF --> SS
JobEntity --> SS
CI --> SS
SS --> FB
Sources: src-tauri/src/fit.rs#55-63src-tauri/src/fit.rs#335-376src-tauri/src/experience.rs#45-72src-tauri/src/experience.rs#96-127
The hard_filters function checks for "Dealbreakers" and "Cautions" src-tauri/src/fit.rs#265-266
- Dealbreaker: A hard conflict (e.g., location mismatch, comp below floor). If any dealbreaker fires, the final score is collapsed to
0src-tauri/src/fit.rs#8-9 - Caution: A noteworthy mismatch that does not zero out the score but is surfaced to the user.
| Check | Logic | Level |
|---|---|---|
| Arrangement | Fired if job.remote is known and conflicts with target.work_arrangements. |
Dealbreaker |
| Location | Fired if the job is not remote and the metro does not match targeted metros. | Dealbreaker |
| Compensation | Fired if job.comp_high is known and is less than target.comp_floor. |
Dealbreaker |
| Visa | Fired if job.visa_sponsorship is explicitly false but user requires it. |
Dealbreaker |
Sources: src-tauri/src/fit.rs#21-32src-tauri/src/fit.rs#265-333
The engine calculates five sub-scores (0–100), which are then weighted by the user's FitWeightssrc-tauri/src/fit.rs#356-361
| Sub-score | Function | Logic Summary |
|---|---|---|
| Seniority | seniority_fit |
Two-track model (IC/Mgmt) + YOE reducer. |
| Skills | skills_fit |
Coverage of required (80%) and preferred (20%) skills. |
| Comp | comp_fit |
Proximity of job high-end to user target vs. floor. |
| Arrangement | arrangement_fit |
Exact match (100) vs. known mismatch (15) vs. neutral (50). |
| Domain | domain_fit |
Matching job domains against user's targeted domains. |
Sources: src-tauri/src/fit.rs#55-60src-tauri/src/fit.rs#94-158src-tauri/src/fit.rs#165-184src-tauri/src/fit.rs#191-200
The seniority_fit calculation is the most complex component of the engine, accounting for both title-based rank and chronological experience src-tauri/src/fit.rs#94-99
Seniority is split into Track::Ic and Track::Managementsrc-tauri/src/fit.rs#67-70 The level_track helper maps strings to these tracks and a numerical rank src-tauri/src/fit.rs#74-86
Seniority Rank Distance Penalties:
- Exact Match: 100
- Distance 1: 60 (e.g., Senior vs. Mid)
- Distance 2: 30
- Distance 3+: 10
- Cross-track mismatch: 10 (e.g., IC applying for Dept Head)
Sources: src-tauri/src/fit.rs#11-12src-tauri/src/fit.rs#132-137
The YOE reducer scales the base seniority score down if the candidate's total months of experience is less than the job's minimum requirement src-tauri/src/fit.rs#91-93
Formula:reduced_score = base_score * min(candidate_months / (job_yoe_min * 12), 1.0)
The candidate_months is calculated in experience.rs by computing the "envelope" (earliest start to latest end) of all experience entries src-tauri/src/experience.rs#96-127
The result of a scoring run is encapsulated in FitBreakdown, which is serialized and stored in the Job's Markdown frontmatter src-tauri/src/fit.rs#55-63
pub struct FitBreakdown {
pub seniority: i64,
pub skills: i64,
pub comp: i64,
pub arrangement: i64,
pub domain: i64,
pub flags: Vec<Flag>,
pub score: i64, // Combined weighted score (0 if dealbreaker)
}
On the frontend, the fit_score integer is mapped to a FitBand for UI display (e.g., "Strong", "Good", "Mismatch") src/lib/fit.ts#7-14
Fit Scoring Logic Mapping
flowchart LR
UI["UI Components"]
subgraph subGraph1 ["Svelte Frontend (src/lib/)"]
FitBand["fitBand() (fit.ts)"]
SortRoles["sortRoles() (rolesView.ts)"]
end
subgraph subGraph0 ["Rust Backend (src-tauri/src/fit.rs)"]
ScoreFit["score_fit()"]
HF["hard_filters()"]
SF["seniority_fit()"]
SK["skills_fit()"]
end
ScoreFit --> FitBand
FitBand --> UI
ScoreFit --> SortRoles
SortRoles --> UI
Sources: src-tauri/src/fit.rs#335-376src/lib/fit.ts#7-14src/lib/rolesView.ts#11-30
The final score is a weighted average based on FitWeights. If weights are not provided, the engine defaults to equal distribution (20% per dimension) src-tauri/src/fit.rs#356-361
Sources: src-tauri/src/fit.rs#340-348