feat: initialize resume agent with OfferPai sync

This commit is contained in:
Codex
2026-08-05 20:20:18 +08:00
commit 61ec750031
197 changed files with 33291 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
"""RESUME_ENRICHING 模块队列引擎(V1 全链路收集)。
组件事件分发见 enrichment_collectors.pyfsm.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