Files
resume-agent/backend/app/enrichment_record_collectors.py

126 lines
5.1 KiB
Python

"""记录类模块收集器:类型选择、经历卡片提交、条目确认、卡内调整。"""
from __future__ import annotations
from typing import Any
from .enrichment_modules import PICKER_OPTIONS, ModuleSpec
from .fsm import ANCHOR_FIELDS, FIELD_LABELS, FSMError, Transition, assistant_turn, component
from .fsm_enrichment import (
add_another_transition,
advance_or_finish,
mark_completed,
progress_block,
)
from .models import ComposerMode, Stage
from .record_card import record_card
from .validators import record_entry_errors
def collect_choice(profile: dict[str, Any], spec: ModuleSpec, payload: dict[str, Any]) -> Transition:
value = str(payload.get("value") or "")
labels = dict(PICKER_OPTIONS[spec.name])
if value not in labels:
raise FSMError("invalid_record_type", "请选择列出的经历类型", status_code=422)
profile["enrichment"]["module_draft"] = {"record_type": value}
return Transition(
Stage.RESUME_ENRICHING,
profile,
assistant_turn(
f"好,{labels[value]}。请在卡片中填写这段经历。",
[progress_block(profile, spec), record_card(profile, spec)],
mode=ComposerMode.UI_ONLY,
),
)
def collect_record_fields(profile: dict[str, Any], spec: ModuleSpec, payload: dict[str, Any]) -> Transition:
draft = profile["enrichment"]["module_draft"]
existing = draft.get("entry") if isinstance(draft.get("entry"), dict) else {}
record_type = (
draft.get("record_type") or existing.get("record_type") or spec.record_type or profile.get("anchor_type")
)
description = str(payload.get("description") or "").strip()
if spec.kind == "anchor_note":
if not description:
raise FSMError("invalid_record", "请填写经历描述", status_code=422, missing_fields=["description"])
entry = {"record_type": record_type, "module": spec.name, "description": description}
else:
entry = {
"record_type": record_type,
"module": spec.name,
**{field: str(payload.get(field) or "").strip() for field in ANCHOR_FIELDS.get(record_type, [])},
}
if description:
entry["description"] = description
errors = record_entry_errors(entry, ANCHOR_FIELDS.get(record_type, []))
if errors:
raise FSMError("invalid_record", "核心字段缺失或格式有误", 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={k: v for k, v in entry.items() if k not in {"record_type", "module"}},
labels=FIELD_LABELS,
),
],
mode=ComposerMode.UI_ONLY,
),
)
transition.polish_description = True
return transition
def confirm_module_entry(
profile: dict[str, Any], spec: ModuleSpec, payload: dict[str, Any]
) -> Transition:
entry = profile["enrichment"]["module_draft"].pop("entry", None)
if not isinstance(entry, dict):
raise FSMError("invalid_state", "没有待确认的条目", status_code=409)
proposal = entry.pop("pending_proposal", None)
if isinstance(proposal, dict) and payload.get("use_optimized") is True:
entry["description"] = str(proposal.get("optimized_description") or "").strip()
entry["provenance"] = proposal.get("source", "ai_expanded")
entry["confirmed"] = True
entry["rewrite_confirmed"] = True
if spec.kind == "anchor_note":
anchor = profile.setdefault("anchor", {})
for key in ("description", "highlights", "metrics", "provenance"):
if entry.get(key):
anchor[key] = entry[key]
mark_completed(profile, spec.name)
return advance_or_finish(profile, refresh=True)
profile["records"][entry.get("record_type") or spec.record_type].append(entry)
if spec.multi:
return add_another_transition(profile, spec, refresh=True)
mark_completed(profile, spec.name)
return advance_or_finish(profile, refresh=True)
def edit_module_entry(profile: dict[str, Any], spec: ModuleSpec) -> Transition:
entry = profile["enrichment"]["module_draft"].get("entry")
if not isinstance(entry, dict):
raise FSMError("invalid_state", "没有可调整的条目", status_code=409)
if spec.kind == "record_form":
card = component("CompetitionFields", module=spec.name, title=spec.prompt, value=entry)
else:
card = record_card(profile, spec, value=entry)
return Transition(
Stage.RESUME_ENRICHING,
profile,
assistant_turn(
"请直接在卡片中修改这段经历。",
[progress_block(profile, spec), card],
mode=ComposerMode.UI_ONLY,
),
)