Spaces:
Sleeping
Sleeping
| """ | |
| 90% feasibility gate + status system (spec items #2-gate, #3-status). | |
| Before generating, estimate whether the candidate can realistically reach a | |
| 90%+ JD match AFTER aggressive plausible expansion β and decide how to proceed. | |
| Status lifecycle for any generated resume: | |
| READY_90_PLUS β JD match >= 90 and ATS readability >= 90 | |
| READY_90_PLUS_REVIEW_RECOMMENDED β reached 90 but used risky/review terms | |
| NEEDS_REPAIR β below 90, still in the repair loop | |
| NEEDS_USER_INPUT β below 90; needs user-confirmed credentials/terms | |
| NOT_ELIGIBLE_LOW_FIT β JD clearly unrelated to candidate background | |
| PARSE_FAILED β exported file failed parse validation | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import List | |
| from .jd_analyzer import analyze_jd | |
| from .candidate_fit import ( | |
| classify_all_fit, includable, review_terms, ask_user_terms, blocked_terms, | |
| ) | |
| from .ats_scoring_v2 import score_jd_match, score_ats_readability | |
| # Status constants | |
| READY = "READY_90_PLUS" | |
| READY_REVIEW = "READY_90_PLUS_REVIEW_RECOMMENDED" | |
| # External-feedback repair only: internal + independent >= 90 AND the pasted | |
| # external-checker gaps were mostly resolved (no fabrication). | |
| READY_95_EXTERNAL_ALIGNED = "READY_95_EXTERNAL_ALIGNED" | |
| NEEDS_REPAIR = "NEEDS_REPAIR" | |
| NEEDS_USER_INPUT = "NEEDS_USER_INPUT" | |
| LOW_FIT = "NOT_ELIGIBLE_LOW_FIT" | |
| PARSE_FAILED = "PARSE_FAILED" | |
| # ββ Maximum ATS Mode statuses (User-Confirmed Skill Expansion) βββββββββββββββ | |
| # READY_MAX_ATS_95_PLUS β internal + independent + readability pass AND | |
| # external-style coverage >= 95% (or pasted external score >= 95). | |
| READY_MAX_ATS_95_PLUS = "READY_MAX_ATS_95_PLUS" | |
| # READY_90_PLUS_EXTERNAL_ALIGNED β the same gates pass AND coverage >= 90%. | |
| READY_90_PLUS_EXTERNAL_ALIGNED = "READY_90_PLUS_EXTERNAL_ALIGNED" | |
| # NEEDS_USER_CONFIRMATION β remaining gaps are HIGH-risk-but-supportable terms | |
| # the user can confirm with one click (then we regenerate). | |
| NEEDS_USER_CONFIRMATION = "NEEDS_USER_CONFIRMATION" | |
| # BELOW_TARGET_REPAIRABLE β below target but remaining gaps are LOW/MEDIUM, so | |
| # the system should keep repairing rather than stop. | |
| BELOW_TARGET_REPAIRABLE = "BELOW_TARGET_REPAIRABLE" | |
| # Statuses that represent a downloadable, target-meeting result. | |
| MAX_ATS_READY_STATUSES = frozenset({ | |
| READY, READY_REVIEW, READY_95_EXTERNAL_ALIGNED, | |
| READY_MAX_ATS_95_PLUS, READY_90_PLUS_EXTERNAL_ALIGNED, | |
| }) | |
| class FitAssessment: | |
| eligible_for_auto_resume: bool | |
| estimated_max_score: int | |
| estimated_max_with_review: int | |
| explicit_matches: List[str] = field(default_factory=list) | |
| plausible_matches: List[str] = field(default_factory=list) | |
| adjacent_matches: List[str] = field(default_factory=list) | |
| risky_matches: List[str] = field(default_factory=list) | |
| blocked_matches: List[str] = field(default_factory=list) | |
| recommendation: str = "generate" # generate|generate_with_review|ask_user|skip | |
| def to_dict(self): | |
| return self.__dict__ | |
| def _synthetic_text(base_text: str, terms: List[str]) -> str: | |
| """A best-case resume text = base + the terms we'd include, for estimating | |
| the achievable JD-match ceiling.""" | |
| return base_text + "\n" + " . ".join(terms) | |
| def assess_job_fit_for_90(jd_text: str, base_resume_text: str, | |
| llm=None, cfg: dict = None, | |
| mode: str = "aggressive_plausible_match") -> FitAssessment: | |
| req = analyze_jd(jd_text, llm=llm, cfg=cfg) | |
| verdicts = classify_all_fit(req, base_resume_text) | |
| inc = [v.keyword for v in includable(verdicts)] | |
| rev = [v.keyword for v in review_terms(verdicts)] | |
| ask = [v.keyword for v in ask_user_terms(verdicts)] | |
| blk = [v.keyword for v in blocked_terms(verdicts)] | |
| by_status = lambda s: [v.keyword for v in verdicts if v.fit_status == s] | |
| # Estimate ceilings: score a synthetic resume containing the include set, | |
| # and another that also adds the risky/review set. | |
| est_inc = score_jd_match(_synthetic_text(base_resume_text, inc), req).score | |
| est_rev = score_jd_match(_synthetic_text(base_resume_text, inc + rev), req).score | |
| # Recommendation | |
| if est_inc >= 90: | |
| rec, eligible = "generate", True | |
| elif est_rev >= 90: | |
| rec, eligible = "generate_with_review", True | |
| elif ask and est_rev >= 80: | |
| rec, eligible = "ask_user", False | |
| elif est_rev >= 70: | |
| # Worth generating, will likely land in NEEDS_REPAIR/REVIEW | |
| rec, eligible = "generate_with_review", True | |
| else: | |
| rec, eligible = "skip", False | |
| return FitAssessment( | |
| eligible_for_auto_resume=eligible, | |
| estimated_max_score=est_inc, | |
| estimated_max_with_review=est_rev, | |
| explicit_matches=by_status("explicit"), | |
| plausible_matches=by_status("plausible"), | |
| adjacent_matches=by_status("adjacent"), | |
| risky_matches=rev, | |
| blocked_matches=blk, | |
| recommendation=rec, | |
| ) | |