feat: add health mission category, trim-to-sign-off, max_tokens cap for cover letters
- _MISSION_SIGNALS: add health category (pharma, clinical, patient care, etc.) listed last so music/animals/education/social_impact take priority - _MISSION_DEFAULTS: health note steers toward people-first framing, not industry enthusiasm — focuses on patients navigating rare/invisible journeys - _trim_to_letter_end(): cuts output at first sign-off + first name to prevent fine-tuned models from looping into repetitive garbage after completing letter - generate(): pass max_tokens=1200 to router (prevents runaway output) - user.yaml.example: add health + social_impact to mission_preferences, add candidate_voice field for per-user voice/personality context
This commit is contained in:
parent
9b24599832
commit
30a2962797
2 changed files with 53 additions and 2 deletions
|
|
@ -20,6 +20,14 @@ mission_preferences:
|
||||||
music: "" # e.g. "I've played in bands for 15 years and care deeply about how artists get paid"
|
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"
|
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"
|
education: "" # e.g. "I tutored underserved kids for 3 years and care deeply about literacy"
|
||||||
|
social_impact: "" # e.g. "I want my work to reach people who need help most"
|
||||||
|
health: "" # e.g. "I care about people navigating rare or poorly-understood health conditions"
|
||||||
|
# Note: if left empty, Para 3 defaults to focusing on the people the company
|
||||||
|
# serves — not the industry. Fill in for a more personal connection.
|
||||||
|
|
||||||
|
# Optional: how you write and communicate. Used to shape cover letter voice.
|
||||||
|
# e.g. "Warm and direct. Cares about people first. Finds rare and complex situations fascinating."
|
||||||
|
candidate_voice: ""
|
||||||
|
|
||||||
# Set to true to include optional identity-related sections in research briefs.
|
# Set to true to include optional identity-related sections in research briefs.
|
||||||
# Both are for your personal decision-making only — never included in applications.
|
# Both are for your personal decision-making only — never included in applications.
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,20 @@ _MISSION_SIGNALS: dict[str, list[str]] = {
|
||||||
"social good", "civic", "public health", "mental health", "food security",
|
"social good", "civic", "public health", "mental health", "food security",
|
||||||
"housing", "homelessness", "poverty", "workforce development",
|
"housing", "homelessness", "poverty", "workforce development",
|
||||||
],
|
],
|
||||||
|
# Health is listed last — it's a genuine but lower-priority connection than
|
||||||
|
# music/animals/education/social_impact. detect_mission_alignment returns on first
|
||||||
|
# match, so dict order = preference order.
|
||||||
|
"health": [
|
||||||
|
"patient", "patients", "healthcare", "health tech", "healthtech",
|
||||||
|
"pharma", "pharmaceutical", "clinical", "medical",
|
||||||
|
"hospital", "clinic", "therapy", "therapist",
|
||||||
|
"rare disease", "life sciences", "life science",
|
||||||
|
"treatment", "prescription", "biotech", "biopharma", "medtech",
|
||||||
|
"behavioral health", "population health",
|
||||||
|
"care management", "care coordination", "oncology", "specialty pharmacy",
|
||||||
|
"provider network", "payer", "health plan", "benefits administration",
|
||||||
|
"ehr", "emr", "fhir", "hipaa",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
_candidate = _profile.name if _profile else "the candidate"
|
_candidate = _profile.name if _profile else "the candidate"
|
||||||
|
|
@ -99,6 +113,15 @@ _MISSION_DEFAULTS: dict[str, str] = {
|
||||||
f"cause {_candidate} cares deeply about. Para 3 should warmly reflect their genuine "
|
f"cause {_candidate} cares deeply about. Para 3 should warmly reflect their genuine "
|
||||||
"desire to apply their skills to work that makes a real difference in people's lives."
|
"desire to apply their skills to work that makes a real difference in people's lives."
|
||||||
),
|
),
|
||||||
|
"health": (
|
||||||
|
f"This company works in healthcare, life sciences, or patient care. "
|
||||||
|
f"Do NOT write about {_candidate}'s passion for pharmaceuticals or healthcare as an "
|
||||||
|
"industry. Instead, Para 3 should reflect genuine care for the PEOPLE these companies "
|
||||||
|
"exist to serve — those navigating complex, often invisible, or unusual health journeys; "
|
||||||
|
"patients facing rare or poorly understood conditions; individuals whose situations don't "
|
||||||
|
"fit a clean category. The connection is to the humans behind the data, not the industry. "
|
||||||
|
"If the user has provided a personal note, use that to anchor Para 3 specifically."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -189,6 +212,24 @@ def build_prompt(
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def _trim_to_letter_end(text: str) -> str:
|
||||||
|
"""Remove repetitive hallucinated content after the first complete sign-off.
|
||||||
|
|
||||||
|
Fine-tuned models sometimes loop after completing the letter. This cuts at
|
||||||
|
the first closing + candidate name so only the intended letter is saved.
|
||||||
|
"""
|
||||||
|
candidate_first = (_profile.name.split()[0] if _profile else "").strip()
|
||||||
|
pattern = (
|
||||||
|
r'(?:Warm regards|Sincerely|Best regards|Kind regards|Thank you)[,.]?\s*\n+\s*'
|
||||||
|
+ (re.escape(candidate_first) if candidate_first else r'\w+')
|
||||||
|
+ r'\b'
|
||||||
|
)
|
||||||
|
m = re.search(pattern, text, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
return text[:m.end()].strip()
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
def generate(
|
def generate(
|
||||||
title: str,
|
title: str,
|
||||||
company: str,
|
company: str,
|
||||||
|
|
@ -227,8 +268,10 @@ def generate(
|
||||||
if feedback:
|
if feedback:
|
||||||
print("[cover-letter] Refinement mode: feedback provided", file=sys.stderr)
|
print("[cover-letter] Refinement mode: feedback provided", file=sys.stderr)
|
||||||
|
|
||||||
result = _router.complete(prompt)
|
# max_tokens=1200 caps generation at ~900 words — enough for any cover letter
|
||||||
return result.strip()
|
# and prevents fine-tuned models from looping into repetitive garbage output.
|
||||||
|
result = _router.complete(prompt, max_tokens=1200)
|
||||||
|
return _trim_to_letter_end(result)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue