Files
resume-agent/backend/app/skill_classifier.py

76 lines
3.1 KiB
Python

"""Deterministic display categories for confirmed resume skills."""
from __future__ import annotations
from collections.abc import Iterable, Mapping
_EXACT_CATEGORY_RULES: dict[str, str] = {
"sql analysis": "产品、设计与分析",
"data analysis": "产品、设计与分析",
"user research": "产品、设计与分析",
"数据分析": "产品、设计与分析",
"用户研究": "产品、设计与分析",
}
_CATEGORY_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
("编程语言与框架", (
"python", "java", "javascript", "typescript", "go", "golang", "c++", "c#",
"fastapi", "django", "flask", "spring", "spring boot", "node.js", "nodejs",
"react native", "pytorch", "tensorflow", "编程语言", "软件工程",
)),
("前端", (
"vue", "react", "angular", "html", "css", "sass", "tailwind", "webpack", "vite",
"前端", "小程序",
)),
("后端与数据存储", (
"postgresql", "postgres", "mysql", "sqlite", "redis", "mongodb", "elasticsearch",
"kafka", "rabbitmq", "sql", "clickhouse", "后端", "数据库", "缓存", "消息队列",
)),
("AI 与数据智能", (
"langgraph", "langchain", "llamaindex", "rag", "pgvector", "bge-m3", "bge", "tei",
"机器学习", "深度学习", "人工智能", "计算机视觉", "自然语言处理", "pandas", "numpy",
)),
("云、DevOps 与工具", (
"docker", "kubernetes", "k8s", "git", "github actions", "gitlab ci", "jenkins",
"linux", "terraform", "aws", "azure", "aliyun", "云原生", "容器",
)),
("产品、设计与分析", (
"figma", "axure", "tableau", "power bi", "excel", "data analysis", "sql analysis",
"product", "user research", "产品", "原型", "需求分析", "项目管理",
)),
)
def classify_skills(
skills: Iterable[object], preferred: Mapping[str, str] | None = None
) -> list[dict[str, list[str] | str]]:
"""Group confirmed user skills without changing their display order.
`preferred` maps skill -> category assigned by the recommender (LLM); it wins
over the keyword rules, which remain the fallback for manual edits.
"""
preferred_normalized = {
" ".join(str(skill).casefold().split()): str(category).strip()
for skill, category in (preferred or {}).items()
if str(category).strip()
}
grouped: dict[str, list[str]] = {category: [] for category, _ in _CATEGORY_RULES}
grouped["其他技能"] = []
seen: set[str] = set()
for value in skills:
skill = str(value or "").strip()
normalized = " ".join(skill.casefold().split())
if not normalized or normalized in seen:
continue
seen.add(normalized)
category = preferred_normalized.get(normalized) or _EXACT_CATEGORY_RULES.get(normalized) or next(
(name for name, keywords in _CATEGORY_RULES if any(keyword in normalized for keyword in keywords)),
"其他技能",
)
grouped.setdefault(category, []).append(skill)
return [
{"category": category, "skills": values}
for category, values in grouped.items()
if values
]