Files

122 lines
4.4 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.
"""Skill-suggestion cards and selection handling for the Builder."""
from __future__ import annotations
from copy import deepcopy
from typing import Any
from ..fsm import FSMError, Transition, component
from ..models import Stage
from ..resume_skill_advisor import recommend_skill_candidates
from ..skill_groups import update_skill_groups
from .constants import u
from .state import _set_stream_phases
from .turns import _next_step_turn, recommended_section
def _skill_choice_card(candidates: list[dict[str, Any]]) -> dict[str, Any]:
return component(
"ChoiceChips",
module="builder_skill_select",
title=u("确认岗位技能"),
description=u("请选择你愿意确认加入简历的技能;未选择的候选不会写入。没有合适的也可以暂时跳过。"),
multiple=True,
skippable=True,
skip_label=u("暂不添加"),
options=[
{
"value": str(candidate["skill"]),
"label": f'{candidate["skill"]}{u("")}{candidate["category"]}{u("")}',
}
for candidate in candidates
],
)
def _builder_skill_candidates(
profile: dict[str, Any],
resume_content: dict[str, Any],
skill_suggester: Any | None,
) -> list[dict[str, Any]]:
if skill_suggester is None:
return []
existing = [
str(skill).strip()
for group in resume_content.get("skill_groups") or []
if isinstance(group, dict)
for skill in group.get("skills") or []
if str(skill).strip()
]
working_profile = deepcopy(profile)
working_profile["tags"] = {**dict(working_profile.get("tags") or {}), "skills": existing}
facts: list[dict[str, Any]] = []
for section in resume_content.get("sections") or []:
if not isinstance(section, dict):
continue
for entry in section.get("items") or []:
if isinstance(entry, dict):
facts.append(deepcopy(entry))
working_profile["experiences"] = facts
return recommend_skill_candidates(
working_profile,
existing,
u("根据我选择的目标岗位推荐可确认技能"),
skill_suggester,
)
def _process_skill_selection(
profile: dict[str, Any],
state: dict[str, Any],
action: str,
payload: dict[str, Any],
resume_content: dict[str, Any],
) -> Transition:
if action == "skip":
state["pending_skill_candidates"] = []
_set_stream_phases(profile, "suggesting_next")
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
_next_step_turn(recommended_section(profile, resume_content), prefix=u("好的,先不添加技能。")),
lifecycle="dismissed",
)
if action != "select":
raise FSMError("invalid_builder_skill_selection", "Confirm or skip the skill suggestions", status_code=422)
selected = payload.get("values")
if not isinstance(selected, list):
selected = [payload.get("value")]
allowed = {
str(item.get("skill") or "").strip()
for item in state.get("pending_skill_candidates") or []
if isinstance(item, dict) and str(item.get("skill") or "").strip()
}
chosen = [str(value).strip() for value in selected if str(value or "").strip() in allowed]
if not chosen:
raise FSMError("invalid_builder_skill_selection", "Select at least one suggested skill or skip", status_code=422)
existing = [
str(skill).strip()
for group in resume_content.get("skill_groups") or []
if isinstance(group, dict)
for skill in group.get("skills") or []
if str(skill).strip()
]
preferred = {
str(item.get("skill") or "").strip(): str(item.get("category") or "").strip()
for item in state.get("pending_skill_candidates") or []
if isinstance(item, dict) and str(item.get("skill") or "").strip() and str(item.get("category") or "").strip()
}
content = update_skill_groups(resume_content, [*existing, *chosen], preferred_categories=preferred)
state["pending_skill_candidates"] = []
_set_stream_phases(profile, "saving", "suggesting_next")
return Transition(
Stage.BUILDER_CONVERSATION,
profile,
_next_step_turn(
recommended_section(profile, content),
prefix=u("已添加") + " " + u("、").join(chosen) + u("。"),
),
lifecycle="confirmed",
resume_content=content,
)