"""Injectable entry expansion protocol and deterministic P0 implementation.""" from __future__ import annotations import re from typing import Any, Protocol _BULLET_PREFIX = re.compile(r"^(?:[•●▪◦]\s*|[-*]\s+|\d+[.)、]\s*)") class EntryExpander(Protocol): """Produce an optimization proposal without mutating the source entry.""" def expand(self, entry: dict[str, Any], *, context: dict[str, Any]) -> dict[str, Any]: ... class RuleBasedEntryExpander: """Conservative local fallback used when no model is configured or available.""" def expand(self, entry: dict[str, Any], *, context: dict[str, Any]) -> dict[str, Any]: entry_type = str(context.get("entry_type") or "") description = str(entry.get("description") or "").strip() highlights = [ str(value).strip() for value in entry.get("highlights") or [] if str(value).strip() ] material = description or ";".join(highlights) if material: optimized = _polish_text(material) else: optimized = _description_from_structured_facts(entry, entry_type) if not optimized: return {"optimized_description": "", "changes": [], "source": "rule_polish"} if entry_type != "education": optimized = normalize_bullet_description(optimized) changes = ["统一为简洁、正式的简历表达"] if not description and not highlights: changes = ["根据已填写的结构化事实补充经历描述"] return { "optimized_description": optimized, "changes": changes, "source": "rule_polish", } def normalize_bullet_description(text: str) -> str: """Normalize existing lines into resume bullets without rewriting their text.""" bullets: list[str] = [] for raw_line in text.splitlines() or [text]: line = raw_line.strip() if not line: continue line = _BULLET_PREFIX.sub("", line).strip() if line: bullets.append(f"• {line}") return "\n".join(bullets) def _polish_text(text: str) -> str: replacements = ( (r"^做过", "完成"), (r"^做了", "完成"), (r"^拿了奖(?:项)?", "获得奖项"), (r"^参与了", "参与"), (r"^使用了?\s*(?=[A-Za-z0-9])", "基于 "), (r"^帮忙", "协助"), (r"^负责", "承担"), (r"^参加", "参与"), (r",将", ",推动"), (r",获得", ",并获得"), (r"降低了", "降低"), (r"提升了", "提升"), (r"优化了", "优化"), ) parts: list[str] = [] for raw in re.split(r"[。;;\n]+", text): part = raw.strip(" ,,。;;") if not part: continue for pattern, replacement in replacements: part = re.sub(pattern, replacement, part) parts.append(part) return "\n".join(parts) def _description_from_structured_facts(entry: dict[str, Any], entry_type: str) -> str: if entry_type in {"work_experience", "internship_experience"}: company = str(entry.get("company") or "").strip() position = str(entry.get("position") or "").strip() return f"在{company}担任{position}。" if company and position else "" if entry_type == "project_experience": name = str(entry.get("project_name") or "").strip() role = str(entry.get("project_role") or "").strip() return f"参与{name},担任{role}。" if name and role else "" if entry_type == "competition": name = str(entry.get("name") or "").strip() award = str(entry.get("award") or "").strip() return f"参加{name}并获得{award}。" if name and award else "" if entry_type == "campus_experience": organization = str(entry.get("organization") or "").strip() role = str(entry.get("role") or "").strip() return f"在{organization}担任{role}。" if organization and role else "" return ""