Skip to content

データソース連携インベントリ

spec: data-integration-inventory commit: 28ee7670 / 2026-05-05

0. Summary

WingStats Manager は試合データを 2 系統 から取り込む:

  1. ローカルゲームログStreamLogParserV2 / LogParser 経由、ローカルファースト)
  2. 戦績サイト(ブラウザ自動化)(Playwright 経由、オプトイン)

両者は findMatchByOpponentUids または時刻 ±5 分窓での照合により players テーブル上で 1 行にマージされる。本ドキュメントはそれぞれが何を持っているか・何を持っていないか・現状の DB 列との対応関係を網羅的に整理し、次の改修 spec の素材を提供する。

結論サマリー:

  • ローカルログのみが持つ: BAN / Pick の細部、所持キャラ 4 枠 (available_character_ids)、TYPE マーカーによる Rank/Normal 確定判定、log_file への trace
  • ブラウザのみが持つ: BattleId、Score (rank score) before/after、avatar URL、damage/kill/death の正確な数値、awakening kill 等の戦績詳細、シーズン横断 summary
  • 両方あるが粒度が違う: 試合 type(log は文字列、browser は BattleType 数値)、結果(log は Win/Lose、browser は BattleResult 数値 + WinnerDetails/LoserDetails の team 構造)
  • 照合の弱点: findMatchByOpponentUids完全一致(対戦相手 UID 集合の正確一致)+ 直近時刻でのみ動く。saveBattleDetails(matchId 未指定経路)は 時刻 ±5 分窓 + UID 一致。複数プロファイル / 同一相手連戦 / battle_list 取得遅延に弱い

1. データソース定義

1.1 ローカルログ系

ゲームクライアントが出力する 日付.txt ログを chokidar(リアルタイム)または一括解析で読む。

経路入力出力(型)出典
一括解析ログファイル全文AnalysisResult { matches: MatchData[] }src/utils/logAnalyzer/LogParser.tstypes.ts:138
ストリーム行追加イベントMatchData(試合確定時に emit)src/utils/logAnalyzer/core/fsm/StreamFsm.tscore/aggregator/Aggregator.ts
パース行1 行LineEventsrc/utils/logAnalyzer/core/line/LineParser.ts

得られる主な情報types.ts:33-103):

  • 試合メタ: index, timestamp, type (Rank/Normal/null)、startTimetimestampLine
  • BP データ: players[] {name, id, team, availableCharacterIds[]}bans[] {characterId, bannerName, bannerId, bannerTeam}picks[] {characterId, playerName, playerId, team}
  • 試合結果: players: PlayerResult[] {name, result: 'Win'|'Lose', id?, availableCharacterIds?}
  • フェーズ: IDLE/BP/LOADING/INGAME/RESULTMatchPhase / types.ts:108

得られない情報(コード上明示なし):

  • BattleId(戦績サイト固有)
  • Score (rank score) before/after
  • damage / kill / death の 正確な数値(log は kill 通知のテキストを抽出する経路があるが、数値の信頼性は browser 経由が高い前提)
  • avatar 画像 URL
  • シーズン情報

1.2 ブラウザ自動化系

Playwright で xzysteam.shengtiangames.com の戦績ページを叩いて raw JSON を取得し、api_responses テーブルに保存しつつ、構造化したものを matches / players / season_summaries / season_shows に流し込む。エンドポイントごとに 4 種類の入力。

battle_list (試合一覧)

reseponses/battle_list.json:data[] のサンプル:

json
{
  "BattleId": "1771323294000000112",
  "BattleResult": 0,           // 0=負け、1=勝ち想定
  "StartTime": 1771323357,     // Unix 秒
  "RoundTime": 147,            // 試合時間(秒)
  "BattleType": 22,            // 試合タイプ(22 = 2v2 ランク?)
  "BeatHeroNum": 0,            // 自分のキル数
  "DeathHeroNum": 2,           // 自分のデス数
  "TotalDamage": 2830,
  "IsMvp": false
}

型: BattleListItemelectron/database/types.ts:158

matchesid = BattleId)と players(自分行のみ)を INSERT OR IGNORE で作る。saveBattleListItems() (electron/database.ts:325)。

battle_detail (1 試合の詳細)

reseponses/battle_detail.json:data 構造(要旨):

data
├── BattleType
├── WinnerDetails: [PlayerEntry...]
└── LoserDetails:  [PlayerEntry...]

PlayerEntry
├── BattleInfo
│   ├── BeatAwakeCnt        // 覚醒中キル
│   ├── BeatCnt              // 通常キル
│   ├── BeatedCnt            // 被キル
│   ├── BeatFreidnCnt        // 味方への誤射数(typo は API 側)
│   ├── Debarrass            // デバフ解除回数
│   ├── ExportDamage         // 与ダメ
│   └── Role
│       ├── code, id, RoleCode
│       ├── name, name_jp, name_ko, name_tw, english_name
│       ├── img / img_jp / img_en / img_tw / img_preview
│       ├── battle_img_left / battle_img_right / battle_img_2v2_bg
│       ├── avatar_link / avatar_link_balance / avatar_link_xcx
│       ├── images.role_lh_img
│       ├── properties (JSON 文字列で title / cv / power / health / role_intro 等)
│       └── cost, sort, status
└── UserInfo
    ├── Avatar               // ユーザーアバター URL
    ├── DaDuanId             // 段位 ID
    ├── Id                   // ユーザー UID(数値)
    ├── IsMvp
    ├── Name
    ├── ScoreAfter
    └── ScoreBefore

saveBattleDetails(BattleDetail) (electron/database.ts:316、本体は electron/database/StatsService.ts:265-345)。UPDATE players SET kill_count, death_count, damage, score_before, score_after, is_mvp, rank_id, character_id, awake_kill_count, friendly_kill_count, debuff_clear_count, avatar_url WHERE match_id = ? AND uid = ?character_id も同時に確定させる(saveBattleListItems では NULL で挿入されるため)。

型: BattleDetailelectron/database/types.ts:268)。

season_list / battle_season_summary

reseponses/seasonlist.json:data[] { Code, Name, StartTime, EndTime } — シーズン一覧。

reseponses/battle-season-summary.json:data { T1, T2 } — チームタイプ別(T1=2v2, T2=1v1 想定)の現シーズン累計。各チーム:

ArenaScore | GradeDanId | MvpCnt | TotalCnt | WinRate

saveSeasonSummary / saveSeasonShowelectron/database.ts:769-771)。season_summaries / season_shows テーブル(SeasonSummaryRow / SeasonShowRowtypes.ts:185-237)。

winrate.json / memo.json(キャラマスタ)

戦績サイト由来のキャラ別 winrate / pick rate / ban rate。role_code ベース。これは試合データではなく キャラクターマスタ更新yarn update-characters)に使うので本 inventory のスコープ外。

1.3 DB スキーマ

electron/database/DatabaseConnection.tsCREATE TABLE 群(要旨。詳細は docs/database-design.md):

テーブル主要列書き込み元
matchesid (TEXT PK), timestamp, date, time, type, duration, log_file, created_atlog + browser(saveBattleListItems は INSERT OR IGNORE)
playersid AI, match_id, uid, name, team (A/B), result, character_id, available_character_ids (JSON), kill_count, death_count, damage, score_before, score_after, is_mvp, rank_id, awake_kill_count, friendly_kill_count, debuff_clear_count, avatar_urllog(先に挿入)+ browser(saveBattleDetails で UPDATE)
bansid AI, match_id, character_id, banner_uid, banner_name, teamlog のみ
picksid AI, match_id, character_id, player_uid, player_name, teamlog のみ
season_summariesid AI, captured_at, captured_date, t{1,2}_arena_score / grade_dan_id / mvp_cnt / total_cnt / win_rate, raw_jsonbrowser のみ
season_showsid AI, captured_at, captured_date, t{1,2}_max_score / arena_score / total_cnt / win_cnt / win_rate / score_map, top_role_list, raw_jsonbrowser のみ
api_responsesid AI, url, status, content_type, timestamp, endpoint, body, is_json, json_code, json_msgbrowser のみ(ResponseStorageService
profilesid AI, name, uids (JSON), player_names (JSON), log_dir, is_default, created_atUI(profile manager)
log_files(未確認、CHANGELOG では言及あり)log(startup-import)

2. フィールド対応表

凡例: 取得・保存される / 取れない / 部分的・条件付き / 該当なし

2.1 試合メタ

項目logbattle_listbattle_detailDB 列 (matches)UI 利用備考
試合 ID ローカル生成(timestamp ベース) BattleIdmatches.id (TEXT)MatchListTab、MatchDetaillog 経路は内部 ID、browser 経路は BattleId をそのまま使う。両者の一致は照合ロジック頼み
試合開始時刻 timestamp (ISO) StartTime (Unix 秒) data.StartTime (Unix 秒)matches.timestamp (TEXT ISO)全画面単位差: log は ISO 文字列、browser は Unix 秒 → ISO 変換(saveBattleListItems 内)。誤差確認は §4
日付 date (YYYY-MM-DD) StartTime から導出matches.dateMatchListTab フィルタ
時刻 (time) 別列に保存している経路ありmatches.time(未調査)log のみ
試合 type (Rank/Normal) TYPE マーカー優先 → BP数据日志 → プレイヤー数 BattleType(数値、対応マップ要) BattleTypematches.type (TEXT)MatchListTab、PlayerDashboardlog は 'Rank'/'Normal'/null、browser は 22 等の数値 → 文字列マッピングが必要(コード上どこで変換しているか要再確認)
duration(試合時間) log では明確に取れない RoundTime (秒)(直接フィールドなし)matches.duration (INTEGER)(未調査)browser の RoundTime が信頼できる
log_file 生ログのファイルパスmatches.log_fileDB ビューア、デバッグtrace 用途
BattleId(戦績サイト)(URL から逆引き、BrowserAutomationService.ts:451matches.id(browser 経路)log 経由で作った match に BattleId は 付かない(次の改修候補)
BattleResult(自視点 win/lose 数値) 自分の result で判定可 BattleResult (0/1) Winner/Loser に分かれているので集計可players.result('Win'/'Lose')に変換MatchListTab、Dashboardlog は文字列、browser は数値

2.2 チーム / プレイヤー

項目logbattle_listbattle_detailDB 列 (players)UI 利用備考
チーム所属 (A/B) TeamA/TeamB 自分のみ(敵味方の分離なし) Winner/Loser を team A/B に変換できるplayers.team ('A'/'B')MatchDetaillog は明示的、browser は勝敗ベースで暗黙
プレイヤー UID 文字列 自分のみ(呼び出し側引数で渡す) UserInfo.Id (数値)players.uid (TEXT)全画面書式違い: log=string, browser=number → .toString() 変換(StatsService:336
プレイヤー名 自分のみ UserInfo.Nameplayers.name (TEXT)全画面同 UID で名前変更があるとき優先順が曖昧
アバター URL UserInfo.Avatarplayers.avatar_urlMatchDetailView?(要確認)UI で表示しているか未確認
段位 ID (DaDuanId) UserInfo.DaDuanIdplayers.rank_id戦績バッジ UI 想定
ScoreBefore / ScoreAfterplayers.score_before/score_afterMatchDetailView (line 180-184)勝率推移の精度に直結
キャラクター BP の Pick から(NULL で挿入) BattleInfo.Role.RoleCodeplayers.character_id全画面log → INSERT 時に決定、browser → UPDATE で上書き
所持キャラ枠 (4 キャラ) availableCharacterIds[]players.available_character_ids (JSON)PlayerDashboard、MatchDetailonly-in-log の代表例
IsMvp 自分のみ 全員players.is_mvp (INT 0/1)MatchDetailView (line 188)
キル数 (BeatCnt) BeatHeroNum(自分のみ) BattleInfo.BeatCntplayers.kill_countMatchDetailView, MatchTableRowbrowser 経由で全員分が揃う
デス数 (BeatedCnt) DeathHeroNum(自分のみ) BattleInfo.BeatedCntplayers.death_count同上
与ダメ (ExportDamage) TotalDamage(自分のみ) BattleInfo.ExportDamageplayers.damage同上log は取れず、browser 経由のみ
覚醒キル (BeatAwakeCnt)players.awake_kill_count(未確認)only-in-detail
味方誤射 (BeatFreidnCnt)players.friendly_kill_count(未確認)typo 注意
デバフ解除 (Debarrass)players.debuff_clear_count(未確認)

2.3 Pick / Ban

項目logbattle_listbattle_detailDB 列UI 利用備考
BAN リスト bans[]bans.character_id, banner_uid/name, teamMatchDetail、PlayerDashboard 「BAN 統計」only-in-log
Pick リスト picks[] Role.RoleCode から復元可(実装してない)picks.character_id, player_uid/name, teamMatchDetail、PlayerDashboard 「Pick 統計」log のみ。browser detail から再構築する余地はあるが、現状は log 依存
Pick 候補(fallback) isCandidateFallback, candidateCharacterIds[](DB に candidateCharacterIds 列なし)(UI 未使用?)log 解析中の暫定情報、DB に永続化されていない

2.4 戦績詳細(battle stats)

§2.2 の下半分と重複するが、ここでは「すべて browser 経由」が要点:

項目logbattle_listbattle_detailDB 列UI 利用備考
kill_count / death_count(自分のみ) 全員players.kill_count/death_countMatchDetailView, MatchTableRowbrowser 必須
damage(自分のみ)TotalDamage 全員 ExportDamageplayers.damage同上
score_before / score_after 全員players.score_before/score_afterMatchDetailViewrank score 推移の真実
is_mvp(自分のみ) 全員players.is_mvpMatchDetailView
rank_id (DaDuanId) 全員players.rank_id(未確認)
awake_kill / friendly_kill / debuff_clear 全員専用列あり(未確認)
avatar_url 全員players.avatar_url(未確認)

2.5 シーズン / その他

項目logseason_listbattle-season-summaryseason-show(同 endpoint 別構造?)DB 列備考
シーズン一覧 (Code, Name, StartTime, EndTime)api_responses に raw、専用列はなし)キャラマスタ生成の補助
当シーズン累計 (T1/T2 別 ArenaScore, GradeDanId, MvpCnt, TotalCnt, WinRate)season_summaries.t{1,2}_*プレイヤーダッシュボードで「現在のシーズン累計」を表示するソース
シーズン show(試合数 / WinCnt / ScoreMap / TopRoleList)(別 endpoint)season_shows.t{1,2}_* + top_role_list (JSON)別途 endpoint 経由
キャラ画像系 URL(5〜8 種) BattleInfo.Role.img / img_jp / img_en / img_tw / img_preview / battle_img_*(DB に列なし)現状 public/images/starward/ローカル画像 を使うため未保存
キャラ properties (CV / 紹介文 / power / health)(JSON 文字列でネスト)キャラマスタ補助reseponses/winrate.json 経由の yarn update-characters で別途取り込み

3. 差分レポート

3.1 only in log(ローカルログのみが持つ)

項目現状の挙動想定される問題修正規模依存
BAN 詳細 (banner UID / 名前 / team)bans テーブルに保存。UI で表示済みlog を取り込んでいないユーザー(browser のみ運用)は BAN 統計が空になるMbrowser から取得する API がない / 機能要望次第
Pick の細部(candidateCharacterIds, isCandidateFallback)解析時のメタだが DB に永続化されていないデバッグ時にどの判定経路で character が決まったかが分からないSDB スキーマ追加(picks.is_candidate_fallback)or 削除して諦める
所持キャラ 4 枠log でのみ取れる。available_character_ids JSON 列に保存。Dashboard で活用browser のみ運用では 4 枠統計が空L戦績サイトに該当機能ない場合は仕様として諦め
TYPE マーカーによる Rank/Normal 確定判定log の TYPE: 行が最優先browser の BattleType 数値→文字列のマッピングが log と一致しているか未検証Sマッピング表の文書化
log_file パス(trace 用)log のみ。browser 経由 INSERT は NULLbrowser 経由で作られた match のデバッグ手がかりが減るS列の意味として OK(browser 経由は raw を api_responses に持つ)

3.2 only in browser(ブラウザのみが持つ)

項目現状の挙動想定される問題修正規模依存
BattleIdsaveBattleListItems 経由で作る match の id に直接使われるlog 経由で作った match には付かない。後から battle_list を取り込んでも、log 由来の match と紐付かない可能性M照合ロジック改修(§4 も参照)
ScoreBefore / ScoreAfterplayers.score_before/score_after に UPDATE。MatchDetailView で表示log のみ運用ではすべて NULLS仕様として OK(browser opt-in)
アバター URL / 段位 IDUI 利用は未確認UI 改修で活かせる余地あり(player chip にアバター表示など)SUI 仕様検討
全プレイヤーの kill / death / damage 等の正確値saveBattleDetails でまとめて UPDATEbattle_detail 取得が遅延 / 失敗するとずっと NULLMリトライ戦略・履歴遡及
シーズン累計 / 段位(season_summaries / season_shows)別テーブルに raw 保存UI で活用しているか確認要(ダッシュボードで使う設計だが現状の UI 接続を未確認)MUI 設計

3.3 両方あり / 優先順あり

項目現状の優先順妥当性修正規模依存
試合 type (Rank/Normal)log が先(INSERT 時に決まる)→ browser の BattleType で上書きしていないlog の判定が正確(TYPE マーカー優先)なので OK。browser の数値 type は補助に留める(現状 OK)
character_idlog の Pick で INSERT → battle_detail で UPDATE 上書きbrowser detail の方が「実際に使ったキャラ」として信頼できるので妥当(現状 OK)
プレイヤー名log の名前が INSERT、browser detail の名前で 上書きしない(UPDATE 文に name 列なし)同 UID で名前変更があるとき log 名が残る。browser から取った最新名で上書きしたいケースもあるS仕様確認次第
試合 result(Win/Lose)log が先に決定一貫しているはず(同じ試合なら結論は同じ)。差分が出たら異常整合性チェックがあれば良い

3.4 両方あり / 書式違い

項目logbrowser現状の変換注意点
UIDstringnumber (UserInfo.Id)player.uid.toString()(StatsService:336)leading zero がある UID で破綻するケースは無さそう
試合開始時刻ISO 文字列Unix 秒saveBattleListItems 内で ISO に変換タイムゾーン: new Date(unix*1000).toISOString() は UTC 出力。log 側は localtime のままならズレる可能性 → §4.2 のケース
BattleType'Rank' / 'Normal' / null数値(22 等)log → そのまま挿入。browser → 数値のまま(?要確認)一貫したマッピング表が無いと UI でフィルタが破綻する
BattleResult'Win' / 'Lose'0 / 1saveBattleListItems 内で変換(要確認)

3.5 両ソース不在 / UI 要望あり(推測)

ヒアリング次第のセクション。以下は実装計画書 / 改善計画から拾った想定:

項目用途取得経路の候補修正規模
対戦相手の所持キャラ 4 枠(自分以外)overlay の B-3 提案log の BP データから他プレイヤー分も取れる場合あり(実装次第)M
試合内のキル時刻軸配信向けハイライトlog のキル通知行を時系列でまとめるL
シーズン横断の対面別勝率Dashboard 拡張DB 集計のみで OK(既存データで足りる)S

3.6 DB 列にあるが UI 未使用(要監査)

由来UI で読まれている箇所削除候補?
players.avatar_urlbrowser detail(要 grep)MatchDetailView で読まれているか未確認UI で使いたい候補
players.rank_id (DaDuanId)browser detail(要 grep)同上
players.awake_kill_count / friendly_kill_count / debuff_clear_countbrowser detail(要 grep)UI で詳細スタッツを出す要望次第
season_shows.top_role_list (JSON)browser shows(要 grep)データ取得はしているが活用未確認
season_*.raw_jsonデバッグ用バックアップUI 利用なし残しておく(trace 用途)

3.7 雑多な気づき

  • BeatFreidnCnt の typo は API 側。こちらが直すと API 変更時に壊れるので命名はそのまま受け入れて、DB 列名 (friendly_kill_count) で修正してある(OK)。
  • Role.propertiesJSON 文字列のさらに文字列化 という二重エンコード。パースするときは JSON.parse(JSON.parse(props)) 相当が要る場合がある。
  • Role.images.role_lh_img のような未使用画像 URL も含まれる。全部取り込まずキャラマスタ更新時にだけ参照する方針でよさそう。

4. 照合ロジックの仕様

4.1 findMatchByOpponentUids(opponentUids: string[], startTime: number): string | null

実体: electron/database.ts:386-416、export: :767

入力:

  • opponentUids: string[] — 自分以外(敵チーム + 味方の自分以外)のプレイヤー UID 文字列
  • startTime: number — 試合の Unix 秒(戦績サイト由来の StartTime

マッチング条件:

  1. players テーブルから uid IN (?, ?, …) の行を引き、match_id で GROUP BY、HAVING COUNT(DISTINCT uid) = ${opponentUids.length}対戦相手 UID の集合が完全一致 する match_id を候補として全件取得
  2. 候補が 1 件: そのまま返す
  3. 候補が複数件: 各候補の matches.timestamp を ms に直し、startTime * 1000 との 絶対値差が最小 のものを返す
  4. 候補ゼロ: null

戻り値: match_id (TEXT) or null

副作用: なし(read-only の SELECT のみ)

4.2 取りこぼしケース

ケース再現条件現状の挙動回避策
opponent UIDs が部分一致log 取り込みが失敗して 1 名分の player レコードが欠けているHAVING COUNT(DISTINCT) = N で落ちる → nulllog 取り込み失敗を別途検知。あるいは >= N-1 の許容を入れる(リスクあり)
同一相手と短時間連戦同 4 人で続けて 2 試合候補 2 件 → startTime 最近接で選ぶ。間違った試合に detail が UPDATE される可能性startTime の許容幅を狭める(現状は最近接、許容なし)。あるいは BattleId を別経路で持たせる
StartTime のずれlog の試合終了時刻と browser の StartTime が ±N 秒ずれるsaveBattleDetails 経路(matchId 未指定)は ±300 秒の窓で検索。findMatchByOpponentUids は最近接で 1 件選ぶので比較的ロバスト±300s は経験則。短時間連戦と組み合わさるとミスマッチ
複数プロファイル1 台で 2 アカウントを切り替えて使う場合、両方の試合を 1 つの DB に持つfindMatchByOpponentUids はプロファイル境界を見ないprofile 列を players に持つ / プロファイルごとに DB 分離
battle_list の取得遅延log で試合は取り込まれたが、戦績サイト側がまだ反映されていないexecuteFullWorkflow (BrowserAutomationService.ts) のリトライで補完される。ただしユーザーが workflow を手動で叩かないと進まないUI で「未 detail 試合」を可視化(既に部分的に実装あり? 要確認)
log なしで battle_list を先に取り込みlog 自動監視 OFF + browser 自動化のみ運用saveBattleListItemsINSERT OR IGNOREBattleId をそのまま match.id にして作る。Pick / Ban は永遠に空のまま仕様として OK(log opt-in)。UI で「Pick/Ban 不明」を区別表示すべき
opponentUids が 0 件サンプルデータが壊れているnull を返す例外的だが、上位呼び出し側がログを出すべき
uid が string と number で混在log 由来 players.uid は string、browser 検索キーも .toString() で文字列化一致するはず万が一 leading zero を含む UID があるとき型変換でズレる可能性(実例なし)

4.3 副作用と保証

  • findMatchByOpponentUids 自体は read-only。
  • 呼び出し元 BrowserAutomationService.ts:556 / :928 は、見つかった matchIdsaveBattleDetails({ matchId, ... }) に渡す。matchId 未指定経路(StatsService.saveBattleDetails:265-345)が動くのは log 由来 match と既に紐付いているケースで、そこでも ±300 秒窓 + UID 一致で UPDATE 対象を選ぶ。
  • どちらの経路も INSERT ではなく UPDATE なので、誤った match に書いても 元の log データは破壊しない(character_id を上書きするケースだけ注意)。

5. 推奨アクションリスト

優先度順。次 spec の requirements.md にそのまま貼れる粒度。

  • [x] A1: BattleId を log 由来 match にも紐付ける(M / DB 変更) — spec data-integration-improvement (PR #34) で実装済み

    • 動機: §3.2 BattleId / §4.2 「短時間連戦」「StartTime ずれ」の両方を一気に緩和
    • : matches.battle_id 列を追加 → findMatchByOpponentUids 成功時に BattleIdmatches に書き戻す。以降の照合は BattleId 直引きで OK
    • 依存: マイグレーション必要、saveBattleDetails の経路調整
    • 実装: findMatchByBattleId を新設し、BrowserAutomationService の matchId 解決を 3 段(BattleId → UID 集合 → 時刻 fuzzy)に変更。saveBattleDetails で UPDATE 1 件以上のとき matches.battle_id が NULL のみ書き戻す
  • [x] A2: BattleType の数値 → 文字列マッピングを単一ソース化(S) — spec data-integration-improvement (PR #34) で実装済み

    • 動機: §3.4 書式違い。log の 'Rank'/'Normal' と browser の 22 の対応がコード散在
    • : src/utils/logAnalyzer/battleTypeMap.ts(仮)に対応表を集約 → log 解析側 / browser 取り込み側の双方が import
    • 実装: src/utils/logAnalyzer/battleType/battleTypeMap.ts に集約。既知 ID(21, 22)→ '2v2'、未知 → 'Unknown'saveBattleListItemsmapBattleTypeId() 経由に置換
  • [ ] A3: avatar_url / rank_id / awake_kill_count 等の UI 反映を点検(S〜M)

    • 動機: §3.6 DB 列にあるが UI 未使用。データを取っているのに見えていないなら無駄
    • : MatchDetailView / MatchTableRow の表示項目を再設計 → 配信オーバーレイの拡張候補(B 系)と接続
  • [ ] A4: saveBattleListItems 経路で players.name を UPDATE 上書きする選択肢(S)

    • 動機: §3.3 名前の優先順不確定
    • : 設定で「browser 取得時に最新名で上書き」をオプトインに
  • [ ] A5: opponent UIDs 部分一致への耐性を判断(M)

    • 動機: §4.2 1 名分欠けで丸ごと miss する設計上の弱点
    • : >= N - 1 の許容を入れる or 入れない、を実データで判断(false-positive リスク評価)
  • [ ] A6: 複数プロファイル分離(L / 仕様判断あり)

    • 動機: §4.2 複数プロファイル
    • : players.profile_id 列を追加 + 照合時に絞り込み
  • [ ] A7: 過去 log の遡及 enrich(M)

    • 動機: §3.2 battle_list 取得遅延
    • : 「過去 N 件の matches で score_before が NULL のものを再取得」する運用ボタン or 自動
  • [x] A8: picks.is_candidate_fallback 等の log 解析メタを永続化するか削除するか決める(S) — spec data-integration-improvement (PR #34) で 永続化 に決定

    • 動機: §3.1。デバッグ用途で有用かを判断
    • 判断: 既存 UI(MatchDetailView.tsx)で isCandidateFallback / candidateCharacterIds が参照されていたが DB 列が無く log 由来の in-memory データのみで動いていた → DB 列を追加して永続化
    • 実装: picks.is_candidate_fallback (INTEGER 0/1) と picks.candidate_character_ids (TEXT JSON) を追加、PickData 型に対応フィールド、mapPickRow ヘルパーで JSON parse / boolean 変換を一元化
  • [x] A9: Role.properties の二重 JSON エンコードに対する正規化(S)feature/data-integration-A9-role-properties で実装済み

    • 動機: §3.7 雑多な気づき。キャラマスタ更新時に hidden bug の温床
    • : update-characters 内でパース → DB 列としては正規化された値だけ保存
    • 実装: scripts/data/parseRoleProperties.ts を新設(1 重/2 重エンコード/object/null をすべて吸収、最大 3 階層まで展開)。CharacterDBItemproperties 構造化フィールドを追加し、title_jp / cv_jp / power / health 等を正規化済みで src/data/characters.json に永続化(68 キャラ更新済み)。parseRoleProperties.test.ts で 15 ケース(null / 単一 / 二重 / 不正 JSON / 数値強制 / 暴走防止)を網羅
  • [x] A10: UID の string / number 境界をテスト化(S) — spec data-integration-improvement (PR #34) で実装済み

    • 動機: §3.4 書式違い。今は事故が起きていないが、将来 leading zero を含む UID が出たら破綻
    • : findMatchByOpponentUids / saveBattleDetails のユニットテストに「数値 UID」「文字列 UID」のミックスケースを足す
    • 実装: electron/database/__tests__/StatsService.battleId.test.ts(7 ケース)+ MatchRepository.battleId.test.ts(6 ケース)。number / leading-zero string / 安全整数超過 string / 混在を網羅

6. メタデータ

  • 調査 commit: 28ee7670b6264afc6980fa691d866b5b91e46fab
  • 調査日: 2026-05-05
  • 調査範囲のソース:
    • src/utils/logAnalyzer/{types,LogParser}.tscore/{line,fsm,aggregator}/*
    • electron/database/{DatabaseConnection,types,MatchRepository,StatsService}.ts
    • electron/database.ts
    • electron/services/{BrowserAutomationService,ResponseStorageService}.ts
    • electron/ipc/BrowserAutomationHandlers.ts
    • reseponses/{battle_list,battle_detail,seasonlist,battle-season-summary,winrate}.json
    • src/components/tabs/db-viewer/components/MatchDetailView.tsx ほか
  • 関連ドキュメント:
  • 次 spec のたたき台: §5 推奨アクションリストをそのまま要件カタログに使う。優先度の上位(A1 / A2 / A3)から spec 化する想定。
  • 進捗(2026-05-06): data-integration-improvement spec (PR #34) で A1 / A2 / A8 / A10 を完了。feature/data-integration-A9-role-properties で A9 を完了。残タスクは A3 / A4 / A5 / A6 / A7(UI 反映点検 / 名前ポリシー / 部分一致 / 複数プロファイル / 過去 log enrich)。