"""记录类模块收集器:类型选择、经历卡片提交、条目确认、卡内调整。""" 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, ), )