@@ -42,6 +42,9 @@ defmodule Manavault.AI.DeckAnalysis do
4242 Keep the final analysis compact: use one concise paragraph for each narrative field and three to
4343 five concise items for each standard list when the deck supports that many. Use deeper reasoning
4444 to improve the analysis rather than making the final response longer.
45+ Every suggested card must be legal in the deck's format. For Commander decks, its color identity
46+ must also be contained within deck.commander_color_identity. Omit any card whose legality or
47+ color identity you cannot verify rather than guessing.
4548 The facts object contains authoritative metadata calculated by ManaVault. Use its counts instead
4649 of recounting deck.cards.
4750
@@ -117,7 +120,7 @@ defmodule Manavault.AI.DeckAnalysis do
117120 """
118121 end
119122
120- def response_schema do
123+ def response_schema ( custom_instructions \\ nil ) do
121124 string = % { type: "string" }
122125 strings = % { type: "array" , items: string }
123126 nullable_bracket = % { type: [ "integer" , "null" ] , minimum: 1 , maximum: 5 }
@@ -132,6 +135,11 @@ defmodule Manavault.AI.DeckAnalysis do
132135 }
133136 }
134137
138+ custom_sections =
139+ if custom_instructions? ( custom_instructions ) ,
140+ do: custom_sections ,
141+ else: Map . put ( custom_sections , :maxItems , 0 )
142+
135143 % {
136144 type: "object" ,
137145 additionalProperties: false ,
@@ -156,8 +164,15 @@ defmodule Manavault.AI.DeckAnalysis do
156164 }
157165 end
158166
159- def normalize_result ( result , payload ) when is_map ( result ) do
167+ def normalize_result ( result , payload , custom_instructions \\ nil )
168+
169+ def normalize_result ( result , payload , custom_instructions ) when is_map ( result ) do
160170 with { :ok , normalized } <- normalized_fields ( result ) ,
171+ normalized <-
172+ if ( custom_instructions? ( custom_instructions ) ,
173+ do: normalized ,
174+ else: Map . put ( normalized , :custom_sections , [ ] )
175+ ) ,
161176 normalized <-
162177 Map . merge ( normalized , % {
163178 official_bracket: value ( result , :official_bracket ) ,
@@ -168,7 +183,7 @@ defmodule Manavault.AI.DeckAnalysis do
168183 end
169184 end
170185
171- def normalize_result ( _result , _payload ) ,
186+ def normalize_result ( _result , _payload , _custom_instructions ) ,
172187 do: { :error , "The AI provider returned an invalid analysis." }
173188
174189 def render_markdown ( result ) do
@@ -288,8 +303,27 @@ defmodule Manavault.AI.DeckAnalysis do
288303 if payload . deck . format == "commander" do
289304 with true <- valid_bracket? ( official ) ,
290305 true <- valid_bracket? ( practical ) do
291- official = max ( official , game_changer_minimum ( payload . facts . game_changer_count ) )
292- { :ok , Map . merge ( result , % { official_bracket: official , play_bracket: practical } ) }
306+ game_changer_count = payload . facts . game_changer_count
307+ minimum = game_changer_minimum ( game_changer_count )
308+
309+ result =
310+ Map . merge ( result , % {
311+ official_bracket: max ( official , minimum ) ,
312+ play_bracket: practical
313+ } )
314+
315+ result =
316+ if official < minimum do
317+ Map . put (
318+ result ,
319+ :bracket_rationale ,
320+ corrected_bracket_rationale ( game_changer_count , minimum , practical )
321+ )
322+ else
323+ result
324+ end
325+
326+ { :ok , result }
293327 else
294328 _invalid -> { :error , "The AI provider returned an invalid Commander bracket." }
295329 end
@@ -315,6 +349,20 @@ defmodule Manavault.AI.DeckAnalysis do
315349 defp game_changer_minimum ( count ) when count <= 3 , do: 3
316350 defp game_changer_minimum ( _count ) , do: 4
317351
352+ defp corrected_bracket_rationale ( game_changer_count , minimum , practical ) do
353+ game_changers =
354+ if game_changer_count == 1 ,
355+ do: "1 Game Changer" ,
356+ else: "#{ game_changer_count } Game Changers"
357+
358+ "The official Commander Brackets guidelines require at least Bracket #{ minimum } because " <>
359+ "the deck contains #{ game_changers } . Based on the rest of the list, it is expected to " <>
360+ "play like Bracket #{ practical } ."
361+ end
362+
363+ defp custom_instructions? ( instructions ) ,
364+ do: is_binary ( instructions ) and String . trim ( instructions ) != ""
365+
318366 defp section ( title , content ) , do: "## #{ title } \n \n #{ content } "
319367
320368 defp list_section ( title , items ) do
0 commit comments