generated from kgod/ai-review-template
feat: builder 简历生成 + 轻度优化 + 简历导入交付副本
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
"""RESUME_ENRICHING 模块组件事件分发与结构化收集器(竞赛/标签/联系方式)。
|
||||
|
||||
记录类模块(RecordFields/ChoiceChips/条目确认/卡内调整)见 enrichment_record_collectors.py。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .enrichment_custom import (
|
||||
CUSTOM_MODULES,
|
||||
custom_card_picker_transition,
|
||||
finish_custom_enrichment,
|
||||
)
|
||||
from .enrichment_modules import ENRICHMENT_MODULES, TAG_SEQUENCE, ModuleSpec
|
||||
from .enrichment_record_collectors import (
|
||||
collect_choice,
|
||||
collect_record_fields,
|
||||
confirm_module_entry,
|
||||
edit_module_entry,
|
||||
)
|
||||
from .fsm import FSMError, Transition, assistant_turn, component
|
||||
from .fsm_enrichment import (
|
||||
advance_or_finish,
|
||||
begin_module,
|
||||
current_module,
|
||||
defer_enrichment,
|
||||
ensure_enrichment_state,
|
||||
mark_completed,
|
||||
progress_block,
|
||||
skip_module,
|
||||
)
|
||||
from .models import ComposerMode, Stage
|
||||
from .validators import competition_entry_errors, normalize_tags
|
||||
|
||||
|
||||
def process_module_event(
|
||||
profile: dict[str, Any],
|
||||
component_data: dict[str, Any],
|
||||
action: str,
|
||||
payload: dict[str, Any],
|
||||
) -> Transition:
|
||||
ensure_enrichment_state(profile)
|
||||
name = component_data.get("component_name")
|
||||
if action in {"defer", "finish_enrichment"}:
|
||||
return defer_enrichment(profile)
|
||||
if name == "CustomCardPicker":
|
||||
return _handle_custom_card_picker(profile, payload)
|
||||
spec = current_module(profile)
|
||||
if spec is None:
|
||||
raise FSMError("no_active_module", "当前没有进行中的完善模块", status_code=409)
|
||||
if component_data.get("module") != spec.name:
|
||||
raise FSMError("stale_module", "该组件不属于当前模块", status_code=409)
|
||||
if action in {"skip", "skip_module"}:
|
||||
return skip_module(profile, spec)
|
||||
if action in {"edit", "edit_anchor"}:
|
||||
return edit_module_entry(profile, spec)
|
||||
if name == "ChoiceChips":
|
||||
return collect_choice(profile, spec, payload)
|
||||
if name == "RecordFields":
|
||||
return collect_record_fields(profile, spec, payload)
|
||||
if name == "CompetitionFields":
|
||||
return _collect_competition(profile, spec, payload)
|
||||
if name == "TagsInput":
|
||||
return _collect_tags(profile, component_data, spec, payload)
|
||||
if name == "ExperienceConfirmCard":
|
||||
return confirm_module_entry(profile, spec, payload)
|
||||
if name == "AddAnother":
|
||||
return _handle_add_another(profile, spec, payload)
|
||||
raise FSMError("invalid_component", f"Unsupported module component: {name}", status_code=422)
|
||||
|
||||
|
||||
def _collect_competition(profile: dict[str, Any], spec: ModuleSpec, payload: dict[str, Any]) -> Transition:
|
||||
entry = {
|
||||
"record_type": "competition",
|
||||
"module": spec.name,
|
||||
"name": str(payload.get("name") or "").strip(),
|
||||
"award": str(payload.get("award") or "").strip(),
|
||||
"date": str(payload.get("date") or "").strip(),
|
||||
"description": str(payload.get("description") or "").strip() or None,
|
||||
}
|
||||
errors = competition_entry_errors(entry)
|
||||
if errors:
|
||||
raise FSMError(
|
||||
"invalid_competition", "竞赛信息不完整或格式有误", status_code=422, missing_fields=errors
|
||||
)
|
||||
profile["enrichment"]["module_draft"] = {"entry": entry}
|
||||
transition = Transition(
|
||||
Stage.RESUME_ENRICHING,
|
||||
profile,
|
||||
assistant_turn(
|
||||
"请确认这条竞赛记录。",
|
||||
[
|
||||
progress_block(profile, spec),
|
||||
component(
|
||||
"ExperienceConfirmCard",
|
||||
module=spec.name,
|
||||
confirmation_kind="module_entry",
|
||||
title="确认竞赛记录",
|
||||
value={key: entry[key] for key in ("name", "award", "date", "description")},
|
||||
labels={"name": "竞赛名称", "award": "获奖名称", "date": "获奖时间", "description": "经历描述"},
|
||||
),
|
||||
],
|
||||
mode=ComposerMode.UI_ONLY,
|
||||
),
|
||||
)
|
||||
transition.polish_description = True
|
||||
return transition
|
||||
|
||||
|
||||
def _handle_add_another(profile: dict[str, Any], spec: ModuleSpec, payload: dict[str, Any]) -> Transition:
|
||||
value = str(payload.get("value") or "")
|
||||
if value == "again":
|
||||
profile["enrichment"]["module_draft"] = {}
|
||||
return begin_module(profile, spec)
|
||||
if value == "next":
|
||||
mark_completed(profile, spec.name)
|
||||
if profile["enrichment"].get("custom_mode"):
|
||||
return custom_card_picker_transition(profile, refresh=True)
|
||||
return advance_or_finish(profile)
|
||||
raise FSMError("invalid_action", "请选择再添加一段或进入下一项", status_code=422)
|
||||
|
||||
|
||||
def _handle_custom_card_picker(
|
||||
profile: dict[str, Any], payload: dict[str, Any]
|
||||
) -> Transition:
|
||||
value = str(payload.get("value") or payload.get("card_type") or "")
|
||||
if value == "finish":
|
||||
return finish_custom_enrichment(profile)
|
||||
module_name = CUSTOM_MODULES.get(value)
|
||||
if module_name is None:
|
||||
raise FSMError("invalid_card_type", "请选择列出的简历卡片", status_code=422)
|
||||
enrichment = profile["enrichment"]
|
||||
enrichment["current"] = module_name
|
||||
enrichment["module_draft"] = {}
|
||||
enrichment["custom_mode"] = True
|
||||
return begin_module(profile, ENRICHMENT_MODULES[module_name])
|
||||
|
||||
|
||||
def _collect_tags(
|
||||
profile: dict[str, Any],
|
||||
component_data: dict[str, Any],
|
||||
spec: ModuleSpec,
|
||||
payload: dict[str, Any],
|
||||
) -> Transition:
|
||||
expected = component_data.get("field")
|
||||
field = str(payload.get("field") or "")
|
||||
if field != expected or field not in TAG_SEQUENCE:
|
||||
raise FSMError("invalid_field", "提交字段与当前组件不符", status_code=422)
|
||||
profile["tags"][field] = normalize_tags(payload.get("value") or payload.get("values") or [])
|
||||
|
||||
mark_completed(profile, spec.name)
|
||||
return advance_or_finish(profile, refresh=True)
|
||||
|
||||
Reference in New Issue
Block a user