Files

97 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Candidate rewrite for Builder entries (light STAR optimization)."""
from __future__ import annotations
from copy import deepcopy
import re
from typing import Any
from .state import _dedupe_strings
from ..experience_optimizer import _fact_text_is_preserved, split_description_parts
def _candidate_rewrite(
agent: Any, profile: dict[str, Any], entry: dict[str, Any], section: str, *, instruction: str | None = None,
ensure_facts: bool = False,
) -> dict[str, Any]:
try:
proposal = agent.expander.expand(
deepcopy(entry),
context={
"job_type": profile.get("job_type"),
"target_position": profile.get("target_position"),
"entry_type": section,
"instruction": instruction,
},
)
except Exception:
proposal = {}
original = str(entry.get("description") or "").strip()
optimized = str(proposal.get("optimized_description") or "").strip() or original
if ensure_facts:
# Explicit user-requested revision: still-missing material facts are folded
# back in (the user asked for them; this is not a silent auto-append).
missing = _uncovered_material_facts(optimized, original)
if missing:
if "• " in optimized:
optimized = optimized + "".join(f"\n{fact}" for fact in missing)
else:
optimized = f"{optimized.rstrip('。')}{''.join(missing)}。"
return {
"optimized_description": optimized,
"changes": proposal.get("changes") or [],
"source": proposal.get("source") or "ai_expanded",
"uncovered_facts": _uncovered_material_facts(optimized, original),
**({"generation_source": proposal["generation_source"]} if proposal.get("generation_source") else {}),
}
def _uncovered_material_facts(candidate: str, original: str) -> list[str]:
"""Material user facts the candidate dropped. Reported, never auto-appended."""
uncovered = [fact for fact in _material_fact_fragments(original) if not _fact_is_preserved(fact, candidate)]
fragments = split_description_parts(original)
if len(fragments) >= 2:
# Structured descriptions (feature lists, tech stack, outcomes) are checked
# fragment by fragment, so a dropped feature module is reported even when the
# tech stack survived. Single-sentence descriptions keep the regex-only path.
ledger = [
{"id": f"fragment_{index}", "source": "user_form", "field": "description_part", "text": fragment}
for index, fragment in enumerate(fragments, start=1)
]
uncovered.extend(
fragment
for index, fragment in enumerate(fragments, start=1)
if not _fact_text_is_preserved(f"fragment_{index}", ledger, candidate)
)
return _dedupe_strings(uncovered)
def _material_fact_fragments(text: str) -> list[str]:
facts: list[str] = []
patterns = (
r"gpa\s*[:]?\s*\d+(?:\.\d+)?\s*/\s*\d+(?:\.\d+)?",
r"(?:排名\s*)?(?:前\s*百分之\s*\d+(?:\.\d+)?|前\s*\d+(?:\.\d+)?\s*%|top\s*\d+(?:\.\d+)?\s*%)",
r"(?:专业|年级)?(?:排名)?前(?:十|二十|三十|五十)",
r"(?:获得|荣获|获评|获奖|取得)[^。;;\n]{0,30}(?:奖学金|奖项|荣誉|一等奖|二等奖|三等奖|优秀[^。;;\n]{0,12})",
r"(?:完成|参与|负责|主导|开发|设计|实现|搭建|推进|开展)[^。;;\n]{0,40}(?:课程项目|课程设计|项目|竞赛|实验室|实践|实训|研究|论文)",
r"(?:服务|覆盖|面向|参与|支持|管理|处理|完成|交付|提升|降低|增长)[^。;;\n]{0,20}?\d+(?:\.\d+)?\s*(?:%|人|名(?:学生|用户|客户|参与者)?|次|天|周|月|小时|万元|万|千|个|项|篇|场)",
)
for pattern in patterns:
facts.extend(match.group(0).strip(" \t,") for match in re.finditer(pattern, text, flags=re.IGNORECASE))
tool_pattern = r"\b(?:python|sql|java|javascript|typescript|vue|react|excel|power\s*bi|tableau|pandas|tensorflow|pytorch|docker|git|linux)\b"
facts.extend(match.group(0).strip() for match in re.finditer(tool_pattern, text, flags=re.IGNORECASE))
return _dedupe_strings([fact for fact in facts if fact])
def _fact_is_preserved(fact: str, candidate: str) -> bool:
normalized_fact = _normalize_material_fact(fact)
normalized_candidate = _normalize_material_fact(candidate)
return bool(normalized_fact) and normalized_fact in normalized_candidate
def _normalize_material_fact(value: str) -> str:
normalized = value.casefold().replace("百分之", "%")
normalized = re.sub(r"(?:排名|专业排名|年级排名)?前\s*(\d+(?:\.\d+)?)\s*%?", r"top\1", normalized)
normalized = re.sub(r"top\s*(\d+(?:\.\d+)?)\s*%?", r"top\1", normalized)
return re.sub(r"[\s,,。;;:]", "", normalized)