generated from kgod/ai-review-template
feat: initialize resume agent with OfferPai sync
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
"""RESUME_ENRICHING 模块队列引擎(V1 全链路收集)。
|
||||
|
||||
组件事件分发见 enrichment_collectors.py;fsm.py 通过函数内 import 调用,避免循环依赖。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .enrichment_modules import (
|
||||
ENRICHMENT_MODULES,
|
||||
ENRICHMENT_QUEUES,
|
||||
PICKER_OPTIONS,
|
||||
RECORD_KEYS,
|
||||
TAG_TITLES,
|
||||
ModuleSpec,
|
||||
skill_suggestions,
|
||||
)
|
||||
from .fsm import Transition, assistant_turn, component
|
||||
from .models import ComposerMode, JobType, Stage
|
||||
from .record_card import record_card
|
||||
|
||||
|
||||
def ensure_enrichment_state(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
"""惰性创建 records/tags/enrichment;旧会话现场建队列。"""
|
||||
records = profile.setdefault("records", {})
|
||||
for key in RECORD_KEYS:
|
||||
records.setdefault(key, [])
|
||||
profile.setdefault("tags", {"skills": [], "certificates": []})
|
||||
enrichment = profile.setdefault("enrichment", {})
|
||||
if "queue" not in enrichment:
|
||||
try:
|
||||
job_type = JobType(str(profile.get("job_type")))
|
||||
except ValueError as exc:
|
||||
raise ValueError("profile contains an unsupported job_type") from exc
|
||||
enrichment["queue"] = list(ENRICHMENT_QUEUES[job_type])
|
||||
enrichment.setdefault("index", 0)
|
||||
enrichment.setdefault("current", None)
|
||||
enrichment.setdefault("skipped", [])
|
||||
enrichment.setdefault("completed", [])
|
||||
enrichment.setdefault("module_draft", {})
|
||||
enrichment.setdefault("custom_mode", False)
|
||||
return profile
|
||||
|
||||
|
||||
def current_module(profile: dict[str, Any]) -> ModuleSpec | None:
|
||||
name = (profile.get("enrichment") or {}).get("current")
|
||||
return ENRICHMENT_MODULES.get(name) if name else None
|
||||
|
||||
|
||||
def enrichment_progress(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
enrichment = profile.get("enrichment") or {}
|
||||
total = len(enrichment.get("queue") or [])
|
||||
completed = len(enrichment.get("completed") or [])
|
||||
# total 固定为队列长度;skip 只切换到下一模块,不推进度(PR 评审结论)
|
||||
ratio = completed / total if total > 0 else 1.0
|
||||
return {
|
||||
"completed": completed,
|
||||
"skipped": len(enrichment.get("skipped") or []),
|
||||
"total": total,
|
||||
"ratio": ratio,
|
||||
}
|
||||
|
||||
|
||||
def begin_enrichment(profile: dict[str, Any]) -> Transition:
|
||||
ensure_enrichment_state(profile)
|
||||
enrichment = profile["enrichment"]
|
||||
queue = enrichment["queue"]
|
||||
if enrichment["index"] >= len(queue):
|
||||
from .enrichment_custom import custom_card_picker_transition
|
||||
|
||||
return custom_card_picker_transition(profile)
|
||||
profile["enrichment_finished"] = False
|
||||
spec = ENRICHMENT_MODULES[queue[enrichment["index"]]]
|
||||
enrichment["current"] = spec.name
|
||||
enrichment["module_draft"] = {}
|
||||
return begin_module(profile, spec)
|
||||
|
||||
|
||||
def progress_block(profile: dict[str, Any], spec: ModuleSpec) -> dict[str, Any]:
|
||||
progress = enrichment_progress(profile)
|
||||
return component(
|
||||
"ProgressCard",
|
||||
module=spec.name,
|
||||
completed=progress["completed"],
|
||||
skipped=progress["skipped"],
|
||||
total=progress["total"],
|
||||
percent=round(progress["ratio"] * 100),
|
||||
actions=["defer"],
|
||||
)
|
||||
|
||||
|
||||
def begin_module(profile: dict[str, Any], spec: ModuleSpec) -> Transition:
|
||||
blocks = [progress_block(profile, spec)]
|
||||
if spec.name in PICKER_OPTIONS and not profile["enrichment"]["module_draft"].get("record_type"):
|
||||
options = [{"value": v, "label": l} for v, l in PICKER_OPTIONS[spec.name]]
|
||||
blocks.append(
|
||||
component("ChoiceChips", module=spec.name, field="record_type", title=spec.prompt, options=options, skippable=True)
|
||||
)
|
||||
elif spec.kind in {"record_fields", "anchor_note"}:
|
||||
blocks.append(record_card(profile, spec))
|
||||
elif spec.kind == "record_form":
|
||||
blocks.append(component("CompetitionFields", module=spec.name, title=spec.prompt))
|
||||
elif spec.kind == "tags":
|
||||
field = spec.optional_fields[0]
|
||||
profile["enrichment"]["module_draft"] = {"field": field}
|
||||
data = {
|
||||
"module": spec.name,
|
||||
"field": field,
|
||||
"title": TAG_TITLES[field],
|
||||
"description": spec.prompt,
|
||||
}
|
||||
if field == "skills":
|
||||
data["suggestions"] = skill_suggestions(profile.get("target_position"), profile)
|
||||
blocks.append(component("TagsInput", **data))
|
||||
transition = Transition(
|
||||
Stage.RESUME_ENRICHING,
|
||||
profile,
|
||||
assistant_turn(spec.prompt, blocks, mode=ComposerMode.UI_ONLY),
|
||||
)
|
||||
if spec.kind == "tags" and spec.optional_fields[0] == "skills":
|
||||
transition.suggest_skills = True
|
||||
return transition
|
||||
|
||||
|
||||
def advance_or_finish(profile: dict[str, Any], *, refresh: bool = False) -> Transition:
|
||||
enrichment = profile["enrichment"]
|
||||
enrichment["index"] += 1
|
||||
enrichment["module_draft"] = {}
|
||||
if enrichment["index"] >= len(enrichment["queue"]):
|
||||
from .enrichment_custom import custom_card_picker_transition
|
||||
|
||||
transition = custom_card_picker_transition(profile)
|
||||
else:
|
||||
spec = ENRICHMENT_MODULES[enrichment["queue"][enrichment["index"]]]
|
||||
enrichment["current"] = spec.name
|
||||
transition = begin_module(profile, spec)
|
||||
transition.refresh_resume = refresh
|
||||
return transition
|
||||
|
||||
|
||||
def defer_enrichment(profile: dict[str, Any]) -> Transition:
|
||||
profile["enrichment"]["current"] = None
|
||||
profile["enrichment"]["module_draft"] = {}
|
||||
profile["enrichment_finished"] = True
|
||||
return Transition(
|
||||
Stage.CONTENT_READY,
|
||||
profile,
|
||||
assistant_turn(
|
||||
"好的,随时可以回来继续完善。",
|
||||
[component("ContentReadyCard", can_continue=True)],
|
||||
mode=ComposerMode.UI_ONLY,
|
||||
),
|
||||
lifecycle="confirmed",
|
||||
)
|
||||
|
||||
|
||||
def skip_module(profile: dict[str, Any], spec: ModuleSpec) -> Transition:
|
||||
if profile["enrichment"].get("custom_mode"):
|
||||
from .enrichment_custom import custom_card_picker_transition
|
||||
|
||||
return custom_card_picker_transition(profile)
|
||||
skipped = profile["enrichment"]["skipped"]
|
||||
if spec.name not in skipped:
|
||||
skipped.append(spec.name)
|
||||
return advance_or_finish(profile)
|
||||
|
||||
|
||||
def mark_completed(profile: dict[str, Any], name: str) -> None:
|
||||
completed = profile["enrichment"]["completed"]
|
||||
if name not in completed:
|
||||
completed.append(name)
|
||||
|
||||
|
||||
def add_another_transition(profile: dict[str, Any], spec: ModuleSpec, *, refresh: bool = False) -> Transition:
|
||||
"""multi 模块确认一段后的"再添加/下一项"卡片。"""
|
||||
transition = Transition(
|
||||
Stage.RESUME_ENRICHING,
|
||||
profile,
|
||||
assistant_turn(
|
||||
"已写入简历。",
|
||||
[
|
||||
progress_block(profile, spec),
|
||||
component(
|
||||
"AddAnother",
|
||||
module=spec.name,
|
||||
title="还要再添加一段吗?",
|
||||
options=[
|
||||
{"value": "again", "label": "再添加一段"},
|
||||
{"value": "next", "label": "进入下一项"},
|
||||
],
|
||||
),
|
||||
],
|
||||
mode=ComposerMode.UI_ONLY,
|
||||
),
|
||||
lifecycle="confirmed",
|
||||
)
|
||||
transition.refresh_resume = refresh
|
||||
return transition
|
||||
Reference in New Issue
Block a user