Files

200 lines
7.1 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.
"""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