fix: remove hardcoded personal values — Phase 1 audit findings
- 3_Resume_Editor.py: replace "Alex'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
This commit is contained in:
parent
63fcfeadef
commit
7d6ce555f0
5 changed files with 66 additions and 26 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# app/pages/3_Resume_Editor.py
|
# 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.
|
FILL_IN fields highlighted in amber.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -12,7 +12,7 @@ import yaml
|
||||||
|
|
||||||
st.set_page_config(page_title="Resume Editor", page_icon="📝", layout="wide")
|
st.set_page_config(page_title="Resume Editor", page_icon="📝", layout="wide")
|
||||||
st.title("📝 Resume Editor")
|
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"
|
RESUME_PATH = Path(__file__).parent.parent.parent / "aihawk" / "data_folder" / "plain_text_resume.yaml"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,20 @@ career_summary: >
|
||||||
|
|
||||||
nda_companies: [] # e.g. ["FormerEmployer"] — masked in research briefs
|
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"
|
docs_dir: "~/Documents/JobSearch"
|
||||||
ollama_models_dir: "~/models/ollama"
|
ollama_models_dir: "~/models/ollama"
|
||||||
vllm_models_dir: "~/models/vllm"
|
vllm_models_dir: "~/models/vllm"
|
||||||
|
|
|
||||||
|
|
@ -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)")
|
_stage("Generating brief with LLM… (30–90 seconds)")
|
||||||
name = _profile.name if _profile else "the candidate"
|
name = _profile.name if _profile else "the candidate"
|
||||||
career_summary = _profile.career_summary if _profile else ""
|
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.
|
prompt = f"""You are preparing {name} for a job interview.
|
||||||
{f"Candidate background: {career_summary}" if career_summary else ""}
|
{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
|
Produce a structured research brief using **exactly** these {_section_count} markdown section headers
|
||||||
(include all eight even if a section has limited data — say so honestly):
|
(include all {_section_count} even if a section has limited data — say so honestly):
|
||||||
|
|
||||||
## Company Overview
|
## Company Overview
|
||||||
What {company} does, core product/service, business model, size/stage (startup / scale-up / enterprise), market positioning.
|
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.
|
Culture issues, layoffs, exec departures, financial stress, or Glassdoor concerns worth knowing before the call.
|
||||||
If nothing notable, write "No significant red flags identified."
|
If nothing notable, write "No significant red flags identified."
|
||||||
|
|
||||||
## Inclusion & Accessibility
|
{_accessibility_section}
|
||||||
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.
|
|
||||||
|
|
||||||
## Talking Points for {name}
|
## Talking Points for {name}
|
||||||
Five specific talking points for the phone screen. Each must:
|
Five specific talking points for the phone screen. Each must:
|
||||||
- Reference a concrete experience from {name}'s matched background by name
|
- Reference a concrete experience from {name}'s matched background by name
|
||||||
|
|
|
||||||
|
|
@ -62,27 +62,45 @@ _MISSION_SIGNALS: dict[str, list[str]] = {
|
||||||
|
|
||||||
_candidate = _profile.name if _profile else "the candidate"
|
_candidate = _profile.name if _profile else "the candidate"
|
||||||
|
|
||||||
_MISSION_NOTES: dict[str, str] = {
|
_MISSION_DEFAULTS: dict[str, str] = {
|
||||||
"music": (
|
"music": (
|
||||||
f"This company is in the music industry, which is one of {_candidate}'s genuinely "
|
f"This company is in the music industry — an industry {_candidate} finds genuinely "
|
||||||
"ideal work environments — they have a real personal passion for the music scene. "
|
"compelling. Para 3 should warmly and specifically reflect this authentic alignment, "
|
||||||
"Para 3 should warmly and specifically reflect this authentic alignment, not as "
|
"not as a generic fan statement, but as an honest statement of where they'd love to "
|
||||||
"a generic fan statement, but as an honest statement of where they'd love to apply "
|
"apply their skills."
|
||||||
"their CS skills."
|
|
||||||
),
|
),
|
||||||
"animal_welfare": (
|
"animal_welfare": (
|
||||||
f"This organization works in animal welfare/rescue — one of {_candidate}'s dream-job "
|
f"This organization works in animal welfare/rescue — a mission {_candidate} finds "
|
||||||
"domains and a genuine personal passion. Para 3 should reflect this authentic "
|
"genuinely meaningful. Para 3 should reflect this authentic connection warmly and "
|
||||||
"connection warmly and specifically, tying their CS skills to this mission."
|
"specifically, tying their skills to this mission."
|
||||||
),
|
),
|
||||||
"education": (
|
"education": (
|
||||||
f"This company works in children's education or EdTech — one of {_candidate}'s ideal "
|
f"This company works in education or EdTech — a domain that resonates with "
|
||||||
"work domains, reflecting genuine personal values around learning and young people. "
|
f"{_candidate}'s values. Para 3 should reflect this authentic connection specifically "
|
||||||
"Para 3 should reflect this authentic connection specifically and warmly."
|
"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:
|
def detect_mission_alignment(company: str, description: str) -> str | None:
|
||||||
"""Return a mission hint string if company/JD matches a preferred industry, else None."""
|
"""Return a mission hint string if company/JD matches a preferred industry, else None."""
|
||||||
text = f"{company} {description}".lower()
|
text = f"{company} {description}".lower()
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ _DEFAULTS = {
|
||||||
"ollama_models_dir": "~/models/ollama",
|
"ollama_models_dir": "~/models/ollama",
|
||||||
"vllm_models_dir": "~/models/vllm",
|
"vllm_models_dir": "~/models/vllm",
|
||||||
"inference_profile": "remote",
|
"inference_profile": "remote",
|
||||||
|
"mission_preferences": {},
|
||||||
|
"candidate_accessibility_focus": False,
|
||||||
"services": {
|
"services": {
|
||||||
"streamlit_port": 8501,
|
"streamlit_port": 8501,
|
||||||
"ollama_host": "localhost",
|
"ollama_host": "localhost",
|
||||||
|
|
@ -58,6 +60,8 @@ class UserProfile:
|
||||||
self.ollama_models_dir: Path = Path(data["ollama_models_dir"]).expanduser().resolve()
|
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.vllm_models_dir: Path = Path(data["vllm_models_dir"]).expanduser().resolve()
|
||||||
self.inference_profile: str = data["inference_profile"]
|
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"]
|
self._svc = data["services"]
|
||||||
|
|
||||||
# ── Service URLs ──────────────────────────────────────────────────────────
|
# ── Service URLs ──────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue