"""IMP-33 u3 — AI fallback prompt builder (fallback path only). System+user prompt for the Anthropic client (u4). MDX is READ-ONLY (`feedback_ai_isolation_contract`); output is constrained to the u2 schema; frame_id swap is forbidden (V4 rank-1 protected, `feedback_phase_z_spacing_direction`). Inputs per Stage 2 plan: V4 result (route=ai_adaptation_required, cardinality), frame_contract, frame_visual HTML, figma_to_html_agent partial JSON, Internal Region, MDX text. """ from __future__ import annotations import json from typing import Any from src.phase_z2_ai_fallback.schema import FORBIDDEN_KINDS, ProposalKind V4_ROUTE_AI_ADAPTATION = "ai_adaptation_required" _ALLOWED_KINDS = ", ".join(sorted(k.value for k in ProposalKind)) _FORBIDDEN_KINDS = ", ".join(sorted(FORBIDDEN_KINDS)) SYSTEM_PROMPT = ( "You are an IMP-33 AI fallback adapter for Phase Z slide composition.\n" "STRICT RULES:\n" " 1. MDX text in the user payload is READ-ONLY. Do NOT rewrite, " "compress, or paraphrase MDX.\n" " 2. Output MUST be a single JSON object conforming to AiFallbackProposal.\n" f" 3. proposal_kind MUST be one of: {_ALLOWED_KINDS}.\n" f" 4. Do NOT propose any of: {_FORBIDDEN_KINDS}.\n" " 5. Do NOT change frame_id — V4 rank-1 frame is locked.\n" " 6. Preferred Task-12 output is proposal_kind='design_adaptation_plan': " "plan frame/layout/repeat changes only; do NOT output final text slots.\n" " 7. Do NOT create titles, labels, summaries, body text, or slot content. " "The code layer will place MDX verbatim text.\n" " 8. Keep declared frame slots (text/table/image/details) accounted for.\n" " 9. Respect Internal Region containment; place content units within " "the declared region only." ) def build_ai_fallback_prompt( *, v4_result: dict[str, Any], frame_contract: dict[str, Any], frame_visual_html: str, figma_partial_json: dict[str, Any], internal_region: dict[str, Any], mdx_text: str, ) -> dict[str, str]: """Build system+user prompt strings for the fallback AI adapter. Raises: ValueError: when ``v4_result.route`` is not ``ai_adaptation_required`` — the fallback prompt MUST NOT be built outside this route (normal-path AI call count must remain 0; PZ-1). """ route = v4_result.get("route") or v4_result.get("imp05_route_hint") if route != V4_ROUTE_AI_ADAPTATION: raise ValueError( f"build_ai_fallback_prompt: v4_result.route={route!r} is not " f"{V4_ROUTE_AI_ADAPTATION!r}; fallback prompt MUST NOT be built " "outside the AI adaptation route." ) user_payload = { "v4": { "route": route, "cardinality": v4_result.get("cardinality") or v4_result.get("cardinality_signature"), "label": v4_result.get("label"), "frame_id": v4_result.get("frame_id"), "rank": v4_result.get("rank"), }, "frame_contract": frame_contract, "frame_visual_html": frame_visual_html, "figma_partial_json": figma_partial_json, "internal_region": internal_region, "mdx_text_READ_ONLY": mdx_text, "task12_output_contract": { "preferred_proposal_kind": ProposalKind.DESIGN_ADAPTATION_PLAN.value, "allowed_operation_examples": [ "increase_repeat_count", "split_group_across_repeat_blocks", "rebalance_zone_ratio", "compact_spacing", "use_repeatable_frame", ], "forbidden_content_fields": [ "text", "title", "label", "body", "slots", "mdx_text", "summary", "raw_html", "raw_css", ], }, } return { "system": SYSTEM_PROMPT, "user": json.dumps(user_payload, ensure_ascii=False), }