Amazon Fraud Detector (AFD)のスコアリング結果を、Ruby on Rails SDK使用して取得する方法

# 前書き
現在、**Amazon Fraud Detector (AFD)** を活用した不正検知システムを開発しています。
本記事では、`Ruby on Rails`の **SDK** を使用して`AFD`のスコアリング結果を取得する方法について、備忘録も兼ねてまとめます。


# Amazon Fraud Detector (AFD)の概要
- 機械学習を活用して不正行為をリアルタイムで検出するAWSのサービス
- 開発者は、過去の不正データを基にカスタムモデルを作成し、不正取引やアカウント乗っ取りなどを検出できる
- 高度な不正検知システムを簡単に導入できる

## 主な特徴
- 自動機械学習（AutoML）：コード不要で不正検知モデルの構築が可能
- リアルタイムで不正リスクを判定：ユーザーのアクションごとにAIが即座に不正リスクをスコア化
- カスタムルールで自動対応：事前に設定したルールを基に、不正の疑いがある場合にアラートやブロックを実行
- 他のAWSサービスとの連携：`Amazon S3`、`AWS Lambda`、`Amazon SNS`等と連携可能

## 活用例
- 決済詐欺の防止
- 偽アカウントの検出
- プロモーション不正の抑制


# 前提条件
- Ruby on Railsの環境が構築済み
- AFD上で、モデルとディテクターが構築済み


# 構築手順
:::note warn
注意
- 本記事では、AFDに送信するデータを「**メールアドレス**」と「**IPアドレス**」のみに限定し、それらの情報を基にスコアリングを算出する構成としている
- その他の属性は送信せず、本記事の範囲外とする
:::

## 設定関連

### Gemfile
- `Gemfile`に下記のGemを追加した後、`bundle exex install`を行う

```Gemfile
# aws-sdk
gem 'aws-sdk-frauddetector'
```

### マイグレーション

- AFDの結果を保存する為、マイグレーションファイルを作成する
```sh
rails generate migration CreatAfdScores
```

- マイグレーションファイルを修正した後、マイグレーションを実行する
```20250321000000_create_afd_scores.rb
class CreatAfdScores < ActiveRecord::Migration[8.0]
  def change
    create_table :afd_scores do |t|
      t.integer :score, null: false
      t.string :judgment_result, null: false
      t.timestamps
    end
  end
end
```

- マイグレーションの実行コマンド
```sh
rails db:migrate
```

### 環境変数(credentials.yml.enc)

- `credentials.yml.enc`に、以下の設定を追加する
```credentials.yml.enc
afd_aws:
  region:            AFDが使用できるリージョン
  access_key_id:     A******************7
  secret_access_key: I**************************************S
```

## 構築関連

### コントローラーの設定
- 以下の処理を任意のコントローラーの`new` or `create`メソッド内に記載する
（通常、新規データの作成時にスコアリングを行う為、`new`や`create`での実装が推奨される）

```任意のcontroller.rb
# AfdClientのインスタンスを作成
afd_client = AfdClient.new
result = afd_client.get_score(
  email:      base.email,
  ip_address: base.ip
)

# afd_socersテーブルにデータを保存する
AfdScore.create!(
  score:           result[:score],
  judgment_result: result[:judgment_result]
)
```

### サービスレイヤーの設定

- `app/services/`に **afd_client.rb** ファイルを作成する
- 以下のコードを`afd_client.rb`に記載する
```afd_client.rb
require 'aws-sdk-frauddetector'

class AfdClient

  # RailsとAFDの相互通信を行う為の認証処理。
  def initialize
    @client = Aws::FraudDetector::Client.new(
      # credentials.yml.encからAWSの認証情報を取得
      region: Rails.application.credentials.afd_aws[:region],
      credentials: Aws::Credentials.new(
        Rails.application.credentials.afd_aws[:access_key_id],
        Rails.application.credentials.afd_aws[:secret_access_key]
      ),
      http_open_timeout: 10, # 接続開始までのタイムアウトを設定
      http_read_timeout: 10  # データ読み取りのタイムアウトを設定
    )
  end

 def get_score(email:, ip_address:)
 
    begin
    # AFDに通信して、結果を取得
    response = @client.get_event_prediction(
      detector_id: "構築したdetector_idを記載",
      event_id: SecureRandom.uuid,
      event_type_name: "構築したイベントの名前を記載", 
      event_timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
      entities: [
        {
          entity_type: "AFDで構築したエンティティタイプ名を記載", 
          entity_id: "エンティティタイプ名_#{Digest::SHA256.hexdigest(email)}" 
        }
      ],
      event_variables: {
        "email"      => email,
        "ip_address" => ip_address
      }
    )
    rescue Aws::FraudDetector::Errors::ServiceError, Net::OpenTimeout, Net::ReadTimeout => e
      Rails.logger.error(e.message, email, ip_address)
      return { score: 0, judgment_result: "error" }
    end    

    score = response.model_scores.first&.scores.values.first || 0
    result = response.rule_results.first&.outcomes&.first || "unknown"

    return { 
      score: score,
      judgment_result: result
    }

  rescue => e
    Rails.logger.error("AFD Error: #{e.message}")
    { score: 0, judgment_result: "error" }
  end

end
```

---------------------------------------

**get_event_predictionメソッドで設定するパラメータと説明**
| パラメータ | 説明 |
| :-: | - |
| detector_id<br>型 : String | AFDに設定したDetector ID。どの不正検知モデルを使用するかを指定する<br><br>入力例<br>`"my_fraud_detector_v1"` |
| event_id<br>型 : String | 各リクエストごとに一意のIDを割り当てる為の識別子。通常、`SecureRandom.uuid`を使用する<br><br>入力例<br>`"123e4567-e89b-12d3-a456-426614174000"` |
| event_type_name<br>型 : String | AFDに設定したイベントの種類。どのイベント定義を使用するかを指定する<br><br>入力例<br>`"transaction_event"` |
| event_timestamp<br>型 : String | イベントが発生した時刻<br><br>入力例<br>`"2025-03-13T12:34:56Z"` |
| entities<br>型 : Array(Hash化) | 不正検知の対象となる ユーザーや取引（[エンティティ](https://docs.aws.amazon.com/ja_jp/frauddetector/latest/ug/entity.html)） を識別する為の情報を設定する<br><br>入力例<br>`[ { entity_type: "customer", entity_id: "customer_abcdefg" } ]` |
| event_variables<br>型 : Hash | 不正リスクを判定する為に、取引やアクセス情報（イベント変数）をAFDに送信する<br><br>入力例<br>`{ "email" => "test@example.com", "ip_address" => "192.168.1.1" }` |

---------------------------------------

**補足**
`app/services/`の用途
- モデル (`app/models/`) に直接書くべきでない複雑な処理を分ける
- MVCの「サービス層」として利用し、コードの再利用性と可読性を向上させる
- Fat Controller（肥大化したコントローラ）やFat Modelを防ぐ為に使用する

# 参考資料
- [[AWS公式] Amazon Fraud Detector ]()
- [[AWS公式] Class: Aws::FraudDetector::Client](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/FraudDetector/Client.html)
- [[AWS公式]get_event_prediction-instance_method](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/FraudDetector/Client.html#get_event_prediction-instance_method)
- [[GitHub 公式] aws-sdk-ruby](https://github.com/aws/aws-sdk-ruby)


# 感想
今回は、AFDを活用した Railsの処理を構築しました。
特に、例外処理の実装が難しく、試行錯誤を重ねました。

また、本記事では触れていませんが、RSpecのテスト実装に最も苦戦しました。
RSpecについては、今後も引き続き学習し、理解を深めていきたいと思います。