Database Design
WingStats Manager の SQLite スキーマと、electron/database/ 配下に分割された repository パターン の構成。
技術スタック
| 項目 | 選定 |
|---|---|
| DB | SQLite (better-sqlite3) |
| ORM | なし(生 SQL + prepared statements) |
| 配置場所 | Electron メインプロセス (electron/database/) |
| アクセス | IPC 経由で renderer から呼び出し |
Repository 構成
code-split-and-coverage spec で electron/database.ts (旧 868 行) を repository に分割し、その後の機能追加で増えて、今は Repository 8 つと StatsService。electron/database.ts は薄い facade(MatchDatabase)として残り、内部で各 repository を組み立てる。
electron/database/
├── DatabaseConnection.ts # 接続管理 + マイグレーション
├── MatchRepository.ts # matches / players / bans / picks
├── ProfileRepository.ts # profiles
├── StatsService.ts # 集計の facade(stats/ に委譲)
├── BattleListRepository.ts # browser 自動化由来の battle_list を matches に保存
├── SeasonRepository.ts # シーズン情報 (season_summaries / season_shows)
├── ApiResponseRepository.ts # API レスポンスの生キャッシュ
├── MilestoneRepository.ts # 記念スナップショット (milestone_snapshots)
├── CharacterStatsRepository.ts # 所持キャラ戦績スナップショット
├── SeasonFetchStateRepository.ts # 全シーズン取得の進捗 (season_fetch_state)
├── matchesSavedListeners.ts # 試合保存後に呼ぶ聞き手
├── match/ # 試合詳細・起動時取り込み・移行などの純関数
├── stats/ # StatsCalculator / ApiResponseWriter / MatchReconciler / 対戦・詳細クエリ
└── types.tsRepository とテーブルの対応
| Repository | 主な責務 | 扱うテーブル |
|---|---|---|
DatabaseConnection | 接続 + マイグレーション + トランザクション | (全) |
MatchRepository | 試合の保存 / 取得 / 削除 | matches, players, bans, picks |
ProfileRepository | プロファイル管理 | profiles |
StatsService | 統計集計と、API 詳細の書き戻し・ログ/API 試合の突き合わせ(stats/ に委譲) | matches, players, bans, picks |
BattleListRepository | browser 自動化の battle_list を matches / players に保存。API だけの重複行は子テーブルごと消す | matches, players(削除時は bans, picks も) |
SeasonRepository | シーズン管理 | season_summaries, season_shows |
ApiResponseRepository | API レスポンスの生キャッシュ | api_responses |
MilestoneRepository | 記念スナップショット(節目ごとに 1 行追記) | milestone_snapshots |
CharacterStatsRepository | 所持キャラ戦績スナップショット(追記のみ) | character_stat_snapshots, character_stat_entries, character_ownership_entries |
SeasonFetchStateRepository | 全シーズン取得の進捗(role_id × season_code) | season_fetch_state |
純関数クエリ層
electron/database/match/matchDetailQueries.ts は Database インスタンスを引数に取る純関数群 (fetchMatchPlayers / fetchMatchBans / fetchMatchPicks / fetchMatchDetail)。MatchRepository から利用する。テストでは in-memory better-sqlite3 を渡せる。
テーブル設計
matches - 試合テーブル
| カラム名 | 型 | 説明 |
|---|---|---|
id | TEXT PRIMARY KEY | 試合 ID(log 由来は timestamp ベース、browser 由来は BattleId と同値) |
battle_id | TEXT, NULL 許可, UNIQUE | 戦績サイト由来の BattleId。log 由来は saveBattleDetails の照合成功時に書き戻し |
timestamp | TEXT | 試合開始日時(ISO8601) |
date | TEXT | 日付(YYYY-MM-DD) |
time | TEXT | 時刻(HH:MM:SS、後付けマイグレーション) |
type | TEXT | 試合タイプ(log 由来: Rank_2v2 等 / browser 由来: mapBattleTypeId() 経由) |
duration | INTEGER | 試合時間(秒) |
log_file | TEXT | 元ログファイルパス |
created_at | TEXT | DB 登録日時 |
照合フォールバック階段(BrowserAutomationService が battle_detail を保存する経路):
findMatchByBattleId(BattleId)—matches.battle_id直引き(決定的)findMatchByOpponentUids(opponents, StartTime)— 対戦相手 UID 集合一致 + 最近接時刻- miss なら
saveBattleDetails(matchId 未指定)— 時刻 ±5 分窓 + UID 一致による UPDATE
players - プレイヤー結果テーブル
基本カラム
| カラム名 | 型 | 説明 |
|---|---|---|
id | INTEGER PRIMARY KEY | 自動採番 ID |
match_id | TEXT | 試合 ID(FK: matches.id) |
uid | TEXT | プレイヤー UID |
name | TEXT | プレイヤー名 |
team | TEXT | チーム('A' or 'B') |
result | TEXT | 結果('Win' or 'Lose') |
character_id | INTEGER | 使用キャラ ID |
available_character_ids | TEXT (JSON 配列) | 所持キャラ 4 枠(spec: available-chars) |
戦績詳細カラム(API 連携時に取得)
| カラム名 | 型 | 説明 | 対応する JSON |
|---|---|---|---|
kill_count | INTEGER | 撃破数 | BattleInfo.BeatCnt |
death_count | INTEGER | 被撃破数 | BattleInfo.BeatedCnt |
damage | INTEGER | 与ダメージ | BattleInfo.ExportDamage |
score_before | INTEGER | 試合前スコア | UserInfo.ScoreBefore |
score_after | INTEGER | 試合後スコア | UserInfo.ScoreAfter |
is_mvp | INTEGER | MVP 判定(0/1) | UserInfo.IsMvp |
rank_id | INTEGER | 段位 ID | UserInfo.DaDuanId |
awake_kill_count | INTEGER | 覚醒撃破数 | BattleInfo.BeatAwakeCnt |
friendly_kill_count | INTEGER | 味方撃破数 | BattleInfo.BeatFreidnCnt |
debuff_clear_count | INTEGER | デバフ解除数 | BattleInfo.Debarrass |
avatar_url | TEXT | アバター URL | UserInfo.Avatar |
ログ解析データには戦績詳細が含まれないため、これらは NULL 許容。BrowserAutomationService の API 連携時に UPDATE される。
bans - BAN テーブル
| カラム名 | 型 | 説明 |
|---|---|---|
id | INTEGER PRIMARY KEY | 自動採番 ID |
match_id | TEXT | 試合 ID(FK: matches.id) |
character_id | INTEGER | BAN されたキャラ ID |
banner_uid | TEXT | BAN したプレイヤー UID |
banner_name | TEXT | BAN したプレイヤー名 |
team | TEXT | チーム('A' or 'B') |
picks - PICK テーブル
| カラム名 | 型 | 説明 |
|---|---|---|
id | INTEGER PRIMARY KEY | 自動採番 ID |
match_id | TEXT | 試合 ID(FK: matches.id) |
character_id | INTEGER | ピックされたキャラ ID |
player_uid | TEXT | プレイヤー UID |
player_name | TEXT | プレイヤー名 |
team | TEXT | チーム('A' or 'B') |
is_candidate_fallback | INTEGER | 候補絞り込み fallback で決まった Pick かどうか(0/1) |
candidate_character_ids | TEXT (JSON 配列) | fallback 時の候補キャラ ID 配列 |
命名規約: snake_case ↔ camelCase
DB 列名は snake_case、TypeScript 側は camelCase。SELECT 時に AS 句で変換する。
SELECT character_id AS characterId, player_uid AS playerUid FROM picksこれを怠ると、UI 側で「データなし」表示になる典型バグが発生する。
IPC API
renderer (window.electronAPI.db.*) から呼ぶ主要メソッド。詳細は Electron Main と electron/preload/api/db.ts を参照。
db.startupImportPreview() // 監視フォルダの未取り込みログをプレビュー
db.startupImportRun() // プレビュー分を解析して DB に保存
db.getMatches(options?)
db.getMatchDetail(matchId)
db.saveMatchesSkipExisting(matches) // 解析済み試合をまとめて保存(既存 ID はスキップ)
db.getCharacterStatsByUid(uid)
db.getOpponentAvailableCharacterStats(uid)
db.getWinRateTrend(uid, days?)
db.deleteMatch(matchId)
db.reconcileLogApiMatches(options?) // log 由来 / API 由来の試合を照合処理フロー
ログインポートフロー
戦績詳細取得フロー(ブラウザ自動化)
データソースの違い:
- ログ解析: 試合基本情報、チーム構成、BAN/PICK、勝敗のみ
- API 連携: 上記 + K/D/A、ダメージ、スコア変動、MVP 等の詳細戦績
ファイル構造
electron/
├── database.ts # facade(MatchDatabase)
├── database/ # 中身は「Repository 構成」の図を参照
│ ├── DatabaseConnection.ts
│ ├── *Repository.ts # Repository 8 つ
│ ├── StatsService.ts
│ ├── matchesSavedListeners.ts
│ ├── match/
│ ├── stats/
│ ├── types.ts
│ └── __tests__/
└── preload/api/db.ts # 公開 APIテスト戦略
- In-memory better-sqlite3: repository テストは
:memory:で接続を作り、各テストで fresh schema を立てる - 純関数クエリ層 (
matchDetailQueries.ts) は DB インスタンスを DI で受けるため、in-memory DB を直接渡せる - 既存テスト:
electron/database/__tests__/,electron/database/match/__tests__/
今後の拡張候補
- [ ] K/D/A 統計の計算・表示
- [ ] スコア推移グラフ
- [ ] 平均ダメージ・MVP 率の表示
- [ ] データエクスポート機能(CSV / JSON)
- [ ] log の自動監視・自動インポート(spec:
realtime-log-monitoring)
Last verified: 2026-06-01 / commit 12bb761f