Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Callable, Dict, Any, List, Tuple | |
| import re | |
| class Guideline: | |
| id: str | |
| description: str | |
| condition: Callable[[Dict[str, Any]], bool] | |
| validate: Callable[[str, Dict[str, Any]], Tuple[bool, str]] | |
| enforce: Callable[[str, Dict[str, Any]], str] | |
| class GuidelineEngine: | |
| def __init__(self, rules: List[Guideline]) -> None: | |
| self.rules = rules | |
| def check_and_enforce(self, text: str, ctx: Dict[str, Any]) -> Tuple[str, List[str], List[str]]: | |
| matched: List[str] = [] | |
| fixed: List[str] = [] | |
| out = text or "" | |
| for g in self.rules: | |
| try: | |
| if not g.condition(ctx): | |
| continue | |
| matched.append(g.id) | |
| ok, _ = g.validate(out, ctx) | |
| if not ok: | |
| out = g.enforce(out, ctx) | |
| fixed.append(g.id) | |
| except Exception: | |
| # fail-safe, do not block | |
| continue | |
| return out, matched, fixed | |
| # ---------- Helpers ---------- | |
| _BUZZWORDS = [ | |
| "results-driven", "team player", "people person", "perfectionist", | |
| "multi-tasker", "multi tasker", "dynamic go-getter", "rockstar", | |
| "guru", "ninja" | |
| ] | |
| _WEAK_OPENERS = [ | |
| (re.compile(r"^\s*[-β’]\s*responsible for\s+", re.I), "- Led "), | |
| (re.compile(r"^\s*[-β’]\s*tasked with\s+", re.I), "- Executed "), | |
| (re.compile(r"^\s*[-β’]\s*worked on\s+", re.I), "- Delivered "), | |
| (re.compile(r"^\s*[-β’]\s*helped\s+", re.I), "- Supported "), | |
| (re.compile(r"^\s*[-β’]\s*assisted with\s+", re.I), "- Supported "), | |
| (re.compile(r"^\s*[-β’]\s*handled\s+", re.I), "- Managed "), | |
| ] | |
| def _enforce_exact_length(text: str, target_len: int) -> str: | |
| if target_len <= 0: | |
| return text or "" | |
| txt = (text or "") | |
| if len(txt) == target_len: | |
| return txt | |
| if len(txt) > target_len: | |
| return txt[:target_len] | |
| return txt + (" " * (target_len - len(txt))) | |
| def _ensure_headings(text: str) -> str: | |
| """Ensure key headings exist: SUMMARY, SKILLS, EXPERIENCE, EDUCATION.""" | |
| t = text or "" | |
| low = t.lower() | |
| out = t | |
| def add_heading(h: str) -> None: | |
| nonlocal out | |
| if h.lower() not in low: | |
| out = (out + f"\n\n{h}\n").strip() | |
| for h in ["SUMMARY", "SKILLS", "EXPERIENCE", "EDUCATION"]: | |
| if h.lower() not in low: | |
| add_heading(h) | |
| return out | |
| def _strip_tabs(text: str) -> str: | |
| return (text or "").replace("\t", " ") | |
| def _scrub_buzzwords(text: str) -> str: | |
| out = text or "" | |
| low = out.lower() | |
| for bw in _BUZZWORDS: | |
| if bw in low: | |
| out = re.sub(re.escape(bw), "", out, flags=re.I) | |
| return out | |
| def _strengthen_action_verbs(text: str) -> str: | |
| lines = (text or "").splitlines() | |
| fixed: List[str] = [] | |
| for ln in lines: | |
| new_ln = ln | |
| for pat, repl in _WEAK_OPENERS: | |
| if pat.search(new_ln): | |
| new_ln = pat.sub(repl, new_ln) | |
| break | |
| fixed.append(new_ln) | |
| return "\n".join(fixed) | |
| def _remove_first_person(text: str) -> str: | |
| # Remove leading "I " / "My " in bullets only | |
| lines = (text or "").splitlines() | |
| out: List[str] = [] | |
| for ln in lines: | |
| m = re.match(r"^\s*[-β’]\s*(i|my|we)\b", ln, flags=re.I) | |
| if m: | |
| ln = re.sub(r"^\s*([-β’]\s*)(i|my|we)\b\s*", r"\1", ln, flags=re.I) | |
| out.append(ln) | |
| return "\n".join(out) | |
| def _ats_plain_text(text: str) -> str: | |
| # normalize bullets and strip odd symbols | |
| out = _strip_tabs(text) | |
| out = out.replace("β’\t", "- ").replace("β’ ", "- ") | |
| out = re.sub(r"[β βͺβ¦ββββ¦β¦]", "-", out) | |
| return out | |
| def _enforce_uk_habits(text: str) -> str: | |
| # normalize currency symbol spacing and percentages | |
| out = re.sub(r"\s*Β£\s*", " Β£", text or "") | |
| out = re.sub(r"\s*%\s*", "%", out) | |
| return out | |
| def _allowed_skills_from_profile(ctx: Dict[str, Any]) -> List[str]: | |
| p = (ctx.get("profile_text") or "").lower() | |
| # naive split of alphanum skill-like tokens | |
| cands = re.findall(r"[a-zA-Z][a-zA-Z0-9+_.#-]{2,}", p) | |
| seen: Dict[str, int] = {} | |
| for c in cands: | |
| seen[c.lower()] = 1 | |
| return list(seen.keys()) | |
| def _no_invented_skills(text: str, ctx: Dict[str, Any]) -> Tuple[bool, str]: | |
| allowed = set(_allowed_skills_from_profile(ctx)) | |
| if not allowed: | |
| return True, "no baseline" | |
| skills_block = re.search(r"(?is)\n\s*(skills|core skills)[\s:]*\n(.+?)(\n\n|$)", text or "") | |
| if not skills_block: | |
| return True, "no skills block" | |
| block = skills_block.group(0) | |
| found = re.findall(r"[A-Za-z][A-Za-z0-9+_.#-]{2,}", block) | |
| for f in found: | |
| if f.lower() not in allowed: | |
| return False, f | |
| return True, "ok" | |
| # ---------- Rule sets ---------- | |
| def build_resume_rules() -> List[Guideline]: | |
| return [ | |
| Guideline( | |
| id="exact_length", | |
| description="Enforce exact target length when provided", | |
| condition=lambda ctx: bool(ctx.get("target_len")), | |
| validate=lambda txt, ctx: (len(txt or "") == int(ctx.get("target_len", 0)), "len"), | |
| enforce=lambda txt, ctx: _enforce_exact_length(txt, int(ctx.get("target_len", 0))), | |
| ), | |
| Guideline( | |
| id="headings_present", | |
| description="Ensure key headings exist", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (all(h.lower() in (txt or "").lower() for h in ["summary", "experience", "education", "skills"]), "headings"), | |
| enforce=lambda txt, ctx: _ensure_headings(txt), | |
| ), | |
| Guideline( | |
| id="ats_plain_text", | |
| description="Normalize bullets/tabs for ATS", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: ("\t" not in (txt or ""), "tabs"), | |
| enforce=lambda txt, ctx: _ats_plain_text(txt), | |
| ), | |
| Guideline( | |
| id="buzzword_scrub", | |
| description="Remove common buzzwords", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (not any(bw in (txt or "").lower() for bw in _BUZZWORDS), "buzz"), | |
| enforce=lambda txt, ctx: _scrub_buzzwords(txt), | |
| ), | |
| Guideline( | |
| id="verb_strengthen", | |
| description="Strengthen weak bullet openers", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (True, "noop"), | |
| enforce=lambda txt, ctx: _strengthen_action_verbs(txt), | |
| ), | |
| Guideline( | |
| id="remove_first_person", | |
| description="Remove first-person pronouns on bullets", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (not re.search(r"^\s*[-β’]\s*(i|my|we)\b", txt or "", re.I | re.M), "pronouns"), | |
| enforce=lambda txt, ctx: _remove_first_person(txt), | |
| ), | |
| Guideline( | |
| id="uk_normalization", | |
| description="Normalize UK currency/percent spacing", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (True, "noop"), | |
| enforce=lambda txt, ctx: _enforce_uk_habits(txt), | |
| ), | |
| Guideline( | |
| id="no_invented_skills", | |
| description="Prevent skills not evidenced in profile", | |
| condition=lambda ctx: True, | |
| validate=_no_invented_skills, | |
| enforce=lambda txt, ctx: txt, # log-only to avoid false positives | |
| ), | |
| ] | |
| def build_cover_rules() -> List[Guideline]: | |
| return [ | |
| Guideline( | |
| id="exact_length", | |
| description="Enforce exact target length when provided", | |
| condition=lambda ctx: bool(ctx.get("target_len")), | |
| validate=lambda txt, ctx: (len(txt or "") == int(ctx.get("target_len", 0)), "len"), | |
| enforce=lambda txt, ctx: _enforce_exact_length(txt, int(ctx.get("target_len", 0))), | |
| ), | |
| Guideline( | |
| id="ats_plain_text", | |
| description="Normalize bullets/tabs for ATS", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: ("\t" not in (txt or ""), "tabs"), | |
| enforce=lambda txt, ctx: _ats_plain_text(txt), | |
| ), | |
| Guideline( | |
| id="buzzword_scrub", | |
| description="Remove common buzzwords", | |
| condition=lambda ctx: True, | |
| validate=lambda txt, ctx: (not any(bw in (txt or "").lower() for bw in _BUZZWORDS), "buzz"), | |
| enforce=lambda txt, ctx: _scrub_buzzwords(txt), | |
| ), | |
| ] | |
| def apply_resume_guidelines(text: str, ctx: Dict[str, Any]) -> Tuple[str, List[str], List[str]]: | |
| engine = GuidelineEngine(build_resume_rules()) | |
| return engine.check_and_enforce(text, ctx) | |
| def apply_cover_guidelines(text: str, ctx: Dict[str, Any]) -> Tuple[str, List[str], List[str]]: | |
| engine = GuidelineEngine(build_cover_rules()) | |
| return engine.check_and_enforce(text, ctx) |