diff --git a/app/pages/3_Resume_Editor.py b/app/pages/3_Resume_Editor.py index 092c2a3..bca0008 100644 --- a/app/pages/3_Resume_Editor.py +++ b/app/pages/3_Resume_Editor.py @@ -1,6 +1,6 @@ # app/pages/3_Resume_Editor.py """ -Resume Editor — form-based editor for Alex's AIHawk profile YAML. +Resume Editor — form-based editor for the user's AIHawk profile YAML. FILL_IN fields highlighted in amber. """ import sys @@ -12,7 +12,7 @@ import yaml st.set_page_config(page_title="Resume Editor", page_icon="📝", layout="wide") st.title("📝 Resume Editor") -st.caption("Edit Alex's application profile used by AIHawk for LinkedIn Easy Apply.") +st.caption("Edit your application profile used by AIHawk for LinkedIn Easy Apply.") RESUME_PATH = Path(__file__).parent.parent.parent / "aihawk" / "data_folder" / "plain_text_resume.yaml" diff --git a/config/user.yaml.example b/config/user.yaml.example index 8b48c17..ef7c90a 100644 --- a/config/user.yaml.example +++ b/config/user.yaml.example @@ -12,6 +12,20 @@ career_summary: > nda_companies: [] # e.g. ["FormerEmployer"] — masked in research briefs +# Optional: industries you genuinely care about. +# When a company/JD matches an industry, the cover letter prompt injects +# your personal note so Para 3 can reflect authentic alignment. +# Leave a value empty ("") to use a sensible generic default. +mission_preferences: + music: "" # e.g. "I've played in bands for 15 years and care deeply about how artists get paid" + animal_welfare: "" # e.g. "I volunteer at my local shelter every weekend" + education: "" # e.g. "I tutored underserved kids for 3 years and care deeply about literacy" + +# Set to true to include an Inclusion & Accessibility section in research briefs. +# When true, each company brief will assess disability/ADA accommodation signals, +# ERGs, and accessibility culture. Useful if this is a personal factor in your decisions. +candidate_accessibility_focus: false + docs_dir: "~/Documents/JobSearch" ollama_models_dir: "~/models/ollama" vllm_models_dir: "~/models/vllm" diff --git a/scripts/company_research.py b/scripts/company_research.py index 0b66a54..1fd6a3a 100644 --- a/scripts/company_research.py +++ b/scripts/company_research.py @@ -370,6 +370,19 @@ def research_company(job: dict, use_scraper: bool = True, on_stage=None) -> dict _stage("Generating brief with LLM… (30–90 seconds)") name = _profile.name if _profile else "the candidate" career_summary = _profile.career_summary if _profile else "" + accessibility_focus = _profile.candidate_accessibility_focus if _profile else False + _section_count = 8 if accessibility_focus else 7 + _accessibility_section = """ +## Inclusion & Accessibility +Assess {company}'s commitment to disability inclusion and accessibility. Cover: +- ADA accommodation language in job postings or company policy +- Disability Employee Resource Group (ERG) or affinity group +- Product or service accessibility (WCAG compliance, adaptive features, AT integrations) +- Any public disability/accessibility advocacy, partnerships, or certifications +- Glassdoor or press signals about how employees with disabilities experience the company +If no specific signals are found, say so clearly — absence of public commitment is itself signal. +This section is for the candidate's personal decision-making only and will not appear in any application. +""".format(company=company) if accessibility_focus else "" prompt = f"""You are preparing {name} for a job interview. {f"Candidate background: {career_summary}" if career_summary else ""} @@ -385,8 +398,8 @@ Role: **{title}** at **{company}** --- -Produce a structured research brief using **exactly** these eight markdown section headers -(include all eight even if a section has limited data — say so honestly): +Produce a structured research brief using **exactly** these {_section_count} markdown section headers +(include all {_section_count} even if a section has limited data — say so honestly): ## Company Overview What {company} does, core product/service, business model, size/stage (startup / scale-up / enterprise), market positioning. @@ -408,16 +421,7 @@ Draw on the live snippets above; if none available, note what is publicly known. Culture issues, layoffs, exec departures, financial stress, or Glassdoor concerns worth knowing before the call. If nothing notable, write "No significant red flags identified." -## Inclusion & Accessibility -Assess {company}'s commitment to disability inclusion and accessibility. Cover: -- ADA accommodation language in job postings or company policy -- Disability Employee Resource Group (ERG) or affinity group -- Product or service accessibility (WCAG compliance, adaptive features, AT integrations) -- Any public disability/accessibility advocacy, partnerships, or certifications -- Glassdoor or press signals about how employees with disabilities experience the company -If no specific signals are found, say so clearly — absence of public commitment is itself signal. -This section is for the candidate's personal decision-making only and will not appear in any application. - +{_accessibility_section} ## Talking Points for {name} Five specific talking points for the phone screen. Each must: - Reference a concrete experience from {name}'s matched background by name diff --git a/scripts/generate_cover_letter.py b/scripts/generate_cover_letter.py index ca159c5..01e5520 100644 --- a/scripts/generate_cover_letter.py +++ b/scripts/generate_cover_letter.py @@ -62,27 +62,45 @@ _MISSION_SIGNALS: dict[str, list[str]] = { _candidate = _profile.name if _profile else "the candidate" -_MISSION_NOTES: dict[str, str] = { +_MISSION_DEFAULTS: dict[str, str] = { "music": ( - f"This company is in the music industry, which is one of {_candidate}'s genuinely " - "ideal work environments — they have a real personal passion for the music scene. " - "Para 3 should warmly and specifically reflect this authentic alignment, not as " - "a generic fan statement, but as an honest statement of where they'd love to apply " - "their CS skills." + f"This company is in the music industry — an industry {_candidate} finds genuinely " + "compelling. Para 3 should warmly and specifically reflect this authentic alignment, " + "not as a generic fan statement, but as an honest statement of where they'd love to " + "apply their skills." ), "animal_welfare": ( - f"This organization works in animal welfare/rescue — one of {_candidate}'s dream-job " - "domains and a genuine personal passion. Para 3 should reflect this authentic " - "connection warmly and specifically, tying their CS skills to this mission." + f"This organization works in animal welfare/rescue — a mission {_candidate} finds " + "genuinely meaningful. Para 3 should reflect this authentic connection warmly and " + "specifically, tying their skills to this mission." ), "education": ( - f"This company works in children's education or EdTech — one of {_candidate}'s ideal " - "work domains, reflecting genuine personal values around learning and young people. " - "Para 3 should reflect this authentic connection specifically and warmly." + f"This company works in education or EdTech — a domain that resonates with " + f"{_candidate}'s values. Para 3 should reflect this authentic connection specifically " + "and warmly." ), } +def _build_mission_notes() -> dict[str, str]: + """Merge user's custom mission notes with generic defaults.""" + prefs = _profile.mission_preferences if _profile else {} + notes = {} + for industry, default_note in _MISSION_DEFAULTS.items(): + custom = (prefs.get(industry) or "").strip() + if custom: + notes[industry] = ( + f"Mission alignment — {_candidate} shared: \"{custom}\". " + "Para 3 should warmly and specifically reflect this authentic connection." + ) + else: + notes[industry] = default_note + return notes + + +_MISSION_NOTES = _build_mission_notes() + + def detect_mission_alignment(company: str, description: str) -> str | None: """Return a mission hint string if company/JD matches a preferred industry, else None.""" text = f"{company} {description}".lower() diff --git a/scripts/user_profile.py b/scripts/user_profile.py index de2f45b..72437d4 100644 --- a/scripts/user_profile.py +++ b/scripts/user_profile.py @@ -20,6 +20,8 @@ _DEFAULTS = { "ollama_models_dir": "~/models/ollama", "vllm_models_dir": "~/models/vllm", "inference_profile": "remote", + "mission_preferences": {}, + "candidate_accessibility_focus": False, "services": { "streamlit_port": 8501, "ollama_host": "localhost", @@ -58,6 +60,8 @@ class UserProfile: self.ollama_models_dir: Path = Path(data["ollama_models_dir"]).expanduser().resolve() self.vllm_models_dir: Path = Path(data["vllm_models_dir"]).expanduser().resolve() self.inference_profile: str = data["inference_profile"] + self.mission_preferences: dict[str, str] = data.get("mission_preferences", {}) + self.candidate_accessibility_focus: bool = bool(data.get("candidate_accessibility_focus", False)) self._svc = data["services"] # ── Service URLs ──────────────────────────────────────────────────────────