generated from kgod/ai-review-template
105 lines
4.0 KiB
Python
105 lines
4.0 KiB
Python
"""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 ""
|