from __future__ import annotations from copy import deepcopy from dataclasses import dataclass from typing import Any from .fsm import FSMError, Transition, assistant_turn, component from .fsm_enrichment import ( add_another_transition, advance_or_finish, begin_module, current_module, mark_completed, ) from .models import ComposerMode, Stage from .services import ExtractedExperience @dataclass(slots=True) class RewriteConfirmationTransition: stage: Stage profile: dict[str, Any] turn: dict[str, Any] lifecycle: str = "submitted" create_draft: bool = False resume_content: dict[str, Any] | None = None refresh_resume: bool = False def prepare_rewrite_confirmation( profile: dict[str, Any], extraction: ExtractedExperience, proposal: dict[str, Any], *, module: str | None = None, record_type: str | None = None, ) -> tuple[dict[str, Any], dict[str, Any]]: updated = deepcopy(profile) pending = extraction.to_dict() if module is not None: pending["module"] = module if record_type is not None: pending["record_type"] = record_type pending["description"] = extraction.raw_text optimized = str(proposal.get("optimized_description") or "").strip() if optimized and optimized != extraction.raw_text.strip(): pending_proposal = { "optimized_description": optimized, "changes": proposal.get("changes") or [], "source": proposal.get("source", "ai_expanded"), } for key in ("generation_source", "fallback_reason"): if proposal.get(key): pending_proposal[key] = proposal[key] pending["pending_proposal"] = pending_proposal updated["pending_experience"] = pending summary = _confirmation_summary(extraction) turn = assistant_turn( "我已把这段事实整理成正式简历语言,请确认后再写入简历。", [ component( "ExperienceConfirmCard", title="确认 AI 改写", description="只在内容准确时加入简历;需要调整可返回继续描述。", value=summary, ai_proposal=pending.get("pending_proposal"), confirmation_kind="rewrite", ) ], mode=ComposerMode.UI_ONLY, ) return updated, turn def process_rewrite_confirmation( profile: dict[str, Any], action: str, payload: dict[str, Any] | None = None ) -> Transition | RewriteConfirmationTransition: updated = deepcopy(profile) normalized = action.strip().lower() payload = payload or {} pending = updated.get("pending_experience") module = pending.get("module") if isinstance(pending, dict) else None if normalized in {"edit", "revise", "edit_anchor"}: updated.pop("pending_experience", None) spec = _module_spec(updated, module) if spec is not None: return begin_module(updated, spec) return RewriteConfirmationTransition( Stage.RESUME_ENRICHING, updated, assistant_turn( "这版暂不写入。请补充或纠正事实,我会重新整理。", [], mode=ComposerMode.CHAT, ), ) if normalized not in {"confirm", "confirm_anchor", "confirm_rewrite"}: raise FSMError("invalid_action", "Confirm or revise the proposed rewrite") experience = updated.pop("pending_experience", None) if not isinstance(experience, dict): raise FSMError("rewrite_not_pending", "No proposed rewrite is waiting for confirmation") proposal = experience.pop("pending_proposal", None) if isinstance(proposal, dict) and payload.get("use_optimized") is True: experience["description"] = str(proposal.get("optimized_description") or "").strip() experience["provenance"] = proposal.get("source", "ai_expanded") record_type = experience.get("record_type") if record_type: record = {**experience, "confirmed": True, "rewrite_confirmed": True} updated.setdefault("records", {}).setdefault(record_type, []).append(record) else: updated.setdefault("experiences", []).append(experience) updated["ai_rewrites_confirmed"] = True spec = _module_spec(updated, module) if spec is not None: if spec.multi: transition = add_another_transition(updated, spec) else: mark_completed(updated, spec.name) transition = advance_or_finish(updated) transition.refresh_resume = True return transition return RewriteConfirmationTransition( Stage.CONTENT_READY, updated, assistant_turn( "已确认并写入简历。", [ component( "ContentReadyCard", formal_content_ready=True, actions=["continue_enriching", "finish_enrichment"], ) ], mode=ComposerMode.HYBRID, ), lifecycle="confirmed", refresh_resume=True, ) def _module_spec(profile: dict[str, Any], module: Any): if not module: return None spec = current_module(profile) return spec if spec is not None and spec.name == module else None def _confirmation_summary(extraction: ExtractedExperience) -> dict[str, Any]: return { "title": extraction.title, "organization": extraction.organization, "role": extraction.role, "description": extraction.raw_text, }