はじめに
Webサービスで「このユーザーはどれくらい信頼できるか」を数値化したいと思ったことはないでしょうか。
プロフィールリンクサービス myna.me では、OAuth認証の数や活動実績を元に**Trust Score(トラストスコア)**という0〜100のスコアを算出しています。v6で大幅に設計を見直したので、その計算ロジックと設計思想を共有します。
Trust Scoreとは
ユーザーのプロフィール信頼度を0〜100で数値化するスコアです。課金では上がらないことが重要な設計原則です。
スコアに応じて12段階の「級」(十二級〜一級)と6つのレイヤー(Noise → Signal → Channel → Broadcast → Beacon → Origin)に分類されます。
スコア計算の5カテゴリ
| カテゴリ | 配点 | 計算方法 |
|---|---|---|
| 本人認証 Identity | 0-15 | ステップ関数:1接続=8, 2接続=12, 3+=15 |
| プロフィール完成度 | 0-10 | アバター(3)+bio(3)+subtitle(2)+位置(1)+タイムライン(1) |
| アカウント成熟度 | 0-15 | 指数関数的減衰 15*(1-e^(-days/180)) |
| SNS貢献度 | 0-25 | プラットフォーム数+多様性+発信力 |
| レピュテーション | 0-35 | 時間重み付きlog2圧縮 |
合計100点満点です。
実装のポイント
1. 指数関数的減衰でアカウント成熟度を評価
const maturityScore = Math.round(
15 * (1 - Math.exp(-accountAgeDays / 180))
);
登録直後は急速にスコアが上がり、180日を過ぎると緩やかに飽和します。
2. 時間重み付きでレピュテーションを計算
いいねやコネクションは「いつ受けたか」で重みが変わります。
const TIME_BUCKETS = [
{ days: 30, weight: 1.0 },
{ days: 90, weight: 0.7 },
{ days: 180, weight: 0.4 },
{ days: Infinity, weight: 0.2 },
];
3. log2圧縮で大量操作を抑制
const likeScore = Math.min(10,
Math.round(Math.log2(weightedLikes + 1) * 2.5)
);
log2圧縮により、いいね1→10は効果大ですが、100→1000はほぼ変わりません。
4. ネガティブサインによるペナルティ
送信者のTrust Scoreで重み付けされたペナルティを適用します。信頼度の高いユーザーの判断がより重視されます。
12級システム(Signal Path)
スコアを12段階の「級」に変換します。DBにはスコア(integer)のみ保存し、級はスコアから都度算出します。級の定義を変更してもデータマイグレーション不要です。
const GRADE_DEFINITIONS = [
{ grade: 12, min: 0, max: 15, layer: "Noise" },
{ grade: 11, min: 16, max: 29, layer: "Noise" },
{ grade: 10, min: 30, max: 44, layer: "Signal" },
{ grade: 1, min: 97, max: 100, layer: "Origin" },
];
キャッシュ戦略
Trust Scoreの計算はDB集計を伴うため、usersテーブルにキャッシュカラム(trust_score, trust_score_breakdown jsonb)を持ち、fire-and-forgetで非同期更新します。OG画像生成(Edge Runtime)はキャッシュから読み取り、高速レスポンスを実現しています。
設計原則
- 課金で買えない — Pro会員であることはスコアに影響しない
- 時間で劣化する — 古い評価への依存を防ぐ
- 大量操作に耐える — log2圧縮で線形スケールを防止
- 計算可能 — ユーザーに内訳を公開し、改善方法を提示
- 軽量 — DBキャッシュで表示時は参照のみ
まとめ
Trust Scoreは「お金で買えない信頼の指標」として設計しました。指数関数的減衰、時間バケット重み付け、log2圧縮の3つの数学的手法が、ゲーミング耐性の高いスコアリングを実現しています。