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 行) を 6 つの repository に分割。electron/database.ts は薄い facade として残り、内部で各 repository を組み立てる。
electron/database/
├── DatabaseConnection.ts # 接続管理 + マイグレーション
├── MatchRepository.ts # matches / players / bans / picks
├── ProfileRepository.ts # profiles / settings
├── StatsService.ts # 集計クエリ (Match から派生)
├── BattleListRepository.ts # browser 自動化由来の battle_list キャッシュ
├── SeasonRepository.ts # シーズン情報
├── ApiResponseRepository.ts # API レスポンスの生キャッシュ
├── match/
│ └── matchDetailQueries.ts # 試合詳細の純関数クエリ群
├── index.ts
└── types.tsRepository とテーブルの対応
| Repository | 主な責務 | 扱うテーブル |
|---|---|---|
DatabaseConnection | 接続 + マイグレーション + トランザクション | (全) |
MatchRepository | 試合の保存 / 取得 / 削除 | matches, players, bans, picks, log_files |
ProfileRepository | プロファイル / ユーザー設定 | profiles, settings |
StatsService | 統計集計(read-only) | matches, players, bans, picks |
BattleListRepository | browser 自動化のリストキャッシュ | battle_list_cache |
SeasonRepository | シーズン管理 | seasons |
ApiResponseRepository | API レスポンスの生キャッシュ | api_responses |
純関数クエリ層
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 配列 |
log_files - ログファイル管理テーブル
| カラム名 | 型 | 説明 |
|---|---|---|
id | INTEGER PRIMARY KEY | 自動採番 ID |
file_path | TEXT UNIQUE | ログファイルパス |
file_name | TEXT | ファイル名 |
file_size | INTEGER | ファイルサイズ(bytes) |
match_count | INTEGER | 検出した試合数 |
imported_at | TEXT | インポート日時 |
status | TEXT | ステータス('pending' / 'imported' / 'error') |
命名規約: 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.analyzeAndSaveLog(filePath, myUid?)
db.getLogFiles()
db.deleteLogFile(fileId)
db.getMatches(options?)
db.getMatchDetail(matchId)
db.getPlayerStats(uid?)
db.getCharacterStats(type?)
db.getDashboardSummary(uid?)
db.getWinRateTrend(uid, days?)処理フロー
ログインポートフロー
戦績詳細取得フロー(ブラウザ自動化)
データソースの違い:
- ログ解析: 試合基本情報、チーム構成、BAN/PICK、勝敗のみ
- API 連携: 上記 + K/D/A、ダメージ、スコア変動、MVP 等の詳細戦績
ファイル構造
electron/
├── database.ts # 薄い facade(旧 868 行 → 335 行)
├── database/
│ ├── DatabaseConnection.ts
│ ├── MatchRepository.ts
│ ├── ProfileRepository.ts
│ ├── StatsService.ts
│ ├── BattleListRepository.ts
│ ├── SeasonRepository.ts
│ ├── ApiResponseRepository.ts
│ ├── match/matchDetailQueries.ts
│ ├── index.ts
│ ├── 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-05-06 / against commit e351b23a