defmodule Mix.Tasks.Ccxt.ClassifySigning do @shortdoc "Report signing pattern classification for all exchanges" @moduledoc """ Reports signing pattern classification for all exchanges. Loads every compiled exchange spec (priority tiers by default — Tier 1 + Tier 2 + DEX), classifies each into one of 9 signing patterns, and prints a summary grouped by pattern. ## Usage mix ccxt.classify_signing """ use Mix.Task alias CCXT.Signing.Classifier @impl true def run(_args) do exchanges = CCXT.Spec.exchanges() results = Enum.map(exchanges, fn exchange_id -> spec = CCXT.Spec.load!(exchange_id) {pattern, config} = Classifier.classify(spec) {exchange_id, pattern, config} end) by_pattern = Enum.group_by(results, fn {_, pattern, _} -> pattern end) print_summary(by_pattern, length(exchanges)) print_details(by_pattern) print_custom_review(results) end defp print_summary(by_pattern, total) do IO.puts("\n=== Signing Pattern Classification ===\n") by_pattern |> Enum.sort_by(fn {_pattern, exchanges} -> -length(exchanges) end) |> Enum.each(fn {pattern, exchanges} -> IO.puts(" #{format_pattern(pattern)} #{length(exchanges)} exchanges") end) IO.puts("\n Total: #{total} exchanges\n") end defp print_details(by_pattern) do IO.puts("=== Details by Pattern ===\n") by_pattern |> Enum.sort_by(fn {pattern, _} -> Atom.to_string(pattern) end) |> Enum.each(fn {pattern, exchanges} -> ids = exchanges |> Enum.map(fn {id, _, _} -> id end) |> Enum.sort() IO.puts(" #{format_pattern(pattern)}") IO.puts(" #{Enum.join(ids, ", ")}\n") end) end defp print_custom_review(results) do custom = Enum.filter(results, fn {_, pattern, _} -> pattern == :custom end) if custom == [], do: :ok, else: do_print_custom(custom) end defp do_print_custom(custom) do IO.puts("=== Custom Exchanges (manual review needed) ===\n") Enum.each(custom, &print_custom_exchange/1) IO.puts("") end defp print_custom_exchange({exchange_id, _, _}) do parent = Classifier.parent_for(exchange_id) suffix = if parent, do: " (inherits from #{parent})", else: "" IO.puts(" #{exchange_id}#{suffix}") end @pattern_width 40 defp format_pattern(pattern) do String.pad_trailing(inspect(pattern), @pattern_width) end end