简历学历/年限机制完善:学位词同行'至今'推断在读(专硕…2025.09-至今→硕士(在读))、无'X年经验'时按实习/工作/项目时间段累计兜底(教育经历不计入)、年限显示优先级(明确工龄>在读/应届>区间估计),新增回归测试 2 例
This commit is contained in:
+38
-2
@@ -23,11 +23,17 @@ _NAME_RE = re.compile(r"姓\s*名\s*[::]\s*([一-龥]{2,4})")
|
||||
# 简历常见写法:首行裸姓名(无"姓名:"前缀),如" 王伟航 "
|
||||
_BARE_NAME_RE = re.compile(r"^\s*([一-龥]{2,4})\s*$", re.MULTILINE)
|
||||
_YEARS_RE = re.compile(r"(\d+(?:\.\d+)?)\s*年(?:以上)?[^\n,。;]{0,6}?经验")
|
||||
# 时间段兜底:"2023.07-2023.08" / "2021.09-2025.6" / "2025.12-至今"
|
||||
_PERIOD_RE = re.compile(
|
||||
r"(20\d{2})\s*[./年]\s*(\d{1,2})\s*月?\s*[-–—~]+\s*(?:(20\d{2})\s*[./年]\s*(\d{1,2})\s*月?|至今)")
|
||||
_GENDER_RE = re.compile(r"性\s*别\s*[::]?\s*(男|女)")
|
||||
_EDU_RE = re.compile(r"(博士后|博士|专硕|学硕|硕士|研究生|MBA|本科|大专|专科)")
|
||||
_EDU_MAP = {"专硕": "硕士", "学硕": "硕士", "研究生": "硕士"}
|
||||
# 在读学历:「硕士在读」或「大二/大三」这类年级写法 → 本科(在读)
|
||||
_EDU_READING_RE = re.compile(r"(本科|硕士|博士|研究生|MBA)\s*在读")
|
||||
# 学位词同行出现"至今"(教育经历未结束的常见写法):金融科技(专硕)… 2025.09-至今 → 在读
|
||||
_EDU_ONGOING_RE = re.compile(
|
||||
r"(博士后|博士|专硕|学硕|硕士|研究生|MBA|本科|大专|专科)[^\n]{0,30}?至今")
|
||||
_GRADE_RE = re.compile(r"大[一二三四]")
|
||||
_ADDR_RE = re.compile(r"(?:家庭住址|现居(?:住)?(?:地|址)?|联系地址|通讯地址|籍贯)[::]\s*([^\n,。;]{2,30})")
|
||||
|
||||
@@ -53,10 +59,13 @@ def _extract_name(text: str) -> str:
|
||||
|
||||
|
||||
def _extract_education(text: str) -> str:
|
||||
"""学历:优先识别在读("硕士在读"/"大二"年级写法),再匹配学位词。"""
|
||||
"""学历:优先识别在读("硕士在读" / 学位词同行"至今" / "大二"年级写法),再匹配学位词。"""
|
||||
rm = _EDU_READING_RE.search(text)
|
||||
if rm:
|
||||
return f"{_EDU_MAP.get(rm.group(1), rm.group(1))}(在读)"
|
||||
om = _EDU_ONGOING_RE.search(text)
|
||||
if om:
|
||||
return f"{_EDU_MAP.get(om.group(1), om.group(1))}(在读)"
|
||||
if _GRADE_RE.search(text):
|
||||
return "本科(在读)"
|
||||
m = _EDU_RE.search(text)
|
||||
@@ -178,6 +187,33 @@ def _mask_addr(addr: str) -> str:
|
||||
return addr[:6] + "***" if len(addr) > 6 else addr
|
||||
|
||||
|
||||
def _estimate_years(text: str) -> float:
|
||||
"""工作年限兜底估计:无"X年经验"写法时,累加工作/实习/项目分节中的时间段。
|
||||
|
||||
教育经历不计入工龄;无分节时剔除教育相关行后全文兜底。
|
||||
单段封顶 36 个月、总计封顶 10 年,防误识别。
|
||||
"""
|
||||
import datetime as _dt
|
||||
work_titles = {"工作经历", "工作经验", "实习经历", "项目经历", "项目经验"}
|
||||
secs = split_sections(text)
|
||||
body = "\n".join(s["text"] for s in secs if s["title"] in work_titles)
|
||||
if not body.strip():
|
||||
body = "\n".join(ln for ln in text.splitlines()
|
||||
if not re.search(r"大学|学院|学校|教育|学历|学位|GPA", ln, re.IGNORECASE))
|
||||
now = _dt.date.today()
|
||||
months = 0
|
||||
for m in _PERIOD_RE.finditer(body):
|
||||
y1, m1 = int(m.group(1)), int(m.group(2))
|
||||
if m.group(3):
|
||||
y2, m2 = int(m.group(3)), int(m.group(4))
|
||||
else:
|
||||
y2, m2 = now.year, now.month
|
||||
dm = (y2 - y1) * 12 + (m2 - m1) + 1
|
||||
if 0 < dm <= 600: # 超 50 年的区间视为误识别
|
||||
months += min(dm, 36)
|
||||
return round(min(months, 120) / 12, 1)
|
||||
|
||||
|
||||
def parse_resume(text: str, matcher: SkillMatcher | None = None,
|
||||
photo_b64: str = "") -> ResumeProfile:
|
||||
"""从简历文本解析脱敏画像(含结构化基本信息与分节)。"""
|
||||
@@ -194,7 +230,7 @@ def parse_resume(text: str, matcher: SkillMatcher | None = None,
|
||||
name=(name[0] + "*" * (len(name) - 1)) if name else "",
|
||||
phone=(phone_m.group(0)[:3] + "****" + phone_m.group(0)[-2:]) if phone_m else "",
|
||||
email=(email_m.group(0)[0] + "***@" + email_m.group(0).split("@")[1]) if email_m else "",
|
||||
years=float(years_m.group(1)) if years_m else 0.0,
|
||||
years=float(years_m.group(1)) if years_m else _estimate_years(text),
|
||||
skills=matcher.find_names(text),
|
||||
scenarios=matcher.find_scenarios(text),
|
||||
raw_masked_text=masked,
|
||||
|
||||
Reference in New Issue
Block a user