""" Prompt templates for the three deck modes. Output contract — Claude returns a single JSON object: { "deck_name": "string", "strategy_summary": "string", "cards": [ { "name": "Card Name", // append [UNOWNED] when prefer_owned=True "slot": "creature|instant|sorcery|enchantment|artifact|planeswalker|land|battle", "quantity": 1, "reasoning": "1-2 sentences" } ], "cuts": [ // CULL mode only { "name": "Card Name", "reasoning": "..." } ] } """ SYSTEM_PROMPT = ( "You are an expert Magic: The Gathering deck builder specialising in the Commander " "(EDH) format. You have deep knowledge of card synergies, mana curves, colour " "identity rules, staples, budget alternatives, and current metagame trends.\n\n" "You always respond with a single valid JSON object — no markdown fences, no " "preamble, no commentary outside the JSON. Your card names must exactly match " "official Magic card names (English). Every card must be legal in Commander " "and within the commander's colour identity." ) def generate_prompt( commander: str, playstyle: str | None, constraint_text: str, owned_list_text: str, ) -> tuple[str, str]: playstyle_line = ( f"Playstyle preference: {playstyle}" if playstyle else "Playstyle preference: not specified — choose the strongest strategy for this commander." ) user_message = ( f"Build a complete Commander deck for the following commander.\n\n" f"COMMANDER: {commander}\n" f"{playstyle_line}\n\n" f"CONSTRAINTS:\n{constraint_text}\n" f"{owned_list_text}\n" f"DECK REQUIREMENTS:\n" f"- Exactly 99 cards (not counting the commander)\n" f"- All cards must be legal in Commander and within {commander}'s colour identity\n" f"- Include a balanced mana base (35-40 lands for most strategies)\n" f"- Include ramp (8-12 pieces), card draw (8-10 pieces), removal (8-10 pieces), " f"and win conditions appropriate to the playstyle\n" f"- Slot values: creature, instant, sorcery, enchantment, artifact, planeswalker, land, battle\n" f"- Quantity for basic lands may be >1; all other cards quantity = 1\n" f"- The 'reasoning' field must explain why the card fits THIS specific deck\n\n" f"Respond with the JSON object only." ) return SYSTEM_PROMPT, user_message def complete_prompt( commander: str, playstyle: str | None, existing_cards: list[dict], constraint_text: str, owned_list_text: str, ) -> tuple[str, str]: existing_count = sum(c.get("quantity", 1) for c in existing_cards) slots_needed = 99 - existing_count playstyle_line = ( f"Playstyle preference: {playstyle}" if playstyle else "Playstyle preference: infer from existing cards." ) existing_formatted = "\n".join( f"- {c['card_name']} ({c.get('slot', 'unknown')})" + (f" x{c['quantity']}" if c.get('quantity', 1) > 1 else "") for c in existing_cards ) user_message = ( f"The user has a partial Commander deck and needs suggestions to complete it.\n\n" f"COMMANDER: {commander}\n" f"{playstyle_line}\n" f"SLOTS NEEDED: {slots_needed} more cards to reach 99\n\n" f"EXISTING CARDS ({existing_count} cards):\n{existing_formatted}\n\n" f"CONSTRAINTS:\n{constraint_text}\n" f"{owned_list_text}\n" f"INSTRUCTIONS:\n" f"- Suggest exactly {slots_needed} new cards to fill remaining slots\n" f"- Do not repeat any card already in the existing list\n" f"- All cards must be legal in Commander and within {commander}'s colour identity\n" f"- Analyse existing cards to infer strategy and fill gaps (ramp, draw, removal, win-cons)\n" f"- The 'cards' array contains ONLY the new cards you are recommending\n" f"- strategy_summary should describe how the completed deck plays\n\n" f"Respond with the JSON object only." ) return SYSTEM_PROMPT, user_message def cull_prompt( commander: str, existing_cards: list[dict], target_count: int, constraint_text: str, owned_list_text: str, prefer_owned: bool, ) -> tuple[str, str]: current_count = sum(c.get("quantity", 1) for c in existing_cards) cuts_needed = current_count - target_count existing_formatted = "\n".join( f"- {c['card_name']} ({c.get('slot', 'unknown')})" + (" [OWNED]" if c.get("is_owned") else "") for c in existing_cards ) ownership_note = ( "\n- IMPORTANT: Prioritise cutting cards NOT marked [OWNED] first — " "this saves the user money on cards they would have to buy." if prefer_owned else "" ) user_message = ( f"The user's Commander deck is oversized and needs to be culled.\n\n" f"COMMANDER: {commander}\n" f"CURRENT SIZE: {current_count} cards\n" f"TARGET SIZE: {target_count} cards\n" f"CUTS NEEDED: {cuts_needed} cards\n\n" f"CURRENT DECKLIST:\n{existing_formatted}\n\n" f"CONSTRAINTS:\n{constraint_text}\n" f"{owned_list_text}\n" f"INSTRUCTIONS:\n" f"- Recommend exactly {cuts_needed} cards to cut\n" f"- Identify redundancy, weak synergy, overcosted cards, and cards that " f"don't advance the primary strategy\n" f"- Order the 'cuts' array from most-recommended to least-recommended cut{ownership_note}\n" f"- The 'cards' array contains all {target_count} REMAINING cards after cuts\n" f"- Provide specific reasoning for each cut explaining why it's weaker than what stays\n" f"- strategy_summary describes the refined deck after cuts\n\n" f"Respond with the JSON object only." ) return SYSTEM_PROMPT, user_message