Score hybrid/onsite jobs low when job search profile is remote-only
- Require workArrangementMatch in LLM schema; show badge in Fit Assessment - Prompt: remote-only vs hybrid/N days in office is a hard cap (~35 score) - Deterministic cap from JD keywords + isRemote/workFromHomeType when only Remote is selected - hasNonEmptyProfile includes preferredWorkArrangement and preferredLocations so prefs reach the scorer Made-with: Cursor
This commit is contained in:
parent
14a6da4bdf
commit
54adc66e73
@ -54,6 +54,33 @@ function RoleMatchBadge({ score }: { score: number }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WorkArrangementBadge({ score }: { score: number }) {
|
||||||
|
const color =
|
||||||
|
score >= 70
|
||||||
|
? "text-emerald-500 bg-emerald-500/10 border-emerald-500/20"
|
||||||
|
: score >= 40
|
||||||
|
? "text-amber-500 bg-amber-500/10 border-amber-500/20"
|
||||||
|
: "text-red-500 bg-red-500/10 border-red-500/20";
|
||||||
|
|
||||||
|
const label =
|
||||||
|
score >= 70
|
||||||
|
? "Strong remote / location fit"
|
||||||
|
: score >= 40
|
||||||
|
? "Partial location fit"
|
||||||
|
: "Weak location fit";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium",
|
||||||
|
color,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label} ({score}%)
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const FitAssessment: React.FC<FitAssessmentProps> = ({
|
export const FitAssessment: React.FC<FitAssessmentProps> = ({
|
||||||
job,
|
job,
|
||||||
className,
|
className,
|
||||||
@ -73,6 +100,9 @@ export const FitAssessment: React.FC<FitAssessmentProps> = ({
|
|||||||
<Sparkles className="h-3 w-3" />
|
<Sparkles className="h-3 w-3" />
|
||||||
Fit Assessment
|
Fit Assessment
|
||||||
{analysis && <RoleMatchBadge score={analysis.roleTypeMatch} />}
|
{analysis && <RoleMatchBadge score={analysis.roleTypeMatch} />}
|
||||||
|
{analysis && typeof analysis.workArrangementMatch === "number" && (
|
||||||
|
<WorkArrangementBadge score={analysis.workArrangementMatch} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{job.suitabilityReason && (
|
{job.suitabilityReason && (
|
||||||
<p className="text-xs text-foreground/90 leading-relaxed font-medium">
|
<p className="text-xs text-foreground/90 leading-relaxed font-medium">
|
||||||
|
|||||||
@ -40,6 +40,11 @@ const SCORING_SCHEMA: JsonSchemaDefinition = {
|
|||||||
description:
|
description:
|
||||||
"How well the job role type matches what the candidate wants (0-100)",
|
"How well the job role type matches what the candidate wants (0-100)",
|
||||||
},
|
},
|
||||||
|
workArrangementMatch: {
|
||||||
|
type: "integer",
|
||||||
|
description:
|
||||||
|
"How well the job's work location arrangement matches the candidate's preferences: remote vs hybrid vs on-site (0-100). 100 = perfect match (e.g. fully remote job when candidate wants remote only).",
|
||||||
|
},
|
||||||
strengths: {
|
strengths: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: { type: "string" },
|
items: { type: "string" },
|
||||||
@ -69,6 +74,7 @@ const SCORING_SCHEMA: JsonSchemaDefinition = {
|
|||||||
"score",
|
"score",
|
||||||
"reason",
|
"reason",
|
||||||
"roleTypeMatch",
|
"roleTypeMatch",
|
||||||
|
"workArrangementMatch",
|
||||||
"strengths",
|
"strengths",
|
||||||
"gaps",
|
"gaps",
|
||||||
"suggestions",
|
"suggestions",
|
||||||
@ -82,6 +88,7 @@ interface ScoringLlmResponse {
|
|||||||
score: number;
|
score: number;
|
||||||
reason: string;
|
reason: string;
|
||||||
roleTypeMatch?: number;
|
roleTypeMatch?: number;
|
||||||
|
workArrangementMatch?: number;
|
||||||
strengths?: string[];
|
strengths?: string[];
|
||||||
gaps?: string[];
|
gaps?: string[];
|
||||||
suggestions?: string[];
|
suggestions?: string[];
|
||||||
@ -137,6 +144,10 @@ function extractAnalysis(data: ScoringLlmResponse): SuitabilityAnalysis | null {
|
|||||||
typeof data.roleTypeMatch === "number"
|
typeof data.roleTypeMatch === "number"
|
||||||
? Math.min(100, Math.max(0, Math.round(data.roleTypeMatch)))
|
? Math.min(100, Math.max(0, Math.round(data.roleTypeMatch)))
|
||||||
: 50,
|
: 50,
|
||||||
|
workArrangementMatch:
|
||||||
|
typeof data.workArrangementMatch === "number"
|
||||||
|
? Math.min(100, Math.max(0, Math.round(data.workArrangementMatch)))
|
||||||
|
: undefined,
|
||||||
strengths: Array.isArray(data.strengths) ? data.strengths : [],
|
strengths: Array.isArray(data.strengths) ? data.strengths : [],
|
||||||
gaps: Array.isArray(data.gaps) ? data.gaps : [],
|
gaps: Array.isArray(data.gaps) ? data.gaps : [],
|
||||||
suggestions: Array.isArray(data.suggestions) ? data.suggestions : [],
|
suggestions: Array.isArray(data.suggestions) ? data.suggestions : [],
|
||||||
@ -215,6 +226,16 @@ export async function scoreJobSuitability(
|
|||||||
clampedScore = roleTypeMatchCap.score;
|
clampedScore = roleTypeMatchCap.score;
|
||||||
clampedReason = roleTypeMatchCap.reason;
|
clampedReason = roleTypeMatchCap.reason;
|
||||||
|
|
||||||
|
const remoteCap = applyRemoteOfficeMismatchCap(
|
||||||
|
job,
|
||||||
|
clampedScore,
|
||||||
|
clampedReason,
|
||||||
|
data,
|
||||||
|
hasProfile ? jobSearchProfile : null,
|
||||||
|
);
|
||||||
|
clampedScore = remoteCap.score;
|
||||||
|
clampedReason = remoteCap.reason;
|
||||||
|
|
||||||
const penaltyResult = applySalaryPenalty(job, clampedScore, clampedReason, {
|
const penaltyResult = applySalaryPenalty(job, clampedScore, clampedReason, {
|
||||||
penalizeMissingSalary: settings.penalizeMissingSalary.value,
|
penalizeMissingSalary: settings.penalizeMissingSalary.value,
|
||||||
missingSalaryPenalty: settings.missingSalaryPenalty.value,
|
missingSalaryPenalty: settings.missingSalaryPenalty.value,
|
||||||
@ -268,11 +289,127 @@ function applyRoleTypeMatchCap(
|
|||||||
return { score: cappedScore, reason: `${reason} ${capNote}` };
|
return { score: cappedScore, reason: `${reason} ${capNote}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REMOTE_ONLY_OFFICE_MISMATCH_CEILING = 35;
|
||||||
|
const WORK_ARRANGEMENT_MATCH_CAP_THRESHOLD = 40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Candidate selected "Remote" in job search profile but not "Hybrid" or "On-site".
|
||||||
|
*/
|
||||||
|
function candidateWantsRemoteOnly(p: JobSearchProfile): boolean {
|
||||||
|
const wa = p.preferredWorkArrangement.map((s) => s.trim().toLowerCase());
|
||||||
|
if (!wa.includes("remote")) return false;
|
||||||
|
if (wa.includes("hybrid") || wa.includes("onsite")) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job text / metadata suggests hybrid or mandatory office presence (not remote-only).
|
||||||
|
*/
|
||||||
|
function jobSignalsHybridOrOnsite(job: Job): boolean {
|
||||||
|
const blob = [
|
||||||
|
job.title,
|
||||||
|
job.jobDescription ?? "",
|
||||||
|
job.location ?? "",
|
||||||
|
job.workFromHomeType ?? "",
|
||||||
|
job.jobType ?? "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n")
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
const strongRemoteOnly =
|
||||||
|
/\b100%\s*remote\b|\bfully\s+remote\b|\bremote[\s-]only\b|\bcompletely\s+remote\b|\bwork\s+from\s+anywhere\b|\banywhere\s+in\s+the\s+(us|usa|uk|world)\b/.test(
|
||||||
|
blob,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hybridOrOffice =
|
||||||
|
/\bhybrid\b/.test(blob) ||
|
||||||
|
/\bremote[\s-]?hybrid\b/.test(blob) ||
|
||||||
|
/\bhybrid[\s-]?remote\b/.test(blob) ||
|
||||||
|
/\b\d[\d]?\s+days?\s+(a|per)\s+week\b.*\b(in[\s-]?office|on[\s-]?site|onsite|at\s+the\s+office)\b/.test(
|
||||||
|
blob,
|
||||||
|
) ||
|
||||||
|
/\b(in[\s-]?office|on[\s-]?site|onsite|at\s+the\s+office)\b.*\b\d[\d]?\s+days?\b/.test(
|
||||||
|
blob,
|
||||||
|
) ||
|
||||||
|
/\b(one|two|three|four|five|six|seven|eight|nine|ten)\s+days?\b.*\b(in[\s-]?office|on[\s-]?site|onsite)\b/.test(
|
||||||
|
blob,
|
||||||
|
) ||
|
||||||
|
/\b(in[\s-]?office|on[\s-]?site|onsite)\b.*\b(one|two|three|four|five|six|seven|eight|nine|ten)\s+days?\b/.test(
|
||||||
|
blob,
|
||||||
|
) ||
|
||||||
|
/\boffice[\s-]based\b/.test(blob) ||
|
||||||
|
/\bon[\s-]?site\s+(role|position|required|mandatory)\b/.test(blob) ||
|
||||||
|
/\b(required|must)\b.*\b(in[\s-]?office|on[\s-]?site|onsite|in[\s-]?person)\b/.test(
|
||||||
|
blob,
|
||||||
|
);
|
||||||
|
|
||||||
|
const wfh = (job.workFromHomeType ?? "").toLowerCase();
|
||||||
|
if (wfh.includes("hybrid")) return true;
|
||||||
|
|
||||||
|
if (job.isRemote === false) {
|
||||||
|
if (strongRemoteOnly && !hybridOrOffice) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hybridOrOffice) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cap score when candidate wants remote-only but the job is hybrid / on-site, or
|
||||||
|
* when the model admits a poor work-arrangement fit but still scores high.
|
||||||
|
*/
|
||||||
|
function applyRemoteOfficeMismatchCap(
|
||||||
|
job: Job,
|
||||||
|
score: number,
|
||||||
|
reason: string,
|
||||||
|
data: ScoringLlmResponse,
|
||||||
|
jobSearchProfile: JobSearchProfile | null,
|
||||||
|
): { score: number; reason: string } {
|
||||||
|
if (!jobSearchProfile || !candidateWantsRemoteOnly(jobSearchProfile)) {
|
||||||
|
return { score, reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
const officeLikely = jobSignalsHybridOrOnsite(job);
|
||||||
|
const wam =
|
||||||
|
typeof data.workArrangementMatch === "number"
|
||||||
|
? data.workArrangementMatch
|
||||||
|
: null;
|
||||||
|
const llmSaysPoorArrangement =
|
||||||
|
wam !== null && wam < WORK_ARRANGEMENT_MATCH_CAP_THRESHOLD;
|
||||||
|
|
||||||
|
if (!officeLikely && !llmSaysPoorArrangement) {
|
||||||
|
return { score, reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score <= REMOTE_ONLY_OFFICE_MISMATCH_CEILING) {
|
||||||
|
return { score, reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
const cappedScore = Math.min(score, REMOTE_ONLY_OFFICE_MISMATCH_CEILING);
|
||||||
|
const why = officeLikely
|
||||||
|
? "job appears hybrid or on-site"
|
||||||
|
: `work arrangement match ${wam}% < ${WORK_ARRANGEMENT_MATCH_CAP_THRESHOLD}%`;
|
||||||
|
const capNote = `Capped from ${score} to ${cappedScore} (remote-only preference vs ${why}).`;
|
||||||
|
logger.info("Applied remote-only / office mismatch cap", {
|
||||||
|
jobId: job.id,
|
||||||
|
originalScore: score,
|
||||||
|
cappedScore,
|
||||||
|
officeLikely,
|
||||||
|
workArrangementMatch: wam,
|
||||||
|
});
|
||||||
|
return { score: cappedScore, reason: `${reason} ${capNote}` };
|
||||||
|
}
|
||||||
|
|
||||||
function hasNonEmptyProfile(p: JobSearchProfile): boolean {
|
function hasNonEmptyProfile(p: JobSearchProfile): boolean {
|
||||||
return (
|
return (
|
||||||
p.targetRoles.length > 0 ||
|
p.targetRoles.length > 0 ||
|
||||||
p.mustHaveSkills.length > 0 ||
|
p.mustHaveSkills.length > 0 ||
|
||||||
p.dealBreakers.length > 0 ||
|
p.dealBreakers.length > 0 ||
|
||||||
|
p.preferredWorkArrangement.length > 0 ||
|
||||||
|
p.preferredLocations.length > 0 ||
|
||||||
p.aboutMe.trim().length > 0 ||
|
p.aboutMe.trim().length > 0 ||
|
||||||
p.experienceLevel.trim().length > 0
|
p.experienceLevel.trim().length > 0
|
||||||
);
|
);
|
||||||
@ -409,7 +546,14 @@ DEAL-BREAKER RULES (STRICTLY ENFORCE — these override all other criteria):
|
|||||||
- A job mentioning a candidate's skill as a minor "nice-to-have" does NOT make it a good match
|
- A job mentioning a candidate's skill as a minor "nice-to-have" does NOT make it a good match
|
||||||
if the core role is completely different from what the candidate wants.
|
if the core role is completely different from what the candidate wants.
|
||||||
- When in doubt about role type, err on the side of a LOWER score. The candidate would rather miss
|
- When in doubt about role type, err on the side of a LOWER score. The candidate would rather miss
|
||||||
a borderline match than waste time on roles that don't align with their career focus.`
|
a borderline match than waste time on roles that don't align with their career focus.
|
||||||
|
- WORK ARRANGEMENT (read the full job description — do not trust a vague "Remote" tag alone):
|
||||||
|
* If Preferred Work Arrangement is ONLY "Remote" (no Hybrid / On-site selected): any hybrid role,
|
||||||
|
fixed days in office, or mandatory on-site requirement MUST score 0-35 overall and workArrangementMatch 0-25.
|
||||||
|
Examples: "3 days a week in office", "hybrid", "on-site 2 days", "in-person collaboration required".
|
||||||
|
* If the candidate also selected Hybrid or On-site, do not treat hybrid as a hard mismatch.
|
||||||
|
* Fully remote / work-from-anywhere jobs when candidate wants remote: workArrangementMatch 90-100.
|
||||||
|
* Mention hybrid/on-site mismatch in dealBreakerHits when the candidate is remote-only.`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const scoringCriteria = hasProfilePrefs
|
const scoringCriteria = hasProfilePrefs
|
||||||
@ -417,11 +561,12 @@ DEAL-BREAKER RULES (STRICTLY ENFORCE — these override all other criteria):
|
|||||||
- Role type alignment with target roles: 0-40 points (GATING FACTOR — if this is below 15, the total score MUST be below 25 regardless of other criteria)
|
- Role type alignment with target roles: 0-40 points (GATING FACTOR — if this is below 15, the total score MUST be below 25 regardless of other criteria)
|
||||||
- Skills match with role-relevant skills (must-haves weighted 3x, nice-to-haves 1x): 0-25 points
|
- Skills match with role-relevant skills (must-haves weighted 3x, nice-to-haves 1x): 0-25 points
|
||||||
- Experience level match: 0-15 points
|
- Experience level match: 0-15 points
|
||||||
- Location/remote work alignment with preferences: 0-10 points
|
- Location / remote vs hybrid / on-site alignment with preferences: 0-15 points (GATING when candidate is remote-only and job requires office days)
|
||||||
- Industry/domain fit: 0-5 points
|
- Industry/domain fit: 0-5 points
|
||||||
- Career growth and salary alignment: 0-5 points
|
- Career growth and salary alignment: 0-5 points
|
||||||
|
|
||||||
CRITICAL: A "Senior Software Engineer" role and a "QA Automation Engineer" role are FUNDAMENTALLY DIFFERENT job types even if they share programming languages. Evaluate the PRIMARY function of the role, not just keyword overlap.`
|
CRITICAL: A "Senior Software Engineer" role and a "QA Automation Engineer" role are FUNDAMENTALLY DIFFERENT job types even if they share programming languages. Evaluate the PRIMARY function of the role, not just keyword overlap.
|
||||||
|
CRITICAL: If the candidate wants REMOTE ONLY and the posting requires any regular office presence, the TOTAL score cannot exceed ~35 regardless of skills match.`
|
||||||
: `SCORING CRITERIA:
|
: `SCORING CRITERIA:
|
||||||
- Skills match (technologies, frameworks, languages): 0-30 points
|
- Skills match (technologies, frameworks, languages): 0-30 points
|
||||||
- Experience level match: 0-25 points
|
- Experience level match: 0-25 points
|
||||||
@ -459,10 +604,11 @@ ${
|
|||||||
IMPORTANT: Respond with ONLY a valid JSON object. No markdown, no code fences, no explanation outside the JSON.
|
IMPORTANT: Respond with ONLY a valid JSON object. No markdown, no code fences, no explanation outside the JSON.
|
||||||
|
|
||||||
REQUIRED FORMAT (exactly this structure):
|
REQUIRED FORMAT (exactly this structure):
|
||||||
{"score": <integer 0-100>, "reason": "<1-2 sentence explanation>", "roleTypeMatch": <integer 0-100>, "strengths": ["<strength 1>", "<strength 2>"], "gaps": ["<gap 1>"], "suggestions": ["<suggestion 1>"], "dealBreakerHits": []}
|
{"score": <integer 0-100>, "reason": "<1-2 sentence explanation>", "roleTypeMatch": <integer 0-100>, "workArrangementMatch": <integer 0-100>, "strengths": ["<strength 1>", "<strength 2>"], "gaps": ["<gap 1>"], "suggestions": ["<suggestion 1>"], "dealBreakerHits": []}
|
||||||
|
|
||||||
RULES FOR ANALYSIS FIELDS:
|
RULES FOR ANALYSIS FIELDS:
|
||||||
- "roleTypeMatch": How well does this job's role TYPE match what the candidate wants? 100 = perfect role type, 0 = completely wrong type of work.
|
- "roleTypeMatch": How well does this job's role TYPE match what the candidate wants? 100 = perfect role type, 0 = completely wrong type of work.
|
||||||
|
- "workArrangementMatch": How well do location and work arrangement match? E.g. remote-only candidate + fully remote job = 95-100; remote-only + hybrid or N days in office = 0-25.
|
||||||
- "strengths": 2-4 specific things where the candidate is a strong match. Be concrete (e.g. "Has 2 years React experience matching the 1+ year requirement").
|
- "strengths": 2-4 specific things where the candidate is a strong match. Be concrete (e.g. "Has 2 years React experience matching the 1+ year requirement").
|
||||||
- "gaps": 1-3 specific skills/requirements the candidate lacks. Be honest and specific.
|
- "gaps": 1-3 specific skills/requirements the candidate lacks. Be honest and specific.
|
||||||
- "suggestions": 1-3 actionable things the candidate could do to be stronger for this type of role.
|
- "suggestions": 1-3 actionable things the candidate could do to be stronger for this type of role.
|
||||||
@ -471,10 +617,13 @@ RULES FOR ANALYSIS FIELDS:
|
|||||||
EXAMPLE RESPONSES:
|
EXAMPLE RESPONSES:
|
||||||
|
|
||||||
Role mismatch (candidate wants QA/SDET, job is core software engineering):
|
Role mismatch (candidate wants QA/SDET, job is core software engineering):
|
||||||
{"score": 15, "reason": "This is a core software engineering role focused on GPU infrastructure and platform development. Despite shared languages (C#, Java, Python), the primary responsibilities are software development, not testing or quality assurance.", "roleTypeMatch": 10, "strengths": ["Experience with C# and Java", "Familiar with Azure cloud"], "gaps": ["Role is software development, not QA/SDET", "No GPU/HPC infrastructure experience", "No hardware/software interaction experience"], "suggestions": ["Focus on SDET or QA Automation roles at Microsoft instead", "Look for test infrastructure roles in cloud platform teams"], "dealBreakerHits": ["Role type mismatch: Senior Software Engineer (development) vs target of QA Automation/SDET"]}
|
{"score": 15, "reason": "This is a core software engineering role focused on GPU infrastructure and platform development. Despite shared languages (C#, Java, Python), the primary responsibilities are software development, not testing or quality assurance.", "roleTypeMatch": 10, "workArrangementMatch": 70, "strengths": ["Experience with C# and Java", "Familiar with Azure cloud"], "gaps": ["Role is software development, not QA/SDET", "No GPU/HPC infrastructure experience", "No hardware/software interaction experience"], "suggestions": ["Focus on SDET or QA Automation roles at Microsoft instead", "Look for test infrastructure roles in cloud platform teams"], "dealBreakerHits": ["Role type mismatch: Senior Software Engineer (development) vs target of QA Automation/SDET"]}
|
||||||
|
|
||||||
|
Remote-only candidate, hybrid job (skills match but office days required):
|
||||||
|
{"score": 28, "reason": "Strong technical fit but the role is hybrid with multiple days per week in the office; candidate preferences indicate remote-only.", "roleTypeMatch": 85, "workArrangementMatch": 15, "strengths": ["Stack aligns with automation and CI experience"], "gaps": ["Hybrid / in-office requirement conflicts with remote-only preference"], "suggestions": ["Filter for fully remote listings or select Hybrid in job search preferences if open to it"], "dealBreakerHits": ["Work arrangement: hybrid / in-office vs remote-only preference"]}
|
||||||
|
|
||||||
Good match (role aligns with candidate's target):
|
Good match (role aligns with candidate's target):
|
||||||
{"score": 78, "reason": "Strong QA automation role with Playwright requirement matching the candidate's core expertise. CI/CD pipeline ownership aligns well with their DevOps experience.", "roleTypeMatch": 90, "strengths": ["5+ years Playwright experience exceeds the 2-year requirement", "Strong CI/CD pipeline experience with GitHub Actions"], "gaps": ["No experience with the company's specific domain"], "suggestions": ["Highlight regulated-industry QA experience from iGaming role"], "dealBreakerHits": []}`;
|
{"score": 78, "reason": "Strong QA automation role with Playwright requirement matching the candidate's core expertise. CI/CD pipeline ownership aligns well with their DevOps experience.", "roleTypeMatch": 90, "workArrangementMatch": 95, "strengths": ["5+ years Playwright experience exceeds the 2-year requirement", "Strong CI/CD pipeline experience with GitHub Actions"], "gaps": ["No experience with the company's specific domain"], "suggestions": ["Highlight regulated-industry QA experience from iGaming role"], "dealBreakerHits": []}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeProfileForPrompt(
|
export function sanitizeProfileForPrompt(
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export interface UpdateSearchProfileInput {
|
|||||||
|
|
||||||
export interface SuitabilityAnalysis {
|
export interface SuitabilityAnalysis {
|
||||||
roleTypeMatch: number;
|
roleTypeMatch: number;
|
||||||
|
/** How well location / remote vs hybrid / on-site matches preferences (0–100). */
|
||||||
|
workArrangementMatch?: number;
|
||||||
strengths: string[];
|
strengths: string[];
|
||||||
gaps: string[];
|
gaps: string[];
|
||||||
suggestions: string[];
|
suggestions: string[];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user