概要
NetTree のリーグ機能は、1週間ごとの XP をもとにしたリーグ制ランキングです。- すべてのユーザーは 1 週間単位の「リーグシーズン」に参加します
- 同じティア・同じ週のユーザーは、最大 50 人のグループに分かれて競います
- 週の終わりに 昇格 / 降格 / 残留 が判定され、次の週のティアとグループが決まります
- ランキングは
/dashboardと/rankingから閲覧できます
ティア構成
リーグの階級は、次の 7 段階です。- Bronze(ブロンズ)
- Silver(シルバー)
- Gold(ゴールド)
- Platinum(プラチナ)
- Diamond(ダイアモンド)
- Master(マスター)
- Specialist(スペシャリスト)
league_tier という enum として管理されています。
週次サイクル
週の単位
週はYYYY-Www(例: 2024-W05)という ISO 週番号形式の ID で管理されます。
- 月曜 0:00 に週が始まり、日曜 23:50 までを 1 週間とみなします
- 各週は
week_cycleテーブルで管理され、isActive = trueのレコードが「現在開催中の週」です
週の開始と終了
scripts/process-league-transition.ts によって、週次の切り替えとリーグ再編成が行われます。
ざっくりとした処理の流れは次の通りです。
- 現在アクティブな週(前週) を取得
- 現在日時から ターゲット週 ID(今週) を算出し、
week_cycleを作成 or 更新 - 前週が別 ID の場合は、前週の
isActiveをfalseに更新 - ターゲット週を
isActive = trueとしてアクティブ化
ユーザー体験とルール
参加タイミング
- ユーザーがレッスン完了などで XP を獲得したタイミングで、
league-service.tsのaddWeeklyXpが呼ばれます - その週のリーグに未参加の場合、自動的に Bronze ティアのグループに参加します
- 参加後、同じ週・同じグループ内での
weeklyXpが加算されていきます
ランキングとゾーン
各グループ内で、XP の多い順に並べてランキングを作成します。- ランキングは
/rankingページで 50 人まで表示されます - ダッシュボード(
/dashboard)では、上位 10 人をカード形式で抜粋して表示します - UI 上では、順位に応じて以下のゾーンに色分けされます
- 昇格ゾーン(Top 10): 次のティアへ昇格候補
- 残留ゾーン(11〜30位): 現在のティアに残留
- 降格ゾーン(31位以降): 1 ティア降格候補
昇格 / 降格ルール
週末のバッチ(scripts/process-league-transition.ts)で、前週の実績を元に昇格・降格が判定されます。
- 各グループ内で XP 順に並べ、上位から以下を判定
- 昇格枠:
PROMOTION_SLOTS = 10→ 上位 10 人(人数が少ない場合はグループ人数まで) - 降格枠:
DEMOTION_SLOTS = 10→ 下位 10 人(昇格者を除いた中から最大 10 人)
- 昇格枠:
- ティアの境界チェック
- すでに最上位ティアの場合は昇格しません
- すでに最下位ティアの場合は降格しません
- 判定結果は
league_participantに保存されますfinalRank: その週の最終順位isPromoted: 昇格したかどうかisDemoted: 降格したかどうか
次の週のグループ編成
新しい週のグループは、次のルールで編成されます。- すべてのユーザーを取得
- 前週に参加していたユーザーは、昇格 / 降格結果に応じた 次のティア を決定
- 前週に参加していないユーザーは、デフォルトで Bronze ティア に配置
- ティアごとにユーザーをシャッフルし、最大 50 人ずつグループを作成
データモデル
リーグ機能に関連する主なテーブルは次の通りです。week_cycle
1 週間のリーグサイクルを表します。
id: 週 ID(2024-W05など)startDate,endDate: 週の開始・終了日時isActive: 現在開催中かどうか
league_group
同じティア・同じ週の 最大 50 人の対戦グループ を表します。
id: グループ IDweekCycleId: 紐づく週 (week_cycle.id)tier: ティア(bronze〜specialist)
league_participant
各週・各グループにおける ユーザーの参加状況 を表します。
id: 参加レコード IDuserId: ユーザー IDleagueGroupId: 参加しているグループ IDweeklyXp: その週に獲得した XPisPromoted: その週の結果として昇格したかisDemoted: その週の結果として降格したかfinalRank: その週の最終順位joinedAt: その週のリーグに参加した日時
関係性
week_cycle1 件に対して、複数のleague_groupが紐づきますleague_group1 件に対して、最大 50 件のleague_participantが紐づきますleague_participantはuserと 1 対 1 で結び付けられます(その週・そのグループにおける一意の参加情報)
処理フロー
学習時の XP 加算と参加処理
ユーザーがレッスンを完了すると、XP が加算されると同時にリーグ関連の処理が走ります。週次リーグ遷移バッチ
scripts/process-league-transition.ts は、週替わりで以下の処理を行います。
画面との対応
/dashboard
ダッシュボードでは、リーグ情報は次のように利用されています。
- 上部カードで「現在のリーグ」(例: ブロンズリーグ)と 週の残り時間 を表示
- サイドのランキングカードで、上位 10 人の簡易リスト を表示
- 自分の順位に応じて、バッジや色(昇格 / 降格ゾーン)を変化させてモチベーションを演出
/ranking
ランキングページでは、現在所属しているグループ内の詳細なランキングを表示します。
- ヘッダー: 現在のティアと、ティア間の進行状況(Prev / Current / Next)を Duolingo 風に表示
- 残り時間ウィジェット: 週の終了までのカウントダウン
- テーブル形式のランキング:
- Rank / 名前 / ステータス(昇格・降格・残留)/ XP
- 自分の行はハイライト表示
- グループに未参加の場合は、「ランキングに参加していません」とメッセージを表示し、学習開始を促す
関連ファイル
- スキーマ
src/infrastructure/db/schema/league-schema.ts
- サービス
src/services/league-service.ts
- ミドルウェア / API
src/middlewares/get-league-ranking.ts
- バッチ / スクリプト
scripts/process-league-transition.tsscripts/update-weekly-xp.ts(デモ用の XP 更新スクリプト)
- ルート / UI
src/routes/dashboard.tsxsrc/routes/ranking.tsx