generated from kgod/ai-review-template
76 lines
3.1 KiB
Python
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
|
|
]
|