mybook

バトルカードと競合分析 — 技術で勝つ戦略を組み立てる

「ソウタさん、ミライテックの案件、ベイクオフになりました」

Slack に飛び込んできたメッセージを見て、ソウタは思わず椅子から身を乗り出した。ミライテックは従業員 8,000 人を超える大手製造業だ。AI による品質検査の自動化を検討しており、Arclight AI にとって今期最大の案件だった。

「ベイクオフ? つまり他社と比較されるってことですか」

カイがビデオ通話をつないできた。

「そう。競合は NeuralWorks と DataForge の 2 社。3 社で技術評価を受けて、最終的に 1 社に絞られる。2 週間後にそれぞれ技術デモをやる」

ソウタの表情が曇る。NeuralWorks は資金力のある大手で、DataForge は製造業での導入実績が豊富だ。

「正直、厳しい戦いですね……」

「厳しいのは事実だ。でも、準備次第で勝率は大きく変わる。今日はバトルカードの作り方を教えよう」


バトルカードとは何か

バトルカードとは、競合との比較情報を 1 枚のカードに凝縮した営業・技術支援ツールだ。FDE にとって、これは単なる営業資料ではない。技術的な優位性を構造化し、顧客の前で自信を持って語るための「作戦書」だ。

INFO

バトルカードの効果は数字で証明されている。利用者の 71% が勝率の改善を報告し、競合状況での改善率は 93% が 20% 以上と回答している。バトルカードは営業支援ツールの中で最も ROI が高い資産とされる。

「71% が勝率改善って、本当ですか?」

「本当だ。ただし、ちゃんと作ればの話だよ。テンプレートを埋めるだけじゃダメだ。技術者の視点で、本質的な差別化ポイントを抽出する必要がある」


バトルカードの 8 セクション構成

カイはホワイトボードにバトルカードの構造を描いた。

Loading diagram...

「大きく分けると 8 セクションだが、まずはこの 5 カテゴリで構造を掴もう」

各セクションの役割

セクション内容誰が使うか
概要競合の企業情報・ポジション全員
勝因・敗因過去に勝った/負けた理由FDE・営業
機能比較機能ごとの優劣マトリクスFDE
反論対処よくある反論とその対応FDE・営業
トラップ質問競合に不利になる質問FDE
クイックディスミス短い一言で競合を位置づける営業
価格情報競合の価格体系営業
攻撃・防御戦術攻めと守りの具体的な手法FDE

WARNING

バトルカードは社外秘だ。顧客に直接見せるものではない。あくまでチーム内で使う準備資料であり、顧客の前では自然な会話として情報を伝える。


FIA フレームワーク — 競合主張の構造化

「バトルカードに書く内容は、感覚じゃなくフレームワークで構造化する。FIA を使おう」

カイが説明する。

Fact-Impact-Act

Loading diagram...
  • Fact(事実): 検証可能な客観的事実
  • Impact(影響): その事実が顧客にどう影響するか
  • Act(行動): FDE としてどう行動するか

NeuralWorks に対する FIA の例

ソウタはカイの指導のもと、NeuralWorks に対するバトルカードエントリを作り始めた。

要素内容
FactNeuralWorks のモデル推論は GPU 専用で、CPU フォールバックがない
Impact工場のエッジ環境では GPU が使えない場合がある。推論が止まると検査ラインが停止する
ActArclight AI の CPU/GPU 自動切替デモを見せる。エッジ環境の安定性を強調する
要素内容
FactNeuralWorks は API ベースのみで、オンプレミス対応していない
Impact製造業のセキュリティ要件で、品質検査データを外部に送れない場合がある
ActArclight AI のオンプレミス / ハイブリッド構成のリファレンスアーキテクチャを提示する

「事実だけ伝えても相手には刺さらない。『だからあなたにとってこう困る』まで繋げて、『だから私たちはこうします』で閉じる」


Know-Say-Show フレームワーク

「FIA はバトルカードの中身を構造化するものだ。次は、実際の会話でどう使うかの話だ」

Loading diagram...

Know(知る)— 内部で把握しておく情報

顧客には直接言わないが、チームで共有しておくべき情報だ。

  • NeuralWorks の直近の資金調達額と投資家の期待
  • DataForge が製造業 A 社で導入に 18 ヶ月かかった事実
  • 競合の技術スタック(Python/TensorFlow vs Arclight の Rust/ONNX)
  • 競合の解約率や顧客満足度の情報

Say(伝える)— 顧客に伝えるメッセージ

「競合を直接攻撃するんじゃない。自社の強みとして伝えるんだ」

❌ 「NeuralWorks は GPU がないと動きません」
✅ 「Arclight AI はエッジ環境でも安定動作するよう、CPU/GPU 自動切替を標準搭載しています」
❌ 「DataForge は導入に 1 年以上かかります」
✅ 「Arclight AI は PoC から本番稼働まで平均 3 ヶ月で、専任の FDE が伴走します」

Show(見せる)— デモや証拠で示す

  • エッジデバイス上でのリアルタイム推論デモ
  • 既存顧客の導入タイムライン(匿名化済み)
  • ベンチマーク結果(推論速度、精度、リソース消費)

INFO

Say で主張し、Show で証明するのが鉄則だ。「速いです」と言うだけでなく、目の前でベンチマークを走らせる。FDE の技術力が最も活きる瞬間だ。


Winning / Battling / Losing ゾーン分析

「次に、競合ごとにどこで勝てて、どこが五分五分で、どこで負けるかを整理しよう」

Loading diagram...

各ゾーンの戦略

Winning Zone(勝てる領域):

  • ここを評価基準の中心に据える
  • デモで重点的に見せる
  • 顧客の課題とこの領域を結びつける

Battling Zone(五分五分の領域):

  • 差別化ポイントを探す
  • テクニカルな深掘りで微差を見せる
  • 顧客固有の要件で有利に持ち込む

Losing Zone(負ける領域):

  • 評価基準から外すか、重要度を下げる
  • 代替手段やロードマップで補う
  • 「ブランドより実績」のように視点を変える

「勝てるところで戦い、五分のところで差をつけ、負けるところは土俵を変える。これが基本戦略だ」


Win/Loss 分析 — なぜ勝ち、なぜ負けたか

「ソウタ、過去のベイクオフの勝敗記録はあるか?」

「営業チームが Salesforce に記録していますが、『価格で負けた』とか『関係性で勝った』みたいな大雑把なものですね」

「それが問題だ」

WARNING

セラーの 60% が敗因を誤って認識しているという調査結果がある。営業担当は「価格で負けた」と思いがちだが、実際には技術評価の段階で差がついていることが多い。

構造化された Win/Loss 分析の方法

Loading diagram...

ポイントは、営業チームだけでなく顧客にもインタビューすることだ。

分析対象質問例
意思決定基準最終的にどの基準で選びましたか?
技術評価技術デモで最も印象に残った点は?
信頼性ベンダーの信頼性をどう評価しましたか?
リスク採用にあたって最大のリスクは何でしたか?
競合比較他社と比べて優れていた/劣っていた点は?

「勝ちからも負けからも学ぶ。でも負けからの方が 10 倍学べる」


技術評価での競合ポジショニング

評価基準を設計する

「ミライテックが独自に評価基準を作る前に、こちらから提案するんだ」

ソウタは驚いた。

「評価基準をこっちから出すんですか?」

「もちろんだ。顧客は AI の技術評価に慣れていない場合が多い。こちらが『こういう観点で比較すると公平です』と提案すれば、感謝されるし、自社の強みが反映された基準になる」

Arclight AI が提案する評価基準

# 技術評価基準の構造化
class EvaluationCriteria
  CATEGORIES = {
    performance: { label: '推論性能', weight: 25,
      items: %w[リアルタイム推論速度 バッチスループット CPU/GPU両対応 エッジ動作] },
    integration: { label: '統合容易性', weight: 20,
      items: %w[REST_API対応 既存システム連携 オンプレ/クラウド選択 パイプライン構築] },
    reliability: { label: '信頼性', weight: 20,
      items: %w[SLA保証 フェイルオーバー モデル監視 ロールバック] },
    support: { label: '導入支援', weight: 20,
      items: %w[PoC期間 専任技術者 トレーニング ドキュメント] },
    cost: { label: 'コスト', weight: 15,
      items: %w[初期費用 ランニングコスト スケーリング費用 隠れコスト] }
  }.freeze
end

INFO

評価基準の重み付けが鍵だ。Arclight AI が強い「推論性能」と「導入支援」の配点を高くし、ブランド知名度のような負ける領域は基準に入れない。これは不正ではなく、顧客にとって本当に重要な観点を提案しているだけだ。

テクニカルプルーフポイント

ソウタは、各評価基準に対する「証拠」を準備した。

主張証拠タイプ具体的な証拠
CPU 環境で 50ms 以下の推論速度ベンチマークONNX Runtime + 量子化モデルの計測結果
PoC から本番まで平均 3 ヶ月導入事例製造業 B 社のタイムライン(匿名化済み)
オンプレミス完全閉域運用アーキテクチャエアギャップ構成のリファレンス図

Rails で構築する競合インテリジェンスシステム

「バトルカードを個人の知識にしておくのはもったいない。チーム全体で共有・更新できるシステムを作ろう」

ソウタは Rails で社内用の競合インテリジェンスシステムを構築することにした。

データモデル

# app/models/competitor.rb
class Competitor < ApplicationRecord
  has_many :battle_cards, dependent: :destroy
  has_many :win_loss_records, dependent: :destroy
 
  validates :name, presence: true, uniqueness: true
  enum :threat_level, { low: 0, medium: 1, high: 2, critical: 3 }
 
  scope :active, -> { where(active: true) }
  scope :by_threat, -> { order(threat_level: :desc) }
 
  def win_rate
    total = win_loss_records.decided.count
    return 0 if total.zero?
 
    (win_loss_records.won.count.to_f / total * 100).round(1)
  end
end
 
# app/models/battle_card.rb
class BattleCard < ApplicationRecord
  belongs_to :competitor
  belongs_to :author, class_name: 'User'
  has_many :sections, class_name: 'BattleCardSection',
           dependent: :destroy
 
  validates :title, presence: true
  validates :version, presence: true
 
  enum :status, { draft: 0, review: 1, approved: 2, archived: 3 }
  scope :current, -> { approved.order(version: :desc).limit(1) }
 
  def complete?
    BattleCardSection::TYPES.all? { |t| sections.exists?(section_type: t) }
  end
end
 
# app/models/battle_card_section.rb
class BattleCardSection < ApplicationRecord
  belongs_to :battle_card
 
  TYPES = %w[
    overview win_lose_reasons feature_comparison
    objection_handling trap_questions quick_dismiss
    pricing_intel attack_defense_tactics
  ].freeze
 
  validates :section_type, presence: true, inclusion: { in: TYPES }
  validates :content, presence: true
end
 
# app/models/win_loss_record.rb
class WinLossRecord < ApplicationRecord
  belongs_to :competitor
  belongs_to :account_executive, class_name: 'User'
  belongs_to :fde, class_name: 'User', optional: true
 
  validates :deal_name, presence: true
  validates :deal_size, numericality: { greater_than: 0 }
 
  enum :outcome, { won: 0, lost: 1, no_decision: 2 }
  enum :loss_reason, {
    price: 0, features: 1, integration: 2,
    relationship: 3, brand: 4, technical: 5
  }, prefix: true
 
  scope :decided, -> { where(outcome: %i[won lost]) }
end

Win/Loss パターン分析サービス

# app/services/win_loss_analyzer.rb
class WinLossAnalyzer
  def initialize(competitor:, period: 12.months)
    @competitor = competitor
    @period = period
  end
 
  def analyze
    records = WinLossRecord.where(competitor: @competitor)
                           .where('closed_at > ?', @period.ago).decided
    return {} if records.empty?
 
    {
      total: records.count,
      win_rate: (records.won.count.to_f / records.count * 100).round(1),
      top_loss_reason: top_reason(records.lost),
      trend: monthly_trend(records),
      recommendation: recommend(records)
    }
  end
 
  private
 
  def top_reason(losses)
    return nil if losses.empty?
 
    losses.group(:loss_reason).count.max_by { |_, v| v }&.first
  end
 
  def monthly_trend(records)
    records.group_by { |r| r.closed_at.beginning_of_month }
           .sort_by(&:first)
           .last(6)
           .map { |m, rs| { month: m.strftime('%Y-%m'), deals: rs.size } }
  end
 
  def recommend(records)
    win_rate = records.won.count.to_f / records.count * 100
    return 'バトルカードの全面見直しが必要' if win_rate < 40
 
    top = top_reason(records.lost)
    top ? "#{top} への対策を強化せよ" : '現状維持'
  end
end

コントローラ

# app/controllers/battle_cards_controller.rb
class BattleCardsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_competitor
 
  def show
    @battle_card = @competitor.battle_cards.current.first!
    @sections = @battle_card.sections.order(:position)
    @analysis = WinLossAnalyzer.new(competitor: @competitor).analyze
  end
 
  def create
    @battle_card = @competitor.battle_cards.build(battle_card_params)
    @battle_card.author = current_user
 
    if @battle_card.save
      redirect_to competitor_battle_card_path(@competitor, @battle_card),
                  notice: 'バトルカードを作成しました'
    else
      render :new, status: :unprocessable_entity
    end
  end
 
  def search
    @results = BattleCard.approved
                         .joins(:sections)
                         .where('battle_card_sections.content LIKE ?', "%#{params[:q]}%")
                         .distinct
  end
 
  private
 
  def set_competitor = Competitor.find(params[:competitor_id])
 
  def battle_card_params
    params.require(:battle_card).permit(:title, :version, :status, :notes)
  end
end

AWS DynamoDB によるバトルカードストレージ

「社内 Rails アプリは小規模チーム向けだ。でもエンタープライズ規模になると、リアルタイム更新と高速検索が必要になる」

カイが AWS のアーキテクチャを提案した。

Loading diagram...

テーブル設計と CRUD

DynamoDB のシングルテーブル設計を採用する。PK に COMPETITOR#<id>、SK に CARD#<version> を使い、GSI で全バトルカードの横断検索を可能にする。

# lib/aws/dynamo_battle_card.rb
require 'aws-sdk-dynamodb'
 
class DynamoBattleCard
  TABLE_NAME = 'battle_cards'
 
  def initialize
    @client = Aws::DynamoDB::Client.new(
      region: ENV.fetch('AWS_REGION', 'ap-northeast-1')
    )
  end
 
  def put_card(competitor_id:, version:, sections:, author:)
    @client.put_item(
      table_name: TABLE_NAME,
      item: {
        'pk' => "COMPETITOR##{competitor_id}",
        'sk' => "CARD##{version}",
        'gsi1pk' => 'CARD',
        'gsi1sk' => "#{competitor_id}##{version}",
        'sections' => sections,
        'author' => author,
        'updated_at' => Time.current.iso8601
      }
    )
  end
 
  def cards_for_competitor(competitor_id)
    @client.query(
      table_name: TABLE_NAME,
      key_condition_expression: 'pk = :pk AND begins_with(sk, :prefix)',
      expression_attribute_values: {
        ':pk' => "COMPETITOR##{competitor_id}",
        ':prefix' => 'CARD#'
      },
      scan_index_forward: false
    ).items
  end
end

DynamoDB Streams による通知

DynamoDB Streams を有効にし、Lambda でバトルカードの更新を検知して SNS 経由でチームに通知する。

# lib/aws/battle_card_stream_handler.rb
require 'aws-sdk-sns'
 
class BattleCardStreamHandler
  def self.process(event:, context:)
    sns = Aws::SNS::Client.new
    topic_arn = ENV.fetch('BATTLE_CARD_TOPIC_ARN')
 
    event['Records'].each do |record|
      next unless record['eventName'] == 'MODIFY'
 
      new_image = record['dynamodb']['NewImage']
      competitor = new_image.dig('pk', 'S')&.delete_prefix('COMPETITOR#')
 
      sns.publish(
        topic_arn: topic_arn,
        subject: "バトルカード更新: #{competitor}",
        message: "#{new_image.dig('author', 'S')} がバトルカードを更新しました。"
      )
    end
  end
end

INFO

DynamoDB Streams + Lambda + SNS の組み合わせにより、バトルカードが更新されると即座にチーム全体に通知が飛ぶ。ベイクオフ直前の最新情報共有に威力を発揮する。


ベイクオフ当日 — バトルカードの実戦投入

2 週間後、ミライテックの会議室。ソウタは Arclight AI の技術デモを担当した。

ミライテックの CTO が最初に質問した。

「御社のモデルは、GPU がない現場でも動くのですか?」

ソウタはバトルカードの トラップ質問 セクションを思い出した。実はこの質問こそ、Arclight AI が仕掛けた土俵だった。事前に提案した評価基準の中に「エッジ環境での動作」を含めておいたのだ。

「はい。Arclight AI は ONNX Runtime による CPU 推論を標準でサポートしています。実際にお見せします」

ソウタはノート PC で、GPU なしの環境でリアルタイム推論のデモを走らせた。推論速度は 42ms。ミライテックのエンジニアたちが身を乗り出した。

Know-Say-Show の Show だ。

「NeuralWorks さんも候補ですが、エッジ対応はどうなんでしょう」と CTO が呟いた。

ソウタは競合を直接攻撃しなかった。バトルカードの 反論対処 セクションにあった通り、自社の強みとして語った。

「Arclight AI は、クラウドとエッジのハイブリッド構成をリファレンスアーキテクチャとして提供しています。工場のネットワーク状況に関係なく、検査ラインを止めない設計です」


デモ終了後、ミライテックの技術チームからの質問は 1 時間以上続いた。ソウタはバトルカードに書いた FIA の構造で、すべての質問に「事実 → 影響 → 行動」の流れで答えた。

3 日後、カイから連絡が来た。

「ソウタ、ミライテックから連絡があった。Arclight AI に決まったよ」

「本当ですか!」

「決め手は 2 つ。エッジ環境での実動デモと、FDE による導入支援体制。まさにバトルカードで Winning Zone に設定した 2 つだ」

ソウタは深く息を吐いた。準備が勝利を生んだのだ。


まとめ — FDE は技術と戦略の交差点に立つ

カイが最後に語った。

「FDE の仕事は、コードを書くことでも、デモをすることでもない。プロダクトの技術的価値を、顧客の文脈で証明することだ」

バトルカードは、その証明を構造化するツールだ。

  • FIA で競合の弱点を事実ベースで整理する
  • Know-Say-Show で会話の準備をする
  • Winning/Battling/Losing でどこで戦うか決める
  • Win/Loss 分析 で継続的に精度を上げる

「技術力だけでは勝てない。戦略だけでも勝てない。両方を持っている FDE だから、ベイクオフで勝てるんだ」

ソウタは次のベイクオフに向けて、Win/Loss レコードにミライテックの勝因を記録し始めた。今日の勝利が、明日のバトルカードを強くする。