fix: remove hardcoded personal values — Phase 1 audit findings

- 3_Resume_Editor.py: replace "Meghan's" in docstring and caption
- user_profile.py: expose mission_preferences and candidate_accessibility_focus
- user.yaml.example: add mission_preferences section + candidate_accessibility_focus flag
- generate_cover_letter.py: build _MISSION_NOTES from user profile instead of
  hardcoded personal passion notes; falls back to generic defaults when not set
- company_research.py: gate "Inclusion & Accessibility" section behind
  candidate_accessibility_focus flag; section count adjusts (7 or 8) accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
pyr0ball 2026-02-24 19:57:03 -08:00
parent 02017ea861
commit 0cdfb41201
5 changed files with 66 additions and 26 deletions

View file

@ -1,6 +1,6 @@
# app/pages/3_Resume_Editor.py
"""
Resume Editor form-based editor for Meghan'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 Meghan'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"

View file

@ -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"

View file

@ -370,6 +370,19 @@ def research_company(job: dict, use_scraper: bool = True, on_stage=None) -> dict
_stage("Generating brief with LLM… (3090 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

View file

@ -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()

View file

@ -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 ──────────────────────────────────────────────────────────