Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from ..models import ExpertReport | |
| from subenvs.email.hr_tools import build_hr_memo, score_memo | |
| from subenvs.email.graders import grade_response | |
| # Maps CoS brief tasks to the Round 1 email-env task IDs so the right | |
| # task-specific rubric fires inside grade_response. | |
| _BRIEF_TO_EMAIL_TASK = { | |
| "easy_brief": "task_1", | |
| "medium_brief": "task_2", | |
| "hard_brief": "task_3", | |
| "expert_brief": "task_3", | |
| } | |
| _HR_QUERY = ( | |
| "memo style guide audience tone bullet highlights " | |
| "call to action professional direct exemplar" | |
| ) | |
| def _clamp(score: float) -> float: | |
| """Match ceo_brief_env.graders._clamp so every emitted score is in (0.001, 0.999).""" | |
| return max(0.001, min(0.999, round(float(score), 4))) | |
| class HRExpert: | |
| expert_id = "hr" | |
| def run( | |
| self, | |
| task_name: str, | |
| task_meta: dict, | |
| analyst_report: ExpertReport | None, | |
| finance_report: ExpertReport | None, | |
| strategy_report: ExpertReport | None = None, | |
| focused: bool = False, | |
| use_rag: bool = False, | |
| ) -> ExpertReport: | |
| # --- 1. Build the memo from upstream reports ----------------------- | |
| highlights: list[str] = [] | |
| if analyst_report: | |
| highlights.extend(analyst_report.bullet_points[:2]) | |
| if finance_report: | |
| highlights.extend(finance_report.bullet_points[:2]) | |
| if strategy_report: | |
| highlights.extend(strategy_report.bullet_points[:2]) | |
| memory_citations: list[str] = [] | |
| memory_snippets: list[str] = [] | |
| if use_rag: | |
| from memory import get_retriever | |
| hits = get_retriever().query(_HR_QUERY, k=2) | |
| memory_citations = [h.as_citation() for h in hits] | |
| memory_snippets = [h.snippet for h in hits] | |
| if hits: | |
| highlights.insert( | |
| 0, | |
| f"Memo follows {hits[0].source.split('#')[0]} (style SOP retrieved from company memory).", | |
| ) | |
| audience = str(task_meta.get("memo_audience", "team")) | |
| title = str(task_meta.get("title", task_name)) | |
| memo = build_hr_memo(audience, title, highlights) | |
| # --- 2. Three-part scoring ---------------------------------------- | |
| structure_score = score_memo(memo, task_meta.get("hr_required_terms", [])) | |
| email_task_id = _BRIEF_TO_EMAIL_TASK.get(task_name, "task_1") | |
| tone_score = grade_response( | |
| email=task_meta.get("instruction", ""), | |
| response=memo, | |
| task_id=email_task_id, | |
| ) | |
| audience_hit = 1.0 if audience.lower() in memo.lower() else 0.0 | |
| blended = 0.45 * structure_score + 0.45 * tone_score + 0.10 * audience_hit | |
| score = _clamp(blended) | |
| # --- 3. Return a typed ExpertReport ------------------------------- | |
| issues: list[str] = [] | |
| if structure_score < 0.4: | |
| issues.append("hr:weak_structure_or_missing_required_terms") | |
| if tone_score < 0.4: | |
| issues.append("hr:weak_professional_tone") | |
| if audience_hit == 0.0: | |
| issues.append(f"hr:audience_not_referenced:{audience}") | |
| return ExpertReport( | |
| expert_id="hr", | |
| title="HR / Communications Memo", | |
| summary=( | |
| "Drafted the internal memo and scored it on structure, " | |
| "professional tone, and audience relevance." | |
| ), | |
| metrics={ | |
| "memo_structure_score": _clamp(structure_score), | |
| "memo_tone_score": _clamp(tone_score), | |
| "audience_reference": audience_hit, | |
| "memo_score": score, | |
| }, | |
| bullet_points=[ | |
| f"Memo addressed to {audience}.", | |
| f"Structure score {structure_score:.3f}, tone score {tone_score:.3f}.", | |
| "Blended score uses 45% structure, 45% tone, 10% audience bonus.", | |
| ], | |
| issues=issues, | |
| memo=memo, | |
| score=score, | |
| memory_citations=memory_citations, | |
| memory_snippets=memory_snippets, | |
| ) | |