← 一覧に戻る

footballnext-operation

GitHub ↗ HTML 最終push: 2026/10/16 13:04

WIP(現在進行中)

Work In Progress

このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。

現在の状況

テーマ5(プッシュ通知・iOS 5.7.2(ビルド25・78fe327)は App Store で公開済み(10-16 に確認・タグ 5.7.2)。定期・号外の iOS は FCM で、5.7.2 に更新した一般のユーザーにも届く。公開前に直す約束だった定期送信の順番は、server b42a298(10-16 12:51 デプロイ)で修正済み。17:55 の定期送信で確かめる)

  • 10-16 の修正(server b42a298・12:51 デプロイ済み):
    • 定期送信のコミット後の順番を「記事キャッシュの用意 → FCM → OneSignal → 一覧 JSON(teiki_recent.json)」にした。もとは、期限なしの S3 で一覧を書いてから FCM を送っていた。
    • コミット後の送信は NotificationSender::deliverTeiki、一覧の作成は NotificationSender::updateTeikiRecentS3(Aws::boundedConfig(3, 10, 1))に移した。各段の失敗は PUSH_ALERT teiki_<段>_failed に記録して次へ進む。teiki_recent_s3_failed には request_key が入らなくなった。
    • 一覧 JSON を読むのは通知一覧の画面だけで、通知から開く画面は Lambda を読む(iOS と Android のコードで確認)。記事キャッシュの用意(884fb95)は FCM の前に残した(遅れは最大約16秒)。
    • テストは test_notification_sender.php 30件・test_push_lib.php 63件。PHP 5.5・7.4 と、本番両 Web 機の 5.3.29 で通過した。
    • デプロイでは、cron の FN の3行を 12:47:44〜12:51:47 だけ止めた(cron サーバーに ~/crontab_backup_20261016_teikifix.txt を残してある)。両台の md5 は一致し、エラーは出ていない。12:52 の定期の入口と回収は 200、13:00 の監視は PUSH_HEARTBEAT ok alerts=0。
  • 本番の状態:
    • server b42a298(その前は 92cf239・884fb95・65a6988・12e9ebb)。DB は push_jobs・push_deliveries・push_settings。
    • push_settings(10-11 に確認・その後の変更なし):
      • fcm.ios=1・fcm.ios.since=2026-10-11 08:37:02・onesignal.ios=0
      • fcm.android=0・onesignal.android=1(Android の OneSignal は failed=provider_error・既知)
      • cron.auth=enforce
      • android.style=notification
    • 秘密ファイル: 非公開 S3 footballnext-private-config/push/ → 両 Web 機の /etc/footballnext/。cron サーバーは ~/.push_cron_secret。
    • cron: ~/PhpProjects/thomsons-apps-cron(834d844)。定期送信は HTTPS+合言葉。回収は毎分、監視は10分ごと。
    • CloudWatch: footballnext-push-alert・footballnext-push-heartbeat-missing → SNS footballnext-push-alerts(You のメール・確認済み)。
  • 鍵:
    • APNs キー RCX2QQWW35(チーム JFBRCYHN24)を Firebase の開発用・本番用に登録済み(.p8 は You の Downloads に残っている → 不要なら削除)。
    • サーバー用は Firebase 標準の SA(firebase-adminsdk-nwcnq)。手元の JSON はゴミ箱。
  • iOS: 5.7.2(ビルド25)が App Store で公開済み。GitHub の master は 78fe327 で、タグ 5.7.2 を付けた。ビルド24 は申請していない。確認表(計画 3-6)は1(10-12)しか記録が無く、2〜7 と申請の経緯は記録されていない。
  • 次にやること:
    • ⓪17:55 の夕方の定期送信で、新しい順番を確かめる: push_jobs の iOS の FCM が accepted になること、teiki_recent.json の更新時刻が送信の直後であること、error_log に PUSH_ALERT teiki_* が無いこと。
    • ⓪停止のときに、pending・retry_wait の行も取消にする SQL を手順に足すか(計画 7章の未決9・You が保留中)。
    • ⓪BaseballNEXT の計画に、10-11 の追加分と 10-16 の定期送信の順番を反映する。10-11 の追加分は、Firebase の修正・deliverAll の表示・OneSignal の記録・push201608 の curl 化と呼び出し先の期限。
    • ①起動時広告の S3 の切り替え(テーマ2)。5.7.2 は公開済みなので、時期は You が決める。
    • ②GA4 の push_topics_synced で、5.7.2 の広がり具合を見る(更新を促す策は取らない=You 判断)。
    • ③Android の FCM 対応(計画 4章。標準の表示を実機で比べる。チャネルは fn_teiki・fn_alert)→ 3.0.9。
    • ④FCM 専用の鍵に差し替える(オーナーのトムソンの作業が要る)→ 標準 SA の鍵を無効にする。
    • ⑤PUSH_ALERT cron_rejected が外部からの呼び出しで鳴るようなら、NOTICE に下げる。
    • ⑥BaseballNEXT(MAU 3,023 で停止中)の扱い。
    • (任意)Web2 号機の error_log に出ている cake_sessions の主キー重複のエラー(10-14 から・セッションの書き込み)を調べるか。
  • 注意:
    • 本番の PHP は 5.3.29。新規の Lib は CakePHP の外でテストするので、ConnectionManager などの読み込みは App::uses で明示する(20:08 の Fatal の教訓)。
    • server を配備するときは、FN の通知の cron 3行(sendTeikiNotification・processPushQueue・checkPushHealth)を止める(CodeDeploy は上書きコピーで、新旧のファイルが混ざって読まれうる)。枠(07:55 前後・12:11・17:55・21:56)を避ける。cron サーバーの pgrep は -c が使えないので、ジョブ数は ps で数える。
    • トンネルは & で起動して PID で閉じる(pkill -f は、待機中の自分のジョブまで止める。ssh の制御ソケットは scratchpad だとパスが長すぎる)。
    • 監視は「予定枠×有効な OS」で数える。fcm.<os>.since を入れるまで、定期の監視は動かない。
    • 計画書・runbook は .devnotes/FCM_MIGRATION_PLAN.md(2-10 が本番手順、2-12 が 10-16 の修正)。議論ログは discussions/2026-10-10-onesignal-to-fcm.md・2026-10-11-fn-fcm-fix-review.md。

テーマ4(footballnext-android・targetSdk 36 対応リリース完了=v3.0.8 / vc22 を本番100%公開済み(08-29)/残りは Vitals 観察)

  • 経緯: Play の年次 target API level 要件(期限 2026-08-31)で targetSdk 36 化が必須に。5月リリースの vc17(targetSdk 35)が非準拠だった。延長リクエストは出さず期限内対応を選択し、実際に押さずに完了。
  • 本番: production = vc22 / versionName 3.0.8 / targetSdk 36、status=completed・userFraction なし=100%公開。トムソンの内部テストOK後に You が昇格。
  • 入っている修正(vc17→vc22・11コミット push 済み): ①targetSdk 36 化(AGP 8.7.3 据え置き+suppressUnsupportedCompileSdk)②edge-to-edge のインセット共通処理(BaseActivity)③予測型「戻る」移行 ④大画面の向き固定オプトアウト(API 37 で無効化)⑤YouTube origin 指定=全動画が再生不可だった既存バグの修正 ⑥動画詳細のクラッシュガード。
  • ⑤と⑥は 36 対応と無関係の既存バグ。特に⑤は 5月の 6f6feb1(プレイヤー差し替え)以降ずっと壊れており、現行本番でも全動画が error 152 で再生できていなかった。
  • 未解決: MovieDetailActivity のクラッシュ(lateinit movie 未初期化)は発現箇所を塞いだだけで経路は未特定。3シナリオ(通常操作/プロセスキル後のランチャー復帰/動作中のアプリ更新)では再現せず。cached.realm の全消しをやめる案は一度実装したが「キャッシュを起動時に捨てる設計自体は妥当」「必要性が確かめられていない」と判断して撤回した。
  • 次にやること: ①Android Vitals でクラッシュ率・ANR を数日観察(edge-to-edge / 予測型戻るは端末差が出やすい)②同種クラッシュが残れば経路を再調査 ③ADG/i-mobile ネイティブ広告の実機表示確認(今回未実施)。OneSignal プッシュは送信不可と判明(10-10・FCM v1 設定エラー)→テーマ5の FCM 移行で扱う。
  • 注意: Huawei EMUI 実機はアプリの logcat を抑制する(計測はファイル書き出し+run-as で読む)。Realm は削除してもファイル上の文字列を消さないので grep での検証は無効、必ずレコード数で測る。Play への反映は内部テストでも数分〜十数分、端末側が古い版をキャッシュすることもある。Play Developer API のサービスアカウントは ~/.config/gcloud/play-console-sa.json。

テーマ1(ダイジェスト・10-04 トムソン依頼のリーグ追加(キリンカップ=提供元 ID 916・UEFAネーションズリーグ 5)+PK 決着の誤判定修正+得点者欄の PK 戦除外=server 7f85b0c/2b68506/100086c デプロイ済み・アプリ更新不要/09-26〜28 管理表70〜75 対応済み・5.7.1 は 09-22 リリース済み)

  • 経緯: 09-08 仕様v1.2 実装(ビルド18)→ 09-09 リーグ追加対応(server 8f4e720)+仕様v1.3(管理表51〜54・server 8cd859e・iOS 0024f9c・ビルド19)→ 09-12 トムソンが選手詳細を確認し、修正管理表の未対応10行+v2.19/v1.4 を受領→同日対応。
  • 入っているもの: 有効な評価の共通判定(server DigestPresenter::validRating=出場済みかつ 0 より大きい/iOS DigestPlayerMatch.validRating)をグラフ・平均・一覧・シェア画像・MVP に統一。グラフは有効3件以上で表示(未出場の日付は残し点なし・前後を直線で結ぶ)。平均は有効件数で割って小数第2位四捨五入(松田 6.45→6.5)。出場したが評価なしは「評価 -」。試合行は「出場状況/出場時間(欠損-・未出場なし・G/A は 90分 1G)」の2段・各列固定幅・対戦相手名は「…」省略・行高46pt据え置き。パーサーはベンチの評価 0 を null 保存。本番の評価0行15件は NULL に修復済み。
  • 第12次FB(同日): G・A を青太字 11.5pt(時間はグレー 10pt のまま)で「22分 1G 1A」、出場なし・登録外は下段の空行を残さず文字単体を上下中央、2段表示は2段全体を上下中央(iOS 4ceaf9f)。before/after は fb-items66-67-shots/(compare 2枚・66 の右は仮データ)。
  • 配布: 5.7.0(ビルド21)を TestFlight にアップロード(ビルド20 は同日 VALID・トムソン確認済み)。AD-FARM の install() はコメントアウトのまま。
  • 返信: 09-12 の対応報告をチャット本文コピペ形式で出力済み(You 送付)。
  • リリース: 09-12 に第12次FB(G・A青太字・出場なし上下中央)のビルド21 を配布→トムソン確認→申請→審査通過→09-14 リリース完了の LINE 受領。次のマーケティングバージョンを上げるときは pbxproj の MARKETING_VERSION(5.7.0)を更新してからアーカイブ。
  • 09-19 League Cup(48)追加: 実況板リーグに登録(本番DB直接 INSERT=addConfirm 同等・日本語名「EFLカップ/EFL杯」)し、ダイジェスト対象大会にも追加(server 2c7b7c6 デプロイ済み)。今季の過去試合を遡り投入・充填済み(計36試合・終了30・players有21)。鎌田の直近5試合に「EFL杯」が出ることを確認。国内カップ戦を対象にしたのはこの大会のみ(DECISIONS 09-19)。
  • 09-20 管理表68・69: トムソンがストアのスクショ一新+アプリ名/サブタイトル/キーワード微修正のため ASC に 5.7.1 を作成(メタデータ更新も新バージョン申請が必要=ビルド必須)。68=試合詳細(クラブ戦の日本人選手欄・代表戦の日本代表選手欄)の G・A をグレー→青太字(Const.baseColor)に。表記は従来の「1G1A」のまま・色だけ変更(iOS 12c7bbd→f607dbb)。69=J リーグの試合で新着画面の試合一覧が「スタメン」表示に切り替わる件。代表の国内組を J クラブに紐づけた影響で digest_lineups_available が立ち、lives の lineup_announced だけ大会の絞り込みが無かった→DigestPresenter::liveLineupAnnounced(対象大会のみ)で判定(server 11da868・デプロイ済み・本番 API で確認・アプリ更新不要)。
  • 配布・リリース: 5.7.1(ビルド23)ASC VALID→トムソン確認→申請→審査通過→09-22 リリース完了。iOS タグ 5.7.1 を f607dbb に付与済み(5.7.0=4ceaf9f・5.6.0=4fd01fb と同形式)。ビルド22 はスペース入り表記「1G 1A」の旧版で使わない。MARKETING_VERSION=5.7.1(5672fb3)=次に上げるときは pbxproj を更新してからアーカイブ。比較画像は fb-item68-shots/。
  • 09-26〜28 管理表70〜75(日本代表戦まわり): 75=会場名を試合の取り込み時点で保存(FixtureImporter::importRow・これまでは開始1時間前の定期取得まで空だった/783f6ca)。73=試合前の代表一覧は、キックオフ前10日以内の直前の代表戦にラインナップ登録されたメンバー(先発+ベンチ)を使う(DigestDataProvider::getNationalSquadForFixture・提供元スカッドが9月の招集に未更新だったため)。74=試合前一覧のポジションは選手マスタ優先・日本語名もマスタで補う(94358b2)。70・71=試合途中で止まった提供元の選手データ(ウルグアイ戦は51分のまま)は、出場時間を交代・退場の記録から計算し、評価は null、G・A は得点記録。途中データの間は backfill の done を立てず FT 後72時間は再取得を続ける(DigestFixtureParser の players_stale/8137af6)。選手マスタに熊坂光希・中野就斗・谷村海那を追加(J クラブ紐づけ)。72(代表戦の STATS)は現行仕様でトムソン了承・今後の改修候補。ウルグアイ戦は 10-01 に再取得して提供元の確定を確認(評価は出場19人中17人・相馬/田中碧は提供元の評価なしで「-」・出場時間は記録からの計算値と全員一致・代表 MVP は早川 7.6。dev-timer #86 done)。返信2本(管理表/A/B)は You 送付。トムソンは開催地変換表に「エディオンピースウイング広島」を登録予定。
  • 10-04 リーグ追加(トムソン LINE): 依頼の「キリンカップ ID:8846」は API-Football に無く、提供元では Kirin Cup=916。10月の日本代表戦(10/1 エクアドル=準決勝 0-0・PK 5-4、10/5 19:30 NZ=決勝)は親善試合(10)ではなく 916 に入っていて未取り込みだった。916 を実況板リーグに登録(「キリンカップサッカー/キリン杯」)し nationalCompetitionIds に追加(7f85b0c)、エクアドル戦を遡り投入。UEFAネーションズリーグ(5)は実況板のみ(「UEFAネーションズリーグ/ネーションズL」・63試合・終了15試合は events 充填・9/24〜29 分は未取り込み)。取り込みで延長なしの PK 決着を途中データと誤判定する不具合が出た(提供元の status.elapsed=120 を試合の長さに使った+PK 戦の成功を G に数えた)→ 2b68506 で修正しエクアドル戦を再取り込み(フル出場90分・評価あり・G0・代表 MVP 板倉 7.1)。選手マスタに荒木隼人・望月ヘンリー海輝を追加(決勝の試合前一覧=エクアドル戦の登録26人に日本語名で出る)。得点者欄の PK 戦はトムソン②で除外(100086c・試合中の PK 得点と「5 PK 4」は残る)。NZ の表記(「代表」なし)は You 判断で現状維持。返信3本(リーグ追加報告・PK 質問・②対応報告)はチャットで作成済み。
  • 次にやること: ⓪10/5 19:30 キリンカップ決勝(日本対NZ)の試合後確認(代表選手欄の出場時間・評価・MVP/PK になったら得点者欄と途中データ判定)⓪トムソンへ返信(リーグ追加の報告・PK 表示②の対応報告)You 送付 ⓪UNL の試合日(10/5〜7・11/13〜18 JST)に API 残数を確認 ⓪9/28 ベネズエラ戦の試合後に、代表選手欄(出場時間・評価)と途中データ判定の動作を確認 ⓪ウルグアイ戦の再取得結果をトムソンへ報告(報告文は 10-01 にチャットで作成済み・You 送付) ⓪次の試合日に J リーグ=開始時刻のまま/対象大会=「スタメン」切替を実画面で確認 ⓪League Cup R16(10/28〜)で試合一覧ラベル・日本人選手欄の実表示を確認 ①UEL の選手統計が出始めたら backfill 再実行 ③一時設定4項目の戻し(トムソン側)④needs_review 22名のID重複精査 ⑤004 の本番適用確認 ⑥jpPlayer間欠遅延 ⑦MinimumOSVersion 引き上げ ⑧AD-FARM 再開時は install() を戻す。
  • 注意: API-Football はベンチ選手に rating: "0"・minutes: 0 を返すリーグがある→評価 0 は欠損扱い(validRating)。リーグを後から追加したときは importLeagueFixtures で遡り投入→updateFixture で充填(ダイジェスト対象へ入れるなら server デプロイ後に実行・実況板登録だけなら is_publish チームの試合のみ入る)。API は 0 を null で返す(統計ブロックありなら 0 と解釈)・passes.accuracy は成功本数・統計ブロック無し=リーグ非対応。UIStackView の arrangedSubview は isHidden を同値で重ね設定しない。XcodeBuildMCP でセル行や UIControl は touch(text 参照)でタップ・launch_app_sim は bundleId の defaults 必須。sim の選手詳細検証は -DigestBaseURL http://127.0.0.1:8765/ -DigestPlayerPath dev-fixtures/jp-player-*.json(ダイジェストの選手行タップで開く)。SSH用SGは現IPが残っていれば追加不要(トンネルは作業後に閉じる・放置すると切れる)。全ビルド共有JSONへのenum新値追加は旧ビルドを壊す→追加フィールド方式。「出場しているのに登録外」はAPI-FootballのID重複、「未出場なのに出場扱い」はAPI側の一時誤データ/幽霊交代event=updateFixture/{id} 再取り込み、「移籍直後の試合が無い」は取り込み漏れ=/cron/importTeamFixtures/{teamId}。datetime列はJST・unixtime列はUTC基準。ローカルにphpは無い→docker php:5.5-cli でテスト(dev-fixtures/test_*.php)。トムソン返信はmdファイルにせずチャット本文(コピペ形式)で出す。管理表の修正は指摘された範囲だけ実装する(指摘外の見た目変更は提案に留め、確認は番号の二択で聞く)。sim の試合詳細検証は -DigestLiveFixturePath dev-fixtures/<fixture>.json+-DigestLiveHistoriesPath+-DigestOpenLiveHistoryOnLaunch yes(G/A 4パターンは fixture-club-finished-fb68.json)。代表スカッドの取得元は /players/squads?team=12(毎日4:17全入れ替え)で招集発表に連動しない。親善試合では API の players ブロックが試合途中で止まることがある(途中データ判定の閾値は DigestFixtureParser::STALE_SHORT_PLAYER_THRESHOLD)。本番DBの mysql は --default-character-set=utf8mb4 を付ける(無いと日本語が ??? になり INSERT で文字化けする)。管理表は Drive MCP で読めないことがある→Claude in Chrome で gviz/tq?tqx=out:html&gid=<gid> を開いて読む。 トムソンが伝えるリーグ ID は提供元と違うことがある(キリンカップ 8846→916)→ leagues?search= と fixtures?team=12 で照合。延長なしの PK 決着は提供元が status.elapsed=120・extratime 0-0 を返す。PK 戦の記録は type=Goal・comments=Penalty Shootout(elapsed 91〜97/延長後は120)→ 選手集計・得点者欄では除く(DigestFixtureParser::isPenaltyShootoutEvent)。RDS の now() は UTC(datetime 列は JST 運用)。
  • seed SQL正本: digest-jp-players-seed_20260723.sql。sim検証手順はDEVLOG 08-05/08-08/09-12、デバッグ起動引数一覧はDEVLOG 08-23、TestFlight 手順は DEVLOG 09-08、API 枠試算は DEVLOG 09-08 参照。

テーマ2(起動時広告A/B v9・09-28 トムソン合意の「24時間に1回」→ 非参加時の間隔の S3 化(default_interval_minutes)を入れた iOS 5.7.2(ビルド25)が公開済み(10-16 確認)。S3 で 1440 に切り替える時期は You の判断待ち)

  • 09-23〜28 の流れ: 09-23 に結果報告 起動時広告A-Bテスト_結果報告_20260923.html(3巡目 ab-analysis-20260915/claude-analysis-round3.md)を送付(全面24時間化を推奨・下振れ最悪月1.3万円)→ 09-26 トムソンの質問「記事画面の広告(バナー・ネイティブ)の MAX 収益の算出方法」に回答(GA4 fn_ad_revenue=MAX SDK の1表示ごとの収益を、参加者3,574人の参加日から28日間・群×banner/native で合計→月平均為替→人数割り。A 2.80/B 3.13円/人を元データから再計算して一致)→ 09-28 トムソンが納得し「24時間に1回」の方向で合意。
  • 切替の前提: 追跡窓を抜けた端末の間隔はアプリ内定数(ABTest.swift の intervalAMinutesDefault=50)で、S3 だけでは切り替えられない。5.7.2 で非参加時の間隔を S3 設定(新キー・未設定/取得失敗は50分)から読む→リリース後に S3 で1440分へ。戻しも S3 のみ。5.7.1 以前は50分のまま。Android には起動時の全画面広告が無い(iOS のみ)。この旨の返信案は作成済み(You 送付)。
  • 未照合の点: 記事画面の枠(banner/native)は MAX 管理画面と未照合(照合は起動時枠のみ・再現誤差3.5%)。トムソンへの訂正は You の判断で入れていない。聞かれたら MAX 全ユニットのレポートで照合する。
  • 実験は v9(min_build=50505・窓 7/6〜8/17・config_version=9)。クリーンコホート最終規模 3,141人(A=1,548/B=1,593)・SRMなし。#36 残差率 A=5.51%/B=3.84%(④PASS)。
  • 本解析(ab-analysis-20260915/codex-analysis.md・codex-analysis-round2.md・データは data/・計算は codex-scripts/): 主指標(未到達離脱率)は参加日クラスタ再標本化で −1.03pp[−3.29, +1.31]=改善の確証なし。MAX 収益/人(D0〜27・150円/USD仮換算)は app_open −12.7%・banner +14.3%・native +4.0%・合計 +1.08%(診断幅 ±1円/人でゼロを含む)。D7 は +2〜3pp で境界的、D14 は不明。whole_base(既存ユーザー含む)は未到達 −3.18pp・MAX 合計 −0.71%。保留理由: ①canonical/診断イベントの発火範囲が不整合(displayed を ILRD 取得率の分母にできない)②ガードレール⑤⑦(管理画面売上との照合)が判定不能 ③MAX外の売上が未知で「外枠 約1%減で逆転」の境界。採用条件(Codex 提案): ①計測の説明を閉じる+管理画面照合 ②MAX外を含む減収が 0.50円/割当インストール・28日かつ月5,000円以内。
  • 09-22 CSV 照合(~/Downloads/mediation_report_2026-09-19_11_34_32.csv=MAX 管理画面・起動時枠のみ・日次×ネットワーク・7/1〜9/18): GA4 ILRD/CSV=96.5%(7月94.2・8月97.0・9月99.5)→ 起動時枠については⑤合格。Liftoff・ALX が1%以内で一致するので gross 同一基準と判断(公式ドキュメントに明記なし)。ネットワーク別のずれ(AppLovin 90.7%/Pangle 122.6%/AdMob 116.8%)は原因未調査。収益シェアは AppLovin 58.6%/Liftoff 30.3%/Pangle 7.6%(AD-FARM の遮断判断にはこの CSV で足りる)。
  • MAX外枠(iOS コード確認済み): newAdg/adx/line/pangle/zucks のネイティブ+記事バナー=すべてコンテンツ面の枠。起動時・全画面の MAX外広告は無い(i-mobile 全画面は設定画面「more」のみ)。同じ場所の MAX banner/native が B で増えているので外枠も増える方向=逆転は実装上起きにくい。
  • 今ある情報で書ける結論の形: 全面B化の MAX 分月次影響は最悪 −$84(約−1.3万円・起動時枠 −12.7%+補填ゼロ)/whole_base 実測 −$11/点推定 +$17。アクティブの大半が既に50/50なので現状からの変化はその半分程度。**「下振れは最悪でも月1万円規模・外枠は増える方向、その範囲で起動時広告の負担を下げる事業判断」**なら書ける。UX 改善の統計的確証は n≈1,550/群が律速で売上データでは解決しない。Codex 条件②(月5,000円)は最悪ケースが上限をまたぐ=You の許容額しだい。
  • 次にやること: ①(5.7.2 は 10-16 に公開を確認済み)S3 の ab_test_config.json へ default_interval_minutes: 1440・config_version: 10 を入れる(5.7.1 以前は新キーを読まず 50 分のまま。戻すときは S3 で 50)・runbook に時刻を記録 ②月次監視(MAX 合計が切替前3か月平均比 −5% を2か月続けて下回ったら戻す=3巡目 §5.3)。実装は ebb9639(ABTest.fallbackIntervalMinutes・ABConfigLoader の任意キー・showAdIfReady)、仕様は AB_TEST_MEASUREMENT_SPEC の設定配信の節
  • 注意: GA4 MCP の dimension_filter は field_name/string_filter(snake_case)。fn_ad_revenue の customEvent:network は AppLovin/Liftoff Monetize/Pangle・Pangle Native/Google AdMob/APPLOVIN_EXCHANGE/Mintegral 表記。
  • site-ad-scoring WIP は ios stash@{0} 温存(別レーン)。repoルートに codex-site-ad 系の未コミットファイルあり(別レーン・据え置き)。

テーマ3(実況板リーグ追加+海外組チーム紐づけ・2026-08-04 クローズ=残タスク全消化)

  • リーグ追加ポータル化・3リーグ投入・Edit画面チーム検索UI(server 602de4c)は08-01完了。残っていた実操作は08-04に全消化: 7名紐づけ(You実施済みと判明)・J組3名紐づけ・コペンハーゲン is_publish=1・スーペルリーガ20試合取り込み。以後の紐づけ・確認はテーマ1側で管理。

別レーン・保留中(詳細は ROADMAP と過去アーカイブ)

  • サーバ側の要対応: RDS SG 3306/tcp 全開放・DBパスワード平文・cron EC2 AMI 2018.03 EOL(ROADMAP 今月/今四半期)。
  • インフラ継続: API GW 飛び地の原点直叩き移行・OshiraseViewController デッドコード整理・rss-crawler の cron 一元化。
  • A/B実装の経緯(レビューround1〜10・点火前チェックリスト・実機スモーク・凍結仕様・型ズレ修正)は下記 2026-07-08 スナップショットと SPEC.md / runbook を参照。
  • AD-FARM(起動時広告の悪質ネットワーク調査): DEBUG 限定コードは master に含まれるが、5.7.0 配布前に AppDelegate の DebugAdFarm.shared.install() をコメントアウト(564fcd0)。再開時に戻す。
  • Firebase Analytics の sdk-exp 障害(09-29 09:41〜11:47 JST): 5アプリ(FootballNEXT 43件/43人・BaseballNEXT 12件+2件ほか)の「key cannot be nil」クラッシュ通知は、Firebase 側が返した不正な実験データが原因=アプリ・サーバーの対応なし・収束済み(当方の最新クラッシュは 11:38 JST)。Crashlytics の Issue クローズは未実施(You の判断)。詳細は DEVLOG 09-30・DECISIONS 09-30。

過去のWIPアーカイブ

2026-10-16 13:05 時点: テーマ1・3・4・別レーンは変更なし(アーカイブ省略)。

  • テーマ5を書き換えた。
    • 旧の見出し: 「FCM 移行を本番反映済み(FCM の本番送信はまだ停止)/server 92cf239 デプロイ済み/iOS 5.7.2(ビルド25・78fe327)を TestFlight 配布/10-12 に確認表の1を確認。残りの確認表のあと申請を判断」。
    • 旧の次にやること⓪: 10-11 の修正の手順(iOS 78fe327 の push・ビルド25の確認表)、実機テスト、トムソンへの申請依頼、定期送信の updateTeikiRecentS3() が FCM の前にあり期限も無い件(App Store 公開前に必ず直す)。
    • 旧の①: 公開後の切り替え(push_settings は入れ済み・S3)。
    • 新: 5.7.2 が公開済み(10-16 確認)でタグ 5.7.2 を付与。定期送信の順番を server b42a298 で直してデプロイし、17:55 の定期送信で確かめる。
  • テーマ2の見出しと次にやること①の「公開後に」を、公開済みに書き換えた。

2026-10-11 15:25 時点: テーマ1〜4・別レーンは変更なし(アーカイブ省略)。テーマ5の見出し・次にやることを書き換え、10-11 の修正の項目を追加(旧: 見出しは「5.7.2(ビルド24)を TestFlight 配布し、本番の FCM 経由で You の iPhone に届いた(10-10 23:20)/明日の朝に実機テスト → トムソンへ申請依頼」、次にやること⓪は「明日の朝の実機テスト」「トムソンへ 5.7.2 の申請依頼」の2つ → BaseballNEXT 計画のレビューで不具合が見つかり、iOS・server を修正(未コミット)。ビルド25で出し直す予定で、申請依頼はその確認のあと)。

2026-10-10 23:23 時点: テーマ1・3・4・別レーンは変更なし(アーカイブ省略)。テーマ5を書き換え(旧: 「移行先を FCM に決定・トムソンへ確認中」=現状・今の送信経路・移行方針・次にやること=トムソンの返答待ち→権限と鍵の確認→設計の叩き台→実装 → トムソン OK のあと、計画 v3・初期検証・server/iOS 実装・鍵・S3/DB/cron/CloudWatch を本番反映し、5.7.2(ビルド24)で実機に届いた)。テーマ2の見出しと次にやることを書き換え(旧: 「5.7.2 はトムソンの OK 待ち」・次にやること①は「OK → iOS 実装 → TestFlight → 申請」→ 5.7.2 に同梱して配布済み・公開後に S3 で切り替え)。

2026-10-10 18:22 時点: テーマ1〜3・別レーン・保留中は変更なし(アーカイブ省略)。テーマ5(プッシュ通知の OneSignal→FCM 移行)を新設(旧: 該当テーマなし)。テーマ4の次にやること③を書き換え(旧: 「OneSignalプッシュ実配信テスト・ADG/i-mobile ネイティブ広告の実機表示確認(今回未実施)」→ OneSignal は送信不可と判明したのでテーマ5へ移し、③は広告の実機確認のみ)。

2026-10-04 16:09 時点: テーマ2〜4・別レーン・保留中は変更なし(アーカイブ省略)。テーマ1の見出し・次にやること・注意を書き換え、10-04 のリーグ追加の項目を追加(旧: 見出しは「09-26〜28 トムソン管理表70〜75(日本代表戦まわり)対応完了=server 783f6ca/94358b2/8137af6 デプロイ済み・アプリ更新不要/5.7.1 は 09-22 リリース済み」、次にやることは「⓪9/28 ベネズエラ戦の試合後に、代表選手欄(出場時間・評価)と途中データ判定の動作を確認」から始まり、キリンカップ決勝の確認・返信2本・UNL の API 残数確認は無し。注意にリーグ ID の食い違い・PK 決着の提供元データの癖・RDS の now() の記載なし)。

2026-10-01 19:25 時点: テーマ2〜4・別レーン・保留中は変更なし(アーカイブ省略)。テーマ1の2か所を書き換え(旧: 09-26〜28 の項目の「ウルグアイ戦は再取り込み済み(出場19人・評価は全員「-」・代表 MVP は評価で選ぶ仕様のため null)」と、次にやることの「⓪10/1 10:00 ウルグアイ戦を /cron/updateFixture/1628995 で再取得し、提供元の確定を確認(dev-timer #86・トムソンに約束済み)」→ 10-01 に再取得して確定を確認し、次にやることをトムソンへの報告に置き換え)。

2026-09-30 19:45 時点: テーマ1〜4は変更なし(アーカイブ省略)。別レーン・保留中に Firebase sdk-exp 障害の1項目を追加(旧: 該当記載なし)。

2026-09-28 12:50 時点: テーマ3・4・別レーンは変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-22 5.7.1(ビルド23)App Store リリース完了・iOS タグ付与・運用フェーズ」=管理表68・69 と League Cup 追加までの記載 → 09-26 にトムソンから管理表70〜75(日本代表戦まわり)を受領して調査し、75(会場名)を server 783f6ca で修正。09-28 にトムソンの指示で 73・74(94358b2)と 70・71(8137af6)をデプロイし、選手マスタに3名を追加。ウルグアイ戦を再取り込み)。テーマ2を書き換え(旧: 「#37 本解析=全面B化は保留/09-22 トムソン提供 CSV で照合済み・3巡目=事業判断の結論は You の判断待ち」→ 09-23 結果報告を送付(未記録セッション)→ 09-26 トムソンの算出方法の質問に回答 → 09-28 トムソンが「24時間に1回」の方向で合意。切替には iOS 5.7.2 が必要でトムソンの OK 待ち)。

2026-09-22 18:11 時点: テーマ3・4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-20 管理表68+69 対応=server 11da868 デプロイ済み・iOS f607dbb・5.7.1(ビルド23)TestFlight 配布・返信文作成/トムソンのビルド23 確認→申請待ち」→ トムソン確認→申請→審査通過→09-22 にリリース完了の連絡。iOS タグ 5.7.1 を f607dbb に付与。運用フェーズへ)。テーマ2を書き換え(旧: 「enrollment窓クローズ済み(8/17)・次は #37 9/15 本解析」=#36 の残差率・SRM・参考値の記載 → 09-15〜18 の Codex 2巡の本解析結果「全面B化は保留」と、09-22 のトムソン提供 MAX 起動時枠CSVの精査・GA4 照合結果、今ある情報での結論の形を反映)。

2026-09-20 18:26 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-14 5.7.0(ビルド21)App Store リリース完了・選手詳細スタッツのFBラウンドをクローズ・以降は運用フェーズ」+09-19 League Cup 追加 → 09-20 にトムソンからストアのスクショ一新に合わせた 5.7.1 の相談と管理表68(試合詳細 G・A 青太字)・69(J リーグでスタメンラベルに切り替わる)を受領。69 は server 11da868 をデプロイして本番反映(アプリ更新不要)、68 は iOS f607dbb で対応し 5.7.1(ビルド23)を TestFlight 配布・返信文作成まで完了し「トムソンのビルド23 確認→申請待ち」へ。ビルド22 は指摘外のスペース入り表記を含む旧版のため不使用)。

2026-09-19 19:31 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1に追記(旧: 「次にやること」は ①UEL backfill 再実行〜⑧AD-FARM 再開時の install() 戻し のみで League Cup の記載なし → 09-19 に League Cup(48) を実況板リーグへ登録し、ダイジェスト対象大会に追加=server 2c7b7c6・今季過去試合の遡り投入済み。「次にやること」に R16 での実表示確認を追加、リーグ追加時の注意に「遡り投入はデプロイ後」を補記)。

2026-09-14 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-12 トムソン第11次FB(仕様v2.19/v1.4)=server 773cb23・iOS 7c4a888・ビルド20 をトムソン確認OK → 同日第12次FB(管理表66・67=G・A青太字/出場なし上下中央・仕様v2.20/v1.5)=iOS 4ceaf9f・5.7.0(ビルド21)TestFlight 配布・返信文作成/トムソン確認待ち」→ トムソン確認→申請→審査通過→09-14 に 5.7.0(ビルド21)リリース完了の LINE 受領。ASC READY_FOR_SALE を確認し iOS タグ 5.7.0 を付与。選手詳細スタッツの FB ラウンドをクローズし運用フェーズへ)。

2026-09-12 18:50 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-12 トムソン第11次FB(仕様v2.19/v1.4=評価不具合修正+試合別出場時間・列統一)を全消化=server 773cb23 デプロイ・本番データ修復・iOS 7c4a888・5.7.0(ビルド20)TestFlight 配布・返信文作成/トムソン確認待ち」→ 同日中にトムソンがビルド20 を確認OKし、第12次FB(管理表66・67=G・A青太字強調/出場なし・登録外の上下中央・仕様v2.20/v1.5)を受領。iOS 4ceaf9f で対応しビルド21 配布・比較画像・返信文作成まで完了し「ビルド21 確認待ち」へ)。

2026-09-12 16:30 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-09 トムソンのリーグ追加(英2部40・独2部79)+対象選手14名追加に対応=対象大会追加 server 8f4e720・今季過去試合56試合を遡り投入済み/5.7.0(ビルド18)は TestFlight 配布済み・トムソン確認待ち」→ 09-09 に仕様v1.3(管理表51〜54・ビルド19)を別セッションで対応済みと判明。09-12 にトムソン第11次FB(修正管理表10行+仕様v2.19/v1.4=未出場の評価0がグラフ・平均に混入する不具合と、試合行の出場時間2段表示・列統一)を受領し、server 773cb23・本番データ修復15行・iOS 7c4a888・ビルド20 配布・返信文作成まで完了。旧テーマ1本文の詳細は DEVLOG 09-09 参照)。

2026-09-09 16:53 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-08 新仕様v1.2「選手詳細/試合別個人スタッツ」実装完了=server d0525b5 デプロイ・データ投入済み・iOS 5.7.0(ビルド18)TestFlight 配布済み・トムソン確認待ち」→ 09-09 にトムソンが実況板ポータルで Championship(40)/2.Bundesliga(79) を登録し対象選手14名を追加、ダイジェスト対象大会への追加依頼を受けて server 8f4e720 をデプロイし、今季の過去試合56試合を新設の importLeagueFixtures で遡り投入・updateFixture で充填、返信文を出力済み。旧テーマ1本文の詳細は DEVLOG 09-08 参照)。

2026-09-08 19:37 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「09-04 上田綺世リール移籍の取り込み漏れ=修復+再発防止 server e9934f2 デプロイ・トムソンへ事後報告済み/次はアプリ確認返答待ち」→ 09-08 にトムソン新仕様v1.2「選手詳細/試合別個人スタッツ」を受領し、同日に server d0525b5 デプロイ・DDL 005・身長/統計の本番投入・iOS 5.7.0(ビルド18)TestFlight 配布・返信文作成まで完了。別件の Championship/2.BL 追加 API 枠試算も回答済み。旧テーマ1本文の詳細は DEVLOG 09-04 参照)。

(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)

2026-09-04 12:44 時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「5.6.0(ビルド17)App Store リリース完了・FBラウンドはクローズ・以降は運用フェーズ」→ 09-04 にトムソンから上田綺世/リール(79)の試合がダイジェスト・試合一覧に出ないとの調査依頼。所属変更直後のトゥールーズ戦1試合の取り込み漏れと特定し、データ修復+FixtureImporter による再発防止(server e9934f2)をデプロイ、事後報告をチャット本文で作成しYou送付済み。旧テーマ1本文の詳細はDEVLOG 08-30 参照)。

2026-08-30 リリース時点: テーマ2〜4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第10次FB対応完了・トムソン確認OK/残りはビルド17の他項目確認→設定戻し→App Store提出」→ トムソン全項目確認OK→申請→審査通過→5.6.0(ビルド17)リリース完了・タグ付与。FBラウンドをクローズし運用フェーズへ)。

2026-08-30 19:00 時点: テーマ2・テーマ3・テーマ4は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第9次FB対応完了=serverデプロイ・ビルド17配布・返信送付済み・トムソン回答受領→佐藤ID切替を本番適用済み(08-26)/残りは追加返信の送付→ビルド17確認→設定戻し→App Store提出」→ 08-30に第10次FB(シート項目50=終了済み試合の出場判定: 未出場の遠藤航・町野修斗が成績カード表示+出場人数に計上)を受領し、原因=API-Footballの一時的な誤データ(ベンチ選手をstarter・0分で返す)と幽霊交代eventを lineups/events 優先で拾うロジックと特定。server ab03235 でplayersブロックの出場時間・G/A基準に統一・デプロイ・3試合修復(未指摘の大迫敬介含む)・返信送付まで完了し「ビルド17確認待ち」へ。旧テーマ1本文の詳細はDEVLOG 08-26 20:52参照)。

2026-08-29 13:56 時点: テーマ1・テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ4(footballnext-android)を新設(旧「現在の状況」にはテーマ4が無く、Android は別レーン扱いだった)。08-29 に Play の target API level 要件(期限 08-31)対応として targetSdk 36 化を実施し、YouTube 全動画再生不可・動画詳細クラッシュの既存バグ2件も併せて修正、v3.0.8 / vc22 を本番100%公開まで完了した。詳細は DEVLOG 2026-08-29 13:56 参照。

2026-08-26 20:02 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第8次FB(項目39〜47・仕様v2.17)対応完了=ビルド16配布・返信文作成済み/返信送付待ち→ビルド16確認待ち」→ 08-26に第9次FB(シート修正依頼2件=Is Publish表示制御・過去試合「もっと見る」+仕様書v2.18+佐藤龍之介「登録外」の質問+ストア対応相談)を受領し全消化: server cffe5d7(Is Publish共通判定・liveHistoriesカーソル)・iOS 4fd01fb(試合タブもっと見る)・デプロイ検証・ビルド17アップロード・返信文作成まで完了し「返信送付待ち→トムソン回答待ち(アヤックスIs Publish/佐藤ID切替)」へ。佐藤の原因はAPI-FootballのID重複(410840→542861)。旧テーマ1本文の詳細はDEVLOG 08-23 21:21参照)。

2026-08-23 21:21 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第7次FB(項目25〜38・仕様v2.16)対応完了=ビルド15配布・返信送付済み/ビルド15確認待ち」→ 08-23にトムソンが進行中の実画面を確認して第8次FB(シート項目39〜47+仕様書v2.17)を受領し全消化: #45=サーバーの大会フィルタが代表戦を全滅させnational_recent常時空だった根本修正、#40=livesへのlineup_announced追加配信+青「スタメン」切替(caption_id=4案は旧ビルド互換で撤回)、#39/41/42/43/44/46/47=試合状態ピル・チップ幅・スコア所属クラブ基準・パルス等のiOS表示改修。今回から全項目のbefore/after比較画像を作成。server 329b484 デプロイ・iOS 926f17f・ビルド16配布(VALID)・返信文作成まで完了し「返信送付待ち→ビルド16確認待ち」へ。旧テーマ1本文の詳細はDEVLOG 08-19 21:41参照)。

2026-08-19 21:41 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第6次FB(項目22・23)対応完了=ビルド13配布・返信送付済み/ビルド13確認待ち」→ 08-10に別セッションでバッジ#24修正・ビルド14配布があったと判明(DEVLOG未記録)。08-19に第7次FB(シート項目25〜38+仕様書v2.16)を受領し全消化: #25=API側スタッツ配信遅延×1回きりbackfillの穴を特定しデータ修復+72h窓リトライ化、#26〜31=試合一覧のチームロゴ・揃え・2名表示安定化・リーグロゴ縮小、#33〜38=ダイジェスト2セクションのロゴ+クラブ別グループ表示。server efdb9dc デプロイ・iOS ee88a7d・ビルド15配布・返信送付まで完了し「ビルド15確認待ち」へ。旧テーマ1本文の詳細はDEVLOG 08-08 19:47参照)。

2026-08-17 16:11 時点: テーマ1・テーマ3は変更なし(アーカイブ省略)。テーマ2を書き換え(旧: 「v9稼働監視フェーズ・次チェックは #36 8/17 窓クローズ」=#30/#31の結果詳細を含む版 → 08-17に #36 実施=窓クローズ正常・SRMなし(最終規模3,141人)・残差率中間再read PASS を確認し「窓クローズ済み・次は #37 9/15 本解析」へ。#30/#31 の詳細は DEVLOG 2026-07-11/07-22 参照)。

2026-08-08 19:47 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第5次FB(仕様v2.14 項目19〜21)対応完了=ビルド12配布・返信送付済み」→ 同日中にトムソンのMVP順位再変更提案へ逆提案(欠損下位の共通並び)で合意し、項目22(ザルツブルク国旗=country_code全クラブnull配信の根本修正+NEC登録漏れ)・項目23(並び順・MVP共通化)+仕様書v2.15を全消化。デプロイ・ビルド13配布・返信送付まで完了し「ビルド13確認待ち」へ)。

2026-08-08 13:22 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第4次FB(仕様v2.12 項目13〜18)対応完了=ビルド11配布・DM返信済み」→ 08-07にビルド11確認の過程で試合一覧の選手名非表示2件の指摘→調査回答、08-08に第5次FB(シート項目19〜21+仕様書v2.14)を受領し全消化。対象大会分割+MVP選定ルール+シェア画像右揃えを実装・デプロイ・ビルド12配布・返信送付まで完了し「ビルド12確認待ち」へ)。

2026-08-05 19:14 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第3次FB(仕様v2.11)フル対応完了=ビルド10配布済み・DM送付待ち」→ 同日中にトムソンのビルド10確認OK+第4次FB(シート項目13〜18+仕様書v2.12)を受領し全消化。シェアレーン温存差分もビルド11へ統合して配布・DM返信済み。「トムソン確認待ち」へ)。

2026-08-05 16:49 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「第2次FB対応完了=ビルド9配布済み・トムソン最終確認待ち」→ 08-05に第3次FB(仕様v2.11 項目6〜12)全消化・試合タブ空表示バグ修正・リーグ国旗実装・クラブLINEUP/LIVE差異修正・ビルド10配布まで完了し「DM送付待ち」へ。ビルド9は試合タブバグ残存のため確認対象から外す)。

2026-08-04 18:27 時点: テーマ2は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「FB1回目対応済み・トムソン再確認待ち」→ 08-03設定上限拡張+08-04第2次FB対応(仕様v2.7・1行表示)・ビルド9配布・選手データ整備・リーグ88/119追加まで完了し「最終確認待ち」へ。一時設定の戻しはトムソン側タスク化)。テーマ3をクローズ(残っていた紐づけ・コペンハーゲンpublish・Superliga取り込みを08-04に全消化)。

2026-08-01 19:56 時点: テーマ2・テーマ3は変更なし(アーカイブ省略)。テーマ1を書き換え(旧: 「トムソン確認フェーズ・フィードバック待ち」→ FBシートの指摘2件(日本人選手欄の空表示・日本代表期間の扱い)の実装対応・デプロイ・DM返信まで完了し「再確認待ち」へ進んだため)。

2026-08-01 15:26 時点: テーマ1・テーマ2は変更なし(アーカイブ省略)。テーマ3を書き換え(旧: 「実装済み・You のコミット/デプロイ待ち。デプロイ後に3リーグ投入」→ デプロイ・3リーグ投入・チーム紐づけUI改修まで完了したため)。

2026-08-01 13:36 時点: テーマ1・テーマ2は変更なしのため本文維持(アーカイブ省略)。テーマ3(実況板リーグ追加のポータル化)を新設。

2026-07-23 13:40 時点のスナップショット(ダイジェスト テーマ1のみ・テーマ2は継続中のため本文維持)

テーマ1(ダイジェスト新機能・サーバー側 本番稼働開始(運用適用①〜⑤完了 2026-07-23)・残りは管理画面目視+iOS提出)

  • 2026-07-22 実装完走(Codex M0〜M7全承認・DEVLOG 23:22)→ 2026-07-23 運用適用①〜⑤完了(DEVLOG 09:10)。DDL適用・server 8コミットデプロイ済み・選手マスター54名(active52/needs_review22)・チームname_ja 666件・代表スカッド26名+日本語名・crontab4行・S3毎分生成 まで本番稼働中。
  • 本番E2Eで2バグ発見・修正済み: DDLの fnlive_teams.del 参照(bf9b38b)・DataProviderのRO datasource不一致でJOIN欠落(2eca081)。後方互換smoke・/api4/jpPlayer・S3公開200 全PASS。
  • ⑥管理画面目視=You確認済み(7/23)。⑦iOSは5.6.0設定・sim本番データ確認・GitHub push済み(ios 9コミット)。次にやること: You の実機確認(Xcode直挿し)→アーカイブ→App Store提出(リリース時期トムソン調整)→トムソンへ実装完了報告。
  • フォロー項目: needs_review 22名の精査。overseas.json は欧州開幕(8月上旬)まで空構造が正常。任意で直近代表戦の updateFixture/{id} バックフィル。
  • seed SQL正本: digest-jp-players-seed_20260723.sql(operation repo)。cron EC2 crontabバックアップ: ~/crontab_backup_20260723.txt。

2026-07-22 13:57 時点のスナップショット(ダイジェスト旧テーマ1のみ・テーマ2は継続中のため本文維持)

テーマ1(新レーン: トムソン提案「海外組・日本代表ダイジェスト」・API検証済み・DM返信→トムソン返答待ち)

  • 2026-07-22 トムソンから開発仕様書HTML受領(新規3画面+既存修正3画面+ダイジェストタブ/更新バッジ)。読解済み・仕様の練度は高い。
  • API検証完了=実装可能: RapidAPI API-Football 現行プラン7,500req/日内で運用可。全対象リーグ+代表戦で評価/lineups/G/A取得確認済み。v3 /fixtures?id= が全部同梱1req=既存 ovserveFixtures 拡張+選手stats保存で賄える。最繁忙日+2,700〜3,400req/日見込み。
  • 日本語名(APIはローマ字のみ): Claude(サブスク内)で名前解決+WebSearch裏取り+needs_reviewフラグ運用で初期リリースから対応する方針。クラブ名日本語表記も同パイプ。
  • 次にやること: DM送信→トムソン返答後に工数見積もり(→実際は同日に実装許可が出て見積もりを省略し実装へ直行した)。

2026-07-08 17:07 時点のスナップショット(v9再スタート完了直後・全文保存)

テーマ(2026-06-28〜07-08・起動時広告A/B コホートparam型ズレ修正〜5.5.5リリース〜v9実質再スタート 完了)

点火(6/22 20:40・v8/active=true)後、dev-timer「maturation明け」通知を受け watermark=6/25 で GA4 first-look(property 211701453・experiment_id=app_open_interval_2026q3)を実施。 結論:

  • 機構は健全: SRM無し(A=4720/B=4712 installs・χ²≈0.007・p≈0.93)/applied_interval=A:50・B:1440 で群別policy反映済/kill_observed=true は0件/主要funnel(experiment_enrolled/target_launch/content_reached/launch_no_content/app_open_ad_decision/fn_ad_revenue)両群着弾。=実験は正しく走っている。
  • 🔴 重大欠陥: target_cohort_day/enrollment_day/days_since_enrollment が全イベント100% (not set)/空。experiment_id/assigned_group/applied_interval_minutes/kill_observed は正常着弾=永続enrollレコードから算出する日付3 param のみが死んでいる。→ ガードレール④(成熟後 残差率・target_cohort_day集計)と 円/人(days_since_enrollment)が算出不能。assigned_group は生きているので群レベル比較のみ可。
  • 方向性(参考・イベント日基準=未成熟混在・判定不可): 離脱率 launch_no_content/target は A=19.7%・B=15.8%(B が ~3.9pp 低く仮説の向き)。ただし未解決率 A=6.5%/B=4.0% の群間差2.5pp がガードレール④の保留条件に該当し、精緻化に要る maturation 法が欠陥で使えない=結論は出せない。
  • 次アクション(最優先): footballnext-ios で日付3 param の送信箇所を特定し原因究明・修正。事後 param 追加は過去データを復元できないため早期修正が必須(毎日 cohort 解像度を失う)。記録は本ブロック+DECISIONS 2026-06-28+runbook 点火ログ注記。
  • 記録は完了(DECISIONS/WIP/runbook 反映済)。This セッションで ga4 MCP 接続・first-look 4クエリ実行・3ファイル記録まで実施。
  • 修正→レビュー→提出準備まで完了(2026-06-28): gods-talk ab-cohort-param-fix を round1〜3 実施(方針→実コード→最終確認)・release可判定。ios master に修正コミット(型ズレ解消の 28af0f1/窓外gate+validation 698cf5f/実在日チェック 83a4796/バージョン5.5.4 f864eb7)+ operation main に記録(55e9e71 他)。いずれも push 済。sim build SUCCEEDED。
  • 修正内容: 数値/Bool param × text dimension の型ズレを emit境界 ABTest.ga4*Label で解消(target_cohort_day/enrollment_day→ISO・days_since_enrollment→d00〜d27・config_version→cv_N・kill_observed→文字列)。ABAdCollector の追跡窓外(D28+)送信を gate。ga4DayLabel に範囲+実在日(round-trip)validation。保存schemaのInt/Boolは不変。
  • 5.5.4 配信後の運用(v9 config): experiment_id は据置(アプリ制約で config 変更不可)。min_build=50504で5.5.3を除外+解析は enrollment_day >= 5.5.4配信日 でフィルタ+enrollment窓を5.5.4ライブ日起点で取り直す=実質再スタート。雛形 ab_test_config.v9.template.json 用意済(DECISIONS 2026-06-28)。
  • リリース済&着弾検証成功(2026-07-07): 型ズレ修正版は Apple 5.2.3 却下対応で YouTube機能一時非表示+5.5.5 として 2026-07-06 リリース(5.5.4 は未リリース)。翌7/7 GA4 Data API で appVersion=5.5.5 の5 dimension 全て non-empty 着弾を確認(target_cohort_day/enrollment_day=ISO・days_since_enrollment=d00〜d13・config_version=cv_8・kill_observed=false)。旧5.5.3は(not set)。型ズレ修正は本番で成功。汚染(5.5.3既enroll→5.5.5更新勢の古いenrollment_day)も実測で確認=解析は enrollment_day>=2026-07-06 で除外。
  • v9 配置=実質再スタート完了(2026-07-08 00:55 JST): v8→v9 を S3 配置(aws s3 cp --acl public-read --profile thomson-ik・min_build=50505・窓7/6-8/17・config_version=9)。公開URLで HTTP200・v9着弾確認、旧v8はscratchpadにバックアップ、GA4メモ記入、点火ログ表追記(op 323e779)。12時間後 cv_9 着弾89%(2839件/5.5.5)を確認=クリーン再スタート成功。
  • 現在地(2026-07-08 セッション終了時点)=A/B再スタート一区切り。実験は v9・新コホート(窓7/6-8/17)で稼働中。次チェックポイント: dev-timer #30(7/11) 再スタート健全性確認・#31(7/22) 成熟後残差率初回read。本番解析(残差率・円/人)は9月中旬頃、母集団は enrollment_day>=2026-07-06 かつ cv_9。site-ad-scoring WIP は ios stash@{0} 温存(別レーン)。

テーマ(2026-06-19〜・起動時広告A/B 実装レビュー round1〜10 完了・sign-off 確定・実装レーン完了/点火前チェックリスト待ち)

実装コードレビュー(gods-talk)を round10 まで回し、App Open / native lifecycle / kill属性 / TTL / 計測契約の全 high/medium を解消。round8 で sign-off 取得 → round9 で R7-#2 revert(render 失敗の過大計上)を medium 差し戻し → round10 で gate 復元(ios 311e3ad)を確認し「round9 medium close・round8 sign-off 回復・新 high/medium ブロッカーなし」を取得。実装レビューレーンは完了。 本番は中立(active=false / v7 / ttl14日)、5.5.3 据え置きで全修正積載。reviewer は通常 native 表示の短い実機再スモークを「望ましいが medium close の必須前提にしない」と明示(gate は sign-off 済み実装へ復帰・A-1 で rendered=true/描画/ILRD/GA4 impression 確認済み・Release 追加差分はコメントのみ)。残るは運用・外部タスクのみ(GA4 2件登録・5.5.3 提出・点火)。 5.5.3 実機スモーク A-0〜A-3 は前段で全PASS済(expire 人工注入 hook -ABNativeSimulateExpire・#if DEBUG 隔離・本番無効)。

点火前チェックリスト(コード外・必須。完了後に active=true)

  1. GA4 custom 定義の登録 — 完了(2026-06-19・You が管理画面で手登録): 追加2件(AB Kill Observed dim/kill_observed・AB Applied Interval Minutes metric/applied_interval_minutes・分)を登録済=全23件。着弾の DebugView 目視は点火後に実施。登録前データは遡及されないため点火前登録の順序は満たした。
  2. enrollment 窓・判定値 — 確定済(2026-06-19): interval_a=50/interval_b=1440・締切42日/追跡28日/ITT・required_assignment_version=1・min_build=50503(5.5.3+限定)・ttl14日。enrollment窓は窓長42日固定・起点は5.5.3リリース日(+猶予)を点火時セット(旧6/22は過去化で不使用)。詳細は runbook 点火前チェックリスト。
  3. 初期 experiment_active=false での本番 config 取得確認 — 確認済(2026-06-19): 本番 ab_test_config.json 実取得=200・valid・active=false・v7・全互換値(experiment_id/assignment_version=1/event_schema_version=v1/mp_2026q3_r1/interval 50・1440/ttl14日)が ABProtocol と一致。enrollment窓は旧値だが active=false で無効(点火時 v8 で再設定)。on-device 非参加挙動は round4 実機PASS。
  4. 点火・停止時刻を記録する運用準備 — 準備済(2026-06-19): runbook に「点火・停止ログ」表を新設(停止境界の正典・GA4アノテーション併用)。現状 v7/active=false を記録、点火/停止行は実施時記入。 → 点火前タスクの状況(2026-06-19 時点): GA4 2件登録=完了。5.5.3 native スモーク=完了(A-0〜A-3 全PASS)。5.5.3=ビルド準備完了・App Store 申請は2026-06-20 予定(リリース=審査通過後)(点火の enrollment窓起点は store でライブになった日+猶予)。**申請前に実況板バグ(もっと見る→上に戻るでライブカード消失)を修正済(ios f275fed・実機OK)=5.5.3 に同梱。**残る点火手順: S3 を v8/active=true・enrollment窓(リリース日起点42日)・min_build=50503 で配置+点火ログ記入+同時刻に GA4 アノテーション。 配置は --acl public-read・profile=thomson-ik。v8 雛形 ab_test_config.v8.template.json 用意済(固定値埋め済・enrollment 2日付けのみ点火時にリリース日から算出)/runbook に生成・配置手順(date→epoch コマンド込み)を新設。 ライブ ab_test_config.json は v7/active=false のまま据え置き。

実装レビュー(gods-talk)round5〜8 の到達(push済)

  • round5 ios 0089f65 / op ab066e4: R5-#1 native確定destroy(lease制・固定0.6秒撤去・実機スモークPASS)/ #2 pending timeout(30s)+expire / #3 kill属性 snapshot↔applied分離・clear順序。
  • round6 ios 3f64ade / op 546dd0a: R6-#1 callback main集約+timeout世代照合 / #2 dequed-expire無効化 / #3 GA4-only整合(kill_observed必須化) / #5 TTL省略可統一。
  • round7 ios 31dc22b / op 07aa09f: R7-#1 stale判定をcellForRow前段へ+同一pass再deque / #2 renderable !expired + render成功時のみimpression / #3 self-test fail-hard / #4 kill_observed_at削除。
  • round8: 点火 sign-off。残 low(render-false即時再取得なし=fail-closed/self-test範囲/廃止param説明文/runbook commit表記)は全て非ブロッカー。
  • round9 ios 311e3ad / op 4e489da: sign-off 後の R7-#2 revert を reviewer が medium 差し戻し(SDK 契約上 false=render失敗で計上不可)。→ impression gate 復元(true 時のみ recordImpression・false 時 fail-closed)+ expire hook を #if DEBUG 隔離。Debug/Release 両ビルド 0 errors。
  • round10 op(本コミット): gate 復元(311e3ad)を確認し round9 medium close・round8 sign-off 回復・新 high/medium ブロッカーなし を取得。実行ロジックは 31dc22b の R7-#2 と機能的に同等へ復帰。通常 native 表示の短い実機再スモークは「望ましいが medium close の必須前提にしない」と明示。実装レビューレーン完了。
  • 検証: 各round sim build 0 errors・改修ファイル新規警告なし・self-test 7/7。limitation: sim は MAX native fill なしで timeout/expire の end-to-end は未実行(destroy判定は純粋関数 self-test+外部レビューで担保)。

5.5.3 実機スモーク結果(2026-06-19・iPhone 実機・Xcode直挿し)

  • A-0 self-test: PASS(-ABNativeSelfTest で destroy-logic 7/7・abort せず)。
  • A-1 描画・ILRD・impression: PASS。fn_ad_impression(middle_native) が DebugView ライブに出ず調査したが、実はコードバグではなかった: renderNativeAdView は rendered=true(描画成功)で recordImpression→logEvent まで毎回到達し、GA4 Realtime で fn_ad_impression 増加を確認=収集できている。DebugView 非表示は表示/タイミングの癖で、カスタム定義の登録有無とは無関係(DebugView は登録不問で全イベントを出す。登録が効くのは Explore/レポートのみ)。fn_ad_impression は収集OK・slot/format/network も既登録=レポートでも使える=やること無し。
    • 補足の訂正: 途中で出した「Bool gate が impression を握り潰している」という見立ては誤り(rendered=true なので R7-#2 有無に関係なく元々計上されていた)。ただし R7-#2 revert(ios 5eaf3be)を「無駄&危険な分岐の簡素化」として残す判断をした。
    • 【round9 で覆った】この revert は overreach だった。reviewer 指摘: SDK 契約上 renderNativeAdView=false は render 失敗を意味し、観測で false が出なかったことは「false を成功扱いしてよい」根拠にならない(別 network/view 再利用/将来 adapter 差分で false がありうる)。→ impression gate を復元(true 時のみ計上・false 時 fail-closed、ios で revert を差し戻し)。R7-#2 を本アプリの計上判定に使わない、という結論は撤回。
  • A-2 lease 負荷: PASS(スクロール往復・pull-to-refresh・More連打でクラッシュ0・空白固まりなし)。
  • A-3 expire→再deque: PASS。-ABNativeSimulateExpire(load 成功 ad を10秒後に人工 expire)で連続 churn 耐久 → クラッシュ0・新 ad 差し替え継続・stock 暴走なし。debug hook 追加(ios 777128f・launchArg 限定・本番無効)。

確定した実装仕様(GATE凍結済み)

  • experiment_id="app_open_interval_2026q3"/assignment_version=1/measurement_protocol_version="mp_2026q3_r1"。
  • 割当: SHA256(UTF-8("experiment_id:install_uuid")) 先頭8バイト big-endian % 100、[0,50)→A。新 experiment は自動再無作為化。
  • enrollment窓: 2026-06-22 0:00〜2026-08-03 0:00 JST(42日)。追跡 trackingDays=28(JST暦日)。TTL=14日。
  • 詳細値は正典 SPEC.md「measurement_protocol 凍結値」と ABProtocol。

詰まっていること・未決事項

  • R2-#4 / R2-#3 が点火ブロッカー(上記 残 must-fix)。R2-#3 は実機/TestFlight検証必須。
  • ga4 MCP が本セッション未接続=GA4着弾の DataAPI 裏取り不可(点火後検証は接続要 or DebugView 目視)。
  • 月次総売上(iOS・FN・対象月)はトムソン提供待ち=解析フェーズ入力(点火ブロッカーではない)。
  • S3配置は --acl public-read 必須(footballnextバケットはオブジェクト単位ACL運用・無ACLだと403)。profile=thomson-ik。
  • Swift の SWIFT_ACTIVE_COMPILATION_CONDITIONS に DEBUG 未設定→seed/AB_EVENT検証時は上書き要。seed は実fetchをスキップし決定化済(config_version=999999)。
  • 正典 SPEC.md(rev6)が唯一の source of truth。

旧テーマの継続タスク(インフラ・前回まで)

  • API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS OshiraseViewController デッドコード整理(削除済み footballnext-oshirase-v2 を OshiraseViewController.swift:30 が参照中・据え置き)。rss-crawler の cron 一元化。
  • iOS 5.5.1(YouTube WKWebView 化込み)を App Store 提出(実機再生確認は未実施)。footballnext-ios に別件未コミット変更(push通知系・Podfile 等)残置中。
  • (継続)xcuserstate 追跡、android 未push、cake_sessions Fatal 放置。

別件で要対応(サーバ側、今回も触らず)

  • ⚠️ RDS SG が 3306/tcp を 0.0.0.0/0 開放
  • ⚠️ DB パスワードが database.php に平文コミット
  • cron EC2 が Amazon Linux AMI 2018.03(EOL)
  • リードレプリカ復活 / gp2 → gp3 はコスト追加 NG 方針につき凍結中

前テーマの残タスク(多角レビュー・2026-06-05)

  • 改善実装の着手判断(最優先=App Open Ad スロットル復活、広告挿入間隔の緩和)。効果検証はGA4の離脱シグナルで。
  • iOS 5.5.1 を Xcode アーカイブ→App Store 提出。レビュー結論と元データは review-20260605.html。

2026-06-19 14:30 時点のスナップショット(Codex round3 high3+medium4+low1 修正+実機回帰PASS・round4待ち)

点火→緊急停止→round1/2修正→実機A〜D PASS→round3で新high3(判断ミス2件含む)→修正+実機回帰スモークPASS。本番中立(active=false/v7/ttl14日)、5.5.3据え置き。R3-#1/#2 5db2975(decision/show一体claim+placement照合)・#3 5d26ee6(native cap撤去→deinit destroy)・#4-7 ef4d47e・#8 791952f+runbook 0789c73。実機(iPhone14/seed): A native+ILRD / B content_reached / C 群B処置(decision=interval) / D #11 全PASS。次は round4 で R3 確認→5.5.3提出。(→ round5〜8 で native lifecycle/kill/計測の残 high/medium を全解消し round8 で点火 sign-off に到達)

2026-06-19 08:20 時点のスナップショット(点火直前:段階A配置+pre-flight PASS)

テーマ: 点火前チェックリストを全消化。不活性config(active=false)を本番S3配置済み・シミュレータで本番S3取得経路を実証。残りは段階B点火のみ、Youのgo待ち だった。enrollment窓 6/22〜8/3 JST 確定。GA4 custom定義21件登録済。トムソン3項目HTML公開(叩き台確定扱い)。→ この後 点火(active=true)したが Codex実装レビューで重大バグ多数→緊急停止→high/medium修正フェーズへ(上記「現在の状況」)。

2026-06-19 02:32 時点のスナップショット(点火準備フェーズ:BASE実装+GATE凍結+GA4登録完了・段階A配置前)

テーマ: 起動時広告 削減A/B は実装・GATE値凍結・GA4 custom定義登録(21件)まで完了。S3配置用 ab_test_config.json(active=false)+ ab_test_deploy_runbook.md 作成済。当時の残タスク=①トムソン通知 ②enrollment期間決定 ③S3配置→点火 ④Data API検証。本番影響は中立。→ この後 08:20 までに トムソン3項目HTML公開(叩き台確定扱い)・enrollment窓確定(6/22〜8/3 JST)・段階A=不活性config配置・シミュレータpre-flight PASS を完了(上記「現在の状況」)。残るは段階B点火のみ。

2026-06-19 01:03 時点のスナップショット(BASE A〜F 実装完了・点火準備着手前)

テーマ: 起動時広告A/B の計測基盤 BASE A〜F を実コードで実装完了(ios commit 4ebdeb6)。新規4ファイル+既存14ファイル配線。フルビルド成功・シミュレータ実機で experiment_enrolled→target_launch→app_open_ad_decision→content_reached 発火確認。本番影響は中立。当時の次タスク=GATE値確定・S3 config配置・GA4登録・native refactor。→ この後 02:32 までに GATE値叩き台凍結・config/runbook作成・native lifecycle修正・GA4 custom定義 全21件登録 を完了(上記「現在の状況」)。

2026-06-19 01:00 時点のスナップショット(実装計画フェーズ完了・実装着手前)

テーマ: トムソン承認後、実装計画HTMLを作成し Codex(gods-talk)5ラウンドで設計レビュー完了。実装計画 rev6 と正典 SPEC.md rev6 が同期済み。この時点でコード変更はまだ無し(資料・正典・レビュー往復のみ)。確定事項: assignment/activation/enrollment の3分離(3 artifact+版番号)、ForegroundCycle/TargetLaunchContext 2層+60秒gate、可視判定は状態機械、kill/crash対策に write-ahead OpenContextRecord+CAS、GA4送信 at-most-once、収益 canonical fn_ad_revenue 一本化、円/人 固定コホート、暦日 Asia/Tokyo。実装は3レーン(BASE着手可 / BASE契約5点 / GATE-値)。→ この後 2026-06-19 にBASE A〜F を実装完了(上記「現在の状況」)。

2026-06-18 23:55 時点のスナップショット(提案書フェーズ完了・承認前)

  • 起動時広告 削減A/B の提案書フェーズが完了していた状態。提案書 起動時広告A-Bテスト_提案書_20260618.html を thomsons S3 に公開(URL: https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html)、Codex提案書レビュー2ラウンド(.gods-talk/ab-test-proposal-review-*)反映済み。
  • この時点の確定設計: 群A=現状(50分)/群B=24h rolling、ITT、GA4のみ(BigQuery不使用)、起動時は群別didPayRevenue実額・それ以外は混合eCPM概算、残り具合=コンテンツ未到達離脱率+セッション/人+D7/D14。正典 AB_TEST_MEASUREMENT_SPEC.md 初版。
  • 次アクションは「トムソンの承認待ち+計測仕様TODO消化」だった。→ その後承認が出て実装計画フェーズへ移行(上記「現在の状況」)。提案書時点のTODO(rolling24hのabsバグ・固定コホート・収益式の為替/対象範囲・設定取得SLA・事前テスト合格基準)は実装計画 rev6 で大部分が確定仕様化された。

2026-06-18 10:32 時点のスナップショット

テーマ: 起動時広告 削減A/B の測定設計(机上)。50/50無作為A/Bで実測判断する設計を、コードの現状確認込みで詰めた。当時の骨子(※この後の議論で更新済み・最新は「現在の状況」と SPEC 参照): 群A=現状(毎回と誤認)/群B=1日1回、判断②に launch_bounce、3パターン即採用、勝った間隔を S3 展開。コードの現状: 起動時広告は AppDelegate.swift:241 で明示 showAdIfReady()(ifで群分け可)、appOpenAdInterval スロットル+GA4配線済み、間隔は S3(Const.baseS3Url/201811)→UserData.setAppSettings で変更可、広告はパッチワーク(MAX外に AdG/Zucks/LINE、didPayRevenue 空)。当時の次アクション=提案資料化。

2026-06-13 00:42 時点のスナップショット

直近の割り込み対応(完了): iOS YouTube プレイヤーの WKWebView 化。ナレッジ ios-wkwebview-youtube-inline-playback(7channel 由来)を YoutubePlayerView.swift に適用。UIWebView 廃止・baseURL/origin 一致(Const.baseURLString)・https 化・自動再生オフ(タップで再生)。arm64 シミュレータビルド成功。コミット a94aa84(この1ファイルのみ選択コミット)→ push 済み。You が 5.5.1 申請予定(実機再生確認は未実施)。footballnext-ios に別件の未コミット変更(push通知系・Podfile 等)残置中。

テーマ: 今後のアプデ前の足場固め=定期実行の一元化(cron 専用 EC2 へ寄せる)+ AWS Lambda 大掃除。

どこまで進んだか: TFN_cron を cron サーバーへ移管完了(8エンドポイントを crontab 化・Lambda 本体+ルール削除・~/crontab_backup_20260606-073553.txt 退避)。Lambda 大掃除完了 42関数 → 9関数。teiki/article API 経路解析済み(API GW → Dynamo キャッシュ Lambda → CakePHP api4 の三段、execute-api 経由は飛び地数本)。

次にやること(当時): API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS OshiraseViewController デッドコード整理。rss-crawler の cron 一元化。多角レビューの改善実装着手判断(最優先=App Open Ad スロットル復活)。

詰まっていたこと: 削除済み footballnext-oshirase-v2 を iOS が参照中(デッド画面と判断し据え置き)。

2026-06-06 17:32 時点のスナップショット

テーマ: 今後のアプデ前の足場固め。定期実行の一元化(cron 専用 EC2 へ寄せる)+ AWS Lambda の大掃除。

どこまで進んだか: TFN_cron を cron サーバーへ移管完了(8エンドポイントを crontab 化・Lambda 本体+ルール削除・~/crontab_backup_20260606-073553.txt 退避)。Lambda 大掃除完了 42関数 → 9関数(残9は全て現役 or 保守ツール)。teiki/article の API 経路解析済み(API GW → Dynamo キャッシュ Lambda → CakePHP api4 の三段、execute-api 経由は飛び地数本のみ)。

次にやること(当時): API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS OshiraseViewController のデッドコード整理。rss-crawler の cron 一元化。

詰まっていたこと: 削除済み footballnext-oshirase-v2 を iOS が参照中(デッド画面と判断し据え置き)。CloudWatch 長期間1バケット集計の過少値問題は7日日次合算で回避。

2026-06-05 19:50 時点のスナップショット

テーマ: Football NEXT の多角サービスレビュー(large-scale-service-review 試運転)。広告・収益・行動・コードの複数ソースを診断し review-20260605.html に出力。GA4 新規接続。

どこまで進んだか: レビュー完了・HTML出力済み(6ソース三角測量)。GA4 接続済み(MCP ga4 / property 211701453 / ADC流用)。元データ一式が repo ルートに存在。スキル改善済み。

レビュー結論: 広告過多によるデススパイラル(広告売上 2019¥17.5M→2025¥5.4M −69%、レビュー星 2022→2023 で 3.0→1.6 崩落+広告言及率93%、GA4 MAU 約2年−32%、全て時系列一致)。最優先=App Open Ad スロットル復活、次いで不快クリエイティブのブロック・ストア評価運用、中位=有料広告なし課金(StoreKit)・計測基盤。

詰まっていたこと: AdMob/AppLovin MAX が別管理者で面別広告収益・eCPM取得不可。iOS OneSignal 2.12.0 が arm64 simulator 非対応。

2026-06-03 17:15 時点のスナップショット

テーマ: 試合詳細に「得点者」表示を追加するフルスタック機能(サーバ:events_json 保存+V2 で scorers 配信+過去試合バックフィル用 updateFixture / iOS:得点者カード 5.5.1)。本番反映・トムソン確認OK・iOS タグ 5.5.1 push 済み。

どこまで進んだか:

  • サーバ(footballnext-server)完了・デプロイ済み: fnlive_fixtures.events_json 追加(ALTER)。Cron で events 保存、V2Controller::fixture() が scorers(得点時間/選手/アシスト/PK/OG/home-away)を配信。終了試合も埋める updateFixture($id)(/cron/updateFixture/{id})新設。AfterInstall でモデルキャッシュ自動クリア。直近10試合バックフィル済み(全件 scorers=得点数 一致)。
  • iOS(footballnext-ios)完了・push/タグ済み: 5.5.1。得点者カードを記事テーブルのヘッダー(LiveFixtureScorersView)。コミット a3793be、タグ 5.5.1。
  • footballnext-android: 変化なし(Play Console アップロード待ち)。RDS/サーバ基盤: 平常運用。

次に何をするか: iOS 5.5.1 を Xcode アーカイブ→App Store 提出。任意で得点者まわりの dead code 掃除。過去試合の得点者は updateFixture で個別バックフィル。

詰まっていること: iOS dead code(未使用 xib + 空セル)。xcuserstate 追跡継続、android 7コミット未push、cake_sessions Fatal 放置。

2026-05-30 20:00 時点のスナップショット

テーマ: footballnext-ios の近代化(広告SDK全面更新・Zucks/Moloco 追加・GMA13 移行)完了+GitHub リポジトリ運用の正常化。

どこまで進んだか:

  • footballnext-ios 近代化(完了・push 28f5fe7): Xcode 26.5 ビルド復旧、Zucks/Moloco 追加、広告SDK一斉最新化、GMA13/iOS13 化、Liftoff+SKAdNetwork 補完。
  • リポジトリ正常化: operation 配下を GitHub クローン(master / Pods gitignore)へ置換、旧 CodeCommit クローンは ~/Downloads/footballnext-ios-codecommit-backup に退避。
  • footballnext-android: 16KB対応 AAB(3.0.7 / vc17)生成済み、Play Console アップロード待ち。
  • RDS/サーバ: 平常運用。

次に何をするか: 旧 CodeCommit バックアップ削除、実機で MAX Mediation Debugger 再確認、xcuserstate 追跡解除、android AAB アップロード、android 7コミット push 判断。

詰まっていること: GMA 13.3 止まり、dSYM 警告(無害)、xcuserstate 追跡継続、android 7コミット未push、cake_sessions Fatal 放置。

2026-05-21 19:35 時点のスナップショット

テーマ: footballnext-android の 16KB 対応リリース対応 ── コード作業は完了・実機検証済み、Play Console アップロード待ち。RDS/サーバ側は平常運用継続。

どこまで進んだか:

  • footballnext-android 16KB対応(対応期限 2026-05-31): 原因=Realm の native lib のみ。Realm 7.0.8→10.19.0(16KB対応版)で解消。
  • 4年分のツールチェーン近代化(Gradle 8.9 / AGP 8.7.3 / Kotlin 2.1 / targetSdk 35 / minSdk 24)、ViewBinding 全面移行(43ファイル)、依存刷新(OneSignal 5 / AdMob 23 / okhttp 4)、nend撤去、YouTubeプレイヤー差し替え。
  • 実機テストでクラッシュ4件を発見・修正。署名済みリリースAAB(versionCode 17 / 3.0.7)生成、16KBアラインメント検証OK。
  • footballnext-android リポジトリに7コミット(ローカル、origin 未push)。

次に何をするか:

  • AAB を Play Console にアップロード(内部テスト→製品版推奨)、動画/コメント/設定画面の手動確認、OneSignalプッシュ実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認、7コミットの origin push 判断。

詰まっていること・未決事項:

  • footballnext-android の7コミットが未push。cake_sessions Fatal の許容度(放置・頻度上がったら見直し)。

2026-05-19 09:34 時点のスナップショット

テーマ: 平常運用フェーズ(RDS チューニング + baseballnext 統合は完了、エラーログスパイクは原因特定済み・対応保留)

どこまで進んだか:

  • footballnext RDS チューニング(performance_schema 不発弾撤去 / old_articles・impressions 空化 / articles OPTIMIZE / index A 群 DROP)→ DB 9.1GB → 4.1GB、ReadIOPS -61〜70%
  • baseballnext 統合(5/9 完了、5/11・5/19 観察で問題なし、buffer_pool ヒット率 99.99% 維持)
  • エラーログスパイクアラート(5/16・5/17・5/19)の原因特定 = cake_sessions Duplicate entry / bot のパススキャン由来

次に何をするか:

  • 即時アクションなし。観察モード
  • cake_sessions Fatal の頻発が続くようなら対応(A: 攻撃パス拒否 / B: Session→File or Redis)

詰まっていること・未決事項:

  • cake_sessions Fatal の許容度(今は放置、頻度上がったら見直し)

2026-05-09 19:00 時点のスナップショット

テーマ: RDS (footballnext) のチューニング → baseballnext 統合受け入れ準備フェーズへ

どこまで進んだか:

  • performance_schema 不発弾撤去(pending-reboot 解消)
  • old_articles 空化(約 2GB 解放)
  • articles の OPTIMIZE TABLE(2.8GB → 0.76GB、index 75%減)
  • articles インデックス A 群 4 個を Step 1+2 一気に実行(INVISIBLE → 即 DROP、index 499 → 369MB / -130MB)
  • Step 2 効果検証完了: ReadIOPS -61〜70%(FreeableMemory はほぼ変化なし)
  • impressions の機能廃止(INSERT コメントアウト+デプロイ+TRUNCATE で 730MB 解放)
  • DB 全体: 9.1GB → 4.1GB(-54%)
  • baseballnext RDS 実態調査(2.24GB / 90 テーブル / 現役稼働 / SELECT 5.8/秒)

次に何をするか:

  • baseballnext 統合作業は baseballnext-operation プロジェクト側 で進行中。こちらは受け入れ側として現状維持
  • 統合作業中・直後の footballnext RDS のメトリクス観察(FreeableMemory・CPU・IOPS・ヒット率)
  • 統合後にあらためて buffer_pool / インスタンスサイズの判断(Step 3 / medium 昇格 / 現状維持)

2026-05-08 02:24 時点のスナップショット

テーマ: RDS (footballnext / db.t4g.small) の FreeableMemory カイゼン

どこまで進んだか:

  • performance_schema 不発弾撤去(pending-reboot 解消)
  • old_articles 空化(約 2GB 解放)
  • articles の OPTIMIZE TABLE 完了(2.8GB → 0.76GB、特に index 75%減)
  • DB 全体 9.1GB → 5.0GB、FreeStorageSpace +2.66GB
  • hot_keyword_rankings の利用パターン調査(最新dir top2 のみ参照、過去データは死蔵、書き込みも実質停止)
  • 19-20時 FreeableMemory スパイクは「自分の調査クエリ起因の人為的事象」と切り分け完了

次に何をするか(優先順):

  1. インスタンス昇格 db.t4g.small → medium の段取り(メモリ問題の本命解、+$30/月)
  2. CloudWatch アラーム新設(FreeableMemory < 100MB)
  3. hot_keyword_rankings のデータ整理(直近 N 日分残し or TRUNCATE、実行可否は別途判断)
  4. articles のインデックス整理 A 群 4 個(優先度下がった、追加効果は数百MB)

別件で要対応: RDS SG 0.0.0.0/0 開放、DB パスワード平文、cron EC2 AMI 2018.03 EOL、インスタンス昇格


2026-05-07 21:47 時点のスナップショット

テーマ: RDS (footballnext / db.t4g.small) の FreeableMemory カイゼン

どこまで進んだか:

  • 不発弾 performance_schema = 1 (pending-reboot) を engine-default (0) に戻した(再起動時の事故防止)
  • old_articles を TRUNCATE して約 2GB 解放(phpMyAdmin 経由)
  • 現状把握: buffer_pool 1024MB / ヒット率 99.996% / working set < 1GB
  • articles の断片化(data_free 1.1GB)と過剰インデックス(22個 / 2GB)を特定

次に何をするか(優先順):

  1. articles のインデックス A 群 4 個(article_site_id / image_width / title / del)を INVISIBLE に切り替え → 1〜数日観察 → 問題なければ DROP INDEX → OPTIMIZE TABLE(約 1〜1.5GB 圧縮見込み)
  2. CloudWatch アラーム新設(FreeableMemory < 100MB)
  3. 余裕があれば B 群(replicated / comment_update_unixtime)も同手順で

別件で要対応: RDS SG 0.0.0.0/0 開放、DB パスワード平文、cron EC2 AMI 2018.03 EOL、インスタンス昇格

ROADMAP(計画)

ロードマップ

今週

  • ダイジェスト新機能: トムソンへDM返信→実装許可取得(2026-07-22 完了)
  • ダイジェスト新機能: 実装フェーズ M0〜M7(2026-07-22 完了・Codex分業+Claudeレビュー、R-1〜R-18全消化・最終承認)。見積もりステップは実装許可が同日に出たため省略
  • ダイジェスト: 運用適用(順序厳守)(2026-07-23 完了): DDL適用→server push→crontab→S3配信・後方互換smoke まで実施
  • ダイジェスト: 対象選手マスター初期投入+日本語名解決パイプ(Claude)+チームname_ja整備(2026-07-23 完了・07-24 に代表11名を追録して計65名)
  • ダイジェスト: 試合詳細の選手欄刷新+本番データ不具合修正(2026-07-24 完了): クラブ/日本代表の選手欄を仕様に統一、related_articles のデコード型ずれ修正、team_id=0 のチーム誤解決ガード
  • ダイジェスト: iOS実機確認+TestFlight配布(2026-07-24 完了・iPhone 14 で通し確認)
  • ダイジェスト: トムソンのフィードバック対応(1回目)(2026-08-01 完了): FBシートの指摘2件(対象0人試合の日本人選手欄非表示・日本代表初期表示期間への解釈変更)を server 2コミット+iOS 2コミットで対応。デプロイ・TestFlight配布・DM返信済み
  • ダイジェスト: 設定値の入力上限を0〜365へ拡張(3層一致)(2026-08-03 完了・server 6634a4d・デプロイ済み): トムソンの99入力要望対応。モデルバリデーション+presenter/providerクランプを統一
  • ダイジェスト: トムソンのフィードバック対応(2回目・仕様v2.7)(2026-08-04 完了): 代表試合詳細の選手一覧を状態別1行表示へ(試合前=国旗+クラブ名+ポジション、発表後=先発チップ、LIVE=出場中/交代済チップ)。server e59c037+iOS e032349/5ad53eb。行重なりバグ修正済み。TestFlightビルド9配布済み(ビルド8は欠陥で破棄)
  • ダイジェスト: 対象リーグにエールディヴィジ(88)・スーペルリーガ(119)を追加(2026-08-04 完了・server 5c3a226・デプロイ済み): 夏移籍5選手の対象外化を解消
  • ダイジェスト: 久保・上田の「最近5試合」整備(2026-08-04 完了): 久保=ソシエダ6試合バックフィル、上田=昨季フェイエ5試合をfixture手動INSERT+updateFixtureで投入(渡辺剛5試合・小川1試合も副産物)
  • ダイジェスト: トムソンのフィードバック対応(3回目・仕様v2.11)(2026-08-05 完了): 項目6・7=FINISHED選手行の日の丸+配色、項目12=代表PRE_MATCHのリーグ国旗、項目11=選手詳細ヘッダーの国旗、項目9・10=確認代行で差異発見→クラブLINEUP/LIVEに状態チップ+ポジション追加。server 18af16c+iOS 609ab04/bd8de88/5480d48
  • ダイジェスト: 試合タブ空表示バグ修正(2026-08-05 完了・iOS 2278819): ダイジェストタブ出現時のタブ再構築と試合一覧取得の起動時競合。まとめてappend+reloadData方式へ(実況版一覧も同修正)。ビルド9に残存
  • ダイジェスト: TestFlightビルド10(5.6.0(10))配布(2026-08-05 完了): シェアレーン差分を退避してクリーンな 5480d48 からCLIアーカイブ→アップロード
  • ダイジェスト: 本番DBに 004(設定説明文の「初期表示期間」化UPDATE)適用を確認(db/digest/004_rename_national_mode_wording.sql・説明文のみ・値不変)
  • ダイジェスト: トムソンへDM送付+ビルド10確認(2026-08-05 完了・トムソンLINEで「修正確認できました」)
  • ダイジェスト: 第4次FB対応(仕様v2.12 項目13〜18)+シェアレーン統合+ビルド11(5.6.0(11))配布・DM返信(2026-08-05 完了・iOS 3b25410/b41fffa。試合一覧3段構成化・シェア画像の国旗画像化/拡大/平均評価緑/合計欄強調。before/afterスクショは fb-items13-18-shots/)
  • ダイジェスト: トムソンのビルド11確認→追加指摘対応(2026-08-08 完了・確認過程で試合一覧の選手名非表示2件の指摘→08-07調査回答、第5次FB=シート項目19〜21として受領)
  • ダイジェスト: 第5次FB対応(仕様v2.14 項目19〜21)+ビルド12(5.6.0(12))配布・返信(2026-08-08 完了・server 1e59b44+iOS 3fa1a05。対象大会を海外組14大会/日本代表5大会に分割=UECL848/クラブW杯15/E-1(23)追加・MVP候補条件+順位決定・シェア画像クラブ情報の右揃え。検証スクショは fb-items19-21-shots/)
  • ダイジェスト: トムソンのビルド12確認→追加指摘対応(2026-08-08 完了・確認過程でMVP順位再変更の提案→逆提案合意=第6次FB=項目22・23として受領)
  • ダイジェスト: 第6次FB対応(項目22・23=MVP並び順共通化・仕様v2.15+ザルツブルク国旗)+ビルド13(5.6.0(13))配布・返信(2026-08-08 完了・server ac07f72+iOS 2423d53。欠損下位の共通並びで一覧先頭=MVP保証・fixtureクエリにcountry追加でcountry_code全クラブnull配信を根本修正・NEC登録漏れ修正)
  • ダイジェスト: トムソンのビルド13確認→追加指摘対応(2026-08-19 完了扱い・確認過程の指摘は08-10のバッジ#24=ビルド14と、第7次FB=シート項目25〜38として受領)
  • ダイジェスト: 更新バッジ#24修正+ビルド14(5.6.0(14))配布・返信(2026-08-10 完了・iOS b248d50。先読み生成時の即時既読化を排し実表示時のみ既読化+フォアグラウンド復帰時の再判定。※DEVLOG未記録セッション・返信 トムソン返信_更新バッジ修正_20260810.md)
  • ダイジェスト: 第7次FB対応(シート項目25〜38・仕様v2.16=チームロゴ/表示仕様統一)+ビルド15(5.6.0(15))配布・返信(2026-08-19 完了・server efdb9dc+iOS ee88a7d。#25=API側スタッツ配信遅延の特定・9試合データ修復・backfill72h窓リトライ化/試合一覧=チームロゴ・揃え変更・2名常時実名・リーグロゴ縮小/ダイジェスト=ロゴ+クラブ別グループ表示・進行中/これから共通化)
  • ダイジェスト: トムソンのビルド15確認→追加指摘対応(2026-08-23 完了扱い・進行中の実画面確認での指摘=第8次FB=シート項目39〜47として受領)
  • ダイジェスト: 第8次FB対応(シート項目39〜47・仕様v2.17=試合状態/スコア表示/代表経由)+ビルド16(5.6.0(16))配布・返信文作成(2026-08-23 完了・server 329b484+iOS 926f17f。#45=代表直近3試合が大会フィルタで常時空だった根本修正/#40=livesにlineup_announced追加配信+青「スタメン」/試合状態ピル3色+LIVEパルス・チップ固定幅・スコア所属クラブ基準・キックオフ前vs。全項目のbefore/after比較画像は fb-items39-47-shots/compare/)
  • ダイジェスト: 返信文+比較画像9枚+パルス動画のYou送付→トムソンのビルド16確認→追加指摘対応(2026-08-26 完了扱い・ビルド16確認の過程で第9次FB=シート修正依頼2件+質問+ストア相談として受領)
  • ダイジェスト: 第9次FB対応(シート修正依頼2件=Is Publish表示制御・過去試合「もっと見る」・仕様v2.18)+ビルド17(5.6.0(17))配布・返信(2026-08-26 完了・server cffe5d7+iOS 4fd01fb。Is Publish=表示側共通判定(未終了かつ両OFFのみ非表示)/もっと見る=(event_time,id)カーソル+51件判定。佐藤龍之介「登録外」はAPI-FootballのID重複(410840→542861)と特定)
  • ダイジェスト: 第9次FB返信へのトムソン回答対応(2026-08-26 完了・アヤックス Is Publish=ON+板倉のボルシアMG移籍はトムソン側で更新済み。佐藤龍之介のマスタID切替(410840→542861)+旧stats削除+updateFixture/1570342,1570352 を本番適用し、選手詳細に先発2試合・評価が表示されることを確認)
  • ダイジェスト: 第10次FB対応(シート項目50=終了済み試合の出場判定)+3試合データ修復・返信(2026-08-30 完了・server ab03235。playersブロックの出場時間・G/Aを出場判定の正に統一。遠藤航・町野修斗・大迫敬介を修復し「出場17人→15人」。アプリ側変更なし)
  • ダイジェスト: トムソンのビルド17確認(2026-08-30 完了・全項目OK。項目50も同日修正・確認済み)
  • ダイジェスト: 上田綺世/リール(79)の試合が出ない件=トゥールーズ戦(1552754)の取り込み漏れを修復し、FixtureImporter(直近3日以内の開始済み未登録試合の挿入+所属変更/Is Publish ON時の即時取り込み)で再発防止(2026-09-04 完了・server e9934f2・トムソンへ事後報告済み)
  • ダイジェスト: 管理画面フック(FnJpPlayer/FnliveTeam afterSave)の実動確認(任意・上田の行を変更なしで保存して正常終了を見る。Claude側はAuthが要るため未実行)
  • ダイジェスト: needs_review 選手のAPI-Football ID重複精査(佐藤と同様に「出場しているのに登録外」になる選手が他にもいないか、/players/squads?team= で現行IDを突合)
  • 運用: 本番 /api4/jpPlayer の間欠的な30秒超タイムアウトの原因調査(2026-08-05に2回観測・リトライ即成功・player_not_found経路は常時約35秒)
  • ダイジェスト: 一時設定を仕様値へ戻す(days_before=3 / days_after=1 / upcoming=7 / recent=1)※トムソン側で実施の合意(08-04)。リリース済みなので戻っているか本番設定で確認する
  • ダイジェスト: 5.7.1(ビルド23・管理表68 G・A 青太字+ストアメタデータ更新)リリース(2026-09-22 完了・トムソン申請・審査通過。iOS タグ 5.7.1=f607dbb)
  • ダイジェスト: App Store提出→5.6.0(ビルド17)リリース(2026-08-30 完了・トムソン申請・審査通過・READY_FOR_SALE。iOS タグ 5.6.0=4fd01fb)
  • ダイジェスト: 板倉の昨季アヤックス分バックフィル(任意・トムソンから指名があれば。上田と同手順で数分)
  • 実況板/ダイジェスト: トムソン依頼のリーグ追加(キリンカップ=依頼 ID 8846 は提供元に無く 916 で登録・UEFAネーションズリーグ 5)+PK 決着の途中データ誤判定の修正+得点者欄から PK 戦を除外(トムソン②)(2026-10-04 完了・server 7f85b0c/2b68506/100086c。10/1 エクアドル戦を遡り投入・選手マスタに荒木/望月を追加)
  • ダイジェスト: 10/5 キリンカップ決勝(日本対NZ)の試合後確認(代表選手欄の出場時間・評価・MVP。PK になったら得点者欄に PK 戦が出ないこと・途中データと誤判定しないことも見る)
  • 実況板: リーグV3追加画面(addConfirm)のデプロイ→トムソン依頼3リーグ(119 Superliga / 848 UEFA Europa Conference League / 88 Eredivisie)を新画面から投入(2026-08-01 完了・本番でリーグ登録とEredivisie 96試合/ECL 4試合の取り込みを確認)
  • 海外組: 対象選手Edit画面のチーム設定UI改修(2026-08-01 完了・server 602de4c デプロイ済み): 空プルダウン問題を「数値入力+fnlive_teams名前検索補助+存在バリデーション」で解消。addのPK入力も解禁
  • 海外組: 選手↔クラブ紐づけ+コペンハーゲンpublish+Superliga取り込み(2026-08-04 完了): 海外7名はYou実施済みと判明、J組3名(大迫=広島282/早川=鹿島290/長友=FC東京292)をSQL投入(冨安のみ0=クラブ名省略が正)。コペンハーゲン is_publish=1・スーペルリーガ20試合取り込み済み
  • 起動時広告A/B コホートparam型ズレ修正(2026-06-28〜07-08 完了)。first-lookで日付/版/bool paramが全件(not set)→emit境界の非数値ラベル化で解消。Codex3ラウンド。ios 28af0f1/698cf5f/83a4796。
  • Apple 5.2.3却下対応(YouTube discovery)+5.5.5リリース(2026-07-06 リリース)。YouTube動画のメニュー項目+トップ新着カルーセルを一時非表示(可逆)。ios 98c67e2。
  • 本番着弾検証+v9 config配置で実験クリーン再スタート(2026-07-08 完了)。5.5.5で5 dimension全着弾確認→v9(min_build=50505・窓7/6-8/17)をS3配置→cv_9着弾89%確認。
  • 起動時広告A/B 再スタート後の健全性確認(dev-timer #30・2026-07-11 完了・全項目クリーン): SRMなし(A=145/B=144)・cv_8急減(→68件/日)・enrollment_day ISO形成正常・applied_interval/kill_observed/funnel/収益 全て健全。留意: クリーンコホート蓄積 ~100人/日=窓終了までで総勢約4,000人規模。
  • 起動時広告A/B 成熟後残差率の初回read(dev-timer #31・2026-07-22 前倒し完了・ガードレール④ PASS): A=5.38%/B=3.67%・群間差1.71pp・SRMなし。次は #36(8/17)窓クローズ確認・#37(9/15)本解析。
  • 起動時広告A/B enrollment窓クローズ確認+残差率/SRM中間再read(dev-timer #36・2026-08-17 完了・全PASS): 窓は8/17 0時に正常クローズ・クリーンコホート最終規模3,141人(A=1,548/B=1,593)・SRMなし(p≈0.42)・残差率 A=5.51%/B=3.84%(差1.67pp・基準内)。次は #37(9/15)本解析。
  • 起動時広告A/B 本番解析(成熟後 残差率・円/人)(dev-timer #37・2026-09-15〜18 Codex 2巡で完了・ab-analysis-20260915/): 結論「全面B化は保留」。主指標 −1.03pp[−3.29, +1.31]・MAX 収益/人 +1.08%(app_open −12.7% を banner/native が相殺)。保留理由=イベント発火範囲の不整合・管理画面売上との照合なし・MAX外売上が未知。
  • 起動時広告A/B 3巡目=事業判断としての結論(2026-09-22 着手→09-23 結果報告をトムソンへ送付・09-26 算出方法の質問に回答・09-28 トムソンが「24時間に1回」の方向で合意して完了): トムソン提供の MAX 起動時枠CSVで GA4 ILRD の再現誤差 3.5%(⑤合格)・gross 同一基準・MAX外は全てコンテンツ面の枠と確認済み。残りは ①You の判断(今ある情報で「下振れ最悪でも月1万円規模」の形でまとめるか、トムソンへ MAX 全ユニット+MAX外の管理画面CSVを追加依頼するか)②まとめを ab-analysis-20260915 に書く。
  • 起動時広告A/B YouTube動画のしれっと復活(ほとぼり冷めた頃・別バージョン): コメントアウト解除+#if false除去。
  • RDS performance_schema の pending-reboot 解消(2026-05-07 完了)
  • old_articles のデータ廃棄(2026-05-07 完了、約 2GB 解放)
  • articles の OPTIMIZE TABLE(2026-05-08 完了、2.8GB → 0.76GB、index 75%減)
  • articles のインデックス整理 A 群 4 個(2026-05-08 完了、INVISIBLE → 即 DROP、index -130MB / ReadIOPS -61〜70%)
  • impressions の機能廃止と空化(2026-05-08 完了、730MB 解放)
  • 19-20 時 FreeableMemory スパイク調査(2026-05-08 完了、人為的と確定)
  • Step 2(DROP INDEX)効果検証(2026-05-09 完了、dev-timer #14 経由)
  • baseballnext 統合作業(baseballnext-operation 側で進行中、こちらは受け入れ準備状態を維持)
  • footballnext-android: 16KB対応版(v3.0.7 / vc17)を Play Console にアップロード(完了・2026-08-29 に本番稼働を確認)
  • footballnext-ios: 旧 CodeCommit バックアップ(~/Downloads/footballnext-ios-codecommit-backup)の削除(新環境ビルド確認済み)
  • footballnext-ios: 実機で MAX Mediation Debugger 再確認(Liftoff が Completed か、各SDK が LATEST 表示か)
  • footballnext-ios 5.5.1(試合詳細の得点者表示 + YouTube プレイヤー WKWebView 化・自動再生オフ)を Xcode アーカイブ → App Store 提出(You が申請作業中)
  • 【マネタイズ改善・最優先】起動時広告 削減A/B のトムソン提案資料化(2026-06-18 完了)。提案書HTML作成→Codex2ラウンド反映→thomsons S3に公開アップロード。→トムソン承認済み
  • 起動時広告A/B の実装計画HTML作成+Codex 5ラウンド設計レビュー(2026-06-18 完了)。起動時広告A-Bテスト_実装計画_20260618.html rev6 + 正典 AB_TEST_MEASUREMENT_SPEC.md rev6 同期。round5で設計レビュー完了判定。
  • 起動時広告A/B 実装フェーズ(BASE A〜F)(2026-06-19 完了)。footballnext-ios コミット 4ebdeb6。新規4ファイル(ABTest/ABLaunchTracker/ABAdCollector/ABConfigLoader)+既存14ファイル配線。BASE契約5点(OpenContextRecord/CAS・at-most-once・3 artifact+版番号・canonical fn_ad_revenue一本化・MAX枠collector接続)実装。フルビルド成功・シミュレータ実機で全ファネル発火確認。本番影響は中立(S3 config 未配置の間は現行挙動)
  • 起動時広告A/B GATE-値を measurement_protocol として凍結(2026-06-19 完了・叩き台)。割当ハッシュ/clock_anomaly/fail-safe/maturation/円人コホート/residual/収益正規化/事前テスト合格しきい値①〜⑧。要トムソン最終確認は3項目(コホート定義・residual/正規化・合格しきい値)
  • 起動時広告A/B GA4 custom定義 登録(2026-06-19 完了)。ディメンション18(イベント16+ユーザー2)+指標3=計21件、実コードと一致(スクショ検証済)
  • 起動時広告A/B native loader-ad lifecycle 修正(2026-06-19 完了・ios 8dc3b5a)。loader を ad とペアで retain。検証用 seed/NSLog は二重ガードのため残置
  • 起動時広告A/B 点火準備(2026-06-19 完了): トムソン3項目HTML公開・enrollment窓確定・段階A配置・pre-flight PASS。
  • 起動時広告A/B 一度点火→Codex実装レビューで重大バグ→緊急停止(2026-06-19)。active=false / v7 / ttl14日 へ戻し中立化。5.5.3 据え置き。
  • 起動時広告A/B Codex実装レビュー round1(high8) / round2(high4) の修正大半(2026-06-19): #1状態分離・R2-#1 context世代snapshot+TTL14日・#5#7 callback・#6/R2-#m6 impression・#3 build・#11 群A・#9a・#10/#8/R2-#m2/#m4/#m5/#m1/#m3。すべて ios master へ push。
  • 起動時広告A/B R2-#4 起動overlay gap 修正(2026-06-19 bcc9950): LaunchManager 連鎖に boot covering token。負の確認(早期発火しない)済、正の確認は実機へ。
  • 起動時広告A/B R2-#3 native loader handle方式(2026-06-19 55fbe06・compile確認のみ)。実機で native描画+ILRD 検証必須(A/B点火ブロッカー)。
  • 起動時広告A/B 実機検証(You・Xcode→実機): 手順書 ab_test_device_verification.md の A〜D。TestFlight必須でなく実機直挿しで足りる。
  • (任意)Codex round3(R2修正群の最終確認)/R2 low(build符号化文書/fail-close・DEBUG定義+XCTest)。
  • 起動時広告A/B 実装レビュー(gods-talk)round5〜8 完了・点火 sign-off 取得(2026-06-19)。native lifecycle(lease/確定destroy/timeout/expire/同一pass再deque)・kill属性 snapshot↔applied分離・GA4-only整合・TTL統一を全解消。round8 で「点火前チェックリスト完了後 active=true 可・high/medium ブロッカーゼロ」。ios 0089f65→3f64ade→31dc22b、op ab066e4→546dd0a→07aa09f。
  • 起動時広告A/B 点火前チェックリスト(コード外・必須。完了後に active=true): ①GA4 custom定義の実登録(kill_observed/applied_interval_minutes 追加分) ②enrollment窓・判定値の確定 ③初期active=falseでの本番config取得確認 ④点火/停止時刻の運用記録。
  • 起動時広告A/B 5.5.3 実機で MAX native の表示・expire/reload・impression スモーク(reviewer 優先#2。lease本体は round5 実機PASS済、expire/reload は未観測)。
  • 起動時広告A/B 実機スモーク+点火前チェックリスト合格 → 5.5.3 提出→承認後リリース→ S3 再点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ GA4着弾を Data API 検証(要 ga4 MCP 接続)+事前テスト①〜⑧。
  • 起動時広告A/B low(任意・非ブロッカー): build符号化の検証/文書化、通常DebugにDEBUG定義+fake loader/entry で timeout/expire/render false の XCTest整備、render false後の安全なrow reload、runbook の実コードcommit表記更新。
  • 起動時広告A/B のトムソン依頼を握る: ①GA4 Viewer→Editor 権限 ②「iOS・Football NEXT・対象月」の月次総売上。S3 app_open_ad_interval=50(群A基準)は実測確認済み。処置差(50分→24h の抑制割合)は事前テストで実測
  • footballnext-android: targetSdk 36 対応リリース(Play の対応期限 2026-08-31)(2026-08-29 完了・v3.0.8 / vc22 を本番100%公開): AGP据え置き+抑止フラグ、edge-to-edgeインセット共通処理、予測型「戻る」移行、大画面オプトアウト。既存バグ2件(YouTube全動画再生不可・動画詳細クラッシュ)も同梱
  • footballnext-android: リリース後の Android Vitals 観察(数日・クラッシュ率とANR。edge-to-edge と予測型「戻る」は端末差が出やすい)
  • footballnext-android: MovieDetailActivity クラッシュの経路特定(ガードで発現は塞いだが原因は未特定。Vitals に同種が残るなら再調査。3シナリオでは再現せず)
  • 不快クリエイティブのブロック設定(成人/マッチング/ショッキングをメディエーション管理画面でブロック)
  • 広告挿入間隔の緩和(トップ一覧10件中2枠・コメント/動画5件ごと → 間隔拡大)
  • ダイジェスト: 新仕様v1.2「選手詳細/試合別個人スタッツ」実装+5.7.0(ビルド18)TestFlight 配布・返信(2026-09-08 完了・server d0525b5+iOS 564fcd0。DDL 005・身長78人・直近5試合の統計90試合を本番投入。ASC VALID。スクショは player-stats-shots/)
  • ダイジェスト: トムソンのビルド18 確認→仕様v1.3(管理表51〜54=GK セービング/PK内訳/null・0 区別/パス成功率併記/STATS触覚)対応+ビルド19 配布(2026-09-09 完了・server 8cd859e+iOS 0024f9c。※DEVLOG未記録セッション、09-12 に補記)
  • ダイジェスト: 第11次FB対応(修正管理表10行・仕様v2.19/v1.4=未出場の評価0の混入修正・平均の四捨五入・グラフ表示条件・評価なし「-」・試合行の出場時間2段表示と列統一)+本番データ修復+ビルド20 配布・返信(2026-09-12 完了・server 773cb23+iOS 7c4a888。スクショは player-rating-shots/)
  • ダイジェスト: トムソンのビルド20 確認→第12次FB(管理表66・67=G・A青太字強調・出場なし/登録外の上下中央・仕様v2.20/v1.5)対応+ビルド21 配布・返信(2026-09-12 完了・iOS 4ceaf9f。比較画像は fb-items66-67-shots/compare/)
  • ダイジェスト: トムソンのビルド21 確認→App Store 提出→5.7.0(ビルド21)リリース完了(2026-09-14 完了・ASC READY_FOR_SALE・iOS タグ 5.7.0 を 4ceaf9f に付与。申請〜審査はトムソン側)
  • トムソン管理表68(試合詳細の G・A 青太字)+69(J リーグで「スタメン」ラベルに切り替わる)対応+5.7.1(ビルド23)TestFlight 配布・返信(2026-09-20 完了・server 11da868 デプロイ済み=69 はアプリ更新不要/iOS f607dbb。ビルド22 はスペース入り表記の旧版で使わない。比較画像は fb-item68-shots/compare/)
  • トムソンのビルド23 確認→5.7.1 申請(ストアのスクショ・アプリ名等の更新と同時・トムソン側)→リリース後に iOS タグ 5.7.1 を f607dbb に付与(2026-09-22 完了)
  • ダイジェスト: UEL の選手統計が API で提供され次第 /cron/backfillPlayerDetailStats を再実行(今季 UEL は players ブロック無し・6試合が未反映)
  • 実況板/ダイジェスト: Championship(40)・2.Bundesliga(79) 追加(2026-09-09 完了・トムソンがポータル登録+選手14名追加、server 8f4e720 で対象大会追加、importLeagueFixtures で今季過去試合56試合を遡り投入・充填)
  • ダイジェスト: 週末の試合日に RapidAPI 残数を実測して API 枠の見込み(ピーク約4,800/7,500)を裏取り(翌 08:55 JST 直前にヘッダを読む)
  • トムソン管理表70〜75(日本代表戦まわり)対応(2026-09-26〜28 完了・server 783f6ca/94358b2/8137af6 デプロイ済み・アプリ更新不要): 75=会場名を取り込み時に保存/73=試合前の代表一覧を直前10日以内の代表戦の登録メンバーで作る/74=試合前一覧のポジションは選手マスタ優先/70・71=試合途中で止まった選手データは出場時間を交代記録から計算し評価は「-」(FT 後72時間は再取得を継続)。選手マスタに熊坂光希・中野就斗・谷村海那を追加。72 は現行仕様でトムソン了承
  • ダイジェスト: 9/28 ベネズエラ戦の試合後に、代表選手欄(出場時間・評価)と途中データ判定の動作を確認
  • ダイジェスト: 10/1 10:00 ウルグアイ戦を /cron/updateFixture/1628995 で再取得し、提供元が出場時間・評価を確定させたか確認(dev-timer #86・トムソンに約束済み)
  • 起動時広告の全面「24時間に1回」化の実装=iOS 5.7.2 に同梱(2026-10-10・ebb9639。非参加時の間隔を S3 の default_interval_minutes から読む。切り替えは公開後に S3 で=上の「5.7.2 公開後の切り替え」)
  • プッシュ通知: FCM 移行の server・鍵・本番反映(2026-10-10 完了): トムソン OK → APNs キー(RCX2QQWW35)と SA 鍵 → server 12e9ebb+65a6988 デプロイ → 非公開 S3・DDL・cron(834d844・HTTPS+合言葉・回収/監視)・cron.auth=enforce・CloudWatch アラーム2本(You のメール)。FCM の本番送信は停止中
  • プッシュ通知: iOS 5.7.2(ビルド24)を TestFlight 配布・テスト送信が実機に到達(2026-10-10 完了・4718b1e/ebb9639/9a4fe0e)
  • プッシュ通知: 5.7.2 の実機テスト(画像・タップで記事・終了状態からのタップ)→ トムソンへ申請依頼(2026-10-16 に App Store での公開を確認・ビルド25・iOS タグ 5.7.2=78fe327。確認表の2〜7と申請の経緯は記録なし)
  • プッシュ通知: 定期送信の FCM を一覧 JSON(teiki_recent.json)より先に送り、一覧の S3 に期限を付ける(2026-10-16 完了・server b42a298 デプロイ済み。17:55 の定期送信で確かめる)
  • プッシュ通知: 5.7.2 公開後の切り替え(fcm.ios=1・fcm.ios.since・onesignal.ios=0)と、起動時広告の S3(default_interval_minutes: 1440・config_version: 10)※push_settings は 10-11 に切り替え済み。残りは S3 だけで、時期は You が決める(5.7.2 は 10-16 に公開を確認)
  • プッシュ通知: Android の FCM 対応(計画 4章・3.0.9)→ 公開後に fcm.android=1・onesignal.android=0
  • プッシュ通知: トムソン判断待ち(BaseballNEXT〔MAU 3,023 で停止中〕の扱い。iOS の一時有料化と更新の案内はしない=10-10 You 判断)

今月

  • プッシュ通知: FCM 専用のサービスアカウントへ差し替え(ロールは Firebase Cloud Messaging API Admin だけ。作成と権限の付与はオーナー=トムソン)→ S3 の鍵を差し替えて再デプロイ → 標準 SA の鍵を無効化

  • プッシュ通知: PUSH_ALERT cron_rejected の扱い(外からの呼び出しでアラームが鳴るようなら NOTICE に下げる)

  • ダイジェスト新機能の実装着手(2026-07-22 前倒し完了。実装自体がM0〜M7で完了、残りは運用適用=今週欄参照。日本語名解決パイプの実運用だけ未着手)

  • 有料「広告なし/サポーター」課金の新設(StoreKit) ─ 月額¥300–500 or 買い切り。収益多様化+ロイヤル層の受け皿(review-20260605.html 由来)

  • ストア評価の底上げ運用(広告改善とセットでレビュー依頼+ストア返信)

  • リテンション/広告影響の計測基盤(GA4の screen 命名整備・ad_impression 送信・面別計測)

  • footballnext-ios: OneSignal を arm64 simulator 対応の XCFramework(5.x) へ更新(シミュレータ起動不可=QA阻害の解消) ※2026-06-13 に 5.x へ移行済み。FCM 移行で OneSignal 自体を外す予定

  • (任意)digest対象リーグのハードコード14件(_isDigestTargetLeague)に 88 Eredivisie / 119 Superliga を追加するか判断(現状は is_publish=1 のクラブ経由で取り込めており必須ではない。is_publish に依存しない取り込みにしたくなったら)

  • API Gateway 飛び地の原点直叩き移行: /article/{id}(footballnext-article)+/footballnext-teiki-notification-articles/{id} をクライアント側で https://fnext.thomsonsapp.com/api4/... 直叩きに差し替え → 浸透後に Lambda+ルート削除 → 最終的に footballnext API Gateway(7isghft565) を撤去。論点は DynamoDB READキャッシュの代替(必要なら CakePHP 側キャッシュ)。原点は HTTPS 200・iOS は ATS 無効で障壁なし

  • iOS OshiraseViewController のデッドコード整理(削除済み footballnext-oshirase-v2 参照 /oshirase-v2/{id} の除去。90日invocation 0 のデッド画面)

  • baseballnext 統合後の footballnext RDS メトリクス観察(FreeableMemory・CPU・IOPS・ヒット率)

  • CloudWatch アラーム新設: FreeableMemory < 100MB

  • RDS Security Group sg-083bace7c7ee9e450 の 3306/tcp 0.0.0.0/0 開放を是正

  • DB 認証情報を database.php から SSM Parameter Store / Secrets Manager に移管

  • footballnext-android: リリース後の残確認(OneSignalプッシュの実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認)※動画/コメント/設定画面は 2026-08-29 に実機確認済み。OneSignal は 2026-10-10 に送信不可(FCM v1 設定エラー)と判明→FCM 移行(今週欄)で扱う

  • footballnext-android: 5月からの7コミットを origin へ push(2026-08-29 完了・今回の4コミットと合わせて11件を push)

  • footballnext-ios: xcuserstate の追跡解除(.gitignore 済みだが過去コミットで追跡継続)

  • 得点者まわりの dead code 掃除(方針転換で残った未使用 LiveFixtureScorersCell.xib + 空セル)

今四半期

  • footballnext-ios: MinimumOSVersion を 13.0→15.0以上へ引き上げ(2027年春以降 15.0未満はApp Store Connectへアップロード不可。ビルド11アップロード時に警告検出。次の通常アップデートで対応)
  • baseballnext 統合後の最終チューニング判断(buffer_pool 維持 / 縮小 / medium 昇格)
  • articles のインデックス整理 B 群(replicated / comment_update_unixtime、優先度低)
  • cron EC2 (Amazon Linux AMI 2018.03 / EOL) の刷新
  • rss-crawler の cron 一元化: rss-crawler-notice-prod-crawlArticle(RSS発見→SQS投入の実ロジック・毎分稼働)をサーバ側エンドポイント化して cron から叩く。curl ラッパーではないため移行に設計が必要(残存する数少ない定期実行 Lambda の最後の砦)
  • hot_keyword_rankings 書き込み停止(最新 2026-04-09)の真因調査(cron 停止 / 別マシン稼働 / 廃止意図)
  • hot_keyword_rankings の古いデータ整理判断
  • footballnext-ios: Google AdMob を 13.4 へ(Google 製 InMobi アダプタが GMA 13.4 に対応したら)

いつか

  • 実況板: 延長なしの PK 決着で、スコア詳細に「0 延長 0」の行が出る件(提供元が延長なしでも extratime を 0-0 で返すため。10/1 エクアドル戦で確認・トムソン未報告。直すなら LiveComponent::getScoreDetails で延長の有無を記録から判断する)
  • ダイジェスト: 代表戦にもクラブと同様に STATS(個人スタッツ)を表示(管理表72・トムソン「今後の改修候補」09-28): iOS の代表直近ブロックに STATS 列を追加(要リリース)+9/8 以前の代表戦の個人スタッツを再取得
  • ダイジェスト: 管理画面から代表の招集メンバーを登録できるようにする(管理表73①・トムソンが要否を検討中): 活動期間の1試合目も JFA 発表後すぐ反映できる(現行は直前の代表戦の登録メンバー→無ければ提供元スカッド)
  • footballnext-android: targetSdk 37 対応(2027年の Play 期限): 大画面(sw>=600dp)の向き固定オプトアウト PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY が API 37 では無効になるため、それまでに大画面対応が要る。Realm Java(EOL)を抱えたままだと AGP 更新も詰まるので、Realm 離脱とセットで計画したい
  • articles の本文格納方式の見直し(body カラムが varchar(512) で空のまま、本文はどこ?を整理)
  • hot_keyword_rankings / search_logs / temp_rss_load_times のリテンションポリシー策定
  • CakePHP 2 系の依存からの脱却検討
  • gp2 → gp3 ストレージへ移行(コスト追加 NG 方針につき凍結中、必要が出たら再開)
  • リードレプリカ復活(コスト追加 NG 方針につき凍結中)
  • cake_sessions Duplicate entry Fatal の対応(bot パススキャン由来、頻度上がったら格上げ)。候補: ALB/Apache で攻撃パス拒否 / Session 保存先を File or Redis に移行 / try-catch 握り潰し
  • footballnext-ios: AdAttributionKit (iOS17.4+) 対応(全ネットワーク横断、現状は SKAdNetwork のみ)
  • 過去試合の得点者バックフィル拡大(必要に応じ updateFixture で個別。全件一括はクォータ的に非現実的)

DECISIONS(意思決定)

意思決定記録

このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。


2026-10-16: 定期通知の一覧 JSON(teiki_recent.json)は FCM・OneSignal の後に期限付きで書き、記事キャッシュの用意は FCM の前に残す

背景: 定期送信は、期限なしの S3 で一覧 JSON を書いてから FCM を送っていた。そのため S3 が止まると FCM も止まる。iOS 5.7.2 の公開で、定期の FCM が一般のユーザーにも届くようになった。計画書 2-3 の初めには「公開 JSON は通知から開く画面が読むので、更新が成功してから送る」とあった。

決定: 順番を「コミット → 記事キャッシュの用意 → FCM → OneSignal → 一覧 JSON」にした(server b42a298)。

  • 一覧は、号外・お知らせと同じ NotificationSender に移した。Aws::boundedConfig(3, 10, 1) で書き、失敗したら警告して終える。
  • 記事キャッシュの用意(884fb95)は、FCM の前に残す。

理由:

  • 一覧 JSON を読むのは、インフォボタンの通知一覧だけ。通知から開く画面は ID 指定で Lambda を読む(iOS・Android のコードで確認)。このため、一覧の更新を送信の条件にする必要がない。
  • 記事キャッシュは、タップで開いた画面が空になるのを防ぐために、送る前に作る必要がある。すでに期限(接続3秒・全体5秒・最大3回)があるので、FCM の遅れは最大約16秒で済む。
  • 代替案の「一覧も FCM の前に置いて期限だけ付ける」では、S3 の不調で FCM が最大約20秒遅れる。しかも、遅らせても得るものが無い。

2026-10-11: Firebase Messaging の待ち行列を先入れ先出しに直す(Podfile の post_install で SDK を書き換える)

背景: iOS の購読同期(PushTopicSync)は、前のプロセスで終わらずに Firebase に保存された逆の操作が今回の操作の後に実行されると、購読が設定とずれたまま残る。Firebase 11.15 の FIRMessagingPendingTopicsList は、処理中のバッチが無いとき(復元の直後や、バッチが1つ終わって次を選ぶ前)に新しく積んだバッチを先に処理する。GPT レビューで、アプリ側の対策(起動後の最初の同期を記録しない)では順番を保証できないと分かった。

決定: scripts/patch_firebase_topic_fifo.rb で2か所を書き換える。

  1. 処理中のバッチが無いときは、先頭(最も古い)バッチを選ぶ。
  2. 操作を積むたびに再開を予約する(元は同じ操作がすでに積まれていると予約しなかった)。

Podfile の post_install で pod install のたびに当て直し、「未適用」「適用済み」以外の状態なら pod install を止める。アプリ側は「起動後の最初の同期を記録しない」対策は入れず、代わりに起動して最初の同期は記録を見ずに全件を呼ぶ(ビルド24の記録や、強制終了で記録の削除が書かれなかった場合のため。起動のたびに購読 API が6件増える)。

理由: 順番の問題は SDK の中にあり、アプリ側では塞げない。再開は実行中のトピックを飛ばすので、何度呼んでも二重には送らない。Pods は git 管理外なので、post_install で当て直さないと pod install で外れる。対象が無ければ止めることで、SDK の更新で知らないうちに外れることがない。代替案の「サーバーで購読を管理する」は作り直しが大きい。

2026-10-11: 号外・お知らせの付随処理(一覧 JSON・旧世代向けの処理)の AWS 通信は、期限つき・再試行1回の汎用設定にする

背景: 号外・お知らせの送信のあとに、S3 の一覧 JSON・push201607・DynamoDB を更新している。AWS SDK 2.6 の既定は、接続150秒・全体は無期限で、再試行は S3 が3回・DynamoDB が11回。期限だけ付けても再試行と掛け算になり、管理画面の応答が長くなる。

決定: Aws::boundedConfig(3, 10, 1)(1回の通信は接続3秒・全体10秒、再試行は BackoffPlugin::getExponentialBackoff(1))を、この付随処理にだけ使う。サービスごとの再試行の判定(DynamoDB の混雑による 400、S3 の RequestTimeout、資格情報の期限切れ、CRC32)は外れ、そのときはすぐ失敗することを受け入れる(You 了承)。

理由: 付随処理は FCM のあとに回したので、失敗しても通知は届く。失敗は PUSH_ALERT で分かり、一覧は公開アクションで作り直せる。サービスごとの判定を残すには SDK の内部構成を写す必要があり、保守の手間に見合わない。

2026-10-10: FCM のサーバー用の鍵は当面 Firebase 標準のサービスアカウントにし、落ち着いたら FCM 専用の鍵へ差し替える

背景: FCM HTTP v1 で送るにはサービスアカウントの鍵が要る。You の Firebase の権限は「編集者」で、権限の付与はできない(オーナーはトムソン)。iOS の通知は 10/3 から止まっている。

決定: Firebase 標準の SA(firebase-adminsdk)の鍵を You が発行して使う。FCM 専用の SA(ロールは Firebase Cloud Messaging API Admin だけ)は、落ち着いてからトムソンに作ってもらい、差し替えたあと標準の鍵を無効にする。

理由: 専用 SA にはオーナーの作業が要り、待つと復旧が遅れる。標準の鍵は権限が強いが、非公開 S3+root:apache 0640 で配り、git や手元には残さない。

2026-10-10: 秘密情報は非公開 S3+AfterInstall で配り、cron の合言葉はサーバー上のファイルから読む

背景: FCM の鍵と cron の合言葉を、Web 機2台と cron サーバーに置く必要がある。Web 機は SSH・SSM ログインができず、デプロイは CodeDeploy。You の IAM には SSM の権限が無い。cron の crontab は git(thomsons-apps-cron)で管理している。

決定: 非公開 S3 footballnext-private-config/push/ に置き、server の AfterInstall.sh が /etc/footballnext/(root:apache 0640)へコピーする(取れなくてもデプロイは止めない)。cron サーバーには ~/.push_cron_secret(600)を置き、crontab では $(cat $HOME/.push_cron_secret) で読む。

理由: Web 機のロールが S3 フルアクセスを持っていて、追加の権限が要らない。crontab に値を書くと git に秘密が入る。代替案の SSM は You の権限が無く、使えなかった。

2026-10-10: iOS 5.7.2 に起動時広告の「24時間に1回」化を同梱し、非参加時の間隔は S3 の任意キー default_interval_minutes で決める

背景: 09-28 にトムソンと全面「24時間に1回」で合意したが、追跡窓を抜けた端末(=今は全員)の間隔はアプリ内の定数 50 分で、S3 だけでは変えられなかった。FCM 移行で 5.7.2 を出すことになり、You が同梱を決めた。

決定: activation_config に任意キー default_interval_minutes を足す。TTL 内ならその値、未設定・期限切れ・取得失敗は 50 分にする。experiment_active には依存しない。値の変更は config_version を上げる。公開後に S3 で 1440・config_version 10 にする。

理由: 切り替えも戻しも S3 だけで済む。5.7.1 以前は新キーを読まず 50 分のままなので安全。kill-switch 中も同じ値を使うので、実験を止めても全体の間隔は変わらない。

2026-10-10: 新方式(FCM)への更新を促す策は取らず、自然な更新を待つ

背景: 新方式で届くのは 5.7.2 に更新して一度開いた端末だけで、旧版の利用者には OneSignal が止まっているため届かない。

決定: アプリ内の更新の促しも、OneSignal を一時的に有料にしての案内(月約207ドル)もしない。GA4 push_topics_synced で広がり具合を見る(You 判断)。

理由: 費用と手間に見合う効果が読めない。自動アップデートで相当数が行き渡る見込み。

2026-10-10: thomsons-apps-cron(cron の正本)を稼働中の crontab に合わせ、6/6 の未コミット変更は捨てる

背景: push で cron サーバーへ自動デプロイされる仕組みだが、稼働中の crontab はサーバー上で直接編集されていた(6/6 の TFN_cron 移管8行・7/23 のダイジェスト4行が git に無い)。手元には、定期送信などの塊が重複した 6/6 の未コミット変更もあった。

決定: 稼働中の crontab をそのまま ec2-user に写して commit・push し(aee5abd・動作は変わらない)、その上に今回の変更を足した(834d844)。未コミット版は scratchpad に退避して捨てた。

理由: git のまま push すると12行が消え、未コミット版のまま push すると定期送信などが毎分2回動く。稼働中が実際の正しい状態なので、それを正とした。

2026-10-10: プッシュ通知は OneSignal から FCM(トピック方式・iOS/Android 別トピック)へ移行する

背景: OneSignal の無料プランに 10/1 から組織単位で MAU 1,000 の上限が入り、FootballNEXT iOS(MAU 約15,500)は 10/3 から送信が止まった。有料化は月約207ドル。トムソンは無料の代替を希望している。Android も OneSignal 側の FCM v1 設定エラーで送れていない。

決定: 移行先は Firebase Cloud Messaging(HTTP v1)にする。宛先は今の OneSignal セグメントをトピックに置き換える。

  • iOS/Android 別に、定期通知4本(設定 OFF 以外の端末が購読)・全員(定期の ON/OFF と独立)・テストを作る。
  • 旧世代除外(gene_1_2)は不要。
  • 送信結果の記録と停止検知を同時に入れる。
  • トムソンへは「FCM で対応したい・進めてよいか」だけを先に確認する。

理由: FCM は利用料が無料で、この規模(iOS 約9,000台+Android 約1,000台)ならトピックの制約(1端末2,000トピック・購読者数無制限・同時配信1,000)に余裕がある。両アプリとも同じ Firebase プロジェクトに導入済みで、新たな契約が要らない。

  • 代替案の Amazon SNS(月100万通無料)は、端末管理をサーバーで作る必要があり保守が増える。Pusher Beams は無料が1,000端末まで。
  • 1メッセージに apns/android の上書きを付けて共通トピックで送る構成も可能だが、片側ずつ切り替え・停止できるようプラットフォーム別にした。
  • GPT と2ラウンドでレビュー済み(.devnotes/discussions/2026-10-10-onesignal-to-fcm.md)。

2026-10-04: 試合詳細の得点者欄に PK 戦の成功者を出さない(トムソン②)

背景: 提供元(API-Football)は PK 戦の成否を試合中の PK と同じ type=Goal で配信するため、PK 決着の試合では 0-0 でも PK 戦の成功者が91分以降や120分の「(PK)」得点として得点者欄に並んでいた(10/1 エクアドル戦で9行)。PK 戦があった全大会で以前からの挙動。

決定: 得点者欄の組み立て(V2Controller::fixture)で comments=Penalty Shootout の記録を除く。試合中の PK 得点は「(PK)」付きで残し、PK 戦のスコアはスコア欄の「5 PK 4」で示す。

理由: トムソンの選択(①現状維持/②得点者欄から外す のうち②)。server だけで完結し、過去の試合にも効く。本番の PK 決着18試合で、PK 戦の記録はすべて comments で判別できた(PK 戦の記録自体が無い試合は影響なし)。

2026-10-04: 延長の有無は status.elapsed ではなく記録から判断し、PK 戦の記録は G・A・出場の根拠にしない

背景: 提供元は延長なしで PK に進んだ試合でも status.elapsed=120 を返す。途中データ判定(09-28 の決定)がこれを試合の長さに使ったため、10/1 エクアドル戦で90分フル出場の選手が「30分足りない」とされ、出場時間の再計算・評価の消去・PK 成功5人への得点付与が起きた。

決定: PK 戦の記録(comments=Penalty Shootout)は選手の集計から除く。延長ありとみなすのは、PK 戦以外の記録が91分以降にあるか、選手データに105分を超える出場時間があるときだけで、それ以外は試合の長さを90分とする。

理由: score.extratime は延長なしでも 0-0 が入る試合があり(キリンカップ)当てにならない。後半のアディショナルタイムは elapsed=90+extra で届くので、elapsed が90を超える記録は延長戦のもの。出場時間の閾値を105分にしたのは、延長なしの試合でアディショナルタイム込みの値が返っても延長と誤認しないため。延長ありで提供元データが止まり、延長中の記録も無い場合は90分基準になるが、まれなので許容した。

2026-10-04: キリンカップは提供元の ID 916 で登録して日本代表ダイジェストの対象にし、UEFAネーションズリーグ(5)は実況板のみとする

背景: トムソンからキリンカップ「8846」とネーションズリーグ「5」の追加依頼。8846 は API-Football に存在せず、10月の日本代表戦(10/1 エクアドル・10/5 NZ)は親善試合(10)ではなく Kirin Cup(916)で提供されていた。

決定: 916 を実況板リーグに登録し、DigestPresenter::nationalCompetitionIds に加える。5 は実況板リーグに登録するだけで、ダイジェストの対象大会には入れない。日本語名は「キリンカップサッカー/キリン杯」「UEFAネーションズリーグ/ネーションズL」。

理由: 日本代表の試合が入っているのは 916 で、対象大会に入れないと代表戦が実況板にもダイジェストにも出ない。UNL は日本が出ない大会。名前は既存の大会名(CL・EL・UECL・アジア杯)の書き方と JFA の大会名に合わせた。UNL の追加で試合日の API 呼び出しは増えるが、代表ウィーク中はクラブ戦が止まるので枠に収まる見込み。

2026-09-30: Firebase の「key cannot be nil」クラッシュ通知(9/29 分)には対応しない(Firebase 側の障害)

背景: 9/29 に FootballNEXT を含む5アプリで Crashlytics の急増通知が出た。原因は Firebase Analytics の sdk-exp が返した不正な実験データで、GULMutableDictionary が nil キーを書いて落ちる(firebase-ios-sdk #16728・#16737)。

決定: アプリ・サーバーの修正も Firebase SDK の更新もしない。Crashlytics の Issue クローズは You の判断で後日。

理由: 原因は SDK に届く前のサーバー配信データで、報告された影響バージョンも複数(11.14/12.14 と当方 8.15.0)にまたがるため、SDK を上げても防げない。Firebase 側のロールバックで収束済み。再発時は同じ切り分け(Issue ID が別プロジェクトで同一か・クラッシュ時刻の集中)で判断する。Firebase 8.15.0 が古い点は別件で、今回は扱わない。

2026-09-28: 起動時広告は全面「24時間に1回」へ。切り替えは iOS 5.7.2 で間隔を S3 設定化してから行う

背景: 09-23 に送った A/B 結果報告への質問(記事画面の広告の MAX 収益の算出方法)に回答し、トムソンが「24時間に1回」の方向で合意した。追跡窓を抜けた端末の間隔はアプリ内定数(ABTest.swift の intervalAMinutesDefault=50)で、S3 の config だけでは切り替えられない。

決定: 5.7.2 で非参加時の間隔を S3 設定(新キー・未設定や取得失敗は50分)から読むように改修し、リリース後に S3 で1440分へ切り替える。実装はトムソンの 5.7.2 準備 OK を待ってから始める。

理由: 間隔をアプリ内に1440分で直書きすると、元に戻すにも再リリースが要る。S3 設定化すれば、以後の変更と撤回はリリースなしで済む(3巡目 §5.2 の案)。なお、記事画面の枠(banner/native)が MAX 管理画面と未照合である点の訂正は、You の判断でトムソンへの返信に入れていない。

2026-09-28: 提供元の選手データが試合途中で止まった試合は、出場時間を交代記録から計算し、評価は出さない(管理表70・71)

背景: 9/24 ウルグアイ戦で、API-Football の players ブロックが51分時点のまま確定しなかった(最終更新 9/24 20:34、2日後も同じ)。先発全員が51分、後半の途中出場は出場時間も評価も無く、途中の評価(例: 佐野海舟 6.3)が最終評価のように表示されていた。

決定: 終了済み試合で、交代・退場の記録より出場時間が10分以上短い選手が3人以上いて、交代も退場もしていない先発が含まれるときは「途中データ」と判定する。その場合は出場時間を記録から計算し(46分の交代は45分)、評価は null、G・A は得点記録を使う。途中データの間は backfill の done を立てず、FT 後72時間は提供元の確定まで再取得を続ける。

理由: トムソンの選択(②・評価は最終データが無ければ「-」)。閾値は、08-30 に実際に起きた「実行されなかった交代 event」(ずれ2人)や退場で誤判定しない値にした。代表 MVP は評価で選ぶ仕様のため、途中データの試合は MVP が出ない(トムソンに伝達済み)。

2026-09-28: 試合前の代表選手一覧のポジションは選手マスタを優先する(管理表74)

背景: 試合前の一覧は提供元スカッドの分類(菅原=Midfielder)を使い、選手詳細は選手マスタ(DF)を使っていたため、画面によって MF/DF が変わっていた。

決定: 試合前(スタメン発表前)の代表一覧は、選手マスタの position を優先し、マスタに無い選手だけ提供元の分類を使う。日本語名もスカッドに無ければマスタで補う。試合中・試合後は従来どおり、その試合のラインナップの position を使う。

理由: トムソンの指示(画面間の表示統一)。範囲は指摘どおり試合前の一覧だけにした。副作用として、提供元で Attacker の久保・堂安・鎌田も MF 表示になる(返信で伝達済み)。

2026-09-28: 試合前の代表選手一覧は、直前10日以内の代表戦に登録されたメンバーを使う(管理表73)

背景: 提供元(API-Football)の代表スカッド /players/squads?team=12 が9月の招集発表後も更新されず、9/28 ベネズエラ戦の試合前一覧が古いメンバーのままだった(9/24 の登録26人のうち9人が無く、未招集の9人が残っていた)。

決定: 試合前(スタメン発表前)の代表戦では、キックオフ前10日以内の直前の代表戦でラインナップに登録されたメンバー(先発+ベンチ・lineup_confirmed=1)を一覧に使う。直前の代表戦が無い(活動期間の1試合目)、または登録が取れていない場合は、提供元スカッドのまま。

理由: トムソンに出した①管理画面での招集登録、②直前試合からの自動引き継ぎのうち、②が選ばれた(①は今後検討)。②はサーバーのみで、当日の試合に間に合う。10日は、活動期間内の試合間隔(4〜7日)より長く、活動期間どうしの間隔(3週間以上)より短い値。招集31人のうち直前試合で登録外の人は出ない、1試合目は提供元しだい、という限界は返信で伝えてある。

2026-09-20: 新着画面の試合一覧の「スタメン」切替は、海外組・日本代表の対象大会の試合に限定する

背景: 代表の国内組を J クラブの team_id に紐づけた影響で、J リーグの試合でも digest_lineups_available が立ち、横スクロール試合一覧が開始時刻ではなく青「スタメン」表示に切り替わっていた(管理表69)。試合詳細の日本人選手欄は対象大会でガード済みで、lives の配信だけ絞り込みが無かった。

決定: lineup_announced は DigestPresenter::liveLineupAnnounced(digestLeagueIds()=海外組+日本代表の対象大会、かつスタメン取得済み)で判定して配信する。取り込み側のフラグ(digest_lineups_available)の立て方と DB の値は変えない。

理由: 「スタメン」を押した先の試合詳細に選手欄が出ないリーグで切り替えると表示が食い違う。配信側で絞ればサーバーのみの修正で配信中の全ビルドに即時反映でき、アプリ更新を待たない。フラグ自体は選手詳細・backfill の状態判定でも使っており、取り込み側を変えると影響範囲が広い。

2026-09-19: 国内カップ戦の League Cup(EFLカップ・48)を海外組ダイジェストの対象大会に含める

背景: 実況板リーグに League Cup(48) を追加する依頼を受けた。ダイジェストの対象大会(仕様v2.14 の一覧+40/79)は国内リーグと欧州・クラブ国際大会のみで、国内カップ戦はどれも対象外だった。登録だけでは League Cup の試合に日本人選手欄・今日の海外組・直近5試合が出ない。

決定: DigestPresenter::overseasCompetitionIds に 48 を追加し、今季の過去試合(8月の1回戦〜)も importLeagueFixtures→updateFixture で遡り投入する。日本語名は「EFLカップ/EFL杯」。他の国内カップ(FA杯・DFBポカール等)は今回は追加しない。

理由: 対象選手の多い英1部・英2部クラブが出場する大会で、出場した試合が選手詳細や今日の海外組から抜けると不具合に見える。カップ戦は試合数が少なく API 枠への影響は小さい(遡り投入は計31リクエスト)。仕様書一覧との差分の連絡は当面不要と You が判断。

2026-09-12: 「有効な評価」は出場済みかつ 0 より大きい評価だけとし、API が返す評価 0 は欠損として保存・配信・表示のすべてで扱う

背景: 選手詳細のグラフで未出場の試合が 0.0 の点になり、平均評価も未出場の 0 を含めて割られていた(古川 5.4・松田 4.3)。原因は API-Football がベンチ選手に games.rating: "0" を返すリーグがあり、パーサーが 0 をそのまま保存していたこと。null で返るリーグでは点が出ないため、同じ出場なしでも挙動が不揃いだった。

決定: server の DigestPresenter::validRating(played=1 かつ rating>0)を評価の共通判定とし、試合行・平均・グラフ・MVP の評価出力を全てこれに通す。パーサーも 0 以下は null 保存、未出場に補正した行の rating は null にする。iOS も DigestPlayerMatch.validRating で同じ判定を持ち、グラフの表示条件(有効3件以上)と平均(有効件数で割り小数第2位四捨五入)をアプリ側でも同じ元データから算出する。既存の評価0行15件は NULL に修復。

理由: API-Football の評価は 1.0〜10.0 で 0 が実績値になることはない。表示側だけの修正では平均や表示条件に影響が残るため(トムソン指摘)、保存・配信・表示の三層で同じ定義にする。アプリ側にも判定を持たせるのは、古いサーバー応答やキャッシュでも 0 点が出ないようにするため。


2026-09-12: 試合行は「出場状況/出場時間」の2段表示にし、G/A は出場時間の後ろに置く。列は対戦相手名だけ可変幅で他は固定幅

背景: 仕様v2.19/v1.4 で「出場状況の下に出場時間」「各列を全行で揃える」「長いチーム名は縮小せず…で省略」「行高は現状維持」を求められた。これまで G/A は出場状況と同じ行に「途中出場・1G」で出しており、2段化すると上段が仕様の「スタメン/途中出場」から外れる。

決定: 上段は出場状況のみ(12pt)、下段は出場時間(10pt グレー)+G/A(同サイズ・通常文字色の太字)で 90分 1G のように並べる。未出場・登録外は下段なし、出場したが時間欠損は「-」。日付38/vs14/スコア36/出場状況64/評価46/STATS54 を固定幅にし、対戦相手名だけが残り幅を使って末尾「…」で省略。行高は46ptのまま。

理由: 上段を状態だけにすると仕様の画面イメージどおりになり、列幅も安定する。G/A を消すのは情報の後退なので、出場時間と同じ下段に通常色で置いて視認性を保つ。対戦相手名を可変にすれば短い名前でも後続列が左に詰まらない(仕様の要求)。


2026-09-09: リーグを後から追加したときは、定期取り込みの3日窓では拾えない今季の過去試合を「リーグ単位の広い窓の手動導線」で挿入し、updateFixture で充填する

背景: トムソンが実況板ポータルで Championship(40)・2.Bundesliga(79) を追加し対象選手14名を登録したが、定期取り込み crawlNextFixtures は開始済み試合を直近3日以内しか採用しないため、8月開幕からの終了試合が入らず、追加選手の「直近5試合」が空のまま新しい試合を待つことになる。09-04 の移籍直後の穴と同じ構造がリーグ追加でも起きる。

決定: FixtureImporter::importRow の救済窓を引数で上書きできるようにし、importLeague(1リーグ・1シーズンを1リクエストで取り込み、挿入した試合IDを返す)と手動導線 /cron/importLeagueFixtures/{leagueId}/{days} を追加。挿入した試合は /cron/updateFixture/{id} に順に流してスコア・選手スタッツを充填する(FT後72時間を過ぎた試合は backfill の対象外のため)。定期取り込みの既定窓(3日)は変えない。

理由: リーグ追加は年に数回の運用イベントで、定期窓を広げると毎日の取り込みが重くなる。リーグ単位で1リクエストなので枠への影響は最小(56試合の充填で計58リクエスト)。既存行を触らない importRow の契約はそのままなので、稼働中の経路に影響しない。

2026-09-08: 試合別個人スタッツは「統計ブロックがある出場選手の件数 null は 0」と解釈し、率は分母がある項目だけ server 側で算出する

背景: 仕様v1.2 は「取得不可を 0 として扱わない(-表示)」だが、API-Football の fixtures?id= の players.statistics は件数 0 を null で返すことが多く(DF のシュート等)、文字どおり適用すると大半の項目が「-」になる。null が「0」か「未集計」かは値からは区別できない。

決定: DB には生値(null)のまま保存し、Presenter で「その選手に統計ブロックがあり出場している」場合に限り件数系 null を 0 として返す。統計ブロックが無い(リーグ非対応・未出場)試合は stats: null にして STATS を出さない。パス成功率・デュエル勝率・ドリブル成功率は passes.accuracy(成功本数)/total 等から server で整数%を算出し、分母 0 は null(アプリで「-」)。

理由: 表示側で「-」だらけになるのを避けつつ、仕様の「根拠のない率を推測しない」は守れる(率は分母があるときだけ)。生値保存にしておけば解釈を後から変えられる。You 合意(09-08)。

2026-09-08: 身長は players/profiles から選手ごとに1回だけ取得して選手マスターに保存し、空のときだけ自動取得する

背景: 選手ヘッダーの身長は試合レスポンスに含まれず、仕様の「追加エンドポイント原則なし」では取れない。若手選手は API にも値が無いことがある。

決定: fn_jp_players.height_cm を追加し、新規登録時と身長が空で保存されたときに players/profiles?player= から取得して保存(FnJpPlayer::afterSave)。手動一括は /cron/importPlayerProfiles。手入力値は API で上書きしない。未取得はヘッダーで身長だけ省く。

理由: 身長は変わらないので1回で足りる(約80人・枠への影響なし)。管理画面で手入力できる余地を残し、API に無い選手(09-08 時点で3名)は You の調査値で埋めた。

2026-09-08: STATS はクラブ直近5試合のみに出し、GK の配球は「パス成功率」と「成功数/試行数」を別セルにして率を重ねない

背景: 仕様の改修範囲はクラブ直近5試合の行で、代表直近3試合には STATS の記述が無い。GK の配球は「パス成功率」「成功数/試行数」が別項目として定義されており、FP のドリブル・デュエルのように1セルに率を併記すると同じ数字が横並びで2回出る。

決定: STATS 列はクラブ直近ブロックだけに設け(代表直近は従来どおり)、GK 配球は仕様の2セル構成のまま率を重ねない。API 側は national_recent にも stats を返しておく(将来の拡張用・アプリは使わない)。

理由: 仕様どおりで、You 確認済み(09-08「①今のまま」)。API は追加フィールド方式なので旧ビルドに影響しない。

2026-09-04: fixture取り込みは「開始済みでも直近3日以内の未登録試合は挿入」+「所属変更・Is Publish ON時にチーム単位で即時取り込み」に変え、既存行のスコアには触らない

背景: 定期取り込み crawlNextFixtures はリーグ単位で約24時間に1回(1リーグ/10分)、かつ未開始の試合しか採用しない。上田綺世の所属をリールへ変更した直後、次のリーグ・アン取り込みがトゥールーズ戦キックオフの6分後に走り、その試合が永久に欠落した(上田の移籍後初ゴールがダイジェストに出ない)。所属変更を後追いする経路も無かった。移籍期には必ず再発する構造。

決定: 採用判定と書き込みを app/Lib/FixtureImporter に集約し、①開始済みの試合は「直近3日以内かつ fnlive_fixtures に未登録」ならAPIのスコア・状態・finished_time近似(キックオフ+2h、nowで頭打ち)で挿入、既存行は一切更新しない ②FnJpPlayer(所属変更・display_on 0→1・新規)と FnliveTeam(is_publish OFF→ON)の afterSave で、そのチームの試合を fixtures?team=&season=(前年・今年・翌年)から即時取り込む ③手動導線 /cron/importTeamFixtures/{teamId} を用意する。

理由: 定期取り込みの間隔を短くする案(24h→6h等)は取り漏れの確率を下げるだけで、キックオフ直前の変更は依然抜けるため不採用。_updateFixtureFromApi を Lib 化して挿入時に即スタッツまで取る案は ovserveFixtures と共有する稼働中経路のリファクタになり回帰リスクが大きいので、挿入は行のみにとどめ、スタッツは既存の backfillDigestFixtureStats(FT後3〜72h窓)に任せた。既存行を触らないのは、従来コードが未開始前提で goals=0 を書いており、終了済み試合のスコアを潰す事故を防ぐため。負荷は所属変更1回あたりAPI 3リクエスト+定期取り込みでは主キー存在チェックが数件増えるだけ。

2026-09-04: トムソンへの返信文はリポジトリにmdを作らず、チャット本文にコピペ可能な形で出す

背景: これまで トムソン返信_*.md をルートに作ってきたが、You は LINE に貼るだけなのでファイルを開く手間が無駄だった(「なんでトムソンへの返信がmdファイルなのよ」)。

決定: 返信文は ```text ブロックでチャットに出す。記録すべき要点は .devnotes/DEVLOG.md に残す。既存の md 群は削除せず放置(未追跡)。

理由: 成果物の受け渡し先が LINE である以上、中間ファイルは不要。履歴は DEVLOG に集約したほうが検索性も高い。

2026-08-30: 終了済み試合の出場判定は選手スタッツ(出場時間・G/A)を正とし、lineups/eventsは補助に限定する

背景: API-Football がベンチ選手を一時的に「starter・0分・評価0」で返したり、実行されなかった交代を events に残したりするため、lineups の startXI や交代 event を根拠に played=1 にすると未出場選手が「フル出場 0分」「途中出場 -」として表示され、出場人数にも計上された(シート項目50・遠藤航/町野修斗/大迫敬介)。

決定: FINISHED では players ブロックに載っている選手は minutes>0 || goals>0 || assists>0 のときだけ出場扱いにし、該当しなければ bench/played=0 に落とす。players ブロックに無い選手(API がスタッツを配信しない大会)だけ従来どおり startXI/交代 event を根拠にする。LIVE 中の「出場中」判定は event 依存のまま変更しない。

理由: 「0分だけを条件にする」案では町野型(minutes null+交代event)を拾えず、「eventを全面的に信じない」案ではスタッツ未配信大会(EL/ECL予選など)の出場が消える。playersブロックの有無で根拠を切り替えるのが両立点。90+分投入で minutes が null のまま来る稀なケースは未出場扱いになるが、実データでは1分投入も 1 が入っており許容した。

2026-08-29: cached.realm の起動時全消しは変更せず、動画詳細のクラッシュは発現箇所のガードで塞ぐ

背景: MovieDetailActivity が Movie を cached/persistent の両Realmから引けずクラッシュする事象を観測(assert(false) が release では無効化され lateinit movie 未初期化のまま setupUI() へ進む)。調査で SplashActivity が起動のたび cachedRealm.deleteAll() で全22クラスを消し、そのうち Movie と TopBigArticleId は入れ直されないと判明。いったん「入れ直すクラスだけを消す」修正を実装し、実レコード数で 50→0(旧)/50→50(新)まで検証した。

決定: その修正は撤回し、MovieDetailActivity 側で両Realmとも引けなければ finish() して画面を閉じるガードのみ入れる。

理由: 「キャッシュは起動時に捨てる」設計自体は破綻していない ─ Movie を読むのは動画一覧経由でしか到達しない画面だけで、一覧の読み込みが行と項目を同時に作るため通常操作では必ず引ける。実際、通常操作/プロセスキル後のランチャー復帰/動作中のアプリ更新の3シナリオで再現せず(復帰時は常にトップ記事一覧に戻り古い項目は残らない)。クラッシュの発生経路を特定できていない以上、原因と断定できない箇所を変えるのは筋が悪い。加えて Movie を消さなくすると誰も削除しない構造になり無制限に溜まる副作用があり、その尻拭いに上限を足すのは本末転倒だった。経路が不明でも確実に効くガードを選んだ。ガードは debug 限定のマニフェスト差分で本Activityを一時的に外部起動可能にし、存在しない id を渡す対照実験(ガード無し=同一シグネチャで FATAL、ガード有り=画面を閉じるのみ)で検証済み。

2026-08-29: targetSdk 36 化では AGP を上げず suppressUnsupportedCompileSdk で通す

背景: compileSdk 36 は AGP 8.7.3 では正式サポート外で、通常は AGP 8.9+ への更新が要る。対応期限まで3日という制約があった。

決定: AGP は 8.7.3 のまま android.suppressUnsupportedCompileSdk=36 を設定して compileSdk/targetSdk 36 でビルドする。

理由: AGP 8.9+ へ上げると Gradle と Kotlin も追随が必要になり、なにより Realm Java 10.19.0(EOL)の Gradle プラグインを巻き込む。5月の近代化で「jcenter閉鎖とAGP8がGradle/Kotlin/JDK更新を強制する」芋づるを経験しており、期限直前に同じ地雷を踏むリスクを避けた。抑止フラグは警告を消すだけで生成物に影響しない。将来 Realm から離れるタイミングで AGP を上げるのが筋。

2026-08-29: YouTube再生不可はライブラリ更新ではなく origin 指定で解決する

背景: 全動画が エラーコード: 152 で再生不可。android-youtube-player 12.1.2 使用中で、13.0.0 も出ている。GitHub の該当 issue は未解決のまま。

決定: ライブラリは 12.1.2 に据え置き、IFramePlayerOptions の origin を自前ドメインに指定する(併せてレイアウトで自動初期化を切り initialize(listener, options) に変更)。

理由: AARを展開して確認したところ、ライブラリは origin を playerVars だけでなく loadDataWithBaseURL の baseUrl にも使っており、既定値 https://www.youtube.com のせいで「youtube.com 自身からの埋め込み」と名乗る形になっていた。同じ動画IDを実在オリジン(ローカルHTTPサーバを adb reverse 経由で実機Chromeへ)から埋め込むと正常再生し、動画側の制限ではなく読み込み方の問題と確定。原因が特定できている以上、期限直前にライブラリのメジャー更新で他の挙動まで動かす理由がない。実機で4本(縦横・Shorts/通常・複数チャンネル)の再生を確認した。

2026-08-26: 試合一覧の「もっと見る」はoffsetではなく(event_time, id)カーソルで取得する

背景: 仕様v2.18(MATCH-LIST-PAGING)で試合一覧に50件ずつの過去試合追加読み込みが必要になった。既存の /v1/liveHistories は limit/offset 方式で、一覧はキックオフ7時間前までの試合を含むため、読み込み中に新しい試合が窓に入ると offset がずれて重複・欠落が起きうる(仕様は「重複・消失・並び順崩れなし」を要求)。

決定: サーバーに before_time/before_id を追加し、(event_time < t) OR (event_time = t AND id < i)・ORDER BY event_time DESC, id DESC で「最後に表示した試合より過去」を返す。クライアントは pageSize+1(51件)を要求して51件目の有無で「もっと見る」の表示可否を判定し、fixture_id で二重表示も防ぐ。旧ビルド向けの limit/offset 方式は併存させる。

理由: 同時刻キックオフ(タイ)は id を第2キーにすれば連続性が保てる(本番DBで page1 の51件目=page2 先頭・重複0を確認)。レスポンスを配列のまま維持できるので旧ビルドを壊さず、has_more を返すためのオブジェクト化も不要。代替案の「offset+クライアントdedupe」は欠落側を防げないため不採用。

2026-08-26: Is Publish による今後の試合の非表示は表示側(LiveComponent)の共通判定で行い、取り込み・ダイジェストには適用しない

背景: 仕様v2.18(MATCH-LIST-PUBLISH-01)で「両チームIs Publish=OFFの今後の試合は非表示・終了済みは維持」が必要になった。fixture取り込み(_shouldImportFixture)は Is Publish に加えてダイジェスト対象チームも採用するため、取り込み側で絞ると海外組ダイジェストのデータが欠ける。

決定: LiveComponent::publishVisibilityConditions()(status IN 終了系 OR home.is_publish=1 OR away.is_publish=1)を新設し、新着上部lives(buildRecentLives/buildLives)と試合一覧(buildLiveHistories)の3クエリにだけ適用する。取り込みとダイジェスト(選手所属ベース)は変更しない。

理由: 表示側の1関数に集約すれば「試合情報」の全面に同じルールが効き、終了済み維持もSQL条件で自然に表現できる。副作用として is_jp_target=1 かつ is_publish=0 のチーム(現状アヤックスのみ)はダイジェストに残るが試合一覧から消えるため、適用前に本番DBで差集合を数えて返信で確認事項として明示した。

2026-08-23: 新着上部のスタメン発表状態はcaption_idの新値ではなく追加フィールドで配信する

背景: シート項目40(新着上部の試合情報をスタメン発表後に青「スタメン」へ切替)で、当初はサーバーの statusToCaption に caption_id=4 を新設して実装した。しかしlivesのS3 JSONは全配布ビルド共有で、旧ビルドは caption_id 0〜3 しか分岐を持たないため、未知の4を受けるとキャプションが白文字・無背景(xib既定値)で不可視になり、非表示のはずのスコア0-0まで表示される。バージョン別の出し分けはできない。

決定: caption_id/caption_titleは従来のまま維持し、lineup_announced(0/1・fnlive_fixtures.digest_lineups_available 由来)を行に追加配信する。新ビルドのみ caption_id==0 && lineup_announced で青背景#3c76d3「スタメン」に切り替える。共用composerの statusToCaption は変更せず、試合一覧(liveHistories)は従来どおり(仕様v2.17でも試合一覧にスタメンラベルは設けない)。

理由: 旧ビルドはJSONの未知キーを無視するため、追加フィールド方式は後方互換が構造的に保証される。caption_id=4案は配布直後から全既存ユーザーの新着画面を壊すため不採用。副作用として並べ替えバケットの変更も不要になった。

背景: ヘンク vs ウェステルロー(8/15)の試合詳細で日本人選手欄が空になった(シート項目25)。原因はAPI-Football側の選手成績配信が試合翌日まで遅延したことで、FT後3〜24時間に1回だけ再取得する従来のbackfillは、空振り(players_available=0)でも「劣化レスポンスではない」として digest_backfill_done=1 を立ててしまい、二度と再試行しなかった。直近30日で同症状が9試合あった。

決定: backfillの完了条件を「取得実績(digest_players_available=1)」に変更し、スタッツが取れるまで3〜72時間窓内の10分間隔cronで再試行する(1回きり→成功までリトライ)。窓も24h→72hへ延長。

理由: doneフラグの意味を「試行した」から「目的を達成した」に正す方が、遅延配信・一時的なAPI劣化のどちらにも効く。APIコストは最悪でも432req/試合×同時数件で1日クォータ7,500reqに対し無視できる。代替案の「窓だけ延長して1回きり維持」は試行タイミングの運任せが残るため不採用。EL/ECL予選のようにAPIが選手スタッツを恒久的に持たない試合は窓経過で自然に打ち切られる。

背景: トムソンから「MVP順位決定を一覧と同じG+A→評価→出場時間へ戻し、一覧先頭=MVPにしたい」と提案があった。しかし共通ソートは欠損項目をスキップして登録順フォールバックするため、統計未取得の選手が一覧先頭に来ることがあり(本番実例: 全statなしの北野が先頭・MVPは上田)、順位決定を揃えるだけでは一致が保証できなかった。

決定: 「今日の海外組」の選手一覧・MVP選定・シェア画像に限り、共通の並び(G+A合計→評価→出場時間、G/Aなしは並び替え上0として比較、評価なし・出場時間なしは取得済みより下位、表示は「-」維持)を使う。MVPは「ソート済み一覧の先頭に来る候補者」とし、シェア画像のMVP差し込み処理は削除。試合詳細など他画面の並び(欠損スキップ)は変更しない。こちらの逆提案をトムソンが承認し、シート項目23+仕様書v2.15として正式化された。

理由: 候補条件(1G以上/1A以上/評価7.0以上)と欠損下位ルールの組み合わせでは候補が非候補に必ず勝つため、候補がいる限り「一覧先頭=MVP」が構造的に成立し、差し込み等の特例処理が全廃できる。単純に順位決定だけ揃える案は欠損選手が先頭に残り一致が崩れるため、実例を示して仕様側の追加変更を引き出した。本決定により同日昼の「MVP先頭配置はシェア画像のみ適用」決定は失効。

2026-08-08: ダイジェスト対象大会は海外組/日本代表の2正典に分割し、cron取り込みは和集合で判定する

背景: 仕様v2.14で対象大会がID単位で明文化され(海外組14大会・日本代表5大会)、「試合機能とダイジェストの対象大会は個別管理」の方針が加わった。従来は単一の digestLeagueIds() に海外組と代表の大会が混在し、代表分は別途ハードコードもあった。

決定: DigestPresenter に overseasCompetitionIds() と nationalCompetitionIds() の2正典を持たせ、表示側ガード・DataProviderクエリは各リストを参照する。Cronのfixture取り込み判定(_isDigestTargetLeague)だけは両リストの和集合 digestLeagueIds() を使う。

理由: 表示の判定基準は仕様書の2表と1対1に対応させたほうが今後のFB対応で迷わない。一方で取り込みは「海外組か代表かを問わず対象大会の試合を落とさない」ことが目的なので和集合が正しい。単一リスト維持+用途別フィルタの案は、今回のUECL漏れ(実況板には追加したがダイジェスト対象に入れ忘れ)と同種の事故を再発させやすいため不採用。

2026-08-08: MVPの「先頭配置」はシェア画像のみ適用し、アプリ内一覧は並び順を維持してMVP該当カードを強調する

背景: 仕様v2.14の新MVP順位決定(G+A→ゴール数→評価→出場時間)は一覧の並び(G+A→評価→出場時間)とタイブレークが異なるため、MVPが一覧先頭と一致しないケースが生じる。仕様書はシェア画像(10-4)にのみ「MVPは先頭配置」を明記し、アプリ内の並び替えには言及がない。

決定: シェア画像はMVPを先頭へ移動し(並び順上位3人に入らない場合もMVPを優先して掲載)、アプリ内の選手一覧は仕様明記の並び順を維持してMVP該当カードだけを強調する。この解釈は返信文の補足でトムソンに明示した。

理由: 一覧の並び順は仕様に明文があるため勝手に崩さない。シェア画像は「MVP先頭」が明文なのでそちらに従う。両者の乖離が起きるのはG+A同点でゴール数差があるなどの稀なケースで、疑義があればトムソン側から指摘が返る導線(返信補足)を用意した。

背景: 仕様v2.12の項目14〜18(シェア画像調整)が、温存していたシェアレーン未コミット差分(トムソン提供背景share_bgへの差し替え・シェア導線UI)と同じファイル(DigestShareImageRenderer等)に乗るため、コミットと配布の切り分けを決める必要があった。

決定: 部分stashで分離せず、シェアレーン差分と項目14〜18をまとめてコミットし(iOS b41fffa)、ビルド11に同梱して配布する。項目13は独立ファイルのため別コミット(3b25410)。

理由: レンダラー内で差分が絡み合っており分離コストが高い。提供背景の差し替えもトムソン確認対象として自然で、隠す理由がない。これで「シェアレーン温存中」の変則状態が解消され、以後の作業ツリーがクリーンになる。

背景: 仕様v2.11(項目11・12)で代表PRE_MATCH選手行と選手詳細クラブ版ヘッダーに所属リーグ国旗が必要になった。「絵文字ではなくアプリ管理の国旗画像」指定で、国旗画像のソースと国の判定方法を決める必要があった。

決定: serverが fnlive_teams.country(API-Football由来のクラブ本拠地国)を小文字国コード(nl/gb-eng等)へ変換して配信し、iOSは既存の日の丸アイコンと同じプログラム描画(13カ国・キャッシュ付き)で表示する。未知の国コードは国旗省略で安全側に倒す。国の判定に競技ID(competition.id)は使わない。

理由: ①api-sportsの国旗はSVGでSDWebImage非対応、flagcdn等の外部CDNは新規依存になる、アセット同梱は素材調達が必要——に対しコード描画は依存ゼロで「アプリ管理」の文言にも合致(既存の日の丸描画と同パターン)。②competition.idは次戦がCL/ELだと国が取れないため、クラブ本拠地国=国内リーグ国とみなすteam.country基準が仕様の意図(所属クラブが参加するリーグの国)に合う。モナコ等の例外国は現対象選手に不在で許容。

2026-08-04: 国内組選手も選手マスタのteam_idで紐づけ、対象リーグガードで副作用を遮断する(「J組=0が正」を撤回)

背景: 仕様v2.7の画面イメージで、招集メンバー全員(国内組含む)に所属クラブ名を表示することが確定。従来は「J組はteam_id=0のままが正」(team_idを入れるとJリーグの試合詳細に日本人選手欄が出る副作用があるため)としていた。

決定: J組選手にもJクラブのteam_idを紐づけ、代わりに「日本人選手欄・実況板ラベルはダイジェスト対象リーグの試合のみ」というガードをサーバー側に追加して副作用を遮断する。スカッド専用のクラブ列追加(DDL+新画面+二重管理)は採らない。

理由: 選手↔クラブの紐づけを選手マスタ1箇所に保てば既存のEdit画面(チーム名検索UI)がそのまま使え、移籍時の更新も1箇所で済む。ガード自体は本来あるべき防御で、追加して損がない。なお実況板にはJリーグの試合がそもそも配信されていないと後で判明し、ガードは保険として機能する形になった。

2026-08-04: エールディヴィジ(88)とスーペルリーガ(119)をダイジェスト対象リーグに追加する

背景: 2026夏の移籍で板倉(アヤックス)・上田/渡辺剛(フェイエノールト)・小川(NEC)・鈴木淳之介(コペンハーゲン)が両リーグ所属になり、実装時に固定した14リーグのままでは看板級5選手の試合が「今日の海外組」「これからの海外組」「最近5試合」に一切出ない状態だった(トムソンの「上田で確認したい」依頼で顕在化)。

決定: 対象リーグ正典 DigestPresenter::digestLeagueIds() に88・119を追加し、CronControllerに残っていたリーグ配列のコピーも共通参照へ一本化する。

理由: 対象リーグは「日本人選手の所属実態」に追従すべきもので、リストの二重管理は今回のような追加漏れの温床になる。API消費増はエールディヴィジ9試合/節+スーペルリーガ6試合/節程度で予算内。

2026-08-04: ダイジェスト期間設定の入力上限を0〜365に統一する(3層一致)/リリース前の設定戻しはトムソン側で実施

背景: トムソンが確認用に99を入力したいが「0〜30」バリデーションで弾かれていた(08-03)。制限はフォームだけでなくpresenter/providerの読み取りクランプにもあり、フォームだけ緩めても内部で丸められる構造だった。

決定: モデルバリデーション・presenterクランプ・providerクランプの3層すべてを0〜365に統一。確認後の設定戻し(3/1/7/1)はトムソン自身が管理画面で行う運用にする。

理由: 値の意味が「日数」なので365が自然な上限(代表戦のDB取得窓も±365日でそれ以上は無意味)。層ごとに異なる上限は「入力できたのに効かない」混乱の元。設定変更の主体をトムソンに移すことで、確認期間の調整を先方のペースで完結できる。

2026-08-01: 日本人選手欄の非表示判定は「所属ベース+実データ残存時は表示維持」にする

背景: トムソンFB指摘①は「両チームに表示対象の日本人選手が所属していない試合では日本人選手セクション全体を非表示」。文言どおり所属だけで判定すると、選手が移籍した後に旧クラブの過去試合を開いたとき、実際に出場した記録(stats行)まで消えるという副作用がある。

決定: server は「表示行が0件」かつ「両チームに表示対象選手が所属していない」の両方を満たすときだけ japanesePlayers=null にする(実データが残る試合は表示維持)。iOS は表示行0件のブロックをセクションごとスキップする防御を重ね、古いキャッシュJSONにも効かせる。

理由: トムソンの本質的な要望は「空の見出し+空カードを出さない」ことで、実データのある画面を消すことではない。所属だけの判定(文言どおり)は移籍後の過去試合で情報を失い、行有無だけの判定(iOSのみ)はサーバーJSONの意味が不正確なまま残る。両方の組み合わせが完了条件①〜⑤を満たしつつ実コンテンツを消さない唯一の解だった。

2026-08-01: ダイジェスト仕様書は v2.6 のまま維持し、FB対応は実装のみで行う

背景: トムソンFB指摘①の補足に「HTML仕様書のクラブ試合詳細にも同じ条件を追記してほしい」とあり、指摘②を反映した v2.6 にも①は未反映だった。一度 v2.7(①追記版)を作成した。

決定: 仕様書は v2.6 のまま修正せず(v2.7 は破棄)、フィードバック対応はアプリ/サーバーの実装のみで行う。トムソンへのDMにも仕様書更新には触れない。

理由: You の判断。仕様書の版管理はトムソンとのやりとりの中で必要になったタイミングでまとめて行えばよく、実装と同時に版を上げる必然性がない。

背景: 対象選手(fn_jp_players)のEdit画面のチーム欄が、FormHelper の *_id 自動select化+belongsTo無しの合わせ技で「空のプルダウン」になり設定不能だった。トムソンからチームID紐づけの方法を聞かれ、画面から操作できる状態にする必要があった。

決定: FnJpPlayer 専用の scaffold.form.ctp(1画面だけの差し替え・共通テンプレ不変)で Team ID を数値入力にし、直下に「現在のチーム名の常時表示+fnlive_teams の日本語/英語名LIKE検索→候補クリックでID自動入力」の補助を付ける。保存時は「0 or fnlive_teams 実在ID」のみ許可するバリデーションを追加。add 画面のPK(API-Football選手ID)入力も同テンプレで解禁。

理由: belongsTo でプルダウンを出す案は fnlive_teams が数千件規模でUIが破綻。ID直打ちのみの案は誤入力事故(id=0行への誤参照でゾリャ・ルハーンシク表示になった前例)に弱い。採用案はリーグ追加(addConfirm)と同じ「確認してから保存」の思想で、検索先がローカルDBなので API-Football のクォータも消費しない。

2026-08-01: 実況板リーグ追加は「API確認つき専用ページ」方式にし、cron補完経路は温存する

背景: トムソンからのリーグ追加依頼のたびに phpMyAdmin で fnlive_v3_leagues へ id を手挿入していた。ポータルには scaffold の「リーグV3」があるが、CakePHP FormHelper が主キーを強制 hidden にするため add で id を指定できず、実質使えなかった。

決定: scaffold の add を復活させる(フォーム差し替えで id 入力可にする)のではなく、専用ページ addConfirm(ID入力→API-Footballでリーグ名・種別・国・ロゴを取得して確認→追加)を新設。INSERT は id+name+type+logo を入れつつ team_created=0 のままにして、5分毎 cron fillLeaguesForV3 の実績ある補完経路を変えない。scaffold の New ボタンは disableIndexActions に 'Add' を拡張して非表示にし、追加導線を addConfirm に一本化。

理由: 生の id 打ち込みは打ち間違いで意図しないリーグが登録されるリスクがあり、追加前に API でリーグ名を目視確認できる方が運用ミスに強い(今回のユーザー要望そのもの)。また INSERT で team_created=1 まで自前で完結させる案は cron 経路とロジックが二重になるため、従来の phpMyAdmin 運用と同一挙動になる「id 投入だけ UI 化」に留めた。


2026-07-24: 海外クラブ非所属の代表選手も選手マスタに載せ、クラブ成績ブロックは丸ごと非表示にする

背景: 日本代表試合詳細から冨安選手をタップすると「選手情報が見つかりません」になった。原因は、代表の選手リストが代表戦の成績テーブル由来なのに対し、選手詳細APIは海外組ウォッチ用の選手マスタ(fn_jp_players)照合を通ることで、マスタ未登録の選手は必ず弾かれるため。代表招集されているがどの海外クラブにも所属していない選手(冨安・長友など)が該当し、代表スカッド26名中11名がこの状態だった。

決定: 該当11名を team_id=0(海外追跡クラブなし)でマスタに登録し、選手詳細ではクラブ直近5試合ブロック(見出し・評価グラフ・試合リスト・合計)を丸ごと非表示にする。あわせてAPI側で team_id<=0 のときはチーム解決自体をスキップする。

理由: 代替案は①マスタに載せず行をタップ不可にする ②載せてクラブ欄を空表示、の2つ。①は代表戦で活躍した選手の詳細が永久に開けず、代表直近3試合と関連ニュースという有用な情報まで捨てることになる。②は空のグラフや「出場0試合」の合計行が並んで壊れて見える。③(採用案)なら代表成績とニュースは見せつつ、持っていない情報は最初から出さないので破綻がない。なお team_id=0 のままチーム解決すると fnlive_teams の id=0 のゴミ行に誤一致して無関係なクラブ名(ゾリャ・ルハーンシク)が出るため、APIガードは必須。


2026-07-22: ダイジェスト実装レビューでの主要裁定3件(試合一覧拡大・代表対象大会・fixture運用規約)

背景: Codex分業実装(M0〜M7)のマイルストーンレビュー中に、仕様の解釈・運用に関わる裁定が必要になった。

決定:

  1. 試合一覧への試合増加は既定=許容: 日本人選手所属チームの試合取り込みで既存「試合」タブに試合が増える件は、仕様の意図(日本人選手あり表示の追加)と整合するため許容で実装。トムソンに確認中で、否なら後付けできるよう fixture採用判定を CronController::_shouldImportFixture() 1箇所に集約した。
  2. 代表戦の対象大会にアジアカップ(league 7)・W杯アジア予選(league 30)を追加: 仕様書は代表戦の大会を限定しておらず、W杯・親善のみだと2027年1月のアジアカップで代表戦ナビが空になるため。リーグIDは実APIで確認済み。
  3. iOSテストデータの手書き禁止: 手書きfixtureと実サーバー契約のドリフトがLIVE表示バグ(経過時間二重表示)を隠したため、iOSの dev-fixtures/ はサーバーの test_digest_presenter.php 生成物を sync_from_server.sh で同期する方式に規約化。

理由: 1は仕様整合+エンゲージメント面のプラスで、リスクは1箇所集約により可逆。2は「過去・現在・未来の代表戦を横断」という仕様の趣旨を、実カレンダー(アジアカップが半年後)に照らして優先。3はcontract driftという再発性の高い事故クラスへの構造的対策。

2026-07-22: トムソン提案「海外組・日本代表ダイジェスト」を実装可能と回答・データ取得は既存ovserveFixtures拡張方式を基本に

背景: トムソンから新機能の開発仕様書を受領。仕様書内で「現行APIプラン内で運用可能かは実装前に検証(要確認)」とされており、着手判断の前にAPI実現性の確定が必要だった。

決定: RapidAPI API-Football を実叩き検証(約25req)した結果に基づき「実装可能」と回答する。データ取得は新規ポーリング系を作らず、既存 ovserveFixtures(v3 /fixtures?id==events/lineups/選手stats同梱1req・2分間隔)の監視対象を日本人選手所属チームの試合へ拡張し、現在捨てている選手statsをDB保存する方式を基本とする。

理由: 全対象リーグ+CL/EL+代表戦(親善含む)で評価・出場時間・G/A・lineupsの提供を実データで確認。最繁忙日でも+2,700〜3,400req/日=現行プラン7,500req/日内に収まる試算。既存機構の拡張なら実装量・リクエスト数とも最小で済む(ピーク超過が見えたらポーリング3〜5分間隔化で調整可能)。

2026-07-22: 選手・クラブの日本語名はClaude併用の名前解決で初期リリースから対応する

背景: API-Football の名前はローマ字表記のみ。仕様書は「日本語名が間に合わなければ既存表示名(英語)で先行し早期アップデートで切替」というフォールバックを許容していた。

決定: フォールバックを採らず、初期リリースから日本語名表示を狙う。ローマ字名+所属チームを入力に Claude Code(サブスク内)で日本語名を解決し、WebSearch裏取り(クラブ公式/JFA/Wikipedia日本語版)+needs_reviewフラグで人間確認を挟んで選手マスターへ反映する。DB反映経路は初期はCSV/管理画面取り込み(人間confirm自然発生)→精度確認後に管理API直叩きへ移行。クラブ名・リーグ名の日本語表記も同じパイプで整備(チームマスターに日本語名カラム追加)。

理由: 対象規模が小さく(海外組50〜100人+代表招集の増分)サブスク内運用で十分回る。有名選手はほぼ確実、無名若手・カットオフ後移籍のローマ字→漢字曖昧性(一対多)だけが精度リスクで、WebSearch裏取り+review運用で実用精度にできる。トムソンへは「日本語名は初期リリースから対応見込み」と格上げして回答できる。

2026-07-07: 起動時広告A/B 修正版5.5.5リリース済・コホートparam本番着弾を確認・v9はmin_build=50505で確定

背景: 型ズレ修正版は Apple 5.2.3 却下(YouTube discovery)対応で YouTube機能を一時非表示にし 5.5.5 として 2026-07-06 リリース(5.5.4 は未リリース)。翌 7/7 に GA4 Data API で着弾検証。

結果(合格):

  • appVersion=5.5.5 で 5つの dimension が全て non-empty 着弾を確認(7/6-7/7): target_cohort_day=ISO(2026-07-06等)・enrollment_day=ISO・days_since_enrollment=d00〜d13・config_version=cv_8・kill_observed=false。旧 5.5.3 は従来どおり (not set)=型ズレ修正が本番で成功。
  • 予測どおりの汚染を実測: 5.5.3で既enroll→5.5.5更新勢が古い enrollment_day(6/24等)+d07〜d13 を送出。→ 解析は enrollment_day >= 2026-07-06 フィルタで除外する。

決定:

  • v9 config の min_build は 50505(当初 50504 は修正版=5.5.4 想定だったが実リリースは 5.5.5)。5.5.3/未リリース5.5.4 を確実に除外。
  • enrollment 窓 = 7/6 起点(start=1783263600 / end=1786892400 = 2026-07-06〜08-17 JST・42日)。
  • 雛形 ab_test_config.v9.template.json を確定値で更新済。S3 配置(aws s3 cp ... --acl public-read --profile thomson-ik)は You が実施。配置で experiment 実質再スタート。

理由: 実リリースが 5.5.5 になったため min_build を 50505 に合わせないと未修正ビルドが混入し得る。着弾検証が通ったので、あとは v9 配置で新コホートをクリーンに観測開始できる状態。


2026-06-28: 起動時広告A/B 5.5.4で型ズレ修正・experiment_id据置で v9(min_build=50504)実質再スタート

背景: コホート日付param型ズレを 5.5.4 で修正(別エントリ)。v8/5.5.3 期間の cohort データは復元不能のため、5.5.4 配信後を新コホート境界として実験を実質再スタートしたい。クリーンに切るなら新 experiment_id で再無作為化が理想だが、アプリ側 ABProtocol.experimentId(ハードコード)+validate() が config の experiment_id / measurement_protocol_version / event_schema_version 一致を要求するため、config だけでは experiment_id を変えられない(変えると 5.5.4 が fail-close で実験OFF)。

決定:

  • アプリバージョンを 5.5.4 に更新(build符号 50504)。型ズレ修正の配信版。
  • experiment_id は据え置き("app_open_interval_2026q3")。新 experiment_id 再無作為化は更なるアプリ再ビルドが要るため今回はやらない。
  • config v9 で min_build=50504 に引き上げ=5.5.3 端末を実験から完全除外(5.5.3 では config invalid→experiment OFF)。
  • enrollment 窓を 5.5.4 ライブ日起点で取り直す(旧 6/24 起点窓は壊れデータ期間を含むため流用しない)。窓長42日据え置き。
  • 解析母集団は enrollment_day >= 5.5.4配信日 でフィルタ=5.5.3→5.5.4 更新でドラッグインする既enroll勢(古い enrollment_day・初期に壊れた param)を除外。
  • v9 雛形 ab_test_config.v9.template.json を用意(固定値埋め済・enrollment 2日付けのみ配信日から算出)。配置は 5.5.4 が store ライブ後。

理由: experiment_id 変更が config 単独で不可という実装制約(fail-close 設計)下で、クリーン境界を「min_build による旧ビルド除外」+「enrollment_day による解析フィルタ」の2点で代替する。後者は今回 enrollment_day を実際に着弾させたからこそ機能する。窓の取り直しは初日コホート空・壊れ期間混入を避けるため。再無作為化を諦める代わりにアプリ再ビルドを増やさず、既存の永続割当を保ったまま新規 cohort をクリーンに観測できる。


2026-06-28: 起動時広告A/B maturation明け first-look=機構健全だがコホート日付param全件空(解析の根幹が欠落)

背景: dev-timer の「maturation明け」通知(点火 2026-06-22 20:40・enrollment窓 6/24〜8/5・maturation 3日)を受け、watermark=6/25 で first-look サニティを GA4(property 211701453・experiment_id=app_open_interval_2026q3)で実施。これは最終判定ではなくガードレール④(成熟後 残差率)が初めて回せる段階の健全性確認。

発見:

  • 機構は健全: SRM 無し(A=4720/B=4712 installs・χ²≈0.007・p≈0.93)/applied_interval=A:50・B:1440 で群別policy反映/kill_observed=true は0件/主要funnel event は両群で着弾。
  • 🔴 コホート"日付"系 param が全イベントで100% (not set)/空: target_cohort_day enrollment_day days_since_enrollment が experiment_enrolled・target_launch・content_reached・launch_no_content・fn_ad_revenue のすべてで値を持たない。experiment_id/assigned_group/applied_interval_minutes/kill_observed は正常着弾=永続enrollレコードから算出する日付3 param のみが配線/値算出で死んでいる。
  • 方向性(イベント日基準・未成熟混在の参考値): 離脱率 launch_no_content/target は A=19.7%・B=15.8%(B が ~3.9pp 低く仮説の向き)。ただし未解決率 A=6.5%・B=4.0% の群間差2.5pp がガードレール④の保留条件に該当し、精緻化に要る maturation 法が下記欠陥で使えない=現時点で結論は出せない。

決定/帰結:

  • ガードレール④(target_cohort_day による成熟後 残差率)と 円/人(days_since_enrollment による enroll後N日売上÷割当人数)は現状データでは算出不能。assigned_group は生きているので群レベル比較は可能だが、maturation/cohort 解析契約は満たせない。
  • 欠陥修正は時間との勝負: 正典 SPEC明記の通り「事前テスト後の param 追加は過去データを復元できない」。放置するほど cohort 解像度を失う。→ 次アクションは footballnext-ios で日付3 param の送信箇所を特定し原因究明・修正(修正方針が見えた時点で You 判断)。

理由: maturation 明けの first-look の主目的が、判定前にこの種の計測欠陥を捕まえることそのもの。点火機構(割当・config反映・kill)は正常なので実験は走っているが、解析契約の中核 param が欠けたままでは主指標を裏取りできないため、修正を最優先に切り替える。


2026-06-19: 起動時広告A/B 点火前の判定値を確定(GA4追加2件・enrollment窓はリリース起点・min_build=50503)

背景: 実装レビュー sign-off 後、点火前チェックリストの「GA4登録」「enrollment窓・判定値の確定」を詰める段。GA4 Admin MCP は未接続のため登録は手作業、窓は 5.5.3 リリース日に依存。

決定:

  • GA4 追加登録は2件のみ(既登録21件に対し): kill_observed(event-scoped dimension)/applied_interval_minutes(custom metric・分)。登録後 DebugView で app_open_ad_decision に乗るか確認。
  • enrollment 窓は窓長42日固定・起点は 5.5.3 が store ライブになった日(+反映猶予 数日)を点火時セット。旧叩き台 2026-06-22 起点は 5.5.3 未提出で過去化のため流用しない。end = start + 42日。
  • build gating min_build=50503(5.5.3+ 限定)。round1〜8 の全修正は 5.5.3 積載のため旧ビルドを実験から除外。max_build なし。
  • interval_a=50 / interval_b=1440 / 締切42日 / 追跡28日 / ITT / required_assignment_version=1 / ttl 14日 は既凍結のまま。

理由: GA4 は登録前データが遡及されないため点火前必須だが差分は小さい。窓をリリース日に固定するのは「ビルドが store に出る前に窓が開くと初日コホートが空/旧ビルド混入で歪む」ため。5.5.3+ 限定は未修正コードの native 収益欠損・decision 矛盾を実験に混ぜないため。


2026-06-19: 起動時広告A/B native expire/lease lifecycle の最終形を確定・kill_observed_at 廃止・実装レビューで点火 sign-off

背景: 実装レビュー round5〜8 で、flag外の MAX native lifecycle(lease/timeout/expire)と kill 計測属性に残 medium が連続して出た。固定遅延 destroy・stock限定 expire・puts での無効化はいずれも段階的に不十分で、reviewer が更に詰めた。native は experiment flag 外で全ユーザーに作用するため収益欠損(無効広告の計上/枠消失)を出さない fail-closed が必須。

決定:

  • native の destroy は「released(row利用終了) or expired」かつ「displayLeases==0(表示中cell無し)」のときだけ(固定秒数廃止)。expire 済みは loader(for:) を nil にして render/impression を厳密に止める。
  • stale MAX ad の判定は CoreTableView.cellForRowAt の deque/nib選択より前に置き、同一 pass で新 stock を再deque(puts まで遅らせない)。manager→row 逆参照は持たず、Any? 型非依存 API で共通 CoreTableView を AppLovin 非依存に保つ。
  • impression は renderNativeAdView==true のときだけ計上(render失敗の過大計上を排除)。callback は全て main 集約+timeout 世代照合。
  • kill_observed_at を廃止。GA4-only では未登録 param を事後参照できないため、停止後区間の除外は必須 dimension kill_observed、global 停止境界は S3 で experiment_active=false にした時刻の運用記録で扱う。
  • 実装レーンは round8 sign-off をもって実質完了とし、以降は点火前チェックリスト(GA4登録・値確定・実機スモーク・点火/停止時刻記録)の運用準備へ移行。

理由: 逆参照を持たない一方向設計(destroy→loader nil→cellForRow前段でstale再deque)は CoreTableView の汎用性を保ちつつ実装が小さく、main 集約で race を避けられる。固定遅延 destroy は表示終了を保証せず、stock限定 expire は deque後4h超の再表示を取りこぼし、puts での無効化は再deque を保証できなかったため、いずれも棄却した。kill_observed_at は GA4-only 契約と整合せず実用価値が無いため削除。


2026-06-19: 起動時広告A/B 実コードのCodexレビューを必須化・点火を撤回して修正優先・5.5.3据え置き

背景: 提案書・実装計画の机上Codexレビューを通過し点火(active=true)まで実施したが、実装コードのCodexレビュー(ab-test-implementation-review)round1/2 で high/medium 多数が判明。特に「非参加者へのB処置漏れ(policy/measurement未分離)」と「session config固定+短TTLで日次ユーザーが永久非参加」は実験を不成立にする重大バグだった。

決定:

  • 点火を撤回し緊急停止(S3 config を active=false+config_version+1)。本番中立を最優先し、実装が正しいと検証できるまで再点火しない。
  • 実装コードのCodexレビューを必須プロセス化(机上レビューだけでは出ないバグがあるため)。round1→round2と回し、round3まで継続。
  • config snapshot は session全体固定でなく context世代方式(beginTargetContextで last-known-good を1回読み record に固定、fetch結果は次context有効)。session固定は日次ユーザーを壊すため却下。TTLは6h→14日(launch間隔より十分長く)。
  • policy/tracking は canEnroll でなく「既参加(enrolled)+追跡窓内(JST暦日<28)+active/fresh|cached」で判定(isParticipating)。非参加者へのB漏れと締切後の追跡停止を両方解消。
  • native loader 修正は点火ブロッカー(当初「A/B非交絡・収益別経路」で先送り判断したが、MANativeAdLoader.revenueDelegate が weak=誤対応付けでILRD欠損しうる、とのCodex指摘で撤回)。実機/TestFlight検証必須。
  • バージョンは 5.5.3 据え置き(未提出のため番号再利用。全修正を5.5.3に積み、検証完了後に提出)。

理由: A/Bの主指標(1人あたり収益)と GATE(未到達率・収益再現・全枠callback品質)を満たすには、処置と計測の母集団一致・config世代の決定性・収益callbackの完全性が前提。これらが崩れたまま点火するとデータが汚れ無管理に減収する。机上で5ラウンド見ても実コードのTTL相互作用・delegate weak・状態未分離は出なかったため、実装レビューを独立工程として必須化した。


2026-06-19: 起動時広告A/B の enrollment 窓を確定・点火は段階A/Bの2段運用でトムソン確認は叩き台確定扱い

背景: 点火前の残作業(トムソン要確認3項目・enrollment期間・S3配置)を処理する段で、事前登録の作法と本番の不可逆性をどう両立させるかを決める必要があった。

決定:

  • enrollment窓 = 6週間(叩き台どおり)。enrollment_start_at=1782054000(2026-06-22 月 0:00 JST)/enrollment_end_at=1785682800(2026-08-03 月 0:00 JST)。開始をJST深夜0時の月曜境界に置く(enrollment_day のコホート日が揃い解析がきれい)。canEnroll は now ∈ [start, end)。
  • トムソン要確認3項目は叩き台のまま確定扱い。Youより「自分が任されているのでトムソンへの確認は不要」と判断。通知物はHTML化して thomsons S3 に公開済(事前登録の記録として残す)が、合意待ちで点火をブロックしない。
  • 点火を段階A/Bの2段運用に分離: 段階A=不活性config(active=false)を本番S3へ先行配置(中立・挙動不変、本番S3取得経路の確認用)。段階B=experiment_active=true+config_version=2 で再アップロード(実点火)。今回は段階Aまで実行し段階Bは保留(Youの明示goで実行)。

理由: enrollment窓開始を未来日(6/22)に置くと、段階Bを今点火しても窓開始まで誰もenrollしない=S3配置の機械的タイミングと解析窓の開始を分離でき、週末をpre-flight期間にできる。段階A/B分離は、本番S3への書き込み・パース健全性を実ユーザー挙動を変えずに先行検証するため。トムソン確認は事業判断としてYouに委譲されており、叩き台で十分動くため点火ブロッカーから外した。


2026-06-19: 起動時広告A/B の割当ハッシュ定数を凍結・計測モジュールは Foundation-only で実装

背景: BASE 実装着手にあたり、群割当の具体定数(GATE-値の一部)を決める必要があった。また、計測ロジックは本番起動パス・広告収益コードに深く絡むため、検証可能性を担保したかった。

決定:

  • 割当ハッシュを凍結: input=UTF-8("experiment_id:install_uuid") → SHA256 → 先頭8バイトを big-endian UInt64 として % 100 → bucket_map [0,50)→A / [50,100)→B。experiment_id="app_open_interval_2026q3"、assignment_version=1、measurement_protocol_version="mp_2026q3_r1"。テストベクトルを ABTest.verifyTestVectors() に固定。ABProtocol(リポジトリ凍結=measurement_protocol 相当)に集約。
  • 新 experiment では再無作為化を採用(hash 入力に experiment_id を含むため、同一 install_uuid でも別 experiment では別 bucket になる)。bucket 再利用案は採らない。
  • 計測コア(ABTest.swift / ABLaunchTracker.swift)は Foundation のみに依存させ、GA4送信は emit クロージャ注入で外出し。→ アプリ全体をビルドせず standalone swiftc で状態機械を単体検証できる構成にする。広告SDK/Firebase に触る部分(ABAdCollector/ABConfigLoader/AppDelegate配線)は別ファイルへ分離。

理由: GATE-値のうち「割当の決定規則」は実装と同時にテストベクトル付きで固めないと検証できないため先行確定。再無作為化は実装が最も単純(hash入力にIDを含めるだけ)で、フェーズ2の experiment 切り替え時にも公平性が保てる。Foundation-only 分離は、本番クリティカルパスのロジックをフルビルド・実機なしで反復検証可能にし、安全に段階実装するための土台。


2026-06-18: 起動時広告A/B 実装計画を Codex 5ラウンドで設計レビュー完了(配送は at-most-once に転換)

背景: トムソン承認後、実装着手の前に実装計画HTMLを作り、Codex(gods-talk)で繰り返しレビューした。ローカルコードを直接読ませたことで、机上設計では見えなかった穴(delegate未配線・write-ahead保存の必要性・収益event二重計上・総売上母集団の非識別性など)が判明した。

決定:

  • 実装計画 rev6 と正典 AB_TEST_MEASUREMENT_SPEC.md rev6 をもって 設計レビューを完了とする。以降は再レビューラウンドを設けず、実装PRの受入チェックで確認する(round5でCodexが「追加の大規模レビューは不要」と判定)。
  • 実装は 3レーンで進める: BASE(着手可)/BASE契約(永続model・collector・logger API 確定前に潰す5点)/GATE-値(schema凍結前に measurement_protocol として固定)。
  • GA4への計測 event 配送は at-most-once(pending→attempted を先に確定して一度だけ送信、欠損は成熟後残差率に残す)。収益 event は fn_ad_revenue に一本化(app_open_ad_paid 廃止)。設定は 3 artifact 分割(assignment_manifest / activation_config / measurement_protocol)。
  • 確定本文は 正典 SPEC.md を唯一の source of truthとし、実装計画HTMLはその投影と位置づける。

理由: GA4-onlyかつ launch_id を出さず BigQuery 不使用の制約では exactly-once も at-least-once(dedupe不能)も成立しないため、二重計上を防ぐ at-most-once を選択。代替(at-least-once+GA4 dedupe)は本構成では実現できないと判断。収益の二重計上回避のため canonical event を1つに固定。レビューは5ラウンドで指摘の質が逓減し収束したため、これ以上の往復より実装で詰める方が有効と判断。


2026-06-18: 起動時広告A/B 提案書を確定(Codex2ラウンド反映で設計を精緻化・同日の設計を一部上書き)

背景: 同日午前の設計(下のエントリ)を提案書化する過程で、議論と Codex(gods-talk)2ラウンドのレビューにより、いくつかの前提が誤り・脆弱と判明した。実装と解析の正典を固める必要があった。

決定(同日午前の設計を以下に更新):

  • 群Bは「1日1回」→「24時間に1回(rolling)」。群Aは「毎回」ではなく現状=最短50分に1回(本番S3 app_open_ad_interval=50 を実測確認)。
  • データ基盤は GA4のみ・BigQuery不使用(無料運用)。ab_group 集計のため GA4 を Viewer→Editor へ権限変更をトムソンに依頼。
  • 判断は統計的有意差でなく 「事前固定した実務閾値での記述的判断」。主指標1(1人あたり売上)+ガードレール(継続率・クラッシュ率)+終了日 の最小骨子だけ事前固定、閾値は別紙で合意し計測誤差内は判定保留、最終採否はトムソン。「微減+良化=即採用」は撤回し回収試算の上で判断。
  • 解析は ITT(広告表示の有無に関わらず割当全員)。
  • お金: 起動時は群別 didPayRevenue 実額合計を主値(表示回数×eCPMは検算)、それ以外は混合平均eCPMで概算。月次総売上は「iOS・Football NEXT・対象月」に範囲を揃える(Android/旧版混在は不可)。
  • 残り具合: launch_bounce を 「コンテンツ未到達離脱率」 に改称。判定は起動秒数でなく「最初のバックグラウンドまでにコンテンツ接触ゼロか」、content_ready(描画)≠content_visible(広告の背後でなく実際に見えた)を分ける。D7/D14 起点=実験参加日。
  • 「起動時広告 vs 記事内広告 どちらが主因か」は本実験では因果確定せず(変えるのは起動時頻度のみ)→ 中立な表現に。
  • 計測の正典を .devnotes/AB_TEST_MEASUREMENT_SPEC.md に集約。提案書と矛盾したら SPEC を優先。

理由: BigQueryは Owner 権限(info@thomsons.jp)と課金が要り Sandbox でブロック=無料の GA4 のみが現実的。GA4集計だけでは厳密な有意差が出せないので表現を実務閾値判断に落とすのが誠実。群A実測が50分だったため処置差は想定より小さく、期間・最大減収はMAUでなく事前テストで再試算が必要。content_ready は全画面広告の背後で発火し群Aを過小評価しうるため content_visible 必須。

2026-06-18: 起動時広告A/B 提案書を thomsons S3バケットに公開アップロード

背景: 提案書(HTML)をトムソンに共有する必要。既存の review-20260605.html 等を置いている thomsons バケットがある。

決定: 提案書を thomsons バケット(profile thomson-ik/ap-northeast-1)に object ACL public-read で公開アップロード(既存HTMLと同方式)。URLは https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html。

理由: 同バケットは公開HTML共有の前例があり BPA無し・ACL公開可。アップ前に秘密情報スキャンを実施し、メール/APIキー/GA4 property ID/広告ユニットID 等が含まれないことを確認済み。URLを知れば誰でも閲覧可なので取り扱いは注意。

2026-06-18: 起動時広告の削減は「全消し」でなく「50/50無作為A/Bで実測判断」する

背景: 多角レビューの最優先施策は「広告UX是正」。起動時広告は総売上の約25%(トムソンヒヤリング)で、減らすと収益の即時下落が確実な一方、リテンション改善は遅く不確実という非対称な賭け。トムソンは「満足度を測れない」と削減に消極的。決定権はトムソン側にあり、提案を通せる形にする必要があった。

決定:

  • 起動時広告を全消しせず、頻度を絞るダイヤルとして扱い、50/50 の無作為A/B(群A=現状/群B=1日1回)で得失を実測してから判断する。
  • 「満足度」ではなく行動指標で測る:①ユーザーあたり広告収益(impression×eCPM)、②離脱=launch_bounce・週次セッション・D7/D14継続。
  • 収益測定はトムソンの日次データに依存せず自前のimpressionカウントを土台にし、金額は月次総売上1個から2バケツ(起動時=MAX実額/それ以外=混合平均eCPM)に割る方式とする。
  • 採否は3パターン判断(同等以上→即採用/微減だが残り良化→採用/残り不変→戻す)。勝った間隔は S3 設定で全員展開。

理由:

  • 群Aに現状を半分残すので、最悪でも全体の半分は売上を維持=トムソンが許可しやすい(賭け額が見える)。前後比較では自然減(MAU −15%/年)に埋もれるが、無作為A/Bなら自然減が両群に等しくかかり相殺され因果がクリーン。
  • launch_bounce は「率」で自然減に埋もれず、起動時広告に最も因果がタイト。これがトムソンの「記事内広告が主因」説への反証材料にもなる。
  • 広告構成がパッチワーク(MAX外に AdG/Zucks/LINE)で MAX の didPayRevenue だけでは収益を捕捉しきれないため、全SDK共通で取れる impression カウントを測定の土台に据えるのが堅い。didPayRevenue は MAX スライスの実額+月次の答え合わせに用途を限定。

2026-06-13: iOS の YouTube 動画は自動再生せず、タップで再生開始にする

背景: YouTube プレイヤーの WKWebView 化(ナレッジ ios-wkwebview-youtube-inline-playback 適用)に伴い、2015年以来の autoplay=1(動画詳細を開いた瞬間に自動再生)を維持するか選ぶ必要があった。

決定: embed URL を autoplay=0 にし、ユーザーのタップで再生開始する挙動に変更する。

理由: You の明示指示。意図しない音出し・通信を避け、ナレッジの推奨値(7channel 実績構成)とも一致する。

背景: 定期実行の呼び出し元が「cron 専用 EC2」と「AWS Lambda(EventBridge)」に二分。Lambda 側の TFN_cron 等は fnext.thomsonsapp.com を叩くだけの curl ラッパーで、Baseball は同等処理を既に cron 上で実行済みだった。

決定: curl ラッパー型の定期実行は cron 専用サーバーに寄せ、対応する Lambda + EventBridge ルールは削除する。実ロジックを持つ Lambda(rss-crawler-notice-prod-crawlArticle 等)は当面残す。

理由: ラッパーは cron の1行で完全等価に置換でき、二重管理・nodejs8.10 廃止ランタイム・不要コストを解消できる。実ロジック型は移行に設計(サーバ側エンドポイント化)が要るため別扱いとした。

2026-06-06: 稼働ゼロ Lambda の削除は「invocation実測+トリガー+キャッシュ有無」で判定する

背景: 大量の旧 Lambda(TSP系・sports-paper系・test系)が残存。削除可否を安全に判断する必要があった。

決定: ①直近7日/90日の invocation を日次バケット合算で実測(長期間1バケットは過少値になるため不可)②resource-policy/EventSourceでトリガー有無を確認③API Gateway 経由はステージのキャッシュ無効を確認して「0回」が本物だと裏取り——の3点が揃えば削除。コードは毎回バックアップしてから実行。共有リソース(IAMロール ichirokisanuki_lambda、SNS IchiroKisanukiAlert)は触らない。結果 42→9関数。

理由: API キャッシュが効いていると invocation 0 でも現役の可能性がある。三点確認で誤削除リスクを排した。実際 footballnext-oshirase-v2(90日0)は iOS にコード参照が残っていたが、キャッシュ無効=真に未到達と確認できたため据え置き判断ができた。

2026-06-06: 削除済み footballnext-oshirase-v2 は復元しない(デッド画面として次回整理)

背景: 90日invocation 0 を根拠に削除した footballnext-oshirase-v2 を、iOS OshiraseViewController.swift:30 がまだ参照していると後から判明。

決定: 復元せず据え置き。次回アプリ改修で OshiraseViewController ごとデッドコードを除去する。

理由: ステージのAPIキャッシュ無効を確認済みで、90日ゼロ=実際に誰もこの画面に到達していない(到達不能 or 廃止導線)と判断。バックアップ(/tmp/lambda_cleanup3/footballnext-oshirase-v2.zip)は保持しているため、万一必要になれば復元可能。

2026-06-05: Football NEXT のマネタイズ方針 ─ 広告密度を「最大化」から「最適化」へ転換する

背景: 広告ネットワーク売上が2019ピークから2025で−69%、下落が加速。原因を多角レビュー(広告売上・全レビュー3,987件・GA4行動データ・Codecの広告実装解析の6ソース)で診断したところ、広告増量・評価崩落(平均星3.0→1.33)・MAU縮小(−32%)・売上下落が時系列で一致し、広告過多による「デススパイラル」と判定された。

決定: 広告impression最大化路線をやめ、離脱を止めて母数を維持する「最適化」路線へ転換する。具体的には ①App Open Ad のスロットル(AppDelegate でコメントアウト中の頻度ゲート)を復活、②不快クリエイティブのブロック、③広告挿入間隔の緩和を最優先とし、中期で有料広告なし課金(StoreKit)を新設して収益を多様化する。効果検証は AdMob面別データが取れないため GA4 の離脱シグナル(MAU/セッション/継続率)で代替する。

理由: 広告増量は短期impressionを稼ぐが、レビュー・GA4の両面でロイヤル層の離脱を招き、母数減でかえって総売上を押し下げている蓋然性が高い。残存ユーザーはエンゲージ率90%・月35セッションの高価値層で、広告で焼くより保全+課金する方が中長期の期待値が高い。

2026-06-05: GA4 を MCP として接続し、行動データをレビューの常用ソースにする

背景: 当初レビューは継続率・MAU等の行動データが無く代理指標で補っていた。Football NEXT は Firebase Analytics を実装済みで GA4 にデータがあると判明。

決定: Google公式 google-analytics-mcp(pipx) を claude mcp add ga4 -s user で登録(property 211701453=footballnext-bca77、認証は既存ADC流用、property固定)。今後の footballnext 分析で GA4 を常用ソースにする。

理由: Firebase連携の GA4 が既にデータを持っており、サービスアカウント新規発行より既存ADC流用が最短。user スコープにして他プロジェクトからも再利用可能にした。AWESOME FOOTBALL(366217595) は無関係なので除外。

2026-06-03: 得点者データは fnlive_fixtures.events_json に生 events を保存し、整形は配信API側で行う

背景: 試合詳細に得点者(得点時間・選手・アシスト・PK/OG)を表示するため、データの保存先と整形場所を決める必要があった。

決定: 専用テーブルは作らず、既存 scores_json と同パターンで fnlive_fixtures.events_json(TEXT)に API-Football の events 配列を生のまま保存。表示用の scorers 整形(Goal 抽出・home/away 判定・PK/OG・アシスト)は配信API V2Controller::fixture() に集約。OG は API が「得点が入る側」に team を割り当てているため team.id を反転せず素直にマップ(実APIで検証しスコア一致)。

理由: 試合あたりの events は高々数十件で常にセット取得=JSON カラムで十分(新テーブル/関連は過剰)。生保存なら将来カード/交代表示に流用可。整形を1箇所(既存の status/score 整形と同じ場所)に集約すると保守が楽。単一試合レスポンスに events が同梱されるので追加 API コールも不要。


2026-06-03: iOS の得点者カードは記事テーブルの tableHeaderView に手動レイアウトで載せる

背景: 試合詳細はスコアボード用テーブル(高さ固定 184・自己サイズ非対応セル)+記事テーブルの2段構成。得点者カードをどこに置くか。

決定: スコアボード用テーブルには手を入れず、LiveFixtureScorersView(手動フレーム+sizeThatFits で高さ確定)を記事テーブルの tableHeaderView に設定する方式を採用。当初スコアボードテーブルに section 追加+動的高さで実装したが、固定 184 で見切れ・estimatedRowHeight=0 でスコアボードセルが崩壊した。

理由: スコアボードセルが自己サイズ非対応のため同テーブルでの自己サイズ運用は破綻しやすい。記事テーブルのヘッダーに手動レイアウトで載せる方式は自己サイズの曖昧さに依存せず確実で、視覚的にもスコアボード直下・記事の上で仕上がりイメージ通り。


2026-06-03: 過去試合の得点者は updateFixture で個別バックフィル(全件一括はしない)

背景: アプリは過去試合も履歴から閲覧できるため、デプロイ前に終了済みの試合(events_json が空)にも得点者を出したい。一方 RapidAPI にはクォータ制限がある。

決定: 全履歴の一括バックフィルはせず、updateFixture($id)(終了試合もスキップせず強制更新)で必要分を個別に埋める。今回は直近10試合のみ実施。今後終わる試合は ovserveFixtures が終了時に自動で events_json を保存するため対応不要。

理由: 何千試合もの一括取得はクォータを食い潰し非現実的。閲覧されうる直近分だけ個別に埋めれば実用上十分でコストを抑えられる。lazy 取得(閲覧時に API)はリクエスト経路に外部呼び出しが入るため見送り。


2026-05-30: footballnext-ios は GitHub クローンで作業し、Pods はコミットしない

背景: operation 配下の footballnext-ios が、GitHub 移管前の CodeCommit クローンのままだった(remote=CodeCommit、Pods を丸ごとコミットする旧運用)。GitHub 側は移管時に履歴を書き換えて>50MBバイナリを除去し Pods/ を .gitignore 済み。push しようとして発覚(CodeCommit 認証切れ)。

決定: GitHub ichirokisanuki/footballnext-ios(master) を正とし、operation 配下を GitHub クローンに置換。Pods は コミットしない(.gitignore、pod install で都度復元)。今回のソース変更のみを移植して push(28f5fe7)。旧 CodeCommit クローンは ~/Downloads/footballnext-ios-codecommit-backup へ退避。両リポジトリは履歴分岐のため相互 push/force は禁止。

理由: GitHub は100MB超ファイルを拒否し、今回 Pangle バイナリが 124MB=そもそも Pods は載せられない。Pods 非コミット+pod install 運用が唯一現実的で、移管時の方針とも一致する。CodeCommit は実質終了済み。


2026-05-30: footballnext-ios の最小 iOS を 12→13 に引き上げ(iOS12 サポート終了)

背景: MAX の広告SDKを最新化する中で Google Mobile Ads を 12→13 に上げる必要があり、GMA 13 は最小 iOS 13 が必須。

決定: デプロイメントターゲットを iOS 13 に統一し、iOS 12 サポートを終了。これにより GMA 13.3 / InMobi 11.3 等の最新アダプタ群を採用する。

理由: GMA 13 だけでなく InMobi 最新アダプタも GMA 13 を要求するため、最新化するなら iOS13 化は不可避。iOS 12 は2026年では実利用ほぼゼロで実害なし。API 破壊はほぼ無く(GADNativeAd→NativeAd の3箇所のみ)移行コストは小さかった。


2026-05-21: footballnext-android は全面近代化して16KB対応リリースする

背景: Google Play から「16KBメモリページサイズ非対応」の指摘(対応期限 2026-05-31)。footballnext-android の前回リリースは2021年10月で、ビルド環境は4年前のまま。

決定: 16KB対応(Realm 7.0.8→10.19.0)だけでなく、ツールチェーンを全面近代化する。Gradle 6.7→8.9 / AGP 4.2→8.7.3 / Kotlin 1.3→2.1 / JDK 17+ / targetSdk 30→35 / minSdk 19→24。kotlin-android-extensions 廃止に伴い ViewBinding へ全面移行。nend SDK は撤去(サービス終了済み)、広告は AdMob/ADG/i-mobile を維持。YouTubeプレイヤーは旧APIの提供終了により android-youtube-player に差し替え。

理由: 16KB対応の native lib(Realm 10.19.0)と Play 要件の targetSdk 35 を載せるには AGP 8系が必須で、それが Gradle/Kotlin/JDK 更新を芋づる式に強制する。jcenter 閉鎖で旧構成はそもそもビルド不能であり、16KBだけの部分対応は不可能。minSdk 24 は Android 6以下(世界シェア1%未満・2026時点で実害なし)を切る判断で、ネイティブmultidex等でビルドが安定する副次効果がある。


2026-05-21: OneSignal は 5.1.38 に固定(最新5.x系を使わない)

背景: footballnext-android の OneSignal を 3.x から 5.x へ移行する際、バージョンをレンジ指定すると最新の 5.9.2 に解決された。

決定: com.onesignal:OneSignal:5.1.38 にバージョンを固定する。

理由: OneSignal 5.8以降は OpenTelemetry / okhttp5 / wire を推移的に引き込み、kotlin-stdlib を 2.2系へ強制昇格させてしまう。プロジェクトの Kotlin コンパイラ整合とAPKサイズの観点から、これらを引き込まない最後の軽量系列 5.1.x の最新(5.1.38)に固定するのが筋がよい。


2026-05-19: cake_sessions Duplicate entry の PHP Fatal は当面放置

背景: 5/16・5/17・5/19 に CloudWatch 経由でエラーログスパイクのアラートメールが来ていた。調査の結果、原因は fnext.thomsonsapp.com(本番 API ドメイン)に対する bot のパススキャン由来。bot が同一セッション Cookie を使い回して並列リクエストを打つため、CakePHP の DatabaseSession::write() が cake_sessions.PRIMARY の race condition で Duplicate entry を起こして Fatal になる。

決定: 即時対応はせず保留。データ実害なし・RDS への影響軽微・正規アクセスへの影響なしの判断。頭の片隅に置いておき、頻度が上がるか実害が出始めたら対応する。

理由: 攻撃トラフィック自体はどこのサービスでも常時発生しており、いま手を動かす緊急性がない。対策案 A〜E(攻撃パス拒否 / Session→Redis / try-catch 握り潰し / アラート緩和 / WAF 導入)はいずれもコストか副作用があるので、観察モードのまま維持する方が筋がよい。中長期で Session 保存先を File or Redis に移す案は副次効果(DB 負荷減)が大きいので、別タイミングで検討する価値あり。


2026-05-09: Step 3 (innodb_buffer_pool_size 1024→768MB 縮小) は保留

背景: 当初は FreeableMemory 直接対策として buffer_pool 縮小を計画(Step 1+2 のインデックス整理に続く 3 段目として)。しかし baseballnext を同 RDS に統合する計画があることが判明し、統合後はホット working set が 1.25-1.5GB 規模に増える見込み。

決定: Step 3 は実施せず保留。baseballnext 統合シナリオが確定したあとに「buffer_pool 1024MB 維持 / 縮小 / medium 昇格」を再評価する。

理由: 統合前にメモリを絞ると、統合後にすぐ戻すか medium 昇格する二度手間になる。Step 1+2 の効果(ReadIOPS -61〜70%)で I/O 余裕は得られたので、メモリ周りは統合計画の解像度が上がるまで動かさない方が筋がよい。


2026-05-08: articles インデックス A 群は INVISIBLE 観察期間をスキップして即 DROP

背景: 通常は INVISIBLE 化で 1〜数日観察してから DROP INDEX する保守的フローを取るが、A 群 4 個(article_site_id / image_width / title / del)はいずれも他複合 index の完全 prefix で、戻す可能性が極めて低い。

決定: INVISIBLE 化の直後に同セッション内で DROP INDEX まで実行(Online DDL / ALGORITHM=INPLACE / LOCK=NONE)。

理由: A 群は機能的に完全な上位互換が存在するため、観察してもクエリ計画の変動はほぼ起こり得ないと予測。万一戻すとしても CREATE INDEX で再構築可能(数分、Online)。Step 3 の判断材料を早く揃えるためにも観察期間の機会損失を避けた。実測では ReadIOPS -61〜70% で副作用なしを確認。


2026-05-08: impressions テーブルの機能を廃止

背景: 730 MB / 6.98M 行のテーブル。INSERT は V1Controller.php:296 1 箇所のみ、読み取りは管理画面 PortalController の集計用 2 箇所のみで、エンドユーザー API からは未参照。

決定: ユーザーが INSERT 行をコメントアウトしてデプロイ → TRUNCATE で全データ廃棄。テーブル定義は当面残置(ストレージ影響軽微)。

理由: 機能としての価値が低く、毎秒 0.7 INSERT 程度の継続的な書き込みコストを払う意味がない。管理画面の article_from_twitter 集計が空になる影響はあるが、ユーザー判断で許容。


2026-05-08: 19-20 時の FreeableMemory スパイクは cron 由来ではなく人為的と確定

背景: 5/7 19:20 に FreeableMemory が 1 分以内に 148 → 55 MB へ急落するスパイクを観測。当初は cron 起動による定常パターンを疑った。

決定: 過去 7 日同時間帯と比較した結果、5/7 のみ突出した急落。同時刻に走らせていた articles + old_articles への AVG(LENGTH(body)) のフルスキャン集計クエリが原因と特定。cron 由来ではない・調査クローズとして扱う。

理由: 過去 6 日の同時間帯は min 119-151 MB のベースライン揺らぎ範囲で、特異なスパイクなし。「ほかプロジェクト談」で話題になった FreeableMemory 課題は、特定時刻スパイクではなく構造的な低空飛行(avg 141 MB / min 115 MB あたり)の方であり、本命の対策は インスタンス昇格 に帰着するという理解。


2026-05-08: hot_keyword_rankings の古いデータ整理は判断保留

背景: 7.4 年分・約 915 万行の死蔵データが堆積。アプリは「最新 dir の rank top2」しか参照していないため、過去データは完全に不要と判断できる。一方で書き込み自体が 2026-04-09 で止まっており、cron 停止か別マシン稼働かが切り分け未済。

決定: データ削除は今セッションでは実行しない。判断材料を蓄積した上で別途検討する。

理由: 削除自体に技術的リスクは低いが、書き込み停止の真因が不明なまま削除すると「機能の生死」を誤判定する可能性がある。書き込み停止が意図的なのか障害なのかを先に切り分けたい。


2026-05-07: footballnext-operation はサブプロジェクト3つを .gitignore で除外

背景: footballnext-ios / footballnext-android / footballnext-server はそれぞれ独立した Git リポジトリで管理されている。footballnext-operation はそれらを跨いで運用情報をまとめる「指揮塔」用途。

決定: 3 サブプロジェクトのディレクトリを本リポの .gitignore に追加して、追跡対象から外す。

理由: ネスト Git の二重管理を避け、本リポは運用メモ・ドキュメント・スクリプト類に集中させる。


2026-05-07: RDS の performance_schema は OFF を維持

背景: カスタムパラメータグループ general-mysql84 で performance_schema = 1 が user-set かつ pending-reboot 状態で残っていた。再起動すると有効化される「不発弾」の状態。db.t4g.small (2GB) の FreeableMemory は Min 55MB / Avg 141MB と既に黄信号。

決定: reset-db-parameter-group で user override を削除し、engine-default (0) に戻す。

理由: Performance Schema は db.t4g.small クラスでは 200〜400MB 追加で消費する。現状すでにメモリが厳しく、有効化するとさらに悪化する。観測性を取り戻したくなったらインスタンス昇格とセットで再検討。


2026-05-07: old_articles テーブルのデータを廃棄

背景: old_articles は articles 2.0GB 規模のアーカイブテーブル。footballnext-server 内に参照コードはゼロ、過去のアーカイブ運用の名残として DB にだけ残っていた。

決定: phpMyAdmin から TRUNCATE してデータを全廃棄。テーブル本体は当面残置。

理由: アプリ参照ゼロかつ運用も止まっているため、データ保持コストが純粋な無駄。テーブル定義(とインデックス枠)まで消すのは物理影響が軽微なので後回しでよい。


2026-05-07: articles のインデックス整理は調査結果に留めて実行は保留

背景: 22個のインデックスのうち、4個(article_site_id / image_width / title / del)は他複合 index の prefix で明確に冗長。ただし本番運用中のテーブルでロックを伴う変更は慎重に進めたい。

決定: 削除候補リストと推奨手順(INVISIBLE → 観察 → DROP → OPTIMIZE)の作成までで一旦保留。次セッションで実行する。

理由: ロールバック容易性を最大化したい。MySQL 8 の ALTER INDEX ... INVISIBLE を使えば Online でオプティマイザから外せて、問題があれば瞬時に戻せる。今日は調査メモとして残し、メンテ時間帯を意識して実行する。


DEVLOG(作業ログ)

開発日誌

このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。


2026-10-16

13:05 - iOS 5.7.2 の公開を確認してタグ 5.7.2 を付与・定期通知の FCM を一覧 JSON より先に送る修正(server b42a298)を本番デプロイ

やったこと:

  • iOS 5.7.2 の公開とタグ:
    • App Store Connect で 5.7.2 が READY_FOR_SALE(ビルド25・リリースは MANUAL)なのを確認した。10-12 から公開までの申請の経緯は、この DEVLOG に記録が無い。
    • ビルド25は 78fe327 をアーカイブしたもの(10-11 16:07 の記録。コミット 15:34・アップロード 16:00)。78fe327 の後にコミットは無い。
    • 注釈付きタグ 5.7.2 を 78fe327 に付け、GitHub に push した。メッセージは「5.7.2 App Store リリース(FCM 移行・購読同期の修正・起動時広告の間隔の S3 化。ビルド25)」。
  • 公開の影響: push_settings は 10-11 から fcm.ios=1・onesignal.ios=0。このため、5.7.2 に更新した App Store のユーザーにも、定期・号外が FCM で届くようになった。
  • 定期送信の修正(server b42a298): WIP で「App Store 公開前に必ず直す」としていた、定期送信の updateTeikiRecentS3()(期限なしの S3)が FCM の前にある件が残っていたので直した。
    • コミット後の順番を「記事キャッシュの用意 → FCM → OneSignal → 一覧 JSON(teiki_recent.json)」にした。
    • コミット後の送信は NotificationSender::deliverTeiki に移した。号外・お知らせと同じく、各段の失敗は PUSH_ALERT teiki_<段>_failed に記録して次へ進む。もとは、FCM か OneSignal で例外が出るとその後ろが止まっていた。
    • OneSignal に送る内容は変えていない(音なし・<tag>_all・gene_1_2 の除外・画像・見出し)。
    • 一覧の作成は NotificationSender::updateTeikiRecentS3 に移し、Aws::boundedConfig(3, 10, 1) で書く。/api4/updateTeikiRecentS3 はここを呼ぶ。
    • 警告 teiki_recent_s3_failed には request_key が入らなくなった(号外の側と同じ形)。
  • 一覧を最後にしてよい根拠(アプリのコードで確認):
    • 通知のタップで開く画面は、ID 指定で Lambda footballnext-teiki-notification-articles を読む。iOS は NotificationServiceUtil.openTeikiArticleSuite → TeikiArticleSuiteTableView、Android は TeikiNotificationSuiteModel。
    • teiki_recent.json を読むのは、インフォボタンの通知一覧だけ(iOS TeikiTableView・Android TeikiNotificationListViewModel)。
    • 記事キャッシュの用意(884fb95)は、タップで開いた画面が空になるのを防ぐため FCM の前に残した。接続3秒・全体5秒を最大3回なので、FCM の遅れは最大約16秒。
  • テスト:
    • test_notification_sender.php に、定期の順番・OneSignal に送る内容・画像なし・各段の失敗・一覧 JSON の中身を足して30件にした。
    • PHP 5.5・7.4 で通過。本番 Web 機2台の PHP 5.3.29 でも、lint と test_notification_sender.php 30件・test_push_lib.php 63件が通過した(/tmp に置いて流し、消した)。
  • デプロイ(10-11 と同じ手順・You の指示):
    1. cron サーバーの crontab を ~/crontab_backup_20261016_teikifix.txt に保存した。FN の通知の3行(56・58・59行目)だけを #PAUSED-TEIKIFIX でコメントにした(12:47:44)。BaseballNEXT の行(114〜116行目)には触れていない。
    2. 起動済みのジョブが0件になったのを確かめた(12:50)。
    3. AWS_PROFILE=thomson-ik git push(12:50:20)→ Pipeline footballnext が Succeeded(12:51:18)。
    4. 両台の変更3ファイルの md5 が手元と一致した。本番に置かれたファイルの lint も通り、デプロイ後の PHP のエラーと PUSH_ALERT は無かった。
    5. 保存した crontab をそのまま戻した(12:51:47・差分なし)。止めていたのは約4分。
  • 確認:
    • 12:52 の sendTeikiNotification と processPushQueue は 200。
    • 13:00:27 の checkPushHealth は 200 で、PUSH_HEARTBEAT ok alerts=0。
    • 新しい順番での実際の定期送信は、17:55 の枠が最初になる。

決めたこと:

  • 定期の一覧 JSON は FCM と OneSignal の後に、期限付きで書く。記事キャッシュの用意は FCM の前に残す(DECISIONS に記録)。

詰まったこと / 気づき:

  • 計画書 2-3 には「S3 の公開 JSON は通知から開く画面が読むので、更新が成功してから送信可能にする」とあるが、実際のコードと合わない(画面は読まない)。計画書に注記した。
  • cron サーバー(Amazon Linux 2018.03)の pgrep は古く、-c が使えない。ジョブ数は ps -eo pid,etime,args | grep … で数える。
  • Web2 号機(18.181.147.201)の error_log に、cake_sessions の主キー重複で PHP が止まるエラーが出ている(10-14 08:09・15:17、10-15 13:43、10-16 00:25)。DatabaseSession::write から出ていて、今回の変更とは関係なく、まだ調べていない。

次回やること:

  • 17:55 の夕方の定期で、新しい順番を確かめる。push_jobs の iOS の FCM が accepted になること、teiki_recent.json の更新時刻が送信の直後であること、error_log に PUSH_ALERT teiki_* が無いことを見る。
  • 起動時広告の S3 の切り替え(default_interval_minutes: 1440・config_version: 10)。5.7.2 は公開済みなので、時期は You が決める。
  • BaseballNEXT の計画に、10-11 の追加分と 10-16 の定期送信の順番を反映する。

2026-10-12

18:00 - ビルド25の実機確認①: 「夕方」を ON→OFF→ON と連打しても夕方の定期通知が届いた

やったこと:

  • iPhone ミラーリング(phone-harness)で You の iPhone を操作。TestFlight で FootballNEXT が 5.7.2(25)であることを確認した。
  • 設定画面で「夕方の注目記事」を OFF → ON に切り替えた(17:48:09。各タップ直後の画面で、OFF(灰色)→ ON(緑)を確認)。設定を閉じ、ホームに戻ってから、17:49:25 に App スイッチャーで前面に戻した。
  • 17:55:42 の夕方の定期通知(push_jobs の 11 番・ios_teiki_yu・FCM accepted)が You の iPhone に届いた(You 確認)。error_log にエラーは無し。
  • 10-11 17:55・21:56、10-12 朝・昼の定期も、新しいコード(92cf239)で iOS の FCM は accepted。Android は OneSignal のまま failed(provider_error・既知)。

詰まったこと / 気づき:

  • iPhone ミラーリング中は、iPhone の通知センターを開けない(上端からのスワイプが効かない)。届いたかは You に見てもらった。
  • iPhone 本体を触るとミラーリングが切れる(「iPhone が使用されたため…」)。You にロックしてつなぎ直してもらう。
  • FN のアプリは名言のスプラッシュをタップしないと先に進まないことがある。設定画面へは、左上のメニュー → 上の歯車から入る。phone-harness の scroll_until は、メニューの項目(番組表)のタップとして扱われた。
  • 朝の枠は 07:55:42 に作られていた(計画の「07:45」と違う。枠の時刻が変わった可能性)。

次回やること:

  • 確認表の残り(計画 3-6 の 2〜7)。OFF の確認は「夜」を OFF にして 21:56 の通知が届かないことでもできる。
  • 条件送信の手順を作る。

2026-10-11

16:07 - iOS 5.7.2(ビルド25)を TestFlight に配布(購読同期の修正 78fe327 入り)

やったこと:

  • App Store Connect の最新は 24 → 25 にした。78fe327(GitHub には未 push)を Release でアーカイブした(CURRENT_PROJECT_VERSION=25・-allowProvisioningUpdates)。Pods の FIRMessagingPendingTopicsList.m に修正が当たっていることを先に確かめた。
  • アプリ本体と通知拡張が 5.7.2(25)であること、FirebaseMessaging が修正後のソースからコンパイルされていること、OneSignal が入っていないことを確かめた。
  • exportArchive(app-store-connect・upload)→ 15:59 に Upload succeeded → 16:05 に VALID。内部テストは IN_BETA_TESTING、usesNonExemptEncryption=false。
  • 警告は既知のものだけ: 最低 iOS 13.0(2027年4月から 15.0 以上が必須)、広告 SDK の dSYM が無い。

次回やること:

  • ビルド25の確認表(計画 3-6 の1〜7)。条件送信の手順を作る。
  • 申請するか(トムソンに依頼するか)を、確認表の結果で決める。ビルド24は申請しない。

15:50 - FCM 移行の不具合修正の server(92cf239)を本番デプロイ・テスト送信が実機に到達

やったこと:

  • デプロイ前の確認(両 Web 機 3.112.232.196・18.181.147.201、ssh -i ~/.ssh/ichirokisanuki.pem ec2-user@…):
    • PHP 5.3.29 で、変更した本番コードの lint がすべて通った。test_push_lib.php 63件・test_notification_sender.php 18件も通過(/tmp に置いて流し、消した)。
    • 本物の aws.phar(SDK 2.6.5)で、Aws::boundedConfig(3, 10, 1) がリクエストに接続3秒・全体10秒として入り、再試行もこちらの設定に置き換わることを確かめた。既定のクライアントは変わらない。
    • /etc/php.ini は safe_mode Off。open_basedir の設定は無い。curl のリダイレクト設定も効く。http://fnext.thomsonsapp.com はリダイレクトせず 200。
    • 本番 DB で、送信待ちの FCM の行(pending・retry_wait・sending)が0件であることを確かめた。
  • 本番の設定が WIP と違っていた: push_settings は fcm.ios=1・onesignal.ios=0(10-11 08:37:02 に切り替え済み・fcm.ios.since も同時刻)。午前の別作業で、TestFlight 段階で切り替えたものとみられる(DEVLOG に記録が無い)。iOS の定期・号外は FCM で、トピックを購読する TestFlight 端末だけに届いている。
  • デプロイ:
    1. cron サーバー(52.194.87.168)の crontab を ~/crontab_backup_20261011_fcmfix.txt に保存し、FN の通知の3行(56・58・59行目=sendTeikiNotification・processPushQueue・checkPushHealth)だけを #PAUSED-FCMFIX でコメントにした(15:38:23)。
    2. 起動済みのジョブが終わるのを待った(15:40:14 に0件)。
    3. AWS_PROFILE=thomson-ik git push(15:40:24)→ Pipeline footballnext が Succeeded(15:41:20)。
    4. 両台の変更12ファイルの md5 が手元と一致することを確かめた。error_log に PHP のエラー・PUSH_ALERT は無し。
    5. 保存した crontab をそのまま戻した(15:41:40・差分なし)。止めていたのは約3分。
  • 確認:
    • 15:42:52 の processPushQueue は 200。
    • 15:44:44 に管理画面(Claude が You の Chrome で操作・記事 2512097)から「テスト送信」→ 画面は「ios fcm: 受付済み・android fcm: 受付済み」。push_jobs の 6 番に ios_test・android_test とも accepted(受付番号 projects/footballnext-bca77/messages/…)。You の iPhone に届き、タップで記事が正しく開いた。
    • 15:50:27 の checkPushHealth は 200・PUSH_HEARTBEAT ok alerts=0。

詰まったこと / 気づき:

  • Web 機には SSH で入れる(DECISIONS 2026-10-10 の「SSH・SSM ログインができず」は、少なくとも ec2-user の鍵では当てはまらない)。
  • 管理画面のテスト送信は confirm・alert のダイアログを出すので、自動で操作するときは、そのタブで window.confirm・window.alert を差し替えて結果を取り、終わったら再読み込みする。
  • 管理画面のログイン画面・号外画面の下に、CakePHP の SQL のログが出ている(本番でデバッグ表示が有効とみられる。今回の範囲外)。

16:10 - FCM 移行の実装の不具合修正(BaseballNEXT 計画のレビューで発見した5点+GPT 3巡で見つかった Firebase の順番の不具合ほか)・server 92cf239・iOS 78fe327 をコミット(未 push)

やったこと:

  • 発端: BaseballNEXT の FCM 移行計画(~/cdev/baseballnext-operation/.devnotes/FCM_MIGRATION_PLAN.md)のレビューで、FN の実装に5つの不具合が見つかった。
    • iOS: 購読の同期のキャッシュ、APNs トークン到着時に同期を再開しない
    • server: 送る順番、鍵の project_id の照合が無い、設定のキャッシュ
  • iOS(footballnext-ios・78fe327。5.7.2 の 9a4fe0e の次。未 push):
    • PushTopicSync: API を1つでも呼ぶ前に成功記録を消す。保存は、全部成功し、保存の直前に世代と FCM トークンが開始時と同じときだけ。
    • didRegisterForRemoteNotificationsWithDeviceToken: Messaging.messaging().apnsToken = deviceToken と requestSync()。
    • 起動して最初の同期は、記録を見ずに全件を呼ぶ(ビルド24の記録・強制終了で記録の削除が書かれなかった場合のため)。
    • Firebase の修正: FIRMessagingPendingTopicsList が、前のプロセスから復元した古い操作より新しい操作を先に処理することがあった。scripts/patch_firebase_topic_fifo.rb で2か所を直す(Podfile の post_install から)。
      1. 処理中のバッチが無いときは最も古いバッチを選ぶ。
      2. 積むたびに再開を予約する。
    • 「未適用」「適用済み」以外の状態なら pod install を止める。4つの状態で確かめ、pod install で当たることも確認した。Podfile.lock はチェックサム1行だけ変わった。
    • Debug・generic iOS・署名なしでビルドが通った。
  • server(footballnext-server・92cf239。884fb95 の次。未 push):
    • 号外・お知らせの順番を「コミット → FCM → OneSignal → 今の一覧 JSON → 旧世代向けの処理」に変え、各段の失敗を個別に PUSH_ALERT にした。
    • 期限を付けた:
      • push201607: curl で接続3秒・全体10秒
      • S3・DynamoDB: Aws::boundedConfig(3, 10, 1)
      • OneSignal: 接続5秒・全体15秒
    • Fcm で鍵の project_id を footballnext-bca77 と照合する。違えば通信せずに project_mismatch。
    • PushSettings::reload() で、設定を配信1件ごとに読み直す。
    • PushQueue::deliverAll: OS ごとに例外を分け、表示は DB の状態から決める。
    • OneSignal の結果は応答から決める。応答が無い(curl の期限切れなどで false)・空・JSON でないものは、failed ではなく unknown にした。記録に失敗しても応答から決める。
    • 管理画面のラベルに「送信中」を足した。
  • テスト(PHP 5.5・7.4・使い捨て MySQL 8.0。すべて通過):
    • test_push_lib.php: 63件
    • test_notification_sender.php(新規): 18件
    • test_push_queue_mysql.php: 66件
    • 修正を外した複製では、追加したテストが失敗することも確かめた。
  • 実測(本物の aws.phar・php -S):
    • 応答しない相手に、S3・DynamoDB は 5.1秒、push201607 は 10.0秒で打ち切られた。
    • 既定の S3 は、curl に8秒の上限を付けても再試行込みで39秒かかった。
  • GPT レビュー 3巡(discussions/2026-10-11-fn-fcm-fix-review.md):
    • Round 1: トークン取得段階の失敗は保留されない、再起動後の順番、APNs の明示代入、例外のときの queued は誤り、SDK の再試行判定が外れる、push201607 の期限は読み取りだけ、停止=取消は条件つき。
    • Round 2: 「起動後の最初の同期を記録しない」対策では順番を保証できない、送信権が取れないときの表示、OneSignal の記録失敗、確認の方法。
    • Round 3:
      • SDK 修正の2か所目が同じ操作の重複で再開しない、修正済みの判定が甘い
      • ビルド24の記録で同期を省く
      • OneSignal の false を failed にしている
      • 配備中に新旧のファイルが混ざる
    • すべて実コードで裏を取って反映し、議論を閉じた。
  • 計画書(FCM_MIGRATION_PLAN.md):
    • 2-2・2-5・5-2・6-4 の「停止設定は作成済みの再試行にも効く」を事実どおりに訂正した。停止中に判定まで来た配信は skipped(disabled)で、再開しても送らない。来なかった retry_wait は再開後に送られうる。
    • 2-11(server)・3-6(iOS)・3-3 のトリガー・7章の未決9を足した。

決めたこと:

  • iOS の修正は 5.7.2 のビルド25で出し直す前に GPT にレビューしてもらう(You)。コミット・push・デプロイはまだしない(You)。
  • 再起動後の順番の問題は、Firebase SDK を直して積んだ順に処理させる(You。DECISIONS)
  • 回線が戻っただけでは同期し直さない(トークンの取り直しで失敗したときは、前面復帰・再起動・トークン更新でやり直す)を仕様として受け入れる(You)
  • 付随処理の AWS の再試行は汎用の1回にし、サービスごとの判定が外れることを明記する(You。DECISIONS)
  • 起動して最初の同期は記録を見ずに全件を呼ぶ(You。起動のたびに購読 API が6件増える)
  • 配備中は FN の通知の cron 3行(定期送信・回収・監視)を止め、管理画面からは送らない(You。当初の「定期の1行だけ」から広げた)
  • 停止のときに pending・retry_wait も取消にする SQL を手順に足すかは保留(You)

詰まったこと / 気づき:

  • Firebase 11.15 のトピックの待ち行列は、先入れ先出しが保証されていない。
    • 処理中のバッチが無いとき、addOperationForTopic は新しいバッチを current にする。
    • 再起動時の restorePendingTopicsList は再開を呼ばない。バッチが終わるたびの再開は、こちらへの完了通知と別スレッドで競合する。
    • 世代・キャッシュをアプリ側でいくら工夫しても順番は保証できず、SDK を直すしかなかった。
  • 回線が無いときの購読・解除は、HTTP の段階では Firebase が保留して回線の復帰で送り直す(NSURLErrorDomain は completion を返さない)。トークンを取り直す段階の失敗は、すぐエラーで返る。
  • AWS SDK 2.6(Guzzle 3)の期限は request.options(timeout・connect_timeout)で効く。ただし再試行(S3 3回・DynamoDB 11回)に掛け算されるので、client.backoff で回数も絞る必要があった。
  • FULL_BASE_URL は Cake が env('HTTPS') と HTTP_HOST から作る。TLS は ELB で終わるので、実際の値は http://fnext.thomsonsapp.com。curl にするとき、file_get_contents と同じくリダイレクトを追うようにした。
  • test_push_lib.php は 65a6988(App::uses の追加)以降、スタブの App に uses() が無くて落ちる状態だった。
  • php:7.4-cli を --platform linux/amd64 で動かそうとして、手元に無い amd64 版の取得が始まった(止めた)。arm64 の手元のイメージはそのまま使える。
  • server の 884fb95(定期通知の前に記事キャッシュを作る・10-11 12:20)は、この DEVLOG にまだ記録が無い(別の作業)。

次回やること:

  • server:
    • 本番 Web 機の CLI(PHP 5.3.29)で lint とテスト。Web の PHP 設定(open_basedir・safe_mode)を確かめる。
    • FN の通知の cron 3行を止める → 両台に配備 → ファイルの一致・error_log・テスト送信を確認 → 再開(枠 07:45・12:11・17:55・21:56 を避ける)。
  • iOS: ビルド25をアーカイブして TestFlight → 確認表(計画 3-6 の1〜7)。条件送信の手順を作る → 申請するかは結果で決める。
  • iOS の FCM 本番を有効にする前に、定期送信の updateTeikiRecentS3() が FCM の前にあって期限も無い問題を直す。
  • BaseballNEXT の計画に、今回の追加分(Firebase の修正・deliverAll の表示・OneSignal の記録・push201608 の curl 化と呼び出し先の期限)を反映する。

2026-10-10

23:23 - FCM 移行の実装と本番反映=計画 v3(GPT 2巡)・初期検証・server/iOS 実装・鍵の準備・S3/DB/cron/CloudWatch・5.7.2(ビルド24)を TestFlight 配布し、本番の FCM 経由で実機に届いた

やったこと:

  • 実装計画(.devnotes/FCM_MIGRATION_PLAN.md):
    • v1 → GPT Round 3(10点)→ v2 → Round 4(9点)→ v3。
    • 主な反映: 送信状態の DB 管理(push_jobs/push_deliveries・条件付き UPDATE で送信権)、予定枠を副作用より前に確保、OS 別の切り替え、入口の保護、予定枠×OS の監視とハートビート、iOS を先に公開。
  • 初期検証(鍵なし):
    • 本番の PHP は 5.3.29(5.5 ではない)。RS256 は文字列のアルゴリズム名で可。両 Web 機から Google へ TLS 検証つきで届く。
    • 通知関連の表は InnoDB・同じ default 接続。MySQL 8.4・UTC。cron サーバーから HTTPS で届く。
    • Web 機のロールは S3 フル(SSM は You に権限なし)。error_log は CloudWatch に転送済み。
    • 残りのセグメント条件を管理画面で確認(iOS 昼夕夜=!= "0"、Android=!= "false" かつ App Version > 6、AllNewUsers=App Version > 6)。
    • iOS は Firebase 11.15.0 で依存解決・ビルド可。FirebaseMessaging 8.15.0 にはプライバシーマニフェストが無く、追加すると Apple 要件を満たせない。
  • server 実装(12e9ebb):
    • Fcm(HTTP v1・JWT・応答分類)、PushPayload、PushQueue(push_jobs/push_deliveries/push_settings)、PushService、NotificationSender を追加。
    • 定期通知は「枠の確保→優先記事の使用済み→履歴→送信先」を1トランザクションに(MAX(id)+1 を AUTO_INCREMENT に)。
    • 号外・お知らせは Portal(POST・CSRF・要求 ID)へ移し、旧 API は 403。テスト送信6本と J'sアラートを停止。
    • cron に processPushQueue・checkPushHealth を追加し、共有シークレットで判定。AfterInstall で非公開 S3 から秘密ファイルを配置。
    • テスト: 単体 57項目(PHP 5.3/5.5)、DB 45項目(使い捨て MySQL 8.0)。
  • iOS 実装(サブエージェント実装+レビュー。4718b1e):
    • OneSignal を外し FCM トピックへ。購読の同期は世代管理で、同じ状態なら24時間は API を呼ばない(レビューで追加)。
    • 新旧形式のタップ解析、許可確認の非同期化、NSE の一度だけ完了。
    • お知らせ画面の取得先を /api4/oshiraseV2 に直した(You 決定)。
  • 鍵:
    • You が Apple Developer で APNs キー RCX2QQWW35 を作り、Firebase の開発用・本番用に登録した。
    • サーバー用の鍵は Firebase 標準のサービスアカウント(You は編集者なので専用 SA は作れない)。
    • validate_only で4種類とも受付を確認。
  • 本番反映:
    1. 非公開 S3 バケット footballnext-private-config を作成(公開ブロック・暗号化・バージョン管理・HTTPS 限定)し、鍵と cron の合言葉を置いた(手元の鍵はゴミ箱へ)。
    2. DDL を投入(FCM 本番は停止・OneSignal は従来どおり)。
    3. cron サーバーに ~/.push_cron_secret を置いた。
    4. server をデプロイ(12e9ebb)。
    5. thomsons-apps-cron を稼働中の crontab に合わせた(aee5abd)うえで、定期送信を HTTPS+合言葉にし、回収・監視を追加(834d844)。
    6. cron.auth=enforce(合言葉なしは 403)。
    7. CloudWatch にメトリクスフィルタ2本・アラーム2本・SNS(You のメール・購読確認済み・テストメール受信)を作成。
  • 21:56 の夜の定期通知: 新しい仕組みで送信枠1件・teiki_notifications 11629(AUTO_INCREMENT)・OneSignal の記録2件(iOS 受付・Android 失敗)を確認。
  • 5.7.2:
    • 起動時広告の非参加時の間隔を S3 の任意キー default_interval_minutes から読む(未設定・期限切れは 50 分。ebb9639)。単体コンパイルの検証で19項目を確認。
    • 版を 5.7.2 に(9a4fe0e)。ビルド24 を TestFlight に上げて VALID。
  • 実機テスト: 管理画面の「テスト送信」(/portal/sendPushTest)で ios_test に送り、You の iPhone に届いた(本番の FCM 経由で初めて到達)。

決めたこと:

  • サーバー用の鍵は当面 Firebase 標準のサービスアカウントで進め、落ち着いたら FCM 専用の鍵に差し替える(DECISIONS)
  • 秘密情報は非公開 S3+AfterInstall で配り、cron の合言葉はサーバー上のファイルから読む(DECISIONS)
  • 5.7.2 に起動時広告の「24時間に1回」化(S3 の default_interval_minutes)を同梱する(DECISIONS)
  • 更新を促す策(アプリ内の促し・OneSignal を一時有料にして案内)は取らず、自然な更新を待つ(DECISIONS)
  • thomsons-apps-cron は稼働中の crontab を正として揃え、6/6 の未コミット変更(行の重複)は捨てる(DECISIONS)
  • iOS の Firebase は 11.15.0 へ一式上げる(プライバシーマニフェストのため)
  • 警告の通知先は当面 You だけ。お知らせのタップ先は直す

詰まったこと / 気づき:

  • デプロイ直後に Class 'ConnectionManager' not found(20:08 の1分だけ)。CakePHP 2.0 は ConnectionManager をモデルの初回利用時に読み込むため、モデルより先に呼ぶ入口では App::uses が要る(65a6988 で修正)。CakePHP を読まない単体テストでは見えなかった。
  • cron の正本は ~/PhpProjects/thomsons-apps-cron(push で自動デプロイ)だが、稼働中はサーバー上で直接編集されて12行ずれていた。手元にも行が重複した未コミットの変更があった(メモリ化)。
  • 確認用のバックグラウンドジョブを、pkill -f のパターン一致で自分で止めてしまった。Mac のスリープで sleep が止まって確認が遅れた。ssh の制御ソケットはパスが長すぎて使えなかった。→ トンネルは & で起動して PID で閉じる。
  • SNS のメール購読は、確認リンクを押しても反映まで少しかかる(1回目は反映されず再送した)。
  • 合言葉なしの呼び出しも PUSH_ALERT cron_rejected を出すので、外から叩かれるとアラームが鳴る(様子を見て必要なら NOTICE に下げる)。

次回やること:

  • 明日の朝: TestFlight 版で、終了状態からのタップ・画像・記事が開くかをテスト送信で確認する。
  • トムソンへ 5.7.2 の申請を依頼する。
  • 公開後: fcm.ios=1・fcm.ios.since・onesignal.ios=0 を入れる。S3 の A/B 設定に default_interval_minutes: 1440・config_version: 10 を入れる。
  • Android の FCM 対応(標準表示を実機で比べてから決める)。
  • 落ち着いたら FCM 専用の鍵へ差し替え(トムソンにオーナー権限の作業を依頼)。

18:22 - OneSignal 無料プラン新制限で iOS のプッシュ通知が 10/3 から停止していると判明/Android は FCM v1 設定エラーで送信不可/移行先を FCM(トピック方式)に決め、GPT と2巡レビュー・トムソンへ確認の返信文を作成

やったこと:

  • 状況確認と iOS タグ: WIP・DEVLOG から現状を説明。iOS のリリースタグは 5.7.1/5.7.0/5.6.0 が origin まで付与済みと確認。5.5.2〜5.5.5(7/8 リリースの 5.5.5 含む)はタグが無いが、You 判断で後追い付与はしない。
  • トムソン LINE(OneSignal の相談): 10/1 から無料プランに新しい制限が入り、有料化すると月額約 206.92 ドル。無料の代替への移行先と作業を確認してほしい、という依頼。
  • コード調査: サーバー(app/Lib/OneSignal.php・AndroidOneSignal.php・KhOneSignal.php)は OneSignal REST に、ダッシュボード定義のセグメント名で送っている。稼働中の送信は定期通知(api4/sendTeikiNotification・毎分 cron)・号外(sendGogai・管理画面)・お知らせ(sendOshirase・管理画面・iOS のみ)。J'sアラート(js_alert_{teamId})は cron 未登録で停止中とみられる。iOS はタグ push_news_asa/hiru/yu/yoru=0/1/2、Android は "true"/"false"。iOS と Android は同じ Firebase プロジェクト(footballnext-bca77)に登録済み。
  • Web 調査(サブエージェント): 無料プランは組織単位でモバイル MAU 1,000 まで(新規 9/1・既存 10/1 から)。超過の24時間後にモバイル送信が止まり、有料化まで再開しない。Growth=$19+$0.012/MAU(206.92=19+0.012×15,660 と一致)。FCM は無料、トピックは1端末2,000・購読者数無制限。Amazon SNS はプッシュ月100万通まで無料。Pusher Beams の無料は1,000端末。
  • OneSignal API(読み取りのみ)で配信履歴を確認: iOS は 10/2 21:56 の定期通知(約9,000台に配信)が最後で、10/3 07:45 以降は配信数0(10/5 の号外3本も0)。
  • GPT(GPT-6 Astra)と三者議論2ラウンド(.devnotes/discussions/2026-10-10-onesignal-to-fcm.md): トピックで置き換え可能という点は一致。GPT の指摘で次を修正した。
    • 号外の iOS も gene_1_2 を除外していた。Android はお知らせ送信・タップ処理なし
    • 全員向けトピックは定期の ON/OFF と独立にする
    • Android の large icon は FCM 標準表示でも出る
    • TTL を明示する
    • 送信結果を成功扱いしているのを直し、停止を検知できるようにする
    • 並行運用時は iOS で二重通知が起きる(OneSignal のトークン指定無効化 API も候補)
    • 返信の言い切りを避ける
  • OneSignal 管理画面(You ログイン・閲覧のみ):
    • 組織はアプリごとにほぼ別。THOMSON INC.=footballnext iOS(MAU 15,533)+kowai-best(172)+Kakusei(0)で送信停止中。206.92 ドルはこの組織分とみられる
    • footballnext-android は単独組織で MAU 975/1,000。ただし 「FCM API (V1) Update Required」設定エラーで送信不可(配信履歴は 2020-05-20 のテストのみ。停止開始時期は未確認)
    • baseballnext(MAU 3,023)も停止中(10/9〜10 の API 送信はすべて No recipients)。fearstory(2,795)も上限超過
    • セグメント条件: iOS push_news_asa_all=タグ != "0"(購読 9,029)/gene_1_2=App Version < 4.9.4(18)/Android push_news_asa_all=!= "false" かつ App Version > 6(963)
  • 返信: 詳細版の返信文を作ったが、You の指示で「FCM で対応したい・進めてよいか」だけの短い文面にした(チャット本文・You 送付)。

決めたこと:

  • プッシュ通知は OneSignal から FCM へ移行する。宛先はトピック(iOS/Android 別)で置き換える(DECISIONS)
  • iOS 5.5.2〜5.5.5 のリリースタグは後追いで付けない(今後のリリースで付与を継続)

詰まったこと / 気づき:

  • 送信結果を見ていないため、iOS の通知停止に8日間誰も気づかなかった。号外・お知らせは API の結果と無関係に成功表示、定期通知は応答を捨てている。
  • OneSignal の通知一覧 API は年を出さないと誤読する(Android の「5/20」は 2020 年だった)。
  • OneSignal の管理画面は Intercom の空の覆いでクリックが効かないことがある(覆いを非表示にして操作)。セグメントの条件は API では返らず、管理画面の Segment Editor の「View filter JSON」で読める。
  • OneSignal の REST キーがサーバーのソースに直書きされている(既存・今回は触らず)。

次回やること:

  • トムソンの返答待ち → OK なら Firebase(footballnext-bca77)の権限・サービスアカウント鍵と、APNs 認証キー(既存キーの利用可否)を確認
  • 移行設計の叩き台(トピック名・TTL・送信結果の記録と停止検知・購読同期・二重通知対策)→ server → iOS → Android
  • iOS の一時有料化・Android の一時復旧(FCM v1 サービスアカウント登録)・BaseballNEXT の扱いは、トムソンの判断が要る

2026-10-04

16:09 - トムソン依頼のリーグ追加(キリンカップ=提供元 ID 916・UEFAネーションズリーグ 5)/PK 決着試合の途中データ誤判定を修正(server 7f85b0c・2b68506)/得点者欄から PK 戦を除外(トムソン②・server 100086c)

やったこと:

  • 依頼内容(LINE): キリンカップ「ID:8846」と UEFA Nations League「ID:5」を追加してほしい。日本代表の試合結果を探していて見つけた ID とのこと。
  • ID の照合: API-Football /v3/leagues?id=8846 は0件。search=kirin で Kirin Cup=916 と判明し、/v3/fixtures?team=12 で 10/1 日本対エクアドル(準決勝・0-0・PK 5-4・1643339)と 10/5 19:30 日本対ニュージーランド(決勝・1643813)が 916 に入っていることを確認(親善試合 10 ではない)。どちらも未取り込みだった。8846 は提供元に無いので、ポータルの追加画面で弾かれて依頼が来たとみられる(出どころは不明)。5 は UEFA Nations League(2026 シーズン 9/24〜11/17・選手スタッツ coverage あり)。
  • server 7f85b0c(デプロイ済み): DigestPresenter::nationalCompetitionIds に 916 を追加(海外組の対象大会には入れない)。UNL は日本が出ないのでダイジェスト対象外。テスト importer 37(+3)・presenter 78・data provider 13・lib 56 PASS。
  • 実況板リーグ登録(本番DB 直接 INSERT・addConfirm 同等): 916=「キリンカップサッカー/キリン杯」(JFA の大会名「キリンカップサッカー2026」に合わせた)、5=「UEFAネーションズリーグ/ネーションズL」(既存の CL・EL・UECL・アジア杯 の書き方に合わせた)。now() が UTC だったので created/updated を JST に補正。/cron/fillLeaguesForV3 で team_created=1。
  • UNL の取り込み: /cron/crawlNextFixtures/5 で63試合(10/2〜11/18・is_publish の欧州代表25チームが出る試合)。直近3日の終了15試合は updateFixture で events まで充填し、得点者の表示を確認(クロアチア 0-7 イングランドで7件)。試合日の API 増分は ovserveFixtures で約+800回/日の見込み(1日5〜6試合)。代表ウィークはクラブ戦が止まるので枠に収まる想定。9/24〜29 の UNL の試合は3日窓の外で未取り込み(必要なら importLeagueFixtures/5/15→updateFixture)。
  • キリンカップの取り込み: デプロイ後に /cron/importLeagueFixtures/916/30 でエクアドル戦を挿入(NZ 戦は定期 crawl で先に入っていた)→/cron/updateFixture/1643339。
  • 誤判定の発見と修正(server 2b68506・デプロイ済み): 取り込んだエクアドル戦が、出場時間120分基準(フル出場120分・上田75分など)・評価が全員 null・PK 戦で決めた5人に G1 になっていた。原因は2つ。①提供元が延長なしの PK 決着でも status.elapsed=120(score.extratime も 0-0)を返し、途中データ判定(8137af6)がそれを試合の長さに使ったため、90分フル出場が「30分足りない」とされた。②PK 戦の成否が type=Goal(comments=Penalty Shootout・elapsed 91〜97)で届き、途中データ扱いの経路で G に数えた。修正=PK 戦の記録を集計から除く・延長ありとみなすのは「PK 戦以外の記録が91分以降にある」か「105分を超える出場時間がある」ときだけで、それ以外は90分。テスト lib 63(+5・実レスポンス dev-fixtures/api-football-v3-fixture-1643339.json を追加)を php 5.5/7.4 で PASS、修正前のパーサーでは NG になることも確認。再取り込み後=フル出場90分・評価あり・G0・出場18人・代表 MVP 板倉 7.1。EFL カップの PK 決着5試合は対象選手が各1人で閾値(3人)に届かず影響なし。
  • 選手マスタへ2名追加(本番DB 直接 INSERT): エクアドル戦のベンチにいた荒木隼人(33035・サンフレッチェ広島282・DF・4)と望月ヘンリー海輝(455193・町田ゼルビア303・DF・6)。提供元の生年月日で本人確認し、身長も提供元の値を入れた。決勝の試合前一覧(直前の代表戦=エクアドル戦の登録26人)に日本語名・クラブ名で出ることを /v2/fixture/1643813 で確認。
  • 反映確認: digest/national.json にキリンカップの2試合(キリン杯)が入った。
  • 得点者欄の PK 戦(トムソン②): 準決勝の試合詳細で PK 戦の成功者が「91' Ayase Ueda (PK)」のように9行並ぶ件(PK 戦がある全大会で以前からの挙動)を、You がトムソンへ質問(①現状維持/②得点者欄から外す)→ ②。server 100086c(デプロイ済み)=V2Controller::fixture の得点者の組み立てで PK 戦の記録を除く(判定は DigestFixtureParser::isPenaltyShootoutEvent を public にして共通化)。事前に本番DB の PK 決着18試合を検証(11試合は PK 戦の記録すべてに comments あり・7試合は PK 戦の記録自体なし)。デプロイ後の API でエクアドル戦 9→0行(得点者欄ごと非表示)・W杯の PK 決着 7→0・J2 の延長ありの PK 決着 13→4(延長中の2得点は残る)・試合中の PK 得点がある2試合は変化なし・スコア欄の「5 PK 4」は維持。テスト lib 66(+3)。
  • 返信: リーグ追加の報告文・PK 表示の質問文・②の対応報告文をチャット本文で作成(You 送付)。

決めたこと:

  • キリンカップは依頼の 8846 ではなく提供元の 916 で登録し、日本代表ダイジェストの対象にする。UNL は実況板だけ(DECISIONS)
  • 延長の有無は status.elapsed ではなく記録から判断し、PK 戦の記録は G・A・出場の根拠にしない(DECISIONS)
  • 試合詳細の得点者欄に PK 戦の成功者を出さない(トムソン②・DECISIONS)
  • 決勝の相手名「ニュージーランド」(他の代表は「◯◯代表」)は現状維持(You 判断)

詰まったこと / 気づき:

  • トムソンが伝えるリーグ ID は提供元の ID と違うことがある。id= で0件なら search= と日本代表の試合(fixtures?team=12)で照合する(メモリ化)。
  • 延長なしで PK に進む大会(キリンカップ・EFL カップ)の提供元データは、status.elapsed=120・extratime が 0-0 か null・PK 戦の記録の elapsed が 91〜97 になる。延長後の PK 戦は elapsed=120。
  • 実況板のスコア詳細は extratime が 0-0 だと「0 延長 0」の行を出す(エクアドル戦で確認)。トムソンには未報告(ROADMAP いつか)。
  • RDS の now() は UTC(datetime 列は JST 運用)。直接 INSERT では now()+interval 9 hour を使う。
  • 決勝(NZ 戦)は先に定期 crawl で入っていた。is_publish の日本代表がいるので、リーグを登録すれば未開始の代表戦は次の crawl で入る。遡り投入が要るのは開始済みの試合だけ。

次回やること:

  • 10/5 19:30 キリンカップ決勝(日本対NZ)の試合後確認: 代表選手欄の出場時間・評価・MVP、PK になった場合の得点者欄と途中データ判定
  • トムソンへ返信2本(リーグ追加の報告・PK 表示②の対応報告)You 送付
  • UNL の試合日(10/5〜7・11/13〜18 JST)に API 残数を確認

2026-10-01

19:25 - dev-timer #86=9/24 ウルグアイ戦を再取得し、提供元の選手データの確定を確認(評価17人・代表 MVP 早川 7.6)/トムソンへの報告文を作成

やったこと:

  • 今日の dev-timer: 3件。#86(本プロジェクト)を処理して done。#90(eスマート証券からの出金の着金確認)・#91(廃業届・青色申告取りやめ届)は company-operation 側で扱う。
  • #86 の再取得: /v2/fixture/1628995 で before を保存 → /cron/updateFixture/1628995(HTTP 200)→ after と比較。提供元(API-Football)の players ブロックが確定しており、評価が出場19人中17人に付いた(早川 7.6・松木/塩貝 7.5・上田 7.2・伊藤 7.1 ほか)。出場時間は 9/28 に交代記録から計算した値と19人全員一致(途中データの判定が外れ、提供元の値に切り替わった)。相馬・田中碧(各13分)は提供元の rating が "0" のため「-」のまま(validRating で欠損扱い)。
  • 反映確認: 代表ダイジェスト digest/national.json(S3)の MVP が null → 早川友基 7.6。選手詳細 /api4/jpPlayer/33889(佐野海舟)は 77分・評価 6.5。
  • 詳細スタッツ: 提供元の生データ(/v3/fixtures?id=1628995)でも日本の選手は passes・shots・duels が全員0(この親善試合は詳細統計なし)。代表戦の STATS は iOS で表示しない(管理表72・現行仕様)ので画面への影響なし。
  • トムソンとの約束の確認: 9/26 の返信で示した①「数日後に再取得して確認」を、トムソンが 9/28 に②(記録から計算・評価は「-」)とあわせて依頼。9/28 の対応報告で「72時間を過ぎているので数日後にこちらで再取得して確認」と伝えていた(日付は約束していない)。同じ報告で「MVP が表示されなくなる」と伝えていたので、今回の報告文に MVP が戻ったことを入れた。
  • 返信: 報告文をチャット本文で作成(You 送付)。

詰まったこと / 気づき:

  • 提供元の確定は、9/28 12:50 時点では未確定・10/1 19時には確定済み(いつ確定したかは不明)。FT 後72時間の自動再取得の窓では間に合わない例があった。
  • 過去セッションの返信文は .devnotes に要約しか残らない。原文は、セッション検索(search_session_transcripts)で該当セッションを特定 → ~/.claude/projects/<project>/*.jsonl を grep すると読める。

次回やること:

  • トムソンへ再取得結果を報告(You 送付)
  • 9/28 ベネズエラ戦の代表選手欄と途中データ判定の確認(未実施のまま)

2026-09-30

19:45 - Firebase Crashlytics「key cannot be nil」通知5件を調査=Firebase Analytics の sdk-exp 障害(全アプリ同時・アプリ修正不要・収束済み)

やったこと:

  • 届いた通知(9/30 朝・9/29 分の「増加傾向にある安定性に関する問題」): FootballNEXT(ThomsonK001)・BaseballNEXT(ThomsonK005)・Vision Training(jp.thomsons.Vision1)・StudyTimer(jp.thomsons.pomodoro)・channel7-ios(com.tengusoftware.channel7)の5アプリ。すべて -[__NSDictionaryM setObject:forKeyedSubscript:] key cannot be nil(NSInvalidArgumentException)。Gmail MCP で本文を読み、Crashlytics の Issue ID が別プロジェクト間で同一(181010225a8f…)と確認。
  • FootballNEXT の実数(Claude in Chrome で Crashlytics の Issue ページを閲覧): 過去7日で 43件/43人(5.7.1(23) が40件)。9/29 の1日だけ急増し、それ以前は0件。iPhone 98%・iOS 26 が72%・バックグラウンド 33%。BaseballNEXT は 3.3.1 で12件/12人・3.3.0 で2件/2人(メール記載)。Vision/StudyTimer/channel7 の件数は未確認。
  • スタックと発生状況: クラッシュしたスレッドは CoreFoundation → libdispatch の serial queue ワーカーだけで、アプリ側フレームが1つも出ない。確認した4件は 11:18〜11:38 JST に集中し、iOS 18.7.8(iPhone 15)も含む=iOS 26 固有ではない。直前のブレッドクラムは fn_ad_revenue/fn_ad_impression だが network は APPLOVIN_EXCHANGE・Pangle・AppLovin とバラバラ(広告イベントが一番頻繁なログというだけで因果なし)。
  • 原因の特定: 「5アプリ同時・1日集中・network 不問」から共通SDK/サーバー側を疑い、Web 検索で firebase-ios-sdk の Issue #16728・#16737(ほか #16729・#16730)に到達。報告では、9/29 00:41〜02:47 UTC(09:41〜11:47 JST)に sdk-exp が実験IDの無い不正な実験データを返し、SDK の GULMutableDictionary が nil キーを書いて落ちた。GULMutableDictionary は書き込みを dispatch_async に載せるので呼び出し元のフレームが残らない(スタックにアプリ側が出ない理由)。Firebase 側のロールバックで収束し、アプリ更新・SDK 更新は不要。FootballNEXT は Firebase 8.15.0 でも同じ症状。当方の最新クラッシュは 11:38 JST(=02:38 UTC)で、それ以降の新規なし。
  • 対応: アプリ・サーバーは変更なし。Crashlytics の Issue クローズは未実施(You の判断)。切り分け手順をメモリに残した(reference_firebase_sdkexp_incident_20260929)。

決めたこと:

  • 対応しない(SDK を上げても防げない・収束済み)。DECISIONS に記録。

詰まったこと / 気づき:

  • 初動でスレッド名(com.hmdPAG.*=Pangle 系・MTGSK…=Mintegral・APMExperimentWorkerQueue)から広告SDKを疑ったが外れ。決め手は「Issue ID が別プロジェクトで同一」と「発生が1〜2時間に集中」。複数アプリで同時に通知が来たら、まず firebase-ios-sdk の GitHub Issues を検索する。
  • 収束時刻の書き方に開きがある(#16728 は 9/28 23:52 PDT=9/29 06:52 UTC の解決表記、#16737 は不正な配信の停止を 02:47 UTC としている)。判断は当方のクラッシュ時刻と #16737 で行った。

次回やること:

  • 必要なら Crashlytics の5アプリの該当 Issue を「閉じる」(通知停止)。他は WIP どおり。

2026-09-28

12:50 - トムソン指示の管理表70・71・73・74 を対応=server 94358b2・8137af6 デプロイ・選手マスタ3名追加・ウルグアイ戦を再取り込み/A/B はトムソンが「24時間に1回」で合意→5.7.2 の OK 待ち

やったこと:

  • トムソンの返信(管理表): 70・71=②(出場時間は交代記録から計算、最終データが取れていなければ評価は「-」、数日後の再取得も)、72=仕様と了承・今後の改修候補、73=②(直前の代表戦の登録メンバーを次の試合の一覧に使う)・①の管理画面登録は今後検討、74=試合前一覧も選手マスタのポジション優先で統一、75=表示を確認する・「エディオンピースウイング広島」はトムソンが開催地変換表に登録。
  • 73・74(server 94358b2): DigestDataProvider::getNationalSquadForFixture を新設し、試合詳細 API(V2Controller::fixture)から呼ぶ。試合前(スタメン発表前)の代表戦は、キックオフ前10日以内の直前の代表戦で lineup_confirmed=1 の選手(先発+ベンチ)を招集メンバーとし、背番号・ポジション・並び順はその試合の値、日本語名・写真は提供元スカッドから引き継ぐ。直前の代表戦が無い・登録が無い・スタメン発表後・クラブ戦は従来どおり。表示側(buildFixturePlayerBlocks の PRE_MATCH)はポジションを選手マスタ優先にし、日本語名がスカッドに無ければマスタで補う。テスト追加(test_digest_data_provider.php 13件新設・presenter 3件)。
  • 選手マスタへ3名追加(本番DB 直接 INSERT): 9/24 の登録26人のうち熊坂光希(455195・柏レイソル281・MF・27)・中野就斗(355998・サンフレッチェ広島282・DF・15)・谷村海那(352457・横浜F・マリノス296・FW・9)が fn_jp_players に無く、英語名・クラブなしで出るため。国内組の既存行(長友・大迫・早川・相馬)と同じ形(J クラブに紐づけ・display_order 0・needs_review 0)。漢字は JFA の 9/17 発表(初招集4人)で裏取り。中野はクラブでは Midfielder だが代表では Defender なので DF。上書きしないよう UPSERT ではなく INSERT。
  • 73・74 の反映確認: ベネズエラ戦の試合前一覧が、ウルグアイ戦に登録された26人(GK→DF→MF→FW・国内組は J クラブ名+jp 国旗)になり、菅原は外れた。提供元で Attacker の久保・堂安・鎌田はマスタどおり MF になった。
  • 70・71(server 8137af6): DigestFixtureParser::parse に途中データの判定を追加。終了済み試合で、交代・退場の記録より出場時間が10分以上短い(未設定を含む)選手が3人以上いて、交代も退場もしていない先発が含まれるときに「途中データ」と判定する。その場合は出場時間を記録から計算し(先発0分〜、途中出場は交代の分〜、交代・退場・試合終了まで。46分の交代は45分)、評価は null、G・A は得点記録。players_stale を返し、_updateFixtureFromApi は途中データの間 false を返す(backfill の done を立てず、FT 後72時間は再取得を続ける)。ウルグアイ戦の実レスポンスを dev-fixtures/api-football-v3-fixture-1628995.json に追加し、テスト12件追加(誤判定しないケース=確定済み試合・実行されなかった交代 event 1件/2件・退場を含む)。
  • 70・71 の反映確認: /cron/updateFixture/1628995 で再取り込み。試合詳細は出場19人(交代なしの先発3人90分・佐野海舟77分・佐野航大/鎌田45分・上田27分・伊東/鈴木淳之介/久保26分・相馬/田中碧13分・塩貝7分)、評価は全員 null。佐野海舟の選手詳細も77分・評価なし。代表ダイジェストの MVP は評価で選ぶ仕様のため null になった(2025年の代表戦にも MVP null があり、iOS は Optional で対応済み)。
  • 返信(管理表): チャット本文で作成(MVP が出なくなる点・久保/堂安/鎌田の MF 化・招集31人のうち直前試合で登録外の5人は出ない点を明記)→ You 送付。
  • A/B: トムソンの返信(算出方法に納得・「24時間に1回」の方向で良い)。追跡窓を抜けた端末の間隔はアプリ内定数(ABTest.swift の intervalAMinutesDefault=50)で S3 だけでは切り替えられないことをコードで再確認。5.7.2 で非参加時の間隔を S3 設定化する旨の返信案を作成(You 送付予定・OK 後に実装)。Android は起動時の全画面広告が無い(スプラッシュは配信率の読み込みのみ)ので iOS のみ。
  • dev-timer #86(10/1 10:00): ウルグアイ戦の再取得・確定確認。

決めたこと:

  • 代表の試合前一覧は直前10日以内の代表戦の登録メンバーを使う(73)/試合前一覧のポジションは選手マスタ優先(74)/途中で止まった選手データは出場時間を記録から計算し評価は出さない(70・71)/起動時広告の全面24時間化は 5.7.2 で間隔を S3 設定化してから(DECISIONS に記録)

詰まったこと / 気づき:

  • デプロイ完了待ちのスクリプトが直前の pipeline 実行IDを拾った(push 直後は新しい実行がまだ一覧に出ない)。get-pipeline-state のステージ時刻で確認した。push 後は開始時刻で絞ってから待つ。
  • 本番DB の mysql は --default-character-set=utf8mb4 が無いと日本語が ??? になる(INSERT 時は必須)。
  • 回線IPが 8/26 と同じ値に戻っていて、そのときの SG ルールがそのまま使えた。IP 追加の前に describe で確認する。
  • 9月の招集は31人(JFA 9/17 発表)で、直前試合の登録26人を使う方式では5人が出ない。API-Football には10月の日本代表戦がまだ無い(9/24・9/28・11/14・11/17 のみ)。
  • dev-timer の疎通確認は /api/health(スキルに書かれている /health は nginx で 404)。

次回やること:

  • 9/28 ベネズエラ戦の試合後に、代表選手欄と途中データ判定の動作を確認
  • 10/1 10:00 ウルグアイ戦の再取得(#86)
  • トムソンの OK 後に 5.7.2(起動時広告の間隔を S3 設定化)

2026-09-26

21:25 - トムソンDM(管理表70〜75=日本代表戦まわり/A/B資料の算出方法の質問)を調査・返信文作成/管理表75(会場名)を修正=server 783f6ca デプロイ

やったこと:

  • 前提(未記録セッションの補足): 09-23 に起動時広告A/Bの結果報告 起動時広告A-Bテスト_結果報告_20260923.html と3巡目のまとめ ab-analysis-20260915/claude-analysis-round3.md を作成し、トムソンへ送付済みだった(全面24時間化を推奨・下振れ最悪月1.3万円・元に戻せる)。
  • 管理表の読み方: Drive MCP の read_file_content が2回ともタブ構造だけ(セルが空)を返し、内蔵ブラウザは Google 未ログイン。You の依頼で Claude in Chrome から gviz/tq?tqx=out:html&gid=1254944421 を開いて読んだ(メモリに手順を追記)。
  • 70・71(ウルグアイ戦 51分・3選手の欠落): API-Football の players ブロック自体が試合途中(51分)のまま確定していなかった(最終更新 9/24 20:34・9/26 時点も同じ)。アプリは提供元どおりの表示で、選手IDの紐付けは正常。3選手の G・A は得点 event 由来。
  • 72(代表戦の STATS): 現行仕様(仕様v1.2 の改修範囲は「クラブ直近5試合」、iOS は代表ブロックに STATS 列を出さない)。サーバーは代表戦のスタッツも返す。6/30 ブラジル戦は 9/8 以前の試合で未取得だが、API にはある。
  • 73(古い代表メンバー): 取得元は crawlNationalSquad(/players/squads?team=12 を毎日4:17に全入れ替え)。API 側が9月の招集に未更新で、9/24 の登録26人のうち9人(松木・佐野航大・田中聡・相馬ほか)が無く、未招集の9人(長友・冨安・谷口・菅原ほか)が残っていた。アプリの一覧は API と完全一致。
  • 74(菅原の MF): 試合前一覧は提供元スカッドの分類(Midfielder)、選手詳細は選手マスタ(DF)で、画面ごとに参照元が違った。
  • 75(会場名・server 783f6ca): 会場名は ovserveFixtures の開始1時間前からの更新時にしか保存しておらず(FixtureImporter は保存していない)、クラブ戦も同じく空だった。FixtureImporter::importRow で API の fixture.venue.name を保存し、更新対象列にも venue を追加(API に無いときは既存値を残す)。デプロイ後 /cron/importTeamFixtures/12 を実行し、ベネズエラ戦に「Edion Peace Wing Hiroshima」が出るのを確認(11月の2試合は API にも会場なし)。日本語表記は開催地変換表への登録が必要(J の広島戦も英語のまま)。
  • A/B の質問(記事画面の広告の MAX 収益の算出方法): GA4 fn_ad_revenue(MAX SDK の広告1表示ごとの収益・USD)を、参加者3,574人(A 1,783/B 1,791)の参加日から28日間・群×banner/native で合計(A $31.16/B $34.98)→ 月平均為替(ECB)→ 参加者数で割った値(A 2.80/B 3.13円/人・+11.7%)。元データから再計算して報告値と一致。管理画面との照合(誤差3.5%)は起動時枠だけで、banner/native は未照合。
  • 返信: 管理表と A/B を分けてチャット本文で作成。A/B の返信は You の判断で算出方法の説明だけにし、照合範囲の訂正とリリース要否は入れなかった。

詰まったこと / 気づき:

  • コミット 783f6ca のメッセージで、Co-Authored-By 行の前の空行が抜けて件名に連結し、テスト件数も「4件」と誤記した(実際は5件)。push 済みなので直していない。

2026-09-22

18:11 - 起動時広告A/B: トムソン提供の MAX メディエーションCSV(起動時枠)を精査・GA4 ILRD と照合・「今ある情報での結論」の形を整理/5.7.1 App Store リリース完了・iOS タグ付与

やったこと:

  • 前提(未記録セッションの補足): dev-timer #37 の本解析は 09-15〜09-18 に Codex 2巡(ab-analysis-20260915/codex-analysis.md・codex-analysis-round2.md)で実施済み。結論は「全面B化は保留」。主指標(未到達離脱率)は参加日クラスタ再標本化で −1.03pp[−3.29, +1.31]、MAX 収益/人(28日・150円/USD仮換算)は app_open −12.7%・banner +14.3%・native +4.0%・合計 +1.08%(診断幅はゼロを含む)。保留理由は ①canonical/診断イベントの発火範囲の不整合 ②ガードレール⑤(管理画面売上との再現誤差)・⑦が判定不能 ③MAX外(AdG/Zucks/LINE 等)の売上が未知で「外枠が約1%減れば逆転」の境界。採用条件②は「0.50円/割当インストール・28日、かつ月5,000円以内の減収」。
  • トムソン提供CSVの精査(~/Downloads/mediation_report_2026-09-19_11_34_32.csv): MAX 管理画面の日次×ネットワーク別、起動時枠(af73e314ea8d4ea9)のみのフィルタ付き・7/1〜9/18 の80日・欠損なし。使える列は Impressions/Est. Revenue/eCPM(Attempts/Responses/Fill/Win は入札ネットワークのためほぼ0)。合計 346,744 imp/$1,747.08(7月 $686.7・8月 $662.2・9月 $398.2)。ネットワーク別収益シェア AppLovin 58.6%/Liftoff 30.3%/Pangle 7.6%/Google 2.7%/ALX 0.8%/Mintegral ≈0(AD-FARM の GA4 集計と同じ並び)。
  • GA4 ILRD との照合(fn_ad_revenue format=app_open・全ユーザー): GA4/CSV は 7月 94.2%・8月 97.0%・9月 99.5%・計 96.5%=起動時枠についてはガードレール⑤「再現誤差10%未満」合格。ネットワーク別は Liftoff 99.2%・ALX 100.3% と一致する一方、AppLovin 90.7%・Pangle 122.6%・AdMob 116.8% と上下にずれる(原因未調査・7月の差が大きい理由も未確認)。
  • gross/net の判定: AppLovin 公式ドキュメント(Advanced Reporting・ILRD API)に明記なし。上の照合で2ネットワークが1%以内に一致し全体差が3.5%=片方だけ手数料控除後ならあり得ない差なので、CSV の Est. Revenue と GA4 の revenue_usd は同一基準(gross 相当)と判断。第三者記事では MAX の5%手数料は入札側負担(公式確認ではない)。
  • MAX外枠のコード確認(footballnext-ios): MAX外の枠(AdNativeType=newAdg/adx/line/pangle/zucks のネイティブ+記事バナー)はすべて記事一覧・記事内などコンテンツ画面の枠。起動時・全画面の MAX外広告は無い(i-mobile の全画面は設定画面の「more」でのみ呼ばれる)。同じ場所の MAX banner/native が B で増えているので、外枠も増える方向と読める=「外枠1%減で逆転」は実装上起きにくい。
  • 今ある情報での結論の形(You の「精度どのくらい落ちる?」への回答): 全面B化(全A→全B)の MAX 分の月次影響は、最悪(起動時枠 −12.7%・他枠補填ゼロ)で約 −$84(約 −1.3万円)、whole_base 実測(−0.71%)で約 −$11、クリーンコホート点推定(+1.08%)で約 +$17。アクティブの大半がすでに50/50なので現状からの変化はその半分程度。「下振れは最悪でも月1万円規模・外枠は増える方向、その範囲で起動時広告の負担を下げる事業判断」という形なら今の情報で書ける。UX 改善の統計的確証は n≈1,550/群が律速で売上データでは解決しない。MAX外の金額規模は不明のまま(トムソンのデータで埋まるが群別ではないので感度表の前提が変わるだけ)。
  • トムソンへの追加依頼案(未送付): ①MAX 全ユニット分のレポート(フィルタなし・ユニット名列つき)②AdG/Zucks/LINE 等は各管理画面のレポートCSVをそのまま(7〜9月・月次)=経理集計値ではなく管理画面出力にすれば gross/net 確認が不要になる ③gross/net・TZ の確認は依頼から外す。
  • 5.7.1 リリース: トムソンから iOS リリース完了の連絡。iOS リポジトリに注釈付きタグ 5.7.1 を f607dbb(ビルド23 のコミット=origin/master 先頭)に付与して push(5.7.0=1380503/4ceaf9f と同形式)。ビルド番号はタグ注釈に含めていない。

詰まったこと / 気づき:

  • ~/Downloads/admob-report.csv(09-03)は別アプリ(勉強タイマー)の AdMob レポートで今回は無関係。UTF-16 TSV。
  • GA4 MCP の dimension_filter は field_name/string_filter の snake_case 指定。

次回やること:

  • ①You の判断待ち: 今ある情報で3巡目の結論(保留→条件付き採用の事業判断)を ab-analysis-20260915 にまとめるか、トムソンへ追加依頼①②を先に出すか ②AD-FARM の遮断判断に使う収益シェアは今回の CSV で足りている(AppLovin 58.6%=遮断は現実的でない前提で悪質率と突き合わせ)③次の試合日に J リーグの試合が開始時刻表示のまま・対象大会は「スタメン」になることを実画面で確認

2026-09-20

18:26 - トムソン管理表68(試合詳細の G・A 青太字)+69(J リーグで「スタメン」ラベルに切り替わる)対応=server 11da868 デプロイ・iOS f607dbb・5.7.1(ビルド23)TestFlight 配布・返信文作成

やったこと:

  • 状況確認(修正前): トムソンの LINE(ストアのスクショ一新・アプリ名/サブタイトル/キーワード微修正の予定、間に合えば管理表68、あわせてスタメンラベルの件)を受けて調査。ASC には 5.7.1 が作成済み(09-20 JST・PREPARE_FOR_SUBMISSION・ビルド未割当)=スクショ等のメタデータ更新は新バージョン申請が必要なので、68 を見送ってもビルドは要る状態だった。着手前の作業ツリーは iOS の xcuserdata 差分のみ。
  • 管理表69(スタメンラベル・server 11da868): 原因はトムソンの推測どおり。新着画面の横スクロール試合一覧(LiveComponent::buildLives)の lineup_announced は digest_lineups_available を大会の絞り込みなしで返していた。このフラグは DigestFixtureParser::isTargetTeam により「対象選手が1人でも所属するチームの試合」ならリーグを問わず立つため、代表の国内組を J クラブに紐づけた影響で J リーグの試合でも 1 になっていた(試合詳細の日本人選手欄は overseasCompetitionIds でガード済みで、lives だけ抜けていた)。本番 API で J1「町田 - 柏」が lineup_announced=1・他の J1〜J3 は 0 であることを確認して裏取り。DigestPresenter::liveLineupAnnounced(digestLeagueIds()=海外組+日本代表の対象大会かつスタメン取得済み)を新設して buildLives から呼ぶ形に修正(取得項目に league_v3_id を追加)。DB のフラグ自体は触らない。テスト3件追加(presenter 75・lib 44 アサーション PASS・php:5.5-cli コンテナ)。CodeCommit へ push→Pipeline Succeeded→本番 API で「町田 - 柏」が 0・対象大会(ポルトガル)が 1 のままを確認。サーバーのみの対応で 5.7.0 を含む全ビルドに即時反映。
  • 管理表68(G・A 青太字・iOS 12c7bbd→f607dbb): ClubFixturePlayersCell/NationalFixturePlayersCell の statsLabel は元から 12pt 太字で、色だけグレー(Const.disableTextColor)だった。選手詳細の試合行と同じ Const.baseColor に変更し、幅50pt を超える場合に備えて adjustsFontSizeToFitWidth(最小0.8)を追加。シミュレータ(iPhone 17 Pro・ローカルHTTP+-DigestLiveFixturePath)で Gのみ/Aのみ/両方/複数回(2G1A・2A)を確認、重なり・文字切れなし・評価と出場時間の列位置も不変。確認用に dev-fixtures/fixture-club-finished-fb68.json(v3 の G/A を差し替えた仮データ)を追加。before/after 各2枚+横並び比較2枚を fb-item68-shots/ に保存。
  • 表記の取り違えと作り直し: 指摘に無い「1G 1A」のスペース入り表記を自分から提案し、You の「よい」を「揃えてよい」と受け取って実装、5.7.1(ビルド22)をアップロードした後に You の確認で取りやめに。compactGoalsAssists を元の連結表記「1G1A」に戻し(f607dbb)、after スクショと比較画像を撮り直してビルド23 を作り直した。
  • 配布: pbxproj の MARKETING_VERSION を 5.7.1 に更新(5672fb3)。ASC 実数(最新21)を確認して採番、CLI アーカイブ→exportArchive upload。ビルド22・23 とも ASC VALID。5.7.1 に割り当てるのは 23(22 はスペース入り表記の旧版で使わない)。AD-FARM の install() はコメントアウトのまま。iOS は GitHub へ push 済み。
  • 返信: チャット本文コピペ形式で作成(比較画像は送らない前提・簡潔版)。途中でトムソンがスタメン件を管理表69 として追記したため、期待結果(J リーグは開始時刻のまま・対象試合は引き続き「スタメン」)が修正内容と一致することを確認して番号を反映。

決めたこと:

  • 新着画面の試合一覧の「スタメン」切替は、海外組・日本代表の対象大会の試合に限定する(DECISIONS.md 参照)

詰まったこと / 気づき:

  • トムソン管理表の修正は指摘された範囲だけ実装する。指摘外の見た目変更(表記の間隔など)は提案に留め、確認は「現状維持/変更」の番号二択で聞く(1文に混ぜると「よい」がどちらにも読める)。今回はこれでビルドを1本作り直した。
  • ストアのスクショ・アプリ名・サブタイトル・キーワードの更新は新バージョンの申請が必要=メタデータだけの更新でもビルドが要る。
  • exportArchive の MinimumOSVersion 警告(13.0→2027年春以降 15.0 以上必須)は今回も出ている(ROADMAP 今四半期に登録済み・既知)。
  • シミュレータの試合詳細検証: -DigestBaseURL http://127.0.0.1:8765/ -DigestLiveHistoriesPath dev-fixtures/live-histories.json -DigestLiveFixturePath dev-fixtures/<fixture>.json -DigestOpenLiveHistoryOnLaunch yes で起動→スプラッシュが消えてから試合行を touch。クラブ戦・代表戦のセルは詳細 JSON の中身で切り替わる(一覧のどの行を押しても -DigestLiveFixturePath の JSON が開く)。

次回やること:

  • トムソンのビルド23 確認→5.7.1 申請(トムソン側)。リリース後に iOS タグ 5.7.1 を f607dbb に付与
  • 次の試合日に、J リーグの試合がキックオフ前も開始時刻表示のまま・対象大会の試合は「スタメン」に切り替わることを実画面で確認

2026-09-19

19:31 - League Cup(ID:48)を実況板リーグに登録し、海外組ダイジェストの対象大会へ追加(server 2c7b7c6)・今季過去試合の遡り投入

やったこと:

  • 状況確認: 本番DB fnlive_v3_leagues に 48 は未登録。API-Football /v3/leagues?id=48 で実体を確認=England の League Cup(EFLカップ・type Cup・2026シーズン 8/1〜・選手スタッツ coverage あり)。
  • 実況板リーグ登録(本番DB直接 INSERT): ポータル addConfirm と同一内容(id/name/type/logo・team_created=0)で登録。ポータルにログインできないための代替で、cron 補完経路は変えていない。日本語名は name_jp=「EFLカップ」・name_short_jp=「EFL杯」(You 了承済み)。/cron/fillLeaguesForV3→/cron/crawlNextFixtures/48 を手動実行し team_created=1・10試合取り込み(R16 未開始6+3日窓内の R32 終了4)。終了4試合は updateFixture で充填。
  • ダイジェスト対象大会へ追加(You 判断): DigestPresenter::overseasCompetitionIds に 48 を追加(server 2c7b7c6・Pipeline Succeeded)。テスト presenter 72 / importer 29 / lib 44 PASS。iOS は team.country_code 優先のため変更なし。
  • 遡り投入: /cron/importLeagueFixtures/48/60→26件挿入(updated 6・skipped 52・API 1リクエスト)。挿入26件+先行4件を updateFixture に流して充填(HTTP 200・失敗0)。DB: League Cup 計36試合(8/7〜10/29)・終了30・players有21。スタッツ行は森下龍矢/松木玖生/岩田智輝/大橋祐紀(各2試合)、冨安健洋/坂元達裕/田中碧/藤本寛也/鈴木彩艶/平河悠/鎌田大地/守田英正/斉藤光毅/前田大然 ほか。
  • 確認: /api4/jpPlayer/2601(鎌田)の直近5試合に competition 48「EFL杯」31分・評価8.0 が入る。森下(199150)の直近5試合は全て英2部で League Cup は出ていない(カップ戦出場がそれ以前のためとみられるが試合日は未照合)。ダイジェストの S3 出力は未確認(定期 cron の次回更新で反映される想定)。

決めたこと: DECISIONS 1件(国内カップ戦の League Cup をダイジェスト対象大会に含める)。

詰まったこと / 気づき:

  • 実況板リーグに登録するだけなら shouldImport は is_publish チームの試合のみ取り込む。ダイジェスト対象(digestLeagueIds)に入れて初めて is_jp_target チームの試合も入る=遡り投入はデプロイ後に行う。
  • fnlive_v3_leagues は name_jp などが NOT NULL・デフォルトなし。直接 INSERT では全列を明示する。
  • mysql クライアントの -e では \G が使えない(--vertical か -t を使う)。

次回やること: ①週末〜10/28 の R16 で League Cup の試合一覧ラベル・日本人選手欄の実表示を確認 ②仕様v2.14 の対象大会一覧に 48 が増えた件は、必要になったらトムソンへ連絡(今回は不要と You 判断)。

2026-09-14

リリース - 5.7.0(ビルド21)App Store リリース完了・iOS タグ付与

やったこと:

  • トムソンから iOS リリース完了の LINE を受領。ASC で 5.7.0 が READY_FOR_SALE・紐づくビルドが 21 であることを確認(申請〜審査はトムソン側で実施)。
  • iOS リポジトリに注釈付きタグ 5.7.0 を 4ceaf9f(ビルド21 のコミット)に付与して push。5.6.0 と同じ命名(5.6.0 は 4fd01fb)。
  • .devnotes: ROADMAP のリリース項目を完了に、WIP テーマ1 を「リリース完了・運用フェーズ」へ。

次回やること: ①UEL の選手統計が API で提供され次第 /cron/backfillPlayerDetailStats 再実行 ②一時設定4項目の戻し確認(トムソン側)③needs_review 22名の ID 重複精査 ④004 の本番適用確認 ⑤AD-FARM 再開時は install() を戻す ⑥MinimumOSVersion 13.0→15.0 引き上げ(2027年春までに必須・アップロード時に警告あり)。


2026-09-12

18:50 - トムソン第12次FB(管理表66・67=G・Aの青太字強調/出場なし・登録外の上下中央・仕様v2.20/v1.5)=iOS 4ceaf9f・5.7.0(ビルド21)TestFlight 配布・before/after 比較画像・返信文作成

やったこと:

  • 受領内容: トムソンがビルド20 で今回の修正反映を確認済み。G・A を出場時間の後ろに置く案は了承。追加2点=①出場時間はグレーのまま G・A を青太字にし、収まる範囲で少し大きく(「80分 1G 1A」の間隔・下段の横中心を出場状況に揃える)②出場なし・登録外は文字単体を行の上下中央に(時間用の空行を残さない)、2段表示は2段全体を上下中央に。列位置・行高は維持。仕様書 v2.20/v1.5 はリポジトリ直下に保存。
  • iOS(4ceaf9f・push 済み): JpPlayerMatchLineControl.minutesText の G/A を Const.baseColor の太字 11.5pt(時間は 10pt グレーのまま)にし「22分 1G 1A」の形で半角スペース区切りに。未出場・登録外は minutesLabel を隠して statusLabel.bottom = container.bottom に切り替え(出場時は従来どおり minutesLabel.bottom)、container の centerY で1段・2段とも上下中央に。列幅・行高46ptは変更なし。
  • 検証(iPhone 17 Pro sim): 実データ(古川・松田)+ G/A あり確認用の仮データ dev-fixtures/jp-player-furukawa-ga-sample.json(古川 JSON の 9/12 を 1G・8/30 を 1G 1A・8/8 を 2A に)で、青太字の G/A が列内に収まり他列と重ならないこと、「出場なし」が日付・スコアと同じ縦中心に来ること、2段行の全体が中央に来ることを確認。スクショは fb-items66-67-shots/(before=ビルド20・after=ビルド21・compare=左右比較2枚。66 の右側は仮データ)。
  • 配布: CURRENT_PROJECT_VERSION=21 でアーカイブ→exportArchive(app-store-connect/upload)で 5.7.0(21) アップロード。
  • 返信: 対応内容と仮データの注記をチャット本文でコピペ形式で出力。

次回やること: ①You が返信+比較画像2枚をトムソンへ送付 ②トムソンのビルド21 確認→追加指摘対応→App Store 提出(5.7.0・要再アーカイブ)③以降は 16:30 エントリの③〜⑥と同じ。


16:30 - トムソン第11次FB(修正管理表の未対応10項目=仕様v2.19/v1.4「評価不具合修正/試合別出場時間・列位置」)を全消化=server 773cb23 デプロイ・本番データ修復・iOS 7c4a888・5.7.0(ビルド20)TestFlight 配布・返信文作成

やったこと:

  • 受領内容: LINE+修正管理表(番号なしの新規10行)+HTML仕様書 v2.19/v1.4(リポジトリ直下に保存)。①未出場の試合が評価グラフで 0.0 の点になる(古川陽介・松田隼風の 9/5)②同じ出場なしでも片方は点なし・片方は 0.0 と処理が不揃い③平均評価が 5.4/4.3(未出場の0を含めて割っている)④有効な評価2件でもグラフが出る⑤出場したが評価なしの行が空白(「-」にしたい)⑥【新規】出場状況の下に出場時間を2段表示⑦各列を全行で縦に揃える⑧長いチーム名は縮小せず「…」で省略⑨出場時間の欠損・未出場の扱い⑩行高は現状維持・操作維持。
  • 原因特定(本番DB): fn_player_fixture_stats にベンチ(played=0)で minutes=0・rating=0.0 の行が15件(2.ブンデス4・ポルトガル3・ベルギー2・オランダ2・プレミア/ブンデス/ラ・リーガ/英2部 各1)。API-Football がベンチ選手に games.rating: "0" を返すリーグがあり、パーサーの nullableRating が 0 をそのまま保存→presenter が rating=0.0 として配信→アプリのグラフ・平均が 0 点として扱っていた。null で返る行(松田 8/30)は点なしになるため「同じ出場なしで挙動が違う」に見えていた。
  • server(773cb23・Pipeline 16:14 Succeeded): DigestPresenter::validRating($stat)=出場済み(played=1)かつ評価が 0 より大きいときだけ有効。試合行 rating・平均評価(有効件数で割り roundHalfUp で小数第2位四捨五入・0件は null)・rating_chart(有効3件以上で visible)・今日の海外組/代表MVP の rating 出力を全てこの判定に統一。パーサーは nullableRating で 0 以下を null 保存、FINISHED で未出場に補正した行の rating も null に。テスト: lib 44/presenter 72(古川型・松田型・評価0件・validRating の10件追加)/importer 29 PASS。
  • 本番データ修復: UPDATE fn_player_fixture_stats SET rating=NULL WHERE played=0 AND rating=0(15行)。修復後 API: 古川=平均6.7・chart 4点、松田=平均6.5(12.9/2=6.45→6.5)・chart 非表示・8/16 は rating null のまま minutes=1。
  • iOS(7c4a888・push 済み): DigestPlayerMatch.hasAppeared / validRating / averageValidRating を追加し、選手詳細のグラフ(有効3件以上で表示・点は有効な評価のみ・日付は5試合すべて残す)・合計欄の平均(アプリ側でも同じ判定から算出)・シェア画像の平均/グラフ/行の評価を共通化。試合行 JpPlayerMatchLineControl を作り直し: 出場状況の下に出場時間(90分・欠損は「-」・未出場/登録外は下段なし・G/A は時間の後ろに通常色で 90分 1G)、日付38/vs14/スコア36/出場状況64/評価46/STATS54 を固定幅で全行統一し対戦相手名だけ可変幅(byTruncatingTail・縮小なし)、出場したが評価なしは「評価 -」、行高46pt据え置き。
  • 検証(iPhone 17 Pro sim・ローカルHTTP+-DigestPlayerPath で本番APIの古川/松田JSONを配信): 古川=9/5 の点と 0.0 が消え 8/30→9/12 が直線・平均6.7・各列が縦に揃う/松田=グラフ非表示・平均6.5・8/16「途中出場 1分 評価 -」・8/30 と 9/5 は同じ「出場なし」/STATS→CLOSE の展開・行タップの試合詳細遷移とも正常。スクショは player-rating-shots/(before=トムソン添付・after=修正後3枚)。
  • 配布: ASC 最新実数=19(09-09 の v1.3 分)→20採番。CURRENT_PROJECT_VERSION=20 でアーカイブ→exportArchive(app-store-connect/upload)で 5.7.0(20) アップロード・ASC processingState=VALID 確認(一覧反映まで約20分)。AD-FARM の install() はコメントアウトのまま。
  • 返信: 修正内容・原因・出場時間の実装方針(G/A の置き場所)・留意点をチャット本文でコピペ形式で出力。
  • 補足(未記録セッションの記録): 09-09 18:46〜18:51 に仕様v1.3(管理表51〜54=GK セービングの「セーブ/失点/セーブ率」・PK 内訳・GK null/0 区別・パス成功率の成功数/試行数併記・STATS 触覚)を server 8cd859e・iOS 0024f9c で対応し、ビルド19 を配布済み(ASC 09-09 VALID)。DEVLOG には残っていなかったので本エントリで補う。

決めたこと: DECISIONS 2件(有効な評価の定義と 0 の扱い/出場時間の2段表示と G/A の置き場所)。

詰まったこと / 気づき:

  • API-Football はベンチ選手に rating: "0" を返すことがある(2.ブンデスなど。null のリーグもある)。評価は 1.0〜10.0 なので 0 は「欠損」として扱う。同じく minutes: 0 も返るが、出場していない行では表示に使わないので実データとして残す。
  • 平均の四捨五入は浮動小数の表現誤差に注意: PHP round(6.45, 1) は pre-rounding で 6.5 になるが、安全のため sprintf('%.6F') 経由の roundHalfUp にした。Swift 側は (total*10/count).rounded(.toNearestOrAwayFromZero)/10。
  • XcodeBuildMCP の tap はタブボタンに効かないことがある(1回目は無反応・2回目で切替)。launch_app_sim は bundleId を session defaults に入れないと動かない。
  • DB トンネルは長時間放置すると切れる(Lost connection ... reading initial communication packet)→ pkill -f 13306: して張り直す。
  • **DBCache(api4_jpPlayer_* 30秒)**があるので、データ修復直後の API 確認は30秒待つ。

次回やること: ①You が返信をトムソンへ送付 ②トムソンのビルド20 確認→追加指摘対応→App Store 提出(5.7.0・要再アーカイブ)③UEL の選手統計が API で提供され次第 /cron/backfillPlayerDetailStats 再実行 ④一時設定4項目の戻し確認(トムソン側)⑤needs_review 22名の ID 重複精査 ⑥AD-FARM 再開時は install() を戻す。


2026-09-09

16:53 - トムソンのリーグ追加(Championship 40・2.Bundesliga 79)+対象選手14名追加を受け、ダイジェスト対象大会へ追加(server 8f4e720)・今季過去試合56試合の初回投入・返信

やったこと:

  • 状況確認(本番DB): トムソンが実況板ポータルで 40/79 を登録(14:21/14:31 に定期取り込み済み=未開始 280+192 試合、開始済みは3日窓内の 5+1 試合のみ)、対象選手14名を追加(英2部6名: 平河悠/藤本寛也/岩田智輝/大橋祐紀/森下龍矢/斉藤光毅、独2部8名: 関根大輝/髙橋仁胡/三好康児/古川陽介/秋山裕紀/アペルカンプ真大/福田師王/松田隼風)。14名とも FnJpPlayer::afterSave の身長自動取得が本番で動作(09-08 に管理画面経由の実動を確認できていなかったフック)。配信対象(is_publish)は各リーグ8チームに増加。
  • server(8f4e720・Pipeline Succeeded): DigestPresenter::overseasCompetitionIds に 40・79 を追加(DataProvider の全クエリ・試合一覧ラベル・試合詳細の日本人選手欄・今日の海外組・直近5試合に波及)。FixtureImporter::importRow に救済窓の上書き引数、importLeague($leagueId,$season,$now,$window)(1リクエストで1リーグ・今季の試合を取り込み、挿入IDを返す)、手動導線 /cron/importLeagueFixtures/{leagueId}/{days}(1〜400日)を追加。テスト: importer 29(+2)/ presenter 58 / lib 43 PASS。
  • 初回投入: /cron/importLeagueFixtures/40/60→32件・/79/60→24件を挿入(開幕8/8〜)。挿入IDを /cron/updateFixture/{id} に56回流してスコア・選手スタッツを充填(HTTP 200・失敗0)。DB: 英2部 312試合(終了37・players有29=残りはウルブス/ボルトンなど対象選手なし)、独2部 216試合(終了25・players有25)、スタッツ行 44+52。
  • 確認: 追加14名の /api4/jpPlayer に直近試合(英2部5・独2部4)とスタッツが入り、チーム名・大会短縮名(英2部/独2部)も日本語で出る。api4/updateOverseasDigestS3/1 の出力に competition 40×5・79×2 が含まれ、ダイジェストの「これからの試合」にも載る。表示対象は92人全員に身長あり。
  • 返信: 反映内容(対象大会追加・過去試合の遡り投入・身長自動取得)と API 枠の再見込み(各リーグ8チーム→最悪日 +約1,400・ピーク合算約4,800=上限の64%)をチャット本文コピペ形式で出力。

決めたこと: DECISIONS 1件(リーグ追加時の初回投入は広い窓の手動導線で過去試合を挿入し updateFixture で充填する)。

詰まったこと / 気づき:

  • zsh は未クォート変数を単語分割しない(for id in $ids が1語になり curl が1回だけ走った)。ID列はファイルに1行ずつ書いて while read で回す。
  • Pipeline 完了待ちは run_in_background の until ループ(20秒ポーリング)で通知を受けるのが楽。
  • 定期取り込みの3日窓は「リーグを後から追加した」ケースでも今季の過去試合を落とす(移籍直後と同じ構造)。リーグ追加は「ポータル登録→importLeagueFixtures で遡り投入→挿入IDを updateFixture」の3手順で完了する。
  • 対象選手なしの試合(ウルブス/ボルトン等)は players ブロックを解析しないので digest_players_available=0 のまま=正常。

次回やること: ①You が返信をトムソンへ送付 ②トムソンの 5.7.0(18) 確認・新リーグ表示の確認→追加指摘対応→App Store 提出 ③週末の試合日に RapidAPI 残数を実測(08:55 JST 直前)して API 枠の見込みを裏取り ④UEL の選手統計が API で出始めたら backfill 再実行。

2026-09-08

19:37 - トムソン新仕様v1.2「選手詳細/試合別個人スタッツ」実装=server d0525b5 デプロイ・データ投入・iOS 5.7.0(ビルド18)TestFlight 配布・返信文作成/別件: Championship・2.Bundesliga 追加時の API 枠試算

やったこと:

  • 仕様確認: FootballNEXT_選手詳細_試合別個人スタッツ_開発仕様書_v1.2.html(リポジトリ直下に保存)。選手ヘッダーに「MF | #18 | 185cm」、クラブ直近5試合の右端に STATS/CLOSE、行直下にアコーディオン展開(同時1試合・別試合で自動切替)、FP 8指標/GK 専用スタッツ、統計なし・未出場は STATS 非表示、欠損は「-」、受入13項目。現状とのズレ4点(8指標未保存・過去試合は再取得しないと入らない・API は 0 を null で返す・身長は試合レスポンスに無い)を提示し、You と①null→0 ②直近5試合は遡って埋める ③身長は選手ごとに1回別APIで取得しDB保存 ④率は成功数/試行数から算出、で合意。
  • 実装前確認: 海外組14大会は全て今季 statistics_players 対応(UEL は 2026 フラグ0で実データも players ブロック無し)。代表5大会は W杯/親善/予選が対応、アジア杯2027・E-1 は非対応。実データ(ベルギー/スコットランド/デンマーク直近試合)で「0 が null で来る」ことを確認。
  • server(d0525b5・Pipeline 17:44 Succeeded): DDL db/digest/005(fn_player_fixture_stats に stats_available+14列、fn_jp_players.height_cm)を本番適用。DigestFixtureParser::copyDetailStats で statistics の生値を保存(null は null のまま)、DigestPresenter::presentMatchStats が出場かつ統計ありの終了済み試合だけ stats(kind=field/gk・件数 null→0・率は分母>0 のみ)を返し、player.height を追加。PlayerProfileImporter(players/profiles・身長 140〜230 のみ採用)+FnJpPlayer::afterSave(新規/身長空で保存時に自動取得)+/cron/importPlayerProfiles[/1]。/cron/backfillPlayerDetailStats/{limit} でクラブ直近5試合を再取得(DigestDataProvider は共有モデルを reader に切り替えるので使わず自前クエリ)。テスト: lib 43 / presenter 58 / profile importer 8(新規)/ fixture importer 27 PASS。
  • 本番データ投入: 身長 78人中75人自動取得、残り3人(山本天翔177・佐藤龍之介171・安藤晃希170)は API に値が無く You 提供値を DB 直接 UPDATE。バックフィルは候補96試合中90試合に統計反映、残り6試合は全て UEL(league 3)で API 側に players 無し。
  • iOS(387a647〜564fcd0・push 済み): DigestPlayerMatchStats/height をモデルに追加、ヘッダーを縦スタック化してプロフィール行追加、JpPlayerStatsToggleControl(54pt 列・ボタン非表示でも列幅は保持)、JpPlayerMatchStatsView(見出し2ブロック+均等幅セル・GK は3+2セル)、Presenter が expandedFixtureId を持ち beginUpdates/endUpdates 内でセルの isHidden を切り替えて高さアニメーション+展開位置へスクロール。シミュレータで福井太智(FP)・鈴木彩艶(GK)で受入項目を確認、行本体タップの試合詳細遷移も維持。スクショは player-stats-shots/(11枚)。
  • You 指摘の修正: ①STATS→CLOSE→別STATS で展開ビューが行に重なる=UIStackView の arrangedSubview に isHidden を同値で重ね設定すると内部カウンタがずれる既知挙動→状態が変わるときだけ設定(b5bdc19)②固定高さ58ptの行の余白→内容に合わせる(44aec6c)③率併記なしセルの項目名が下がる→縦スタック末尾に空きビュー・数値17pt統一(b18852d)④余白3pt追加→取り消し(31afafb→revert 95ad058)⑤下余白3pt詰め(b97bfa3)⑥項目名を濃いグレー(90,90,94)・行下余白を見出し前とブロック下端で同じ4ptに(8a07116)。GK 配球の「成功数/試行数」に率を重ねないのは仕様どおりで現状維持(You ①)。
  • 配布: You 指摘で左下の AD ボタン(AD-FARM 調査パネル)の install() をコメントアウト(564fcd0)。ASC 最新実数=17→18採番、pbxproj の MARKETING_VERSION を 5.7.0 に更新(bd61977)、CURRENT_PROJECT_VERSION=18 でアーカイブ→exportArchive(app-store-connect/upload)で 5.7.0(18) アップロード成功・ASC processingState=VALID 確認。
  • 返信: 実装内容・データ補足(null→0、率の算出、遡り取り込み、身長3名手入力)・留意点(UEL は API 未提供・API 枠影響なし)をチャット本文でコピペ形式で出力。
  • 別件(トムソン相談): Championship(40)・2.Bundesliga(79) 追加時の API 枠試算。消費構造=配信対象チームの試合を KO1時間前〜終了まで2分おき(約90req/試合)+リーグ取り込み3req/日+バックフィル1〜2req/試合。直近30日の最多日 8/29(35試合)で約3,400req。候補リーグの配信対象は各3チーム(Championship: ウルブス・サウサンプトン・バーンリー/2.BL: ヴォルフスブルク・ザンクトパウリ・キール)→最悪日 +約550req、ピーク重ねても約4,000(7,500 の53%)で収まる。2リーグとも選手統計カバレッジあり。返信案もコピペ形式で出力。

決めたこと: DECISIONS 3件(個人スタッツの null→0 解釈と率の算出/身長の取得・保存方式/STATS の対象範囲と GK 配球の表示)。

詰まったこと / 気づき:

  • API-Football fixtures?id= の players.statistics は 0 を null で返す(DF のシュート等)。リーグによりばらつき(デンマークは 0 明示)。passes.accuracy はこのエンドポイントでは成功本数(率ではない)。統計ブロック自体が無い=リーグ非対応(今季 UEL)。
  • 身長は players/profiles?player= で取れる("182" の文字列・単位なし)。若手(2006〜07年生)は null のことがある。
  • UIStackView の hidden 二重設定バグは表示切替を「状態が変わるときだけ」に限定すれば回避できる。展開の高さアニメーションは UIView.animate 内で beginUpdates/endUpdates+セル内 layoutIfNeeded で滑らか。
  • XcodeBuildMCP: テーブルセルの行やカスタム UIControl は tap の対象に出ないが touch(down+up)を text 参照に対して使えばタップできる。ダイジェストのデバッグ起動引数は Debug 構成でしか効かない。
  • pbxproj の SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG は Debug 構成のみ(一度 Release と誤判定した。XCBuildConfiguration の name = はブロック末尾にあるので直前の name を拾うと1つ前のブロックになる)。AD-FARM 系コードは Release に入らない。
  • iOS の push でローカルに残っていた AD-FARM 3コミット(15893a0/6abce70/b842dbb・DEBUG 限定)も origin に上がった。
  • DigestDataProvider の useRO() は ClassRegistry 共有インスタンスの接続先を reader に切り替えるため、書き込みを伴う cron からは使わない。
  • RapidAPI 経由では /v3/status は 404。残数は各レスポンスの x-ratelimit-requests-remaining で読む(当日リセットは 09:00 JST 前後と推定・平日昼は消費ほぼゼロ)。
  • 現 IP が SG に残っていれば追加不要。トンネルは作業後に pkill -f 13306: で閉じる。

次回やること: ①You が返信2通(5.7.0 配布・API 枠試算)をトムソンへ送付 ②トムソンのビルド18 確認→追加指摘対応→App Store 提出(要再アーカイブ・設定4項目戻しは別途) ③UEL の選手統計が API で提供され次第 /cron/backfillPlayerDetailStats 再実行 ④Championship/2.BL 追加はトムソン判断待ち(追加時: 実況板ポータルで 40/79 登録+ダイジェスト対象なら overseasCompetitionIds に追加) ⑤AD-FARM 調査再開時は AppDelegate の install() を戻す。

2026-09-04

12:44 - トムソン調査依頼「上田綺世/リール(79)の試合がダイジェスト・試合一覧に出ない」=原因特定・データ修復・再発防止(server e9934f2)・返信済み

やったこと:

  • 調査: 所属変更(fn_jp_players 72155→79、9/3 06:33 JST)・リール is_jp_target=1/is_publish=1・選手詳細API(リール所属・次戦9/9 CL)はすべて正常。欠けていたのは 9/4 03:45 JST トゥールーズ vs リール(fixture 1552754・上田が途中出場33分1G・評価7.8)の1試合だけ。API-Football 直叩き(fixtures?team=79&season=2026)と fnlive_fixtures を突き合わせて確定。
  • 原因: crawlNextFixtures は「リーグ単位・24時間に1回・1リーグ/10分・未開始の試合のみ取り込み」。所属変更後の最初のリーグ・アン取り込みが 9/4 03:51 JST=キックオフ6分後で、nowTime > fixtureTime で除外。所属変更を後追いする経路も無かった(updateFixture/{id} は既存行の更新のみ)。9/9 CL・9/13 トロワ戦は同じ03:51の取り込みで入っており、ダイジェストは表示窓(今後3日)に入る9/6から出る予定だった。
  • ①データ修復: 1552754 を手動INSERT(event_time/finished_time=キックオフ+約2h・event_dateはJST)→ /cron/updateFixture/1552754 でスコア0-1・stats(72155: sub_in 57'・33分・1G・7.8)・dpa/dla=1。直後のS3再生成で「今日の海外組」に上田/リールが載ることを api4/updateOverseasDigestS3/1(echo)と S3 digest/overseas.json で確認。
  • ②再発防止(server e9934f2・Pipeline Succeeded 12:36): 新設 app/Lib/FixtureImporter.php に採用判定(shouldImport)・試合/チーム書き込み(importRow)・syncJpTargetTeams・importTeam(fixtures?team=&season= 前年/今年/翌年)を集約。crawlNextFixtures は importRow を呼ぶだけに。開始済みでも 直近3日以内かつ未登録 の対象試合はAPIのスコア/状態/finished_time(キックオフ+2h、nowで頭打ち)で挿入、既存行は触らない。FnJpPlayer::beforeSave/afterSave(所属変更・display_on 0→1・新規)と FnliveTeam::beforeSave/afterSave(is_publish OFF→ON)で importTeam を自動実行。手動導線 /cron/importTeamFixtures/{teamId}。ApiFootballV3::getFixturesFromTeamId 追加。
  • テスト: dev-fixtures/test_fixture_importer.php(App/ClassRegistry/モデルをスタブ)27件PASS(docker php:5.5-apache の php CLI)。本番: /cron/importTeamFixtures/79 → {requests:3, inserted:0, updated:39, skipped:60}、/cron/crawlNextFixtures/61 強制実行 → 200/3.4s・リーグ・アン636件不変・終了済みスコア不変・error_log に新規PHPエラーなし。
  • 返信: 事後報告(結論→時系列の原因→対応①②→アプリ確認依頼)をチャット本文で出し、You がトムソンへ送付済み。

決めたこと: DECISIONS 2件(開始済み試合の救済取り込み+所属変更時の即時取り込み/トムソン返信はチャット本文で出す)。

詰まったこと / 気づき:

  • fnlive_fixtures.updated・fn_jp_players.updated などの datetime 列は PHP date()=JST、last_crawl_time/event_time は unixtime。RDS の now() は UTC。時系列を組むときは9時間差に注意(created は insertOnDuplicateKeyUpdate で毎回上書きされるので初回登録日時としては使えない)。
  • SSHトンネルは放置すると切れる(ServerAliveInterval=30 を付けて再接続)。zsh で M="mysql ..."; $M -e は展開されないので使わない。
  • 管理画面の保存フック(afterSave)は Auth ログインが要るため本番で実行できていない。同じ取り込み本体は cron 導線で動作確認済み。CakePHP は 2.0.4(afterSave($created)・find の callbacks=>false は利用可)。
  • 直近7日に試合が無い jp_target チーム: Southampton/Burnley(チャンピオンシップ=対象外大会)、Wolfsburg/St. Pauli/Salzburg。対象大会外なので仕様どおりだが、needs_review 精査のときに所属の実態と合わせて見直す余地あり。

次回やること: ①You が管理画面で上田の行を変更なしで保存してフックが正常に通ることを確認(任意) ②トムソンのアプリ確認の返答待ち ③SGルール 103.5.140.136 / トンネルの後始末は次回接続時に ④.devnotes 未反映だった トムソン返信_*.md 運用の廃止(今回から本文出し)。

19:00 - 第10次FB(シート項目50=終了済み試合の出場判定)対応=server ab03235 デプロイ・3試合データ修復・返信送付済み

やったこと:

  • 経緯: トムソンからLINEで「実際には出場していない遠藤航(フル出場 0分/評価0.0)と町野修斗(途中出場 -)が終了済み試合の選手成績カードに表示され、『今日の海外組:出場17人』にも含まれている」との指摘(シート項目50・高)。まず修正せず状況調査→You「①実装に進む」→修正・デプロイ・修復・返信文作成→You送付済み。
  • 調査(本番DB+API-Football生データ): 表示も人数集計も fn_player_fixture_stats.played=1 が唯一の根拠(DigestPresenter の今日の海外組フィルタ/totals.appearances=count(todayPlayers))で、played の付け方が2経路で誤っていた。
    • 遠藤(1557383 リバプール vs フォレスト): DBは starter/played=1/minutes=0/rating=0.0(8/30 01:33 backfill書き込み)。現在のAPIは substitutes・substitute:true・minutes null・subst eventなし=backfill時点のAPIがベンチ選手をstarter・0分・評価0で返していた。DigestFixtureParser は substitute===0 なら分数に関係なく starter、FINISHEDなら starter/sub_in を無条件 played=1 にしていた。終了済み試合は ovserveFixtures 対象外+backfill done=1 のため、API訂正後も再取得されず残留。
    • 町野(1575147 ライプツィヒ vs グラードバッハ): DBは sub_in/sub_in_minute=76/minutes=NULL。APIのeventsに「76' Kleindienst OUT→Machino IN」が今も存在するが、playersブロックは Kleindienst 90分・町野 minutes null で矛盾(実行されなかった交代がイベントに残った)。applySubstitution がplayersブロックの判定(bench/played=0)を sub_in/played=1 で上書きしていた。
    • 横断チェック: 終了済み・digest_players_available=1 で「played=1 かつ minutes 0/NULL かつ G/Aなし」は223行中3行のみ=遠藤・町野・大迫敬介(1556031 G大阪 vs 広島・ベンチ未出場が先発0分扱い=遠藤型・トムソン未指摘)。
  • 修正(server ab03235): DigestFixtureParser::parse のFINISHED分岐で、playersブロックに載っている選手($playersSeen)は「minutes>0 または goals/assists>0」を出場の正とし、該当しなければ played=0・starter/sub_in→bench・交代分数クリア。playersブロックに無い選手だけ従来どおり startXI/交代eventを根拠にする。LIVE分岐は変更なし。
  • テスト: dev-fixtures/test_digest_lib.php に遠藤型(starter・0分・評価0)・町野型(未実行の交代event)・正常な途中出場の3ケース追加。ローカルにphpが無いため docker php:5.5-cli/php:7.4-cli で実行し38件PASS(presenter側46件も維持)。
  • デプロイ・修復: CodeCommit push→Pipeline Succeeded(18:53)。/cron/updateFixture/{1557383,1575147,1556031} で再取り込み→本番DBで3行すべて bench/played=0・同パターン残存0件。/api4/jpPlayer/8500,106851 は club_recent が bench、S3 digest/overseas.json の totals.appearances は 17→15。
  • 返信: トムソン返信_項目50出場判定修正_20260830.md を作成しYou送付済み(大迫の件も記載・アプリ側変更なし=ビルド17のまま)。

決めたこと: 終了済み試合の出場判定は選手スタッツ(出場時間・G/A)を正とし、lineups/eventsはplayersブロック欠損時の補助に限定(DECISIONS.md参照)。

詰まったこと / 気づき:

  • API-Footballはベンチ選手を一時的に「starter・minutes 0・rating 0」で返すことがあり、後から訂正される。終了済み試合は再取得しない設計なので、取り込み時点の誤データがそのまま残る。同様に実行されなかった交代がeventsに残る(playersブロックの分数と矛盾)。lineups/eventsより playersブロックの分数を信じるのが安全。
  • 90+分投入で API の minutes が null のまま来る稀なケースは「出場なし」になる(今回の試合では1分投入も 1 が入っていたので実害は低い見込み)。もし発生したら subst event を補助的に許容するか再検討。
  • 横断チェックSQL: played=1 AND (minutes IS NULL OR minutes=0) AND COALESCE(goals,0)=0 AND COALESCE(assists,0)=0 を終了済み+dpa=1 で。修復は /cron/updateFixture/{id} を直接叩けばよい(backfill doneは無関係・shouldPreserveStoredStats はplayersありなら通る)。
  • DB DATETIME列はJST文字列で書かれている(セッションTZはUTCなので FROM_UNIXTIME との比較時に注意)。SSHトンネルは放置すると切れる(Lost connection)ので都度張り直す。
  • 大迫はJ1(対象大会外)のため選手詳細の club_recent には元々出ない。

追記(19:20頃): トムソンから項目50の修正確認OKの連絡あり(LINE)。

追記(リリース): トムソンがビルド17の全項目を確認OK→App Store申請→審査通過→5.6.0(ビルド17)リリース完了(ASC: READY_FOR_SALE・build 17=4fd01fb)。footballnext-ios に注釈付きタグ 5.6.0 を 4fd01fb に打って push。

次回やること:

  • トムソンのビルド17確認待ち(項目4・5・9・10・#22の本番確認継続)→追加指摘があれば対応
  • 設定4項目戻し(トムソン側)→App Store提出(要再アーカイブ)
  • フォロー: needs_review 22名のID重複精査/004の本番適用確認/jpPlayer遅延調査

2026-08-29

13:56 - footballnext-android の targetSdk 36 対応リリース(v3.0.8 / vc22 本番100%公開)+既存バグ2件修正

やったこと:

  • 背景: Play Console に「アプリは Android 16(API レベル 36)以降を対象とする必要があります」の警告。対応期限 2026-08-31(残り3日)。期限後はアプリの更新を出せなくなり、Android 16 端末の新規ユーザーにも配信されなくなる。現行本番は 5月にリリースした vc17(targetSdk 35)。

  • 方針: 延長リクエストは出さず期限内リリースを狙う。並行して対応を進め、間に合わなければ押す構えで着手(結果的に押さずに済んだ)。

  • targetSdk 36 対応(ce010fe):

    • compileSdk/targetSdk 35→36。AGP は 8.7.3 に据え置き、android.suppressUnsupportedCompileSdk=36 で警告のみ抑止(AGP 8.9+ は Realm Java 10.19.0(EOL)の Gradle プラグインを巻き込むリスク)
    • edge-to-edge: BaseActivity に共通処理。ルートビューへシステムバーぶんのパディング+ステータスバー領域を colorPrimaryDark で塗る。DrawerLayout の MainActivity は自前処理のため対象外
    • 予測型「戻る」: onBackPressed 2件を BaseActivity.finishToSplashOnBack() へ移植。MeigenDialog の setOnKeyListener(KEYCODE_BACK) を onCancel() へ(これが無いと起動時の名言ダイアログから先に進めなくなる)
    • 大画面(sw>=600dp)向けに PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY を追加(targetSdk 37 では無効になる暫定措置)
  • YouTube動画が全滅していた既存バグを修正(ac1faf8): 全動画が エラーコード: 152 で再生不可だった。原因は android-youtube-player の既定 origin https://www.youtube.com が playerVars だけでなく loadDataWithBaseURL の baseUrl にも使われ、「youtube.com 自身からの埋め込み」と名乗る形になっていたこと。origin を自前ドメインに変更するだけで解消(ライブラリは 12.1.2 のまま)。5月の YouTube プレイヤー差し替え(6f6feb1)以降ずっと壊れており、現行本番 vc17 でも再生不可だった

  • 動画詳細のクラッシュガード(38e5d72): MovieDetailActivity が Movie を両Realmから引けないとき assert(false) で済ませていたが、release では assert が無効化されて素通りし lateinit movie 未初期化でクラッシュする。両方で引けなければ finish() する形に

  • .gitignore に *.jks / *.keystore を追加(616bdd3・kowai-android と運用を揃えた)

  • リリース: Play Developer API(~/.config/gcloud/play-console-sa.json)経由で内部テストへ AAB を上げ、実機確認しながら vc18→19→20→21→22 と反復。トムソンOK後、You が production へ 100% 公開。native debug symbols(.dynsym 9,191エントリ)は配信中の vc22 の .so から zip を作って後付けアップロード

  • コード: origin へ 11コミット push(今回の4件+5月から保留だった7件)

詰まったこと / 気づき:

  • 検証の対照を取らないと誤った結論を出す。cached.realm の修正検証で、grep でファイル内の文字列が残っているかを見て「消えていない=修正が効いた」と判断しかけたが、対照(旧コード)でも文字列が残った。Realm は削除してもファイル上のバイト列を消さないため検査が無効だった。実レコード数で測り直して初めて 50→0 / 50→50 の差が出た。クラッシュガードの検証でも対照を取り、ガード無しでは同一シグネチャで落ちることを確認してから確定させた
  • edge-to-edge は targetSdk 35 の現行版で既に有効だった(windowOptOutEdgeToEdgeEnforcement 未設定)。36で新たに起きる問題ではなく、5月のリリース時点から表示崩れが出ていた。当初「36化の最大の作業」と見積もったが実際は既存バグの修正だった
  • Android 10 実機での二重パディング回帰が最大の懸念だったが、実測で否定。ビュー階層で android:id/content と直下ルートの bounds が一致([0,108][1080,2110])=インセットは加算されていない。Android 15未満では DecorView が先に消費するため
  • Huawei EMUI はアプリの logcat を抑制する。Log.i が出ないため、計測はファイル書き出し(filesDir)+run-as で読む方式に切り替えた
  • Play への反映は内部テストでも数分〜十数分かかる。端末側の Play がキャッシュ済みの古い版を掴むこともある(vc22 を配信中に vc20 が降ってきた)

次回やること:

  • Android Vitals でクラッシュ率とANRを数日観察(edge-to-edge と予測型「戻る」は端末差が出やすい)
  • MovieDetailActivity のクラッシュ経路は未特定のまま(発現箇所を塞いだだけ)。Vitals に同種が残るなら再調査
  • OneSignalプッシュの実配信テスト、ADG/i-mobile ネイティブ広告の実機表示確認(今回未実施)

2026-08-26

21:06 - 板倉滉のAPI-Football ID裏取り(移籍後もID重複なし)・ボール所在の整理

やったこと:

  • トムソンが板倉滉をボルシアMG(163)へ所属変更したのを受け、佐藤と同型のID重複がないかAPIで裏取り: /players/squads?team=163 にはまだ板倉が載らない(移籍直後はスカッドAPIが遅れる)が、直近試合 8/23 DFBポカール(fixture 1550697)のlineups substitutes に 同じ 38114 で登場・/players?id=38114&season=2026 にもボルシアMG(DFBポカール1試合)が紐づく=重複なし。/api4/jpPlayer/38114 は所属ボルシアMG/ブンデスリーガで返り、club_recent は空(DFBポカールは海外組対象大会外・ブンデス開幕後に蓄積)で正常
  • ボール所在: You=追加返信 トムソン返信_佐藤龍之介ID切替完了_20260826.md の送付のみ/トムソン=ビルド17確認・設定4項目戻し・リリース時期の合図/こちら=合図後のApp Store提出(要再アーカイブ)

気づき: 移籍直後の選手IDの裏取りは /players/squads ではなく「直近試合の lineups に同IDで載るか」で見る(スカッドAPIは更新が遅れるが、試合データは現行IDで即時に出る)


20:52 - トムソン回答受領→佐藤龍之介のID切替(410840→542861)を本番適用・バレンシア2試合再取り込み・返信文作成

テーマ: 第9次FB返信へのトムソン回答(Is Publish対応OK・アヤックスはON化済み・板倉滉はボルシアMG(163)へ所属変更済み・佐藤のID切替は承認)を受け、承認分を即日適用。

やったこと:

  • 本番DB確認: トムソン側の更新を確認(fn_jp_players 38114 板倉=team 163・fnlive_teams 194 Ajax is_publish=1・163 Gladbach is_publish=1/is_jp_target=1)
  • 佐藤ID切替(承認済みSQL適用): UPDATE fn_jp_players SET id=542861, photo_url=…/542861.png WHERE id=410840 → DELETE FROM fn_player_fixture_stats WHERE player_id=410840(not_registered 4行)→ /cron/updateFixture/1570342・/1570352 を再実行(Auth allow('*')・各200)
  • 検証: fn_player_fixture_stats に 542861 の2行(1570342=starter・97分・6.5/1570352=starter・57分交代・6.7)。/api4/jpPlayer/542861 → club_recent 2件(full_time/starter_subbed・評価入り)・photo 542861。/api4/jpPlayer/410840 は player_not_found(旧ID参照は無し)
  • 補足(未対応・問題なし): 代表W杯5試合(5/31〜6/29・stat 24〜26行)に 542861 の行は無い=出場記録なしのため national_recent は0件。これは正しい
  • 返信文: トムソン返信_佐藤龍之介ID切替完了_20260826.md 作成

次回やること: ①返信のYou送付 ②トムソンのビルド17確認待ち(項目4・5・9・10・#22の本番確認待ち継続)③設定4項目戻し(トムソン側)→App Store提出(要再アーカイブ)④needs_review 選手のID重複精査(任意)


20:02 - 第9次FB対応(シート修正依頼2件=Is Publish表示制御・過去試合「もっと見る」/仕様v2.18)+佐藤龍之介「登録外」の原因特定+ストア対応相談への回答+ビルド17配布

テーマ: トムソンLINEで第9次FB受領(シート修正依頼2件=未採番・HTML仕様書v2.18+質問1件+ストア対応の相談)。修正2件をserver/iOS両方で実装し、serverデプロイ・ビルド17配布・返信文作成まで完了。質問(バレンシア佐藤龍之介が「登録外」)はAPI-Football側の選手ID重複が原因と特定。

やったこと:

  • シート読解: Drive MCP read_file_content→ローカルpython解析。新規2件は番号未採番(前回47の続きなので内部的には48=Is Publish・49=もっと見る)。v2.18更新履歴(MATCH-LIST-PUBLISH-01/PAGING-01・02)と食い違いなし
  • #48 Is Publish表示制御(server cffe5d7): LiveComponent に publishVisibilityConditions() を新設し、buildRecentLives/buildLives(新着上部・S3 lives)/buildLiveHistories(試合一覧)の3クエリに共通適用。SQL条件は status IN (終了系) OR home.is_publish=1 OR away.is_publish=1=未終了かつ両チームOFFのみ除外。終了済みは現在値に関係なく維持。取り込み側 _shouldImportFixture はダイジェスト対象チームも取り込むため表示側で別判定
    • 本番DBで影響を事前確認: is_jp_target=1 かつ is_publish=0 は アヤックス(194・板倉滉)のみ(50チーム中49がON)。両チームOFFの未終了試合は166件(直近2週間は11試合=アヤックス/NEC/パルマ/アタランタ/ベンフィカ/スポルティング/セルクル・ブルージュ等)。現行の先頭50件からは2件(モレイレンセ vs ベンフィカ 8/23 Not Started・NEC vs エクセルシオール 中止)が消える。アヤックスはダイジェストには残るが試合一覧から消えるため返信で「ONにするか」確認を依頼
  • #49 もっと見る(server 同コミット+iOS 4fd01fb): server=/v1/liveHistories に before_time/before_id のカーソル引数を追加((event_time < t) OR (event_time = t AND id < i)・order に id DESC を追加・DBCacheキーにも含める)。旧ビルドの limit/offset 方式はそのまま。iOS=LiveHistoryPage(試合タブ)に content/more の2セクション化・pageSize=50・51件要求して51件目の有無で hasMore 判定・追加は最後の行の (event_time, fixture_id) をカーソルに要求・fixture_id で重複排除・isFetching ガード+Loading表示。旧 LiveHistoryList(2022・横スクロール「もっと見る」から遷移)は引数デフォルト値で無変更
    • 検証: 本番DBで新ロジック相当SQLを実行し page1(51件)→50件目をカーソルに page2(51件) で 重複0・51件目=page2先頭(同一event_timeのタイ含め連続) を確認。simビルドSUCCEEDED。本番APIから取った51件JSONをローカルHTTP配信(-DigestBaseURL/-DigestLiveHistoriesPath/-DigestOpenLiveHistoryOnLaunch yes)で試合タブ最下部に「もっと見る」表示→タップで再取得(HTTPログで確認)・重複追加なし・クラッシュなしを確認。スクショ fb-items48-49-shots/after/49_試合一覧_もっと見る.png
  • 質問:佐藤龍之介(Team 532/Player 410840)が「登録外」: 判定元は API-Football v3 GET /fixtures?id= の lineups/players/events(DigestFixtureParser)。直接叩いて確認したところ API側で同一選手のIDが2つ: 410840(旧・岡山/代表2025)と 542861(現行・FC東京2026/バレンシア/代表W杯2026)。バレンシア戦(1570342・1570352)は lineups/players/events すべて 542861 で配信(両試合先発・8/25は97分・評価6.5)→マスタの410840がラインナップに無く not_registered。fn_player_fixture_stats の410840行は4件すべて not_registered、542861行は0件。修正案SQL(マスタid切替+旧行削除+updateFixture 再取り込み)を scratchpad sato_fix.sql に用意し、返信で方針確認中(未適用)
  • ストア対応相談: 「現素材のまま申請・更新文は簡潔」で問題なしと回答。ダイジェストタブはサーバー設定連動(DigestAvailability.shouldShowTab)なので審査後にタブ露出タイミングを調整できる点を補足
  • ビルド17: ASC API最新実数=16→17採番。CURRENT_PROJECT_VERSION=17 でアーカイブ→exportArchive(app-store-connect/upload) → EXPORT SUCCEEDED・5.6.0(17) アップロード成功→ASCで processingState=VALID 確認(反映まで約10分。dSYM警告 Moloco・MinimumOSVersion 13.0 警告は既知)
  • デプロイ検証(本番): Pipeline footballnext Deploy=Succeeded(19:57)。/v1/liveHistories?limit=51→51件でモレイレンセ vs ベンフィカ・NEC vs エクセルシオール(中止)が消え、片方ONのセルティック戦/レンジャーズ戦(中止)は残存。50件目カーソルで before_time/before_id 要求→51件・重複0・51件目=次ページ先頭・event_time単調減少。旧方式 limit=50 も200。S3 201811(新着上部lives)は20:00時点で lives 0=DBでも窓内0件のため正常(/v1/whatsNewArticles 200・result true)
  • 返信文: トムソン返信_項目48-49対応完了_20260826.md(Is Publishの影響範囲=アヤックス確認依頼・直近非表示11試合列挙/佐藤の原因と修正案/ストア相談回答)
  • DB接続: 現IP 61.206.234.48 を SG sg-0461787f6302fd838 port22 に追加(Description claude fb48-49 20260826)→Web1号機トンネルで確認

詰まったこと / 気づき: ①API-Footballは同一選手に複数IDが存在しうる(移籍・年代別代表を跨ぐと新IDが切られる例)。needs_review選手で「出場しているのに登録外」はまずID重複を疑う(/players?search=&team= と /players/squads?team= で現行IDを確認)。②offset方式の追加読み込みは上に新規行が挿入されると重複/欠落するので、(event_time,id) カーソル+クライアント側dedupeの二重防御にした。③XcodeBuildMCPのtap/swipeは直前snapshotのref必須・UITableViewのテキストのみのセルは touch で押せる。④Is Publishのような「表示側フィルタ」は、取り込み側の採用条件(ダイジェスト対象チーム)と食い違う集合が必ず出るので、適用前に本番DBで差集合を数えて返信に書く

次回やること: ①返信はYou送付済み(同日) ②トムソンの回答待ち(アヤックスIs Publish・佐藤ID切替の可否)→OKなら sato_fix.sql 適用+updateFixture/1570342,1570352 ③トムソンのビルド17確認待ち ④設定4項目戻し(トムソン側)→App Store提出(要再アーカイブ)


2026-08-23

21:21 - 第8次FB対応(シート項目39〜47・仕様v2.17=試合状態/スコア表示/代表経由)+全項目before/after比較画像+ビルド16配布・返信文作成

テーマ: トムソンLINEで第8次FB受領(シート項目39〜47の9件+HTML仕様書v2.17。進行中の実画面を初確認しての指摘が中心)。全項目を同日中に消化し、今回は各項目の「修正前|修正後」比較画像も作成。serverデプロイ・ビルド16配布・返信文作成まで完了(返信+添付のYou送付待ち)。

やったこと:

  • シート読解: 項目39〜47(高=39/40/42/43/44/45、中=41/46/47)。v2.17更新履歴と突合し食い違いなしを確認
  • 修正前スクショ: 本番データで#40/43/44/45/47、ダミーJSONで#39/41/42/46(新規fixture3種: fixture-club-lineup-fb39=status試合前×PRE_MATCH_LINEUP、fixture-club-live-fb41、overseas-fb3947=LIVE/LINEUP/PRE 3状態入り。ロゴはmedia.api-sports.io実URL)
  • #45の原因特定(サーバー): DigestDataProvider::getPlayerMatches が日本代表戦(team 12)にも海外組14大会フィルタを適用→代表5大会(W杯=1等)と交差ゼロで national_recent が全選手で常に空だった。経過日数条件ではない。チーム別に大会集合を切替えて修正(server 329b484)。デプロイ後、冨安の選手詳細で直近3試合(ブラジル1-2・スウェーデン1-1・チュニジア4-0)表示を本番確認。ビルド15でも直る
  • #40(サーバー+iOS): livesに lineup_announced(fnlive_fixtures.digest_lineups_available 由来)を追加配信。iOSは caption_id==0 && lineup_announced で青背景#3c76d3「スタメン」へ切替(スコア領域は従来どおり非表示)。caption_id=4新設案は実装後に撤回(旧配布ビルドが未知IDを受けると白文字・無背景で不可視+0-0表示になるため。DECISIONS参照)
  • iOS実装(926f17f): #39=選手ブロックstateからラベル導出・PRE_MATCH_LINEUPは青ピル「スタメン発表」(LiveFixtureStatusBadgeLabel 新設・xibカスタムクラス化)/#41=ステータスチップを「登録外」基準の共通固定幅に(ポジション列縦揃え)/#42=ダイジェスト選手欄の状態テキスト全削除(ロゴ+名前のみ)/#43・#44=スコアを所属クラブ基準へ(DigestFormatters.perspectiveScore/compactPerspectiveScore 新設。シェア画像内の同表記2箇所も統一。代表直近3試合の行は日本基準)/#46=DigestFixtureStatePill 新設(グレー試合前/青スタメン発表/薄赤●+経過分。LIVEのみ外側薄赤円が1.35秒周期で1.0→1.3倍拡大しつつ透明化するパルス。didMoveToWindowで再アタッチ)/#47=caption_id==0 は「vs」表示(LiveHistoryListRowDataにcaptionId追加・構築2箇所)/#40クライアント=Live.lineup_announced追加・Realm schemaVersion 4→5
  • 検証: simビルドSUCCEEDED・本番データで#43(鎌田/冨安「0 - 2」)・#44(「8/22 vs エヴァートン 0-2」)・#45・#47目視PASS。パルスは6秒録画→ffmpegフレーム抽出でハロの拡大・消失を確認。#40は本番APIを3分間隔監視するスクリプトでPSV戦スタメン発表(21:03)を捕捉し、新着上部の「赤LIVE|青スタメン|黄時刻」3状態同居の実画面を撮影
  • 比較画像: fb-items39-47-shots/compare/項目NN_*.png 9枚(PILで修正前|修正後を1枚に合成・タイトル帯+赤/緑ラベル。項目41のみ3パネル=LINEUP/LIVE)。#46は after/46_LIVEパルス動画.mp4 も
  • ビルド16: ASC APIで最新実数=15を確認→16採番。クリーンtree(xcuserstateはstash退避→復元)から CURRENT_PROJECT_VERSION=16 でアーカイブ→app-store-connect/uploadでアップロード→processingState=VALID確認。dSYM警告4件(AppLovin/DTB/InMobi/Moloco)は既知・無害
  • 返信文: トムソン返信_項目39-47対応完了_20260823.md 作成(項目45を先頭・原因説明+ビルド15でも確認可を明記。項目40に対象試合限定/切替タイミング約1時間前/旧ビルド無影響の補足3点)

詰まったこと / 気づき: ①全ビルド共有のJSON(S3 lives)にenum新値を足すと旧ビルドの表示を壊しうる。追加フィールド方式なら旧ビルドは未知キーを無視するので安全。②statusToCaption はlives/liveHistories共用のため、livesだけ変えたい変更は呼び出し側で分岐する。③スタメン発表のような「実データでしか発生しない状態」は、本番APIをポーリングする監視スクリプト+発火時にsim撮影で捕まえられる(今回21:03のPSV戦で成功)。④シミュレータの初回起動は利用規約→購読設定→通知→ATTのプロンプト連鎖があるので、キャッシュクリア(アンインストール)を伴う検証は時間に余裕を持つ

次回やること: ①返信文+比較画像9枚+パルス動画のYou送付 ②トムソンのビルド16確認待ち ③設定4項目戻し(トムソン側)④App Store提出(要再アーカイブ)


2026-08-19

21:41 - 第7次FB対応(シート項目25〜38・仕様v2.16=チームロゴ/表示仕様統一)+ビルド15配布・返信送付

テーマ: トムソンLINEで第7次FB受領(シート項目25〜38・#32は欠番+HTML仕様書v2.16)。ヘンク戦バグ3件+試合一覧のチームロゴ追加・揃え変更+ダイジェスト2セクションのロゴ/クラブ別グループ表示を同日中に全消化し、serverデプロイ・ビルド15配布・返信送付まで完了。

やったこと:

  • シート読解: Drive MCP read_file_content→ローカルpython解析(メモリの定石どおり)。新規は項目25〜38の13件(#32欠番)。#22(ザルツブルク国旗)は「未対応」のままだが、これは8/10返信で合意済みの「ザルツブルクが今日の海外組に載るタイミングで確認」待ち=作業なし。EL 8/21 ザルツブルク戦後の確認を返信で再依頼
  • 項目25 原因究明(ヘンク vs ウェステルロー 試合詳細の日本人選手欄が空): fixture 1558593 は digest_players_available=0・スタッツ0行のまま digest_backfill_done=1。API-Football側で選手成績の配信が試合翌日まで遅延しており(8/16 8:53のbackfill時点でも空)、1回きりのbackfillが「劣化レスポンスではない=done」扱いにして再試行しなかったのが根本原因。現在はAPIに全データあり(伊東7.5・横山6.9ほか5選手)
  • 項目25 修復+再発防止(server efdb9dc デプロイ済み): ①直近30日の同症状9試合(8/15前後のベルギー3・オランダ2・EL/ECL予選4)を既存 /cron/updateFixture/{id} で一括再取り込み→リーグ戦5試合は全て dpa=1・スタッツ入り(ヘンク戦=5選手・本番APIで配信確認)。②backfillを「選手スタッツが取れるまでdoneを立てず3〜72時間窓で再試行」に変更(cron10分間隔=最大432req/試合で許容)。EL/ECL予選4試合はAPI側に選手スタッツ自体が無くlineupsのみ=これ以上は不可
  • 項目26〜31 試合一覧(iOS ee88a7d+server同コミット): #26の崩れは「日本人選手ラベルの centerX=チーム名 制約が長いラベルでチーム名を押し動かす」(チーム名にleading固定なし)のが原因。xibを全面改修=チームロゴ追加(左=チーム名→ロゴ28pt→スコア/右=鏡像・buildLiveHistories に home/away_team_logo 追加)・日本人選手ラベルは左=ロゴ右端に右揃え/右=ロゴ左端に左揃え・リーグロゴ25→22pt。#27は label.bounds.width 実測での選手名⇔人数切替がレイアウト前後で揺れるのが原因→2名以下は常に実名・溢れは「…」(幅実測切替を全廃)
  • 項目33〜38 ダイジェスト(iOS のみ・serverは既に全データ配信済みだった): DigestFixtureCell を改修=リーグロゴ16pt+両チームロゴ22pt+日本人選手を「[クラブ小ロゴ15pt] 選手A 選手B」のクラブ単位グループ表示(余白=選手間10/クラブ間18・折り返しはクラブグループ単位・1グループが1行超のときのみ選手名単位)。進行中/これからの試合はレイアウト共通・LIVE情報のみ差分。overseas.json は competition.logo_url/team.logo_url/jp_players[].team_id を既に持っていたためserver改修不要
  • 検証: sim(iPhone 17)ビルドSUCCEEDED・本番データで試合一覧(ヘンク行含む)とこれからの試合を目視PASS・Auto Layout競合警告0件。進行中カードはローカルHTTP+-DigestBaseURL/-DigestOverseasPath のダミーJSON(2クラブ6選手・出場中/途中交代/出場なし混在)でグループ折り返しまで目視PASS。仕様書v2.16の完成イメージ2枚(試合一覧・これからの試合)をpythonでHTMLから抽出して突合
  • ビルド15: ASC APIで最新ビルド確認中に8/10にビルド14(#24バッジ修正・iOS b248d50)が配布済みと判明(DEVLOG未記録セッション・返信 トムソン返信_更新バッジ修正_20260810.md あり)→今回は15として、クリーンtreeから CURRENT_PROJECT_VERSION=15 でアーカイブ→app-store-connect/uploadでアップロード成功→ASCで processingState=VALID 確認。dSYM警告(InMobi/Moloco)は既知・無害
  • 返信: トムソン返信_項目25-38対応完了_20260819.md をYou送付済み

決めたこと: digest backfillのリトライ方針(DECISIONS.md参照)

詰まったこと / 気づき: ①「対応済みのはず」の外部APIデータも配信タイミング次第で穴が開く。1回きりの補完処理は「取れなかった」を成功扱いにしないこと(doneフラグは目的達成=スタッツ取得で立てる)。②ビルド番号はローカル記録(WIP)だけでなくASC APIで実数確認してから採番する(未記録セッションの配布があり得る)。③試合一覧のUI崩れはxib制約のcenterX起因で、データ修復(#25)とは独立の問題だった

次回やること: トムソンのビルド15確認待ち→追加指摘対応。#22はEL 8/21ザルツブルク戦後にトムソン確認。設定4項目戻し(トムソン側)→App Store提出


2026-08-17

16:11 - 起動時広告A/B: dev-timer #36 enrollment窓クローズ確認+残差率/SRM中間再read(全PASS)

テーマ: dev-timer #36(8/17 17:00予定)を前倒し実行。①enrollment窓クローズ確認 ②SRM再read(全窓) ③残差率中間再read。GA4 Data API(property 211701453)+S3 config実取得。

結果(全項目PASS):

  • ① 窓クローズ正常: S3稼働config=v9のままローカル正本と完全一致(enrollment_end_at=1786892400=2026-08-17 00:00 JST・active=true)。GA4の enrollment_day は 07-07〜08-16が最終・08-17のenroll 0件=canEnroll 窓ゲート機能。(not set) enroll(5.5.3勢)A=304/B=263 は解析除外・無害。target_cohort_day=08-17 の funnel イベントは既enroll勢の起動によるもので正常。
  • ② SRMなし: クリーンコホート最終規模 計3,141人(A=1,548/B=1,593=49.3/50.7%)・χ²≈0.65・p≈0.42。実績約75人/日=見込み約4,000人よりやや小さい着地。9月本解析の検出力見積もりはこの実数が前提。
  • ③ 残差率 中間再read PASS(ガードレール④維持): 成熟 target_cohort_day 07-08〜08-14(maturation3日/watermark08-14)・cv_9 で A=5.51%(10,796/196,022)・B=3.84%(7,540/196,385)=絶対両方<10% ✓・群間差1.67pp<3pp ✓。日次10%超えゼロ(最大 A 07-31=9.35%)・群間差5pp超えゼロ。#31同範囲(07-08〜07-19)の再現値 A=5.37/B=3.66 が記録値(5.38/3.67)と一致=手法整合。

気づき(要留意・9月本解析向け):

  • 7/23以降は汚染コホート勢が D28+ ゲートで追跡窓外に抜け母数急減(1.2〜1.7万→600〜800件/日)。クリーンコホート主体期(07-23〜08-14)の残差率は A=7.07%/B=5.81%(差1.26pp) とやや高め=小母数の分散増。基準内だが本解析はこの水準感が前提。
  • 参考(非確定): 未到達離脱率 A=18.09%/B=14.76%(B が3.33pp低・仮説の向き・#31整合)。ただしクリーン主体期のみだと A=17.34%/B=15.49%(差1.85pp)=新規ユーザーでは効果量が縮む兆候。
  • 中間評価の肌感: 機構は健全・UXはB優勢の向きが3回のread(first-look/#31/#36)で一貫。ただし D7/D14・円/人はユーザー単位 n≈1,550/群で検出力小さめ=本解析は funnel系(イベントベース)中心+円/人の点推定と幅で判断する見込み。Bの採用可否は収益減が許容範囲かどうかに帰着。

後始末: dev-timer #36 を done 化・#37(9/15 17:00 本解析)の pending 登録確認済み。計算スクリプトは scratchpad(一時領域・未保存)。


2026-08-08

19:47 - 第6次FB対応(項目22・23=MVP並び順共通化・仕様v2.15+ザルツブルク国旗)+ビルド13配布・返信送付

テーマ: ビルド12確認過程のトムソン提案(MVP順位を一覧と同一ルールへ戻したい)に「そのままだと一覧先頭=MVPが成立しない」実例付きで逆提案→合意→シート項目22・23+仕様書v2.15として正式化。同日中に全消化し、serverデプロイ・ビルド13配布・返信送付まで完了。

やったこと:

  • 仕様往復: トムソンの「MVP順位決定をG+A→評価→出場時間に戻す」提案に対し、共通ソートの欠損スキップ仕様のため統計未取得選手が一覧先頭に来る実例(本番=北野が先頭・MVPは上田)を示し、「一覧側も欠損下位へ寄せて完全一致を保証する」案を返信→トムソン合意(G/Aなしは0比較・評価/出場時間なしは下位・表示は「-」維持・対象は今日の海外組のみ)。項目23+v2.15として明文化された
  • 項目23実装(server ac07f72/iOS 2423d53): DigestPlayerSorter::sortOverseasToday 新設(欠損下位の共通並び・今日の海外組専用)、pickOverseasMvp は「ソート済み一覧の先頭に来る候補を返す」だけに簡素化(候補は非候補に必ず勝つため候補がいれば構造的に一覧先頭=MVP)。iOSはシェア画像のMVP差し込み処理・不要デバッグバリアントを削除しサーバー配信順をそのまま使用。試合詳細の並び(欠損スキップ)は不変
  • 項目22原因特定(ザルツブルク国旗): iOSの国旗描画は at 対応済み・serverの国名→コード変換も対応済みだったが、fixtureFields() に country 列がなく全クラブで country_code がnull配信だった。国内リーグ勢はiOSの「大会ID→国」フォールバックで隠れ、フォールバック外の欧州大会(EL)のザルツブルクだけ露呈。ホーム/アウェイの country を取得列に追加して修正。さらにNECナイメヘンの country 登録漏れ(空)を発見しDB修正(Netherlands・CL試合で同症状が出る前に解消)。対象26クラブの他は正常
  • 検証: リモートPHPハーネス6ケース全PASS(統計null下位・評価/出場時間なし下位・候補0でnull・G+Aなし0比較・一時キー非漏出・表示用データ非破壊)。simビルド→シェア画像で配信順のままMVPラベル先頭・候補0人でバッジなしを目視PASS。デプロイ後の本番overseas.jsonで**一覧先頭=MVP=上田綺世・統計null勢が最下位・全クラブcountry_code配信(ザルツブルク=at)**の3点PASS
  • 配布: Pipeline Succeeded(ac07f72)→ ビルド13(5.6.0(13))をクリーンtreeからアーカイブ→アップロード成功。返信 トムソン返信_MVP並び順共通化_20260808.md(項目22・23対応版)をYou送付済み。項目22はserver修正のみ=配布済みビルドにも効く
  • 運用: セッション途中で回線IPが変わりSSH不通→SG(port22)に現IP追加(claude digest 20260808・過去運用と同方式)

決めたこと: 今日の海外組の並び順・MVP選定の共通化と欠損下位ルール(DECISIONS.md参照)

詰まったこと / 気づき: 「配信済みのはず」のcountry_codeが実は全クラブnullでも、クライアント側フォールバックが国内リーグを救っていたため2週間気づけなかった。フォールバックは症状を隠すので、配信側の欠落はフォールバック無しの経路(欧州大会)で最初に露呈する

次回やること: トムソンのビルド13確認待ち→追加指摘対応。設定4項目戻し(トムソン側)。App Store提出時は再アーカイブ。

13:22 - 試合一覧日本人選手表示の調査回答(8/7)+第5次FB対応(仕様v2.14 項目19〜21)+ビルド12配布・返信送付

テーマ: 8/7晩のトムソンLINE(試合一覧の選手名非表示2件の原因確認依頼)に調査回答→翌8/8にシート項目19〜21+HTML仕様書v2.14を受領し、同日中に全消化。serverデプロイ・TestFlightビルド12配布・返信送付まで完了。

やったこと:

  • 調査回答(8/7): ①アヤックス/コペンハーゲンで選手名が出ない件=選手登録・チーム紐づけ・表示ONは全て正常(DB実査: 板倉滉→194/鈴木淳之介→400)で、原因は該当2試合がUEFAカンファレンスリーグ(848)=digestLeagueIds() 対象外という「対象大会の判定」。848は8/4に実況板リーグとしては投入済みだがダイジェスト対象には未追加で、元仕様書の対象大会がCL/ELのみだった経緯も特定。②ザルツブルク「日本人選手2名」=iOSの幅フォールバック(2名以下は名前表示を試み、ラベル119pt超で人数表示。仕様v2.7明記どおり)。返信 トムソン返信_試合一覧日本人選手表示調査_20260807.md 送付済み
  • 第5次FB読解(8/8): シート項目19(シェア画像クラブ情報の右揃え)・20(MVP候補条件・選定ルール)・21(対象大会14大会への整理)とv2.14(対象大会ID明文化版)を突合。対象9大会は全て fnlive_v3_leagues に登録・クロール中でリーグ側の追加登録は不要と確認
  • server(1e59b44 デプロイ済み): 対象大会リストを overseasCompetitionIds()(14大会・848/15追加)と nationalCompetitionIds()(1,7,10,23,30・E-1=23追加。従来getNationalFixturesにハードコード)に分割し、試合一覧/試合詳細ガード・DataProvider全クエリを海外組リスト参照へ。cron取り込み判定は和集合。MVPは pickOverseasMvp を新ルールで実装(候補条件=1G以上/1A以上/評価7.0以上、順位=G+A→ゴール数→評価→出場時間、評価なしは取得済みより下位、候補0人でmvp=null)。ローカルPHPなしのためWeb1号機のphpでlint+シート確認観点7ケースをハーネス検証し全PASS
  • iOS(3fa1a05 push済み): シェア画像のクラブ情報を「クラブ名→所属リーグ国旗」右揃え・国旗右端を全カード固定(drawTextWithFlag にFlagEdge.trailing追加、長名は縮小→末尾…)。mvp=null時に先頭カードをMVP扱いする旧フォールバックを撤去(アプリ内一覧・シェア画像とも)。シェア画像はMVP先頭配置(並び順上位3人外でもMVP優先掲載)。デバッグ書き出しに「MVP候補0人」「MVP先頭配置」バリアントを追加
  • 検証: simビルドSUCCEEDED→dev-fixturesローカルHTTP配信+-DigestExportShareImages でシェア画像5種を取得し目視PASS(国旗右端3カード一致/候補0人でバッジ・枠なし/2番目指定選手の先頭強調。fb-items19-21-shots/)。落とし穴: HTTP配信はiosリポジトリルートから行う(リクエストパスに dev-fixtures/ プレフィクスが含まれる。フィクスチャdir直下で立てると404)
  • 本番反映確認: Pipeline Succeeded(rev 1e59b44)→ liveHistoriesでUECL3試合にラベル出現(板倉滉・鈴木淳之介・伊藤敦樹)、overseas.jsonのMVPが新ルールで上田綺世(2G・8.2)に。一覧先頭は統計オールnullの選手=旧ルールならこれがMVPだった実例をもって指摘バグの解消を確認
  • バックフィル: /cron/updateFixture/1607572・1607580 実行。板倉・鈴木はAPI-Football側でもマッチデースカッド外→not_registered が正しい状態(8/7時点でstatsが空プレースホルダだったのは対応前の取り込みガードのため)
  • ビルド12: クリーンtree(xcuserstate等をstash退避)から CURRENT_PROJECT_VERSION=12・Apple IDセッション認証でアーカイブ→exportArchive(app-store-connect/upload)で 5.6.0(12) アップロード成功。dSYM警告(InMobi/Moloco)は既知・無害。返信 トムソン返信_項目19-21対応完了_20260808.md をYou送付済み

決めたこと: 対象大会の2正典分割+cron和集合判定、MVP先頭配置の適用範囲(DECISIONS.md参照)

次回やること: トムソンのビルド12確認待ち→追加指摘対応。設定4項目戻し(トムソン側)。App Store提出時は再アーカイブ。


2026-08-05

19:14 - 第4次FB対応(仕様v2.12 項目13〜18)+シェアレーン統合+ビルド11配布・DM返信済み

テーマ: トムソンLINE(ビルド10確認OK+シート項目13〜18追記+HTML仕様書v2.12受領)を受け、追加6項目を同日中に全消化。温存していたシェアレーン差分もビルド11へ統合し、TestFlight配布→DM返信まで完了。

やったこと:

  • 仕様読解: シートの新規「未対応」6件(項目13〜18)とv2.12更新履歴を突合し、シート外の隠れ仕様変更なしを確認。項目1〜3・6・7・11・12=対応済、8=HTML修正のみ、**4・5・9・10=「本番確認待ち」(LINEUP/LIVE実データ発生時にトムソン側で確認)**の状況も整理
  • 修正前スクショ: 既存デバッグ機構(-DigestExportShareImages+dev-fixturesローカルHTTP配信+-DigestLiveHistoriesPath)でシェア画像5種+sim試合一覧をbeforeとして取得
  • 項目13(試合一覧3段構成): LiveHistoryListCell.xib でコメント吹き出しを右下→上段右端(日付・リーグ名と縦中央揃え)、日本人選手情報を「試合結果」と同高の下段へ、リーグ名×吹き出しの重なり防止制約を追加。行高108→96pt(試合タブ・実況版一覧の2箇所)→ iOS 3b25410
  • 項目14〜18(シェア画像): DigestShareImageRenderer の国旗を絵文字→DigestPlayerRowParts.leagueFlagImage 共用のコード描画画像へ。国判定は team.country_code(server全team payload配信済み・DigestPresenter確認)優先+リーグID→国コード対応表でフォールバック。ヘッダー旗46px(代表版=日の丸・クラブ版=リーグ国旗・サイズ基準統一)、カード旗34px全カード統一、平均評価=緑(なしは「-」グレー)、合計欄数値のみ29→38px・heavy・ベースライン2pt下げ → シェアレーン温存差分(トムソン提供 share_bg 差し替え・DigestShareControl/DigestShareIcon)と合わせて iOS b41fffa
  • 修正後スクショ+項目番号バッジ: 再ビルドでafter取得し、PILで全16枚(before/after各8枚)に赤枠+「項目N」バッジを焼き込み → fb-items13-18-shots/
  • ビルド11配布: クリーンtreeからCLIアーカイブ(CURRENT_PROJECT_VERSION=11・Apple IDセッション認証)→ exportArchive(app-store-connect/upload)で 5.6.0(11) アップロード成功。返信文 トムソン返信_仕様v2.12対応完了_20260805.md(簡潔版・スクショ言及なし)を作成しYou送付済み

気づき:

  • 【新規警告】MinimumOSVersion: 2027年春以降、最低OS 15.0未満のアプリはApp Store Connectへアップロード不可。現在13.0のため来年までに引き上げ対応が必要(ROADMAP今四半期へ追加)
  • dSYM警告4件(AppLovin/DTBiOS/InMobi/Moloco)は従来どおり無害
  • シェア画像のbefore/after取得は DigestShareDebugExporter(ダイジェスト表示+選手詳細表示で自動PNG書き出し)が便利。全パターン(評価なし・長名・未知リーグ)を1回の操作で網羅できる

次回やること: トムソンのビルド11確認待ち→追加指摘があれば対応。設定4項目戻し(トムソン側)。App Store提出時は再アーカイブ(xcarchiveはscratchpad一時領域)。

16:49 - 第3次FB対応(仕様v2.11)フル完了+試合タブ空表示バグ修正+ビルド10配布

テーマ: トムソンLINEの追加指摘(シート項目6〜12+HTML仕様書v2.11)を全消化。途中でYou報告の「試合タブが本番で空」の原因調査から起動時競合バグを特定・修正。実装確認事項もダミーデータ検証で消化し、クラブLINEUP/LIVEの仕様差異を発見→修正。ビルド10をCLIでTestFlight配布し、トムソン向けDM文書まで作成。

やったこと:

  • 仕様読解: シート「FN海外組」タブ全12項目+添付HTML仕様書v2.11(v2.8〜2.11はトムソン側更新)を突合し作業リスト化。リーグ国旗の表示箇所は「代表PRE_MATCH選手行」「選手詳細クラブ版ヘッダー」の2箇所のみ、代表LINEUP=先発11名のみ/LIVE=出場中・交代済のみ等の明文化を確認
  • 項目6・7(iOS 609ab04): 代表/クラブ試合詳細のFINISHED選手行に日の丸追加・G/A青→グレー・評価黒→緑・クラブ見出し青→黒。日の丸描画を共通部品 DigestPlayerRowParts へ切り出し。並び順・出場者絞り込みはserver実装済みをコード確認(sortOverseasMvp=G+A→評価→出場時間、statusRank=bench→not_registered)
  • 試合タブ空表示バグ(iOS 2278819): 本番でも空、の報告を受け調査。/v1/liveHistories は正常(50件0.05秒・全行デコード可)→LLDBアタッチで処理を遅らせると表示される=起動時競合と確定。ダイジェスト表示可否判明時の rebuildPageViewController と LiveHistoryPage の取得完了が競合し、未初期ロードのテーブルへ「配列append→1行ずつinsertRows」する構造が行数不整合を起こす(一時ログ計測でInvalid batch updatesクラッシュ経路も確認)。didFetchDataを「まとめてappend→reloadData」方式へ変更、同型の実況版一覧(LiveHistoryList)も同修正。通常起動2連続+実況版一覧でPASS確認。発症条件は「ダイジェストタブが出る」こと=8月の欧州開幕でoverseasが埋まって顕在化。5.6.0系のみでApp Store版5.5.xは無関係だが、ビルド9には残存
  • 項目12・11(server 18af16c+iOS bd8de88): リーグ国旗対応。server=presentTeamに country_code、代表PRE_MATCH選手行に clubCountryCode を追加(fnlive_teams.countryの英語国名→コード変換 teamCountryCode 新設・未知国null)。テスト3件追加(46+35アサーションPASS・php7.4コンテナ)・デプロイ済み。iOS=国旗をコード描画(13カ国・キャッシュ付き・未知コードは省略)で実装し、代表PRE_MATCH行(16×11・日の丸22×15より小・クラブ名欠損時は国旗ごと省略)とクラブ版ヘッダー(18×12・国名テキスト廃止)へ表示。DigestShareFlagMap に88/119を補完(フェイエ/コペンハーゲンの(国名)欠け解消)
  • シェアレーンとの分離: 項目11の修正対象 JpPlayerDetailCells.swift に温存中シェア差分が被っていたため、該当1ファイルだけstash→クリーン状態で修正・コミット→pop→競合1箇所(プロパティ行)を手動マージ。復元後の全体ビルドPASS・バックアップもscratchpad保持
  • 実装確認事項の消化: dev-fixturesをローカルHTTP配信+デバッグ起動引数(DigestBaseURL/DigestLiveFixturePath等)で全状態を再現し仕様7章と照合。代表LINEUP(11名のみ・先発チップ)/代表LIVE(出場中・交代済のみ・ポジション)/クラブFINISHED(ベンチ短縮行・評価null「-」・並び)=PASS。クラブLINEUP/LIVEに差異発見(LINEUP=選手名のみ・LIVE=成績列表示)→試合前〜LIVEを「日の丸→選手名→状態チップ→ポジション→矢印」へ刷新し状態チップを共通部品化(ベンチ/登録外=白地グレー枠を追加)(iOS 5480d48)。修正後ダミー描画でPASS
  • ビルド10配布: シェアレーン差分を git stash -u で全退避→クリーンな 5480d48 から xcodebuild archive(CURRENT_PROJECT_VERSION=10・Apple IDセッション認証)→-exportArchive(app-store-connect/upload)で 5.6.0(10) アップロード成功。内部グループ自動アクセスで配布操作不要。退避分は復元済み(xcuserstateの衝突はアーカイブ中のXcode書き込みが原因・現在分破棄で解決)
  • トムソン報告: DM用プレーンテキストを トムソン返信_仕様v2.11対応完了_20260805.md に作成(対応5項目+試合タブ件+ビルド10で確認依頼+設定4項目戻し依頼)
  • スクショ: fb-items6-7-shots/ に before/after 8枚+確認用5枚を保存(項目6/7/11/12の前後、LINEUP/LIVE/ベンチ行検証)

決めたこと:

  • リーグ国旗は「server=fnlive_teams.countryから国コード配信+iOS=コード描画画像」方式(DECISIONS.md 参照)
  • 試合タブ修正は「まとめてappend→reloadData」方式(提示2案からYou選択①)

気づき・注意:

  • 本番 /api4/jpPlayer が間欠的に30秒超→タイムアウトする事象を2回観測(リトライで即成功・存在しないIDは常時約35秒)。トムソン確認中に「選手詳細が読み込めない」と言われる可能性あり。原因未調査(player_not_found経路が重い疑い)
  • 試合タブの空表示は非決定的(空/正常/最悪クラッシュの3パターンを確認)。デバッガで遅らせると再現しない典型的な競合だった
  • アップロード時のInMobi/Moloco dSYM警告はサードパーティバイナリ由来で従来どおり非ブロッキング
  • simの試合タブ検証で「引数なし起動でダイジェストタブが出ない」前回観察は今回未再現(毎回表示された)

次回やること: TestFlight処理完了確認→YouがDM送付→You実機でビルド10確認→トムソン再確認→設定4項目戻し(トムソン側)→アーカイブ再利用でApp Store提出。


2026-08-04

18:27 - 仕様v2.7対応(代表選手一覧1行表示)→ビルド9配布→久保・上田の「最近5試合」整備→対象リーグ2追加まで一気通貫

テーマ: トムソンの第2次フィードバック(シート指摘3+確認依頼2件+仕様書v2.7)をフルサイクルで対応。server 2コミット+iOS 2コミットのデプロイ、TestFlightビルド9のCLI配布、選手マスタ紐づけ完了、過去試合バックフィル、ダイジェスト対象リーグ拡張まで完了し「トムソン最終確認待ち」へ。

やったこと:

  • 仕様読解: シート追記(指摘3=試合前選手一覧の1行表示・優先度高)+未番号2行(スタメン発表後/LIVEの確認代行依頼)+v2.7差分(PRE_MATCH 1行表示の明文化のみ)を突合。発表後/LIVEはコード読解で「差異あり」と判定し今回まとめて修正する方針をトムソンへ返信(You送付)。
  • server(e59c037): ①招集メンバーの所属クラブを選手マスタ(team_id)→fnlive_teams解決で club_team 付与(未紐づけはクラブ名省略で行維持)②試合前=GK→DF→MF→FW・背番号順、LIVE=ポジション優先・同ポジション内出場中→交代済のソート追加 ③日本人選手欄・実況板ラベルをダイジェスト対象リーグ限定にするガード ④リーグ正典を DigestPresenter::digestLeagueIds() に一本化。テスト8件追加(43件PASS・php7.4コンテナ)。
  • iOS(e032349+5ad53eb): NationalFixturePlayersCell を状態別1行表示に刷新(日の丸プログラム描画・クラブ名グレー…省略・先発/出場中=塗り緑チップ・交代済=白地緑枠・ポジション・矢印。FINISHEDは従来維持)。行重なりバグをYou発見→行高26pt vs nationalPlayersRowHeight() の1行24pt計算のズレ52pt分が原因→24pt統一で修正。
  • TestFlight: ビルド8(行重なり欠陥・破棄)→ビルド9(5.6.0(9))をCLIでアップロード。手法確立: xcodebuild -exportArchive(method=app-store-connect / destination=upload)+-allowProvisioningUpdates。認証はXcodeログイン済みApple IDセッションが正解(ASC APIキー2本はissuer不一致/ロール空でクラウド署名不可)。ビルド番号はpbxproj不変のままCLI上書き(CURRENT_PROJECT_VERSION=9)。配布は内部グループ「App Store Connect Users」全ビルド自動アクセスで追加操作不要。
  • 選手マスタ: J組3名をSQLで紐づけ(大迫敬介=282広島 / 早川友基=290鹿島 / 長友佑都=292FC東京)。海外7名は既にYou実施済みと判明。冨安は無所属のため0のまま(You判断・クラブ名省略表示)。
  • 久保の最近5試合: ソシエダ昨季6試合を updateFixture バックフィル→5件+グラフ3点+平均★6.6。
  • 上田の最近5試合(トムソン指名): 昨季フェイエ戦がDBに無い(Eredivisie取り込みは今季分のみ)+updateFixtureは既存行のUPDATE専用と判明→API-Football(キーはserverリポジトリの ApiFootball.php)から昨季5試合を取得し fnlive_fixtures へ手動INSERT(必須列は event_time/event_date、他はupdateFixtureが肉付け)→5件+グラフ4点+3G。副産物で渡辺剛5試合・小川1試合も蓄積。
  • 対象リーグ拡張(5c3a226): 夏移籍で板倉(アヤックス)・上田/渡辺剛(フェイエ)・小川(NEC)=エールディヴィジ、鈴木淳之介=スーペルリーガとなり5選手がダイジェスト対象外になる問題を発見→ digestLeagueIds() に88・119を追加、CronController側の取り込み判定コピーも共通参照へ一本化。デプロイ済み。
  • テーマ3宿題消化: コペンハーゲン is_publish=1・/cron/crawlNextFixtures/119 でスーペルリーガ20試合取り込み。
  • 検証: sim4状態(ダミー)+本番実データE2E(アジア杯1/11 PRE_MATCH 26人・クラブ名・並び順・/api4/jpPlayer 久保/上田)。S3毎分生成の継続も確認。

気づき・注意:

  • バックフィルの finished_time は time()(現在時刻)になるため「今日の海外組」に一時的に載る(設定戻し後24hで自然消滅・確認用にはむしろ好都合)。
  • 引数なしsim起動でダイジェストタブが自動表示されない観察(overseas has_display_content=trueでも)。タブ表示ロジックは今回未変更=ビルド7と同一挙動のはず。実機ビルド9で要確認、再現するなら別レーン調査。
  • LIVEの画面イメージにはポジション列が無いが、仕様書本文とシート期待結果に従いポジションありで実装(トムソンへ注記済み)。
  • デバッグ起動(DigestBaseURL指定)中は選手詳細がダミーURL参照になるため選手行タップは正しく動かない(本番検証は実機/APIで)。

次回やること: トムソンへ完了報告送付(ドラフト済み・ビルド9表記)→You実機でビルド9最終確認→トムソン再確認→設定4項目戻し(トムソン側で実施: 3/1/7/1)→アーカイブ→App Store提出。

2026-08-03

23:00 - fnDigestSetting の入力上限を0〜365へ拡張(3層一致)+トムソン返信

テーマ: トムソンLINE「national_days_after / overseas_recent_days に99を入れたい」対応。

やったこと: 制限が3層(モデルバリデーション0〜30・presenterの読み取りクランプ0〜30・providerのクランプ1〜90/0〜30)あると特定し、全て0〜365に統一(フォームだけ緩めても内部で丸められる構造だった)。テスト3スイートPASS。Youがcommit(6634a4d)・デプロイし、翌朝「99入力可+overseas_recent_daysは日数窓であって試合数指定ではない」の統合返信を送付。overseas_recent_days=65等の現行値はSQL直接投入だったためフォームを素通りしていた。


2026-08-01

19:56 - トムソンFBシート読解→ダイジェスト指摘2件の実装対応→デプロイ・DM返信完了

テーマ: トムソンのスプレッドシート「今後のご相談」に新設された「FNダイジェスト_フィードバック」タブの指摘2件(いずれも優先度:高)を読解し、server/iOS 両方の実装対応→コミット→デプロイ(You)→トムソンへDM返信まで完了。ダイジェストは「フィードバック1回目対応済み・トムソン再確認待ち」フェーズへ。

シートの読み方(今後も使う手法):

  • シートは約247MB・45タブの巨大ファイルで、Drive コネクタの xlsx/zip/pdf エクスポートは全滅(too large / timeout)。csv は先頭タブのみ。
  • 確実な読み方: read_file_content を includeComments なしで呼ぶ→結果が大きすぎて tool-results 配下のローカルファイルに保存される→python で解析。タブ見出しは無いが markdown テーブルの列数と先頭行で各タブを特定できる(コーパスは全タブ分入らず末尾切れ)。includeComments ありだとタブ名一覧とコメントだけでセルデータが空になる。手法はメモリにも記録済み。

指摘内容と対応:

  • 指摘① 試合詳細の日本人選手欄: 対象選手が両チームに1人もいない試合でも「日本人選手」見出し+空カードが表示される→セクション全体を非表示に。
    • server a33feee: buildFixturePlayerBlocks で表示行0件かつ両チームに表示対象選手(display_on=1 の team_id)が所属しない場合 japanesePlayers=null。所属者ゼロでも stats 行が残る過去試合(移籍直後)は表示維持。test_digest_presenter.php にテスト3件追加(計35アサーション PASS・php7.4コンテナ)。
    • iOS 92e2e4e: 試合詳細ヘッダーで選手0行のブロックは見出しごとスキップ(クラブ・代表両セル)。古いキャッシュJSON対策の保険。
    • 既存TestFlightビルドは japanesePlayers=null なら元々出さない作りなので、①はサーバーデプロイだけでも旧ビルドで直る。
  • 指摘② 日本代表の期間設定の解釈変更: 「日本代表モード」→「日本代表初期表示期間」。期間は表示可否ではなく初期サブタブ切替専用に。
    • iOS 79bad62: NationalDigest の表示ガードから nationalMode を除去。空状態はデータ0件のときのみ。②はアプリ配信必須(原因がiOS側ガードのため)。
    • 調査の結果それ以外は既に仕様どおりだった: サブタブ常時両方・期間内は日本代表初期表示(DigestPage)・タブ非表示は「期間外かつ海外組なし」のみ(DigestAvailability)・初期選択は現在日時最近傍/同距離未来優先・サーバーの代表戦抽出も期間非依存(±365日)。
    • server 0f30889: 管理画面の設定説明文(DB description)を「初期表示期間」文言へ。seed(003)更新+本番反映用 004_rename_national_mode_wording.sql 新規(説明文のみのUPDATE・値不変)。

やったこと(その他):

  • v2.6 仕様書HTML(日本代表初期表示期間・更新履歴追加版)を Downloads から repo へ取り込み・読解。指摘①は v2.6 未反映だが仕様書は v2.6 のまま修正しない方針(You 判断)。一度作った v2.7 は削除。
  • iOS シミュレータビルド SUCCEEDED(iPhone 17)。シェア機能まわりの既存未コミット差分(DigestCells/DigestShareImageRenderer/share アセット等)には触れず温存。
  • デプロイ: server push(Pipeline)・iOS TestFlight 配布とも You 側で完了。トムソンへDM返信済み(トムソン返信_ダイジェストFB対応_20260801.md)。

気づき:

  • シートのフィードバックタブは「No/画面・機能/現状/修正後/修正指示/再現手順/完了条件/補足/優先度/状況」の10列構造で、完了条件まで書かれており実装にそのまま落とせた。この形式は今後のFBラウンドでも機能する。

次回やること: ①トムソン再確認待ち→追加指摘あればシートの「状況」列とあわせて対応 ②本番DBへ 004(設定説明文UPDATE)の適用確認 ③確認完了後に一時設定戻し(1/7/1)→アーカイブ→App Store提出。

15:26 - トムソン質問「チームID紐づけ」→本番調査→対象選手Edit画面のチーム設定UI改修

テーマ: トムソンから「海外組でチームID未紐づけの選手にIDを紐づけたい」と質問。調査の過程で対象選手Edit画面のチーム欄が設定不能と判明し、UI改修(数値入力+名前検索補助)までデプロイした。

調査でわかったこと:

  • team_id=0 の11名の内訳を確定: 海外クラブ7名(要紐づけ)+Jクラブ3名(長友・大迫敬介・早川)+無所属1名(冨安)。後者4名は仕様上 0 のままが正。
  • 本番RDS確認(Web1号機経由SSHトンネル・現IPはSG許可済みだった): 3リーグ(88 Eredivisie / 119 Superliga / 848 ECL)は投入済み・cron補完済み。fixture取り込みは Eredivisie 96試合・ECL 4試合、Superliga は0件。
  • Superliga 0件の原因: crawlNextFixtures の取り込み判定 _shouldImportFixture は「どちらかのチームが is_publish=1」or「digest対象のハードコード14リーグ × is_jp_target」のみ。88/119/848 は14リーグに含まれず、コペンハーゲン(400)は is_publish=0 のため全滅。アヤックス等は既に is_publish=1 だったので Eredivisie は部分的に入っていた。
  • fillLeaguesForV3 はリーグ名補完のみで、チーム行の生成は fixture 取り込み時(crawlNextFixtures 内)に行われる。
  • 7名の現所属を API-Football(squads/transfers・6req)で裏取り: 板倉=アヤックス194(#4)・上田=フェイエノールト209(#9)・渡辺剛=209(#4)・小川=NECナイメヘン413(#18)・鈴木淳之介=コペンハーゲン400(#20)・菅原=サウサンプトン41(6/29にブレーメンから復帰・英2部=未追跡)・中村敬斗=ランス93(仏2部=未追跡)。

やったこと(footballnext-server・You がコミット 602de4c・デプロイ済み):

  • 対象選手Edit画面のチーム欄が空プルダウンで設定不能と判明(FormHelper の *_id 自動select化+belongsTo無し+選択肢変数無しの合わせ技)。プルダウン化はチーム数(数千件)的に不成立なので、以下で対応:
  • app/View/FnJpPlayer/scaffold.form.ctp 新規(この画面だけの差し替え・共通テンプレ不変): Team ID を数値入力に+直下に補助パネル(現在のチーム名を常時表示・fnlive_teams の日本語/英語名LIKE検索→候補クリックでID自動入力)。検索はローカルDBのみで API 消費なし。
  • あわせて add 画面の ID(API-Football選手ID)入力を解禁。リーグと同じ「PK強制hidden」問題で、選手の画面からの新規追加も実は不可能だった。
  • FnJpPlayerController::searchTeam 新設(JSON・admin認証下・q=LIKE / id=完全一致・20件)。
  • FnJpPlayer にバリデーション追加: team_id は「0 or fnlive_teams 実在ID」のみ(id=0行誤参照事故の再発防止。cron側は validation を通らない経路なので影響なし)。
  • php:7.4 コンテナで3ファイル lint 全PASS。

詰まったこと / 気づき:

  • ローカルから RDS 直結は不可(ホスト名がVPCプライベートIPに解決される)。7/23に確立した Web1号機(3.112.232.196)SSHトンネル経路が引き続き有効で、現IPが SG 許可済みだったのでそのまま使えた。
  • 「リーグを追加すればチームもすぐ揃う」は半分だけ正しい。チーム行は fixture 取り込みの副産物なので、is_publish のチームが1つもないリーグ(今回のSuperliga)はリーグを足しても何も取り込まれない。

次回やること: ①新Edit画面で7名を紐づけ ②コペンハーゲン(400) is_publish=1 化(Portalチーム画面のEditで可) ③/cron/crawlNextFixtures/119 を一度叩く(放置でも24h後に自動巡回) ④overseas.json への反映確認 → トムソンへ紐づけ手順の返信。

13:36 - 実況板リーグ追加のポータル化(API確認つき追加画面)

テーマ: トムソンからのリーグ追加依頼(119 Superliga / 848 UEFA Europa Conference League / 88 Eredivisie)を機に、これまで phpMyAdmin で fnlive_v3_leagues に id を手挿入していた運用をポータルから行えるようにした。

調査でわかったこと:

  • ポータルには scaffold の「リーグV3」ページ(FnliveV3LeagueController)が既に存在していたが、CakePHP の FormHelper が主キーを強制 hidden にするため add で id を指定できず、それが phpMyAdmin 運用になっていた原因。
  • 「id だけ挿入すればうまくいく」仕組みの正体は 5 分毎の cron fillLeaguesForV3(team_created=0 の行を API-Football v3 から name/type/logo 補完して team_created=1 化)。

やったこと(footballnext-server・未コミット):

  • FnliveV3LeagueController に addConfirm アクションを新設: ID入力 →「APIで確認」で API-Football からリーグ名・種別・国・ロゴを取得して表示 →「このリーグを追加する」(JS confirm つき)で INSERT の2段階フロー。ビュー add_confirm.ctp 新規。
  • INSERT は id + name + type + logo を入れつつ team_created=0 のままにして、実績ある cron 補完経路を温存。登録済み ID は赤字警告を出し追加ボタンを出さない(追加時にも再チェックで重複 INSERT 防止)。不正 ID / API 未ヒットはエラー表示で再入力へ。
  • Portal/index.ctp 実況板セクションに「リーグV3追加」リンクを追加。
  • scaffold 共通テンプレ(カスタマイズ済みの lib/Cake/View/Scaffolds/index.ctp)の disableIndexActions に 'Add' 対応を追加し、FnliveV3League だけ「New Fnlive V3 League」ボタンを非表示に(id を指定できない壊れた導線を排除し、追加を addConfirm に一本化)。他の scaffold 画面には影響なし。
  • 認証は既存の Auth(admin ロール)配下に自動で入る。php:7.4 コンテナで全変更ファイル php -l PASS。

詰まったこと / 気づき:

  • docker で PHP lint する際、ホストの lib/ をコンテナの /lib にマウントするとシステムライブラリを潰して entrypoint ごと死ぬ。マウント先は /src 等の無害なパスにする。

次回やること: You がコミット→CodeCommit push→Pipeline デプロイ → 新画面からトムソン依頼の3リーグ(119 / 848 / 88)を投入して動作確認。

2026-07-24

20:30 - 試合詳細の選手欄刷新+本番データでの不具合修正+トムソン確認用の配布準備

テーマ: クラブ/日本代表の試合詳細の日本人選手欄を仕様に寄せて作り直し、本番データで初めて露見した不具合を潰して、トムソンに触ってもらえる状態にした。

やったこと(iOS・UI):

  • クラブ試合詳細の選手欄を刷新: 選手カードを上部固定エリアから関連記事テーブルのヘッダーへ移し、得点者・記事と一緒にスクロールするように変更。見出し「日本人選手」を得点者と同列のカード外見出しへ、チーム名の左にチームロゴを追加。選手間の境界線・「#10 MF・フル出場」のサブ行・「ほか◯名を表示」の開閉を削除して常時全員表示。行高42→32pt。出場列に status 表示(bench=出場なし / not_registered=登録外)と行末シェブロンを追加。
  • 日本代表試合詳細の選手欄も同仕様へ統一(行高は24pt)。
  • 記事テーブルの初回ロード時の自動スクロールを抑止(CoreTableView)。追加読み込み用の「ちょっとスクロール」が初回にも走っていて、選手カードがヘッダーに入って高さが出たことで目に見えるズレになっていた。
  • 選手詳細の細部: セクション見出しの文字・アイコンを約0.8倍、読み込み中スピナーがタイトルと重ならないようアイコン枠へ移動、関連ニュースのサムネイル読み込み中を既存記事セルと同じ loading_l に統一、記事間の境界線を削除、選択ハイライトを新着の記事セルと同じ標準挙動に。
  • 代表戦ナビのカード幅を内容追従に(固定138/124 → 左右8pt余白・下限90pt・上限138pt)。

やったこと(サーバ・DB):

  • 代表スカッド未登録の11名を選手マスタへ追録(冨安・長友・板倉・上田・菅原・中村敬斗・小川・渡辺剛・大迫・早川・鈴木淳之介)。海外追跡クラブ非所属なので team_id=0。本番RDSへ適用してマスタ54→65名。シード正本 digest-jp-players-seed_20260723.sql にも追記(operation ce1545d)。
  • jpPlayer API で team_id=0 のときチーム解決をスキップ(server 2a54c6a・デプロイ済み)。fnlive_teams の id=0 行に誤一致して冨安の所属が「ゾリャ・ルハーンシク」と表示されていた。
  • 前セッションで作った related_articles 追加(server c354fa5)もこの日にデプロイ。Pipeline 2回とも Succeeded、本番 /api4/jpPlayer で確認。

やったこと(本番データで露見した不具合):

  • 関連記事のデコードが本番で失敗していた(「データのフォーマットが正しくない」)。CakePHP がDB値を文字列で返すため "id": "2460525" となり、Int 決め打ちの DigestRelatedArticle が落ちていた。数値/文字列の両対応に修正し、同じ型ずれをローカルでも踏めるようダミーJSONも文字列型へ揃えた。
  • 海外クラブ非所属の選手はクラブ直近5試合ブロックごと非表示に(見出し・グラフ・リスト・合計をまとめて出さない)。

やったこと(配布・ドキュメント):

  • 実機(iPhone 14)へ直挿しビルドで一通り確認。TestFlight 配布は You 側で実施。
  • トムソン確認依頼の説明文を作成: トムソン確認依頼_ダイジェスト_20260724.md。画面ごとの見どころ、管理画面(対象選手 / 表示期間設定)の説明、シーズンオフゆえのデータ薄さと一時設定を広げている旨、評価グラフの3試合条件までカバー。

詰まったこと / 気づき:

  • ダミーJSONと本番レスポンスの型ずれはローカルでは絶対に検出できない。今回の related_articles がまさにそれで、シミュレータのダミーでは通るが本番だけ落ちた。ダミーは本番と同じ型で持つ、を徹底する。
  • シミュレータを再起動すると -Digest*Path 引数が落ちて本番データに戻る。ダミーで見たいときは xcrun simctl launch で毎回明示する。逆に本番で見たいときは引数なしで起動すればよい。
  • 「評価グラフが出ない」「試合が2件しかない」は仕様どおり(正本に「評価データ3試合以上」と明記)。ダイジェスト用の成績テーブルは収集開始以降の試合しか持たないため、シーズンオフの今は薄く見える。

次回やること: トムソンのフィードバック対応 → リリース前に一時設定を戻す(national_days_after=1 / overseas_upcoming_days=7 / overseas_recent_days=1)→ アーカイブして App Store 提出。


2026-07-23

13:40 - 今日の海外組ダイジェストのプレビュー対応(遡り期間の設定値化+5月試合バックフィル)

  • 今日の海外組が確認できない件: 対象8リーグがオフシーズンで、終了24時間以内の試合がゼロ(最新は5月末の前シーズン最終節)。実データで自然表示されるのは8月開幕後。
  • プレビュー対応(server 93120eb): 「今日」の遡り日数を overseas_recent_days(既定1=24時間・1〜90)として fn_digest_settings に追加。DataProvider取得窓とPresenterのtodayフィルタ両方が参照。buildOverseasは省略時1の互換引数でテスト35+32 PASS維持。
  • 一時設定+バックフィル: overseas_recent_days=65 に設定し、5/20〜5/31の対象試合15件を updateFixture/{id} でバックフィル。MVP=横山歩夢(ヘンク・★8.2 1G)ほか実データで「今日のMVP」「今日の海外組」一覧が正常表示。シミュレータで確認(更新バッジ点灯も確認)。
  • ⚠️ 一時設定(要戻し)に追加: overseas_recent_days=65 も after=30/upcoming=30 と同様、確認後 or リリース前に 1 へ戻す。

12:50 - You レビューのUI指摘対応(タブ中央寄せ・サブタブアイコン)

  • 上部タブの右寄り解消(ios ffa6be9): TabViewの中央寄せが「中央タブ境界を画面中心に合わせる」実装で、幅広の「ダイジェスト」タブがあると全体が右に流れて見えた。タブ群全体の幾何中心を画面中心に合わせる方式へ変更(4タブ/3タブ両方で目視確認済み)。
  • サブタブのアイコン欠落(ios 8b5d4da): 正本画像のサブタブは「地球 海外組」「日の丸 日本代表」だが実装は文字のみだった。絵文字はiOS 26のUISegmentedControlで欠字(⍰)になるため、SF Symbol(globe.asia.australia.fill)+Core Graphicsで描いた日の丸をテキストと合成した画像で実装。VoiceOverラベル維持。
  • You のレビュー継続中(追加指摘があれば随時対応)。

11:40 - トムソン確認用の実データ表示化(海外組日数の設定値化+一時拡大・代表モードON・W杯バックフィル)+PHP5.3バグ修正

目的: トムソンに実物UIを確認してもらうため、仕様の枠内でダイジェストを「今」表示させる。

  • 海外組「これから」日数を設定値化(server fb02648): overseas_upcoming_days を fn_digest_settings に追加(既定7=仕様値・0〜30・管理画面可変・削除保護)。DataProvider取得窓とPresenter表示フィルタの両方が参照。テスト35+32 PASS維持。
  • 一時設定: national_days_after=30(日本最終戦6/30から7/30まで代表モードON)・overseas_upcoming_days=30。いずれもトムソン確認後 or リリース前に 1 / 7 へ戻すこと。
  • バックフィル: 日本のW杯4試合+アイスランド戦を updateFixture/{id} で投入(各24〜26行のstats)。MVP実データ: ブラジル戦=冨安★7.7・チュニジア戦=上田★9.7・オランダ戦=鈴木彩艶★7.9 等。
  • 🐛 本番E2Eで3個目のバグ発見・修正(server a437757): 本番はPHP 5.3.29で、5.3のクロージャはクラススコープを持たないため DigestPlayerSorter のusortコールバック内private呼び出しがFatal error。stats投入後に初めて実行される経路でローカルPHP7.4テストでは検出不能だった。該当メソッドをpublic化し、本番機のPHP5.3で全Lib lint+ソート経路スモークPASSを確認して修正。
  • E2E確認: S3 overseas.json=upcoming 12件(セルティック開幕戦〜JPL各節〜ベティスvsソシエダ・日本人選手名入り)/ national.json=mode ON・W杯実データ。シミュレータのクリーンインストールでタブ自然出現→海外組カード・代表ナビ・MVP表示すべて正常。既知の軽微事象: 空データ時代のアプリ内キャッシュが残る起動ではタブ出現が1起動遅れることがある(新規インストールユーザーには無関係)。
  • 残り: You が 5.6.0 を TestFlight へ → トムソン実物確認 → 設定戻し(after=1・upcoming=7)→ リリース調整。

10:00 - ⑦iOS準備完了(5.6.0バージョン設定・sim実データ確認・push済み)+チーム名整備の追い足し

  • バージョン5.6.0設定(build符号化50600・A/B min_build=50505を満たしエンロール継続)→ ios a7d5c17 としてcommit・9コミットをGitHubへpush済み。
  • シミュレータで本番データE2E確認: 5.6.0ビルドSUCCEEDED・起動OK。ダイジェストタブは表示対象なし(海外組空+代表モード外)で非表示=仕様2-5どおり。DEBUG引数で本番S3を指定して強制表示し、海外組空状態・日本代表空状態の文言表示を確認。既存の試合一覧(W杯決勝まで)・新着も正常。
  • 取り込み拡張の実動確認: セルティック開幕戦(8/4)・JPL各節・ソシエダ戦など対象クラブの将来fixtureが既に取り込み済み。8月開幕が7日以内に入り次第overseas.jsonに載りタブが自然表示される見込み。
  • チーム名の追い足し整備: 将来fixtureに登場する名前未整備12チーム(ロンメルSK・ベフェレン・ザンクト・ガレン・マラガ等)の name_long/medium/short/name_ja を投入→未整備ゼロに。
  • 残り(You作業): 実機確認(Xcode直挿し)→ アーカイブ → App Store提出(リリース時期はトムソンと調整)。

09:10 - ダイジェスト運用適用①〜⑤完了(DDL・デプロイ・選手マスター54名・crontab・S3配信)

テーマ: レビューア側TODO(運用適用)を①〜⑤まで一気に実施。トムソン確認は You 回答「試合一覧増加OK」で解消。ダイジェストのサーバー側は本番稼働開始。

やったこと:

  • ①DDL適用: 3ファイルを本番RDSへ適用(MySQL 8.4.8・DB既定utf8mb3だが新テーブルはutf8mb4指定・接続encoding=utf8mb4で整合)。002が fnlive_teams に存在しない del 列参照で失敗→インデックスを is_jp_target 単独に修正して適用(server bf9b38b)。対象リーグ14件は全件登録済み・del=0だった(id 7/30も既存)。
  • ②server push=デプロイ: 6コミットpush→Pipeline Succeeded。後方互換smoke PASS(/v1/liveHistories・/v2/fixture・/api4/articles 全200)。fixture取り込み判定は選手マスター空なら現状同一(試合増加は③以降)と確認してからpush。
  • ③選手マスター初期投入: API-Footballで8リーグ×2025全選手走査(283req・日本人56名抽出)→transfers照会56req→超WORLDサッカー7/22版で2026-27夏移籍26件を突合→JPL 2026-27構成・ラスパルマス昇格失敗をAPI/Webで確認→29クラブの現行スカッドAPIで在籍検証(背番号取得)→54名投入(active 52・needs_review 22・南野/守田は契約満了でdisplay_on=0)。seed SQLは digest-jp-players-seed_20260723.sql に保存。
    • 落とし穴: Le Havre のAPI ID誤り(79=リールだった→111)。fnlive_teams実データとの突合で発見。
  • チームname_ja: 既存 name_medium を name_ja へ一括コピー(666件)→ダイジェスト表記が既存アプリと同一に。syncJpTargetTeams 実行で26クラブ+日本代表に is_jp_target=1。
  • 日本代表スカッド: crawlNationalSquad 初回実行(26名・W杯2026メンバー)+name_ja 26/26投入(crawlはname_jaを上書きしない設計)。
  • ④crontab適用: cron EC2に4行追加(バックフィル10分・スカッド日次4:17・S3生成毎分×2)。適用前バックアップ ~/crontab_backup_20260723.txt。
  • ⑤S3配信・E2E: 本番E2Eでバグ1件発見・修正: DigestDataProvider が親モデルだけ useRO() し belongsTo先が default のままでdatasource名不一致→CakePHPがJOINを生成せず FnliveHomeTeam.id Unknown column(M7で「DataProvider本番E2E未検証」と明記されていたリスク領域そのもの)。関連3モデルもROに揃えて修正(server 2eca081・デプロイ済み)。S3 digest/overseas.json(空構造・8月開幕まで正常)/digest/national.json(代表戦実データ・日本語表記OK)公開200確認。/api4/jpPlayer/32862 で久保建英の全項目返却OK。管理画面3URLは302(Portal認証)疎通のみ確認。

接続経路メモ: RDSは非公開→Webサーバー1号機(3.112.232.196)経由SSHトンネル。cron EC2経由はMySQLユーザーのホスト制限で不可。SG(sg-0461787f6302fd838)のport22に現IPを追加(claude digest deploy 20260723)。MySQL 9.6クライアントは MYSQL_PWD 非対応・パスワードは -p 直渡し。

残タスク: ⑥管理画面の目視確認(Portal→対象選手/表示期間設定)・⑦iOS実機確認→バージョン設定→App Store提出(トムソンとリリース時期調整)・トムソンへ実装完了報告。needs_review 22名の精査(S.Saito/N.Schmidt/K.Kosugiの日本語名、移籍直後組のスカッド反映待ち)。任意: 直近代表戦の過去fixtureを updateFixture/{id} で個別バックフィル(評価/出場データ埋め)。


2026-07-22

23:22 - ダイジェスト新機能 実装フェーズ完走(Codex分業 M0〜M7・全レビュー承認)

テーマ: トムソン実装許可→Codex実装指示書作成→Codex分業でM0(実装計画)〜M7(総合検証)を1日で完走。Claudeが各マイルストーンをレビューし、指摘18件(R-1〜R-18)を全消化して最終承認。

体制と成果物:

  • 指示書 codex-digest-implementation_指示書.md(M0レビューゲート・push禁止・API実コール上限100req・既存API後方互換などの絶対ルール)+仕様書正本をリポジトリに取り込み(5041cdf)。
  • サーバー5コミット(収集基盤/配信API/管理画面+レビュー修正2)・iOS8コミット(海外組/選手詳細/日本代表/試合画面+バッジ+レビュー修正3)。全てローカルcommitのみ・push未実施(server push=本番デプロイのため)。
  • 記録は codex-digest-notes.md に全マイルストーンの完了報告+レビュー結果を蓄積。スクショは codex-digest-shots/ に約30枚。
  • 開発中のAPI-Football実コールは累計4req。CLIテスト: lib 35 / presenter 32 assertions PASS。最終HEADのiOSビルドはレビュー側でも独立実行し BUILD SUCCEEDED。

レビューで本番到達前に潰した主な問題:

  • チーム登録の insertIgnore→upsert化で手動整備済み日本語チーム名が毎クロールAPI英語名で上書きされる回帰(R-3。本番 /v1/liveHistories の実データ確認で発覚)
  • アジアカップ(2027年1月)・W杯予選が代表戦の取り込み対象外でリリース半年でナビが空になる問題(R-7。リーグID 7/30 を実APIで確認して追加)
  • 手書きiOSテストデータと実サーバー契約のドリフトによるLIVE経過時間の二重表示(R-14→再発防止としてR-16: fixture手書き禁止・サーバーPresenterテスト生成物を sync_from_server.sh で同期する規約化)
  • 過去代表戦で選手名が空になるスカッドキャッシュ依存(R-8→stats名スナップショット)、KO+3.5h超で終わる試合が「今日」から漏れる観測窓エッジ(R-1)ほか

設計上の要点(実装で確定):

  • v3 /fixtures?id= 全部同梱1reqを活かし、既存 ovserveFixtures の監視対象拡張+捨てていたstats保存だけでデータ蓄積(新規ポーリングなし)。ダイジェスト2種はS3静的JSON(cron毎分生成・preview引数付き)、選手詳細は /api4/jpPlayer/{id}(DBCache 30秒)。既存APIはキー追加のみで後方互換。
  • 更新バッジは端末保存の確認済みfixture ID集合との差集合方式。サブタブlazy生成により「代表側初期表示では消えない」仕様を構造で担保。

次回やること: 運用適用(notes「レビューア側TODO」参照): DDL適用→server push→対象選手マスター・日本語名パイプ→crontab→S3確認→実機確認→App Store提出。トムソンへ実装完了報告(試合一覧の試合増加の確認回答も待ち)。

13:57 - トムソン新機能提案「海外組・日本代表ダイジェスト」仕様読解+API-Football実叩き検証(実装可能と判断)

テーマ: トムソンから開発仕様書HTML(~/Downloads/FootballNEXT_海外組_日本代表ダイジェスト_開発仕様書_構成調整版.html・6.8MB・画面イメージ21枚埋込)を受領。読解→仕様書内「要確認」だったAPI実現性を実叩きで検証→DM返信文面作成まで。

仕様概要:

  • 新規3画面(海外組ダイジェスト=主役/選手詳細/日本代表ダイジェスト)+既存修正3画面(試合一覧・クラブ試合詳細・日本代表試合詳細)+上部ダイジェストタブ・更新バッジ(ピンクドット)。
  • 対象: 現行8リーグ(スコットランド/ベルギー/ラ・リーガ/セリエA/プレミア/リーグ・アン/ブンデス/ポルトガル)+CL/EL の日本人選手。国内カップ戦・対象外リーグは初期スコープ外。
  • 状態別表示(PRE_MATCH/LINEUP/LIVE/FINISHED)・欠損処理・共通ソート・日本代表モード期間(試合3日前〜翌日・管理画面可変)まで定義済みで練度は高い。
  • トムソン側の数値目標: DAU 1万(MAU1.7万の60%前提)・インストール+500/月・収益50万円/月。X運用(月30投稿・CTR3%/CVR10%)前提入り=開発側コミット数字ではない扱いに注意。

API検証結果(RapidAPI API-Football・実叩き約25リクエスト)→ 現行プランで実現可:

  • 現行プランは 7,500req/日(レスポンスヘッダーで確認)。直近24hの実消費 ~85req(オフシーズン)。
  • カバレッジ: 対象8リーグ+CL/EL+親善試合+W杯すべてで events/lineups/statistics_players(=評価・出場時間・G/A)提供あり。未開幕2026-27だけ全falseだが、これは毎年開幕時にtrueへ切り替わる仕様(2023〜2025は全true)。
  • 実データ確認: ベルギー(ヘント対ヘンク・橋岡大樹の評価7.9取得)・スコットランド(セルティック対ハーツ 40人中32人評価あり。欠けたのは昇降格プレーオフのみ=仕様の欠損処理で吸収可)・日本代表(W杯ブラジル戦・親善アイスランド戦とも評価あり)・代表招集26人は /players/squads?team=12 で取得可。
  • 決定的事実: v3 /fixtures?id=X は events/lineups/選手stats(出場時間・評価・G/A・ポジション・先発フラグ)全部同梱で1リクエスト。既存 ovserveFixtures(KO-1h〜+3.5hを2分間隔・1試合1req)が既にこのエンドポイントを叩いている → 監視対象を日本人選手所属チームの試合へ拡張+今捨てている選手statsをDB保存するだけでダイジェストのデータはほぼ賄える。選手詳細の直近5試合も蓄積から構成。
  • 見積もり: 最繁忙日(土曜に日本人試合20〜25件重複)でも +2,700〜3,400req/日=現行プラン内。必要ならダイジェスト専用試合のポーリングを3〜5分間隔へ調整で安全圏。
  • APIで取れないもの: 日本語名(ローマ字のみ)→ Claude(サブスク内Claude Code)でローマ字名+チーム→日本語名解決・WebSearch裏取り・needs_reviewフラグ運用で初期リリースから対応可能と判断。クラブ名・リーグ名の日本語表記も同パイプで対応(チームマスターにも日本語名カラムが必要)。

やったこと(その他):

  • トムソンへのDM返信文面を作成(実装可能・API検証済みで追加費用不要見込み・日本語名も初期対応見込み・工数見積もり後送)。数値目標への言及は意図的に非含有。
  • 仕様書テキスト・画面イメージは scratchpad に抽出(一時ファイル・未保存)。

次回やること: トムソン返信後に工数・スケジュール見積もり+フェーズ分割案の作成。

08:30 - 起動時広告A/B: dev-timer #31 成熟後残差率の初回read(ガードレール④ PASS)

テーマ: dev-timer #31(7/22 17:05予定)を前倒し実行。ガードレール④=成熟後残差率の初回read。GA4 Data API(property 211701453)。

母集団/定義: config_version=cv_9 かつ成熟 target_cohort_day 07-08〜07-19(maturation=3日・watermark=07-19、07-20/21/22は未成熟で除外)。残差率 = 1 − (content_reached + launch_no_content)/target_launch(群別・イベント数)。5.5.3レガシー勢((not set)/cv_8)は母集団外として除外。

結果(PASS):

  • A(50分/対照): target_launch 150,185 / content_reached 114,947 / launch_no_content 27,159 → 残差 8,079 = 5.38%
  • B(1440分/介入): target_launch 148,656 / content_reached 121,476 / launch_no_content 21,730 → 残差 5,450 = 3.67%
  • 判定: 絶対 A5.38%/B3.67% 両方 <10% ✓、群間差 1.71pp <3pp ✓ → ガードレール④ PASS。主指標を判定保留にしない。
  • 日次安定: per-day で A5.2-5.5% / B3.7-3.9%、外れ日なし(配送グリッチ・実装漏れの兆候なし)。
  • SRM: クリーン新コホート(enrollment_day 07-06〜07-19)A=582 / B=564 users(50.8/49.2%・χ²≈0.28・p≈0.59)→ SRM無し ✓。
  • GA4品質メタ: 約30万event(≪1000万の sampling 閾値)・低カーディナリティ → sampling/thresholding/(other) の結論影響なし。

参考(主指標の向き・非確定): 未到達離脱率 launch_no_content/target_launch は A=18.08% / B=14.62%(B が 3.46pp 低い)=仮説の向き。前回 first-look(A19.7/B15.8)と整合。終了日固定・途中判定しない方針につき結論ではない(本解析は9月中旬)。

後始末: dev-timer #31 を done 化。次チェックポイント登録=#36(8/17)enrollment窓クローズ確認+残差率/SRM 中間再read・#37(9/15)本解析(残差率確定・未到達離脱率本判定・円/人・D7/D14/母集団 enrollment_day>=2026-07-06 かつ cv_9)。


2026-07-11

17:22 - 起動時広告A/B: dev-timer #30 v9再スタート健全性確認(GA4 Data API・全項目クリーン)

テーマ: v9 配置(7/8 00:55)から3日後の健全性確認。dev-timer #30 の通知起点。GA4 Data API(property 211701453)で6クエリ実施。

結果(全項目クリーン):

  • ① 新コホートSRM なし: experiment_enrolled(ISO enrollment_day)A=145/B=144(7/7〜7/11途中・χ²≈0.003・p≈0.95)。日別も 7/8 31/31・7/9 48/47・7/10 50/51 と均衡。
  • ② cv_8 残滓は急減: 1,568(7/8)→873→658→68(7/11途中)。キャッシュv8のTTL/再取得で消滅方向。cv_9 は 23.6k→61.9k→85.1k(7/10) と主流化。(not set) 大量(7/10=369k)は5.5.3勢のキャッシュv8実験分で日々減少中(556k→369k)・cv_9フィルタで解析除外のため無害。
  • ③ enrollment_day 形成正常: ISO 2026-07-07〜07-11 で形成。(not set) enroll(A=273/B=232)は5.5.3勢=解析除外。
  • ④ 追加確認: applied_interval A=50.0/B=1440.0 ぴったり(cv_9 decision A=31,232/B=30,752件)。kill_observed 全61,984件が文字列"false"・true=0件。days_since_enrollment は (not set) ゼロ・d00〜d17のみ(d13〜d17の山=汚染コホート勢・d00〜d05=クリーン新コホート)・D28+なし=窓外gate機能。funnel全イベント着弾。fn_ad_revenue A=4,637人/B=4,696人 両群着弾。

気づき(要留意):

  • クリーンコホートの蓄積は約100人/日(新規インストール由来のみ。既存5.5.5ユーザーの大半は旧enrollment_dayを引きずり除外)。窓終了8/17までで**総勢約4,000人(各群約2,000人)**見込み=6月点火時の初速(3日で9,400人)よりだいぶ小さい。実質再スタートの織り込み済みの帰結だが、9月中旬の本解析の検出力見積もりはこの規模感が前提。
  • ga4 MCP の dimension_filter は snake_case(field_name/string_filter/and_group)でないと Invalid structure で弾かれる。

次回やること:

  • dev-timer #31(7/22 17:05・登録済み確認済) 成熟後残差率の初回read(D0-D7)。
  • 本番解析(残差率・円/人)は9月中旬頃。母集団 enrollment_day>=2026-07-06 かつ cv_9。

2026-07-08

17:07 - 起動時広告A/B: コホートparam型ズレ修正〜5.5.5リリース〜v9再スタートまで完了(複数日の一連作業まとめ)

maturation明けの first-look を起点に、計測欠陥の発見〜修正〜リリース〜実験クリーン再スタートまでを一気通貫で完遂。

やったこと(時系列):

  • 6/28 first-look で重大欠陥発見: GA4 で機構は健全(SRM無し A4720/B4712・p≈0.93/applied_interval A50/B1440/kill 0件)だが、target_cohort_day/enrollment_day/days_since_enrollment/config_version/kill_observed が全件(not set)。根本原因=数値/Bool param を text の event-scoped custom dimension に送っていた型ズレ(String dimensionの assigned_group・metricの applied_interval は正常)。
  • 修正(emit境界で非数値ラベル化): ABTest.ga4*Label helper 追加(保存schemaのInt/Boolは不変)。日付→ISO 2026-07-06、days_since→d00〜d27、config_version→cv_N、kill_observed→"true"/"false"。ABAdCollector の追跡窓外(D28+)送信を gate、ga4DayLabel に範囲+実在日validation。
  • gods-talk で Codex 3ラウンドレビュー(方針→実コード→最終確認)。high2件(数字だけ文字列不可・config_version同型ズレ)他すべて反映、release可判定。ios master 28af0f1/698cf5f/83a4796。
  • Apple 5.2.3 却下対応: 5.5.4 提出→YouTube discovery で却下。Thomson合意でハンバーガーの「Youtube動画」+トップ新着カルーセルを一時非表示(Menus.movie と switch をコメントアウト、HorizontalMoviesRow を #if false。可逆)。返信せず新ビルドで再申請方針。
  • バージョン迷走→5.5.5で確定: ASCで5.5.5版が作れず一時5.5.4戻し(勝手な版上げを指摘され修正)、最終的に You 側で 5.5.5 として TestFlight→申請。7/6 リリース。
  • 7/7 本番着弾検証成功: appVersion=5.5.5 で 5 dimension 全て non-empty 着弾確認(旧5.5.3は(not set))。予測どおりの汚染(5.5.3既enroll→5.5.5更新勢の古いenrollment_day d07〜d13)も実測。
  • 7/8 v9 config を S3 配置(実質再スタート): aws s3 cp --acl public-read --profile thomson-ik で v8→v9 差し替え(min_build=50503→50505・enrollment窓 6/24-8/5→7/6-8/17・config_version=9)。公開URLで HTTP200・v9着弾確認。旧v8はscratchpadにバックアップ。GA4メモ(v9再スタート・7/8)記入。
  • cv_9 着弾確認: 配置12時間後、5.5.5 の約89%が cv_9(2839件)。クリーン再スタート成功。

決めたこと(DECISIONS 2026-06-28×3・2026-07-07 に記録済):

  • experiment_id は据置(アプリ側ハードコード+validate一致要求で config 変更不可)。クリーン境界は min_build=50505+解析フィルタ enrollment_day>=2026-07-06+窓取り直しで担保。
  • DebugView 提出前確認は任意化(dimensionマッピングを判定できないため)、確定検証は配布翌日 Data API に一本化。

次回やること:

  • dev-timer #30(7/11) 再スタート後の健全性確認(新コホートSRM・cv_8残り消滅・enrollment_day>=7/6形成)。
  • dev-timer #31(7/22) 成熟後残差率の初回read(D0-D7)。
  • 本番解析(成熟後 残差率・円/人)は 9月中旬頃(窓終了8/17+追跡28日+成熟3日)。母集団は enrollment_day>=2026-07-06 かつ cv_9。
  • site-ad-scoring WIP は ios stash@{0} 温存中(別レーン)。

2026-06-20

実況板(LiveHorizontalListCell)が「もっと見る→上に戻る」で壊れる不具合を修正(5.5.3 申請前の単発バグ潰し)

テーマ: 5.5.3 申請前に、AB テストとは無関係の UI バグを1件修正。新着タブ先頭の実況板(横スクロールのライブカード)が、記事一覧の「もっと見る」(offset>0 load)を一度してから上に戻ると、ライブカードが消えて緑の「過去 実況版一覧へ」だけが左端に残る壊れ方をしていた(ios f275fed)。

真因: TopArticleTableView.importContents が offset 問わず毎回 realm.delete(realm.objects(Live.self))→content["lives"] から再追加していた。もっと見る(offset>0)のレスポンスは lives が空なので全削除だけ走り Live が DB から消滅。一方、先頭の LiveHorizontalListRow(offset==0 で生成)は古い liveIds を保持し続け、LiveHorizontalListCell.puts は毎回 DB.objects(Live.self).byID(liveId) で引き直すため nil → カード0枚。row.liveIds.count > 0 は保たれるので moreView だけ offsetX=0 に出ていた。

やったこと:

  • 修正①(根本): Live の更新(delete+再追加)を offset==0(フル取得/プルリフレッシュ)時のみに限定。もっと見るでは Live を消さない。
  • 修正②(防御): puts で moreView を「カードが1枚以上描画できたとき(offsetX>0)」だけ出す。あわせて再利用時クリーンアップを LiveListView+LiveListMoreView 両方 remove に拡張し、横スクロール位置を .zero リセット(累積・残留の予防)。

検証: 実機テストで「もっと見る→上に戻る」でカードが正しく残ることを確認(You)。Debug build 0 errors・改修2ファイル新規警告なし。最初 cell 側(moreView 累積)だけ直して的外れ→スクショで真因がデータ側(Live 全削除)と判明し②から①へ。

気づき/判断: importContents は LiveArticleId / LogoLink / Enquete も同様に offset 問わず delete-all している(もっと見るで一時的に消え得る潜在バグ)。今回は報告のあった Live のみ修正。残り3つは表示が DB 引き直しでないか別経路のため即時破綻はしていないが、いずれ offset==0 ゲートで揃えるのが望ましい。5.5.3 申請に本修正を含める。

23:00 - 起動時広告A/B 実装レビュー round10: round9 medium close・sign-off 回復・実装レビューレーン完了

テーマ: round9 で差し戻された medium(R7-#2 revert による render 失敗の過大計上)の修正(gate 復元+#if DEBUG 化、ios 311e3ad / op 4e489da)を gods-talk round10 でレビュー。clean close。

round10 feedback(ab-test-implementation-review-010):

  • [high] 該当なし / [medium] 該当なし。renderNativeAdView の戻り値を保持し true 時のみ recordImpression → round9 medium「render 失敗の過大計上」は解消。false 経路(releaseCurrentLease→releaseAd→adObject=nil→hidden)は main 直列で race・二重 destroy・負 lease・retain cycle なし。loader(for:) の !expired 維持で「live entry render 失敗=Bool gate」「expire 済み再render=loader 取得失敗」両方が閉じる。実行ロジックは 31dc22b の R7-#2 と機能的に同等へ復帰(差分はコメント+expire hook の Debug 限定化のみ)。round9 medium は close。
  • [low] expire hook の #if DEBUG 隔離 完了 — release に残る参照経路なし・Release build 成功で確認。round9 low 推奨を満たす。
  • [low] round8 残 low(render false 即時 reload なし/timeout・render false の XCTest 未整備/kill_observed_at 説明文)は非ブロッカーの将来課題。
  • 点火可否: 311e3ad で round9 medium 解消・round8 sign-off 回復。現行コードで 5.5.3 提出可、点火前チェックリスト完了後に S3 active=true で開始可。新 high/medium ブロッカーなし。通常 native 表示の短い実機再スモークは望ましいが medium close の必須前提にしない(gate は sign-off 済み実装へ復帰・A-1 で rendered=true/描画/ILRD/GA4 impression 確認済み・Release 追加差分はコメントのみ)。

気づき/判断: round9 で私が入れた誤った revert を round10 で正式に巻き戻し close できた。reviewer が実機再スモークを必須前提から外したため、コードレビュー上のブロッカーはゼロ=実装レビューレーン完了。残るは純粋に運用・外部タスク(GA4 2件登録・5.5.3 提出・点火)。

現在地: 実装レビュー round1〜10 完了・sign-off 確定。本番中立(active=false/v7/ttl14日)、5.5.3 据え置きで全修正積載。

次回やること: 5.5.3 提出/リリース(任意で提出前に通常 native 表示の短い実機スモーク)→ GA4 custom定義 2件を手登録 → S3 を v8/active=true・enrollment窓(リリース日起点42日)・min_build=50503 で点火+点火ログ記入。

22:30 - 起動時広告A/B 実装レビュー round9: R7-#2 revert を差し戻し(impression gate 復元)+expire hook を #if DEBUG 化

テーマ: round8 sign-off 後に入れた2変更(R7-#2 revert・expire 人工注入 hook)を gods-talk round9 でレビュー。reviewer が R7-#2 revert を medium 点火ブロッカーとして差し戻し。指摘どおり gate を復元し、hook を #if DEBUG 隔離。Debug/Release 両ビルド 0 errors を確認。

round9 feedback(ab-test-implementation-review-009):

  • [medium] R7-#2 revert は「render 失敗の過大計上」を再導入=点火ブロッカー。MAX SDK 13.6.2 MANativeAdLoader.h は renderNativeAdView:withAd: の戻り値を「正常 render されたら YES」と明記。revert 後は戻り値を捨てて常時 recordImpression するため、SDK が false を返した render 失敗も実表示として計上される。「実機で false が観測されなかった」は「false を成功扱いしてよい」根拠にならない(別 network/view 再利用/将来 adapter 差分で false がありうる)。loader(for:) の !expired ガードは expire 後 render を防ぐが live entry の render 失敗は扱わない。回復性(false 時 hide)と計上 gate(true 時のみ)は分離可能で、最低限 gate を復元すべき。私の「churn リスク」懸念は false 頻発前提で的外れ(churn でなく失敗 ad の回復処理)。
  • [low] expire hook は安全だが Release 除外推奨(#if DEBUG)。[weak self]・main 直列化で leak/race/二重 destroy なし。実 SDK didExpire と同経路の代理として妥当。単独ブロッカーではない。
  • [low] round8 残 low: low1(render false 即時再取得なし)は false 分岐削除で表面上消えたが上記 medium に置換=解消ではない。low2(self-test 範囲)は expire 経路を hook で実機 end-to-end 確認した分前進。low3(kill_observed_at 説明文)は現行契約に誤って含めておらず現状で OK。
  • 点火可否: 現行 777128f のままでは sign-off 維持されず提出・点火を承認しない。gate 復元+Debug/Release build+通常 native 表示の短い再スモークで medium は閉じる。

やったこと(この会話で修正):

  • ① impression gate 復元 MiddleAppLovinMaxNativeAdCell.swift: let rendered = loader.renderNativeAdView(...) の戻り値を保持し if rendered { recordImpression } else { releaseCurrentLease()+releaseAd()+adObject=nil+hidden+return }(round8 の R7-#2 を実質復元・fail-closed)。コメントに revert が誤りだった旨と論点を明記。
  • ② expire hook を #if DEBUG 隔離 AppDelegate.swift(launchArg 設定)+MiddleAppLovinNativeAd.swift(debugSimulateExpire 宣言・asyncAfter 発火の2箇所)。全6参照が #if DEBUG 内に収まることを grep 確認。

検証: XcodeBuildMCP(iPhone 17 / iOS 26.5)で Debug build SUCCEEDED 0 errors・Release build SUCCEEDED 0 errors。改修3ファイルに新規警告なし。Release で debugSimulateExpire が #if DEBUG 除外され参照漏れなしを確認。

気づき/判断: 私の R7-#2 revert は overreach だった。核心は「観測で false が出なかった」と「false は安全」の混同で、SDK 契約上 false は render 失敗を意味するので観測の有無に関係なく計上すべきでない。reviewer 指摘は妥当・却下なし。修正は round8 の sign-off 済み状態への復帰に等しいため round10 再レビューは任意(reviewer が close 条件を明示済み)。

現在地: round9 medium をコードで解消・両ビルド 0 errors。残るは通常 native 表示の短い実機再スモーク(You)。点火前チェックリストは GA4 2件手登録・5.5.3 提出が未。

次回やること: 通常 native 表示の実機再スモーク → 5.5.3 提出/リリース → GA4 2件登録 → S3 を v8/active=true・enrollment窓(当日起点)・min_build=50503 で点火。任意で round10 close 確認。

15:45 - 起動時広告A/B 実装レビュー round5〜8 完了・点火 sign-off 取得

テーマ: gods-talk 実装レビューを round5→8 まで回し、native lifecycle / kill属性 / 計測契約の残 high/medium を全解消。round8 で「5.5.3 提出・点火前チェックリスト完了後に S3 active=true で A/B 開始可・high/medium ブロッカーゼロ」の最終 sign-off を取得。実装レーンは実質完了し、以降は運用準備へ移行。

round5(medium3) ios 0089f65 / op ab066e4:

  • R5-#1 native 確定destroy: entry を class化し released(row利用終了)×displayLeases(描画中cell数)、両成立で destroy(固定0.6秒撤去)。cell が puts で lease 取得・gone/prepareForReuse/deinit で解放。実機スモーク PASS(scroll往復/pull-to-refresh/More連打: クラッシュ0・描画失敗0・loader nil 0)。
  • R5-#2 pending timeout(30s)+didExpireNativeAd(stock限定の失効・補充)。
  • R5-#3 kill属性: effective_interval_minutes(snapshot)を保ち applied_interval_minutes 分離・user property clear を SDK初期化guard前へ。

round6(medium3+low2) ios 3f64ade / op 546dd0a:

  • R6-#1 callback を onMain 集約+didLoad で pendingHandles 在籍世代照合(timeout済み遅延成功は即destroy・cap超過防止)。SDKヘッダで didLoad/didFail のthread保証なしが根拠。
  • R6-#2 didExpire を deque済み含む全entryへ・destroy条件 (released||expired)&&leases==0・cell puts で loader nil 検出→row無効化(4h超の無効広告 render/計上を停止)。
  • R6-#3/#5 GA4-only整合(kill_observed 必須dimension化)+TTL省略可統一。launchArg self-test(-ABNativeSelfTest)導入 7/7。

round7(medium2+low2) ios 31dc22b / op 07aa09f:

  • R7-#1 stale判定を CoreTableView.cellForRowAt の deque/nib選択前へ移動+同一pass再deque(Any?型非依存API で共通table を AppLovin非依存に)。
  • R7-#2 loader(for:) を !expired 必須化+renderNativeAdView==true 時のみ impression(render失敗の過大計上を解消)。
  • R7-#3 self-test を precondition で fail-hard 化。R7-#4 kill_observed_at 削除(GA4-only で事後参照不能)。

round8 → 点火 sign-off:

  • high/medium ブロッカーゼロ。残 low(render-false 即時再取得なし=fail-closed/self-test範囲/廃止param名の説明文/runbook commit表記)は全て非ブロッカー。
  • 点火前チェックリスト(コード外・必須): ①GA4 custom定義の実登録(kill_observed/applied_interval_minutes 含む)②enrollment窓・判定値の確定 ③初期 active=false での本番config取得確認 ④点火/停止時刻の運用記録。

検証: 各round sim build 0 errors・改修ファイル新規警告なし。self-test 7/7(fail-hard後も通過)。R5-#1 lease は実機スモークPASS。limitation: sim は MAX native fill なしのため timeout/expire の end-to-end は未実行(destroy判定は純粋関数 self-test+外部レビューで担保)。

気づき/判断: round を追うごとに残課題が native expire 経路へ収束。私の R5-#2(stock限定)→R6-#2(puts無効化) は段階的に甘く、reviewer が cellForRow前段stale判定・renderable !expired・render Bool gate へ正した(毎回妥当・却下なし)。native は experiment flag 外で全ユーザーに作用するため fail-closed が必須。実装は完了、以降は GA4登録・値確定・実機スモークの運用準備へ。

14:30 - 起動時広告A/B Codex round3 の high3+medium4+low1 を修正+実機回帰スモークPASS

テーマ: Codex 実装レビュー round3 が新たに high3/medium6/low2 を指摘(私の判断ミス2件含む)。点火可否=「3 highが残ブロッカー」との総括。high3+medium4+low1 を修正し、実機で回帰スモーク(decision/native)を確認。

Codexの訂正(私の取りこぼし):

  • R3-#1: 私の decision 修正(完了ハンドラ再評価・d103ad5)が不完全だった。完了時は普通 ready=false で not_ready が latch → 後で ready になり show すると「not_ready なのに displayed」の同型矛盾が再発。
  • R3-#2: 「attempt紐付けは原理的に不可」は誤り。AppLovin SDK に showAdForPlacement: があり MAAd.placement で照合できる(=未実装だっただけ)。
  • R3-#3: R2-#3 の native cap(entries.count>20 無条件 destroy)を残置していた=表示中ad破棄・全ユーザー作用。

やったこと(high):

  • R3-#1/#2 5db2975: decision と show attempt を context 単位で一体・一度だけ claim。emitAdDecisionFinal/noteAdDecisionPending/finalizePendingAdDecision に再設計(非eligible→即final / eligible&ready→claim+show+final(shown) / eligible&not_ready→保留しcontext閉鎖時にnot_readyをfinal / SDK未init→保留)。claimAdAttempt() で show を context 1回に限定。placement に token(=launchId)を載せ show(forPlacement:)、callback は ad.placement 一致(isCurrentAttempt)時のみ funnel へ。完了ハンドラ再評価は app active 時のみ。→ decision と実表示が常に整合・旧attempt遅延callbackの誤帰属を排除。
  • R3-#3 5d26ee6: native cap 撤去。releaseAd(_:) を新設し NativeAdRow.deinit(row利用終了)で生成元loaderへdestroy。表示中ad破棄・上スクロール再表示時のloader(for:)nilを解消。entriesはlive row数で自然有界。 (medium/low):
  • R3-#4/#5/#6/#7 ef4d47e: kill-switchをshow直前に最新last-known-goodで確認(isKillSwitchActive・open contextにも反映)/strictIntの2^53飽和回避・ttl上限60日・config_version上限・samePolicyのttl比較Double化/applyConfigをmainへ集約(thread race排除)/時計巻き戻しfail-close(isWithinTrackingWindow)。
  • R3-#8 791952f+runbook 0789c73: build符号化を成分数・範囲でfail-close(衝突回避)+符号化規則/数値上限/kill-switch反映時間をrunbookに明記。
  • R3-#9(DEBUG定義+XCTest)は test基盤の別作業として先送り。

実機回帰スモーク(iPhone 14・seed): decision=interval が実結果と整合(群B 24h以内→起動時広告抑止・矛盾なし)・content_reached発火・native middle_native の imp/rev 健在(スクロール往復で APPLOVIN_EXCHANGE/Liftoff)・クラッシュ0・描画失敗0。R3 変更の回帰なし。

気づき/判断: 実装レビューは round を重ねるごとに「実機/エッジでしか出ない」層(SDK初期化タイミング・placement照合・table cell lifecycle・thread race・時計異常)まで掘られた。私の誤判断(#2不可・decision修正の不完全・native cap残置)を round3 が捕捉。Codexの指摘は妥当で却下なし。

現在地: Codex round3 の点火ブロッカー(high3)+medium4+low1 を修正・push 済み。実機回帰スモークPASS。残るは Codex round4(R3修正の確認・推奨)か、R3が挙げた個別エッジ(not_ready→ready/21件超native/kill-switch mid-context)の的を絞った実機再検証、その後 5.5.3 提出。

次回やること: Codex round4 で R3 修正を確認 → 必要なら個別エッジ実機検証 → 5.5.3 提出→リリース→点火。R3-#9(DEBUG/XCTest基盤)は別途。

13:30 - 起動時広告A/B 実機検証(iPhone 14・iOS26.5)A〜D 全PASS+実機で発見したdecision矛盾を修正

テーマ: Xcode→実機直挿し(devicectl・Debug+DEBUG override・--console でNSLog捕捉・seed/-FIRDebugEnabled)で検証A〜Dを実施。全PASS。検証中に新たなdecisionバグを1件発見し修正・再検証した。

やったこと/結果:

  • 検証A(native描画+ILRD・最優先)PASS: 記事一覧スクロールで fn_ad_impression/fn_ad_revenue(slot=middle_native・ad_unit=562ca14298b68765・network=Liftoff Monetize・precision=exact)発火=R2-#3 handle方式で native ILRD 欠損なし。footer_banner も発火。クラッシュ0。観測用に ABAdCollector へ #if DEBUG NSLog 追加(d103ad5)。
  • 検証B(content_reached・R2-#4)PASS: ATT表示中で止めた時点で content_reached 0件(連鎖中抑止=負の確認)。プロンプト完了後のコールド再起動で content_reached 1件・time_to_content=1(正の確認)。途中の launch_no_content は「content前に背景化(Mac切替)」の正常動作と確定。
  • 検証C(群B処置)PASS: この端末は群Bバケット。decision_reason=interval・policy_eligible=false・effective_interval_minutes=1440・app_open非表示=24h間隔の処置が実機で動作。
  • 検証D(#11・更新直後)PASS: -ABSimulateUpdaterState(旧キー=30分前・新キー=0 を注入)で移行発火を確認。updater_migration old=… new=(同値)=新キーへ旧値引き継ぎ成功、decision=interval・app_open非表示=アップデート直後の追加表示なし(fd913dd)。
  • 実機で発見→修正したdecision矛盾(d103ad5): コールド起動の最初の didBecomeActive は SDK未初期化のため、R2-#9aの sdk_not_initialized decision が emitAdDecisionOnce(R2-#m1)の「1回」を消費し、SDK初期化後の本当のdecision(interval/shown)を抑止していた(app_open_ad_displayedが出るのにdecision=sdk_not_initialized の矛盾)。→ SDK未初期化時はdecisionを出さず保留し、ALSdk初期化完了ハンドラで showAdIfReady を再評価して本当のdecisionを1回送る方式に変更。再検証で decision_reason=interval を確認。

気づき:

  • TestFlightは検証に不要(Youの指摘どおり)。広告SDKがdevice-onlyなだけで、Xcode→実機直挿し(Debug+実広告配信・ILRD発火)で完結。devicectl の app 引数は -- 区切りが必要(-ABSeedTestConfig が短縮フラグ列に誤解釈され -t で失敗する)。
  • 実機検証はコールド起動の SDK 初期化タイミングという、sim では出ない条件を踏んで decision バグを炙り出した=実機検証の価値。
  • seed は同一version・enrollmentStartArt相対値のため2回目以降は same-version-immutability(R2-#m5)で reject され cached 継続(テスト専用の挙動・本番影響なし)。

現在地: 実機検証 A〜D 全PASS・decisionバグ修正済み。S3 config は中立(active=false/v7/ttl14日)。5.5.3 据え置きで全修正+検証完了=5.5.3 提出可能な状態。残るは任意の Codex round3/R2 low。

次回やること: (任意)Codex round3 で R2修正群+実機発見のdecision修正を最終確認 → 5.5.3 を App Store 提出 → 承認後リリース → 普及を見て S3 点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ GA4着弾を Data API 検証+事前テスト①〜⑧。

12:30 - 起動時広告A/B Codex round2 残りの must-fix(R2-#4 / R2-#3)完了+実機検証手順を整備

テーマ: 11:30 の続き。Codex round2 の残 must-fix だった R2-#4(起動プロンプト連鎖の content_reached 誤確定) と R2-#3(native loader 再構成) を実装し、実機検証手順書を作成。これで round2 の must-fix を全消化。

やったこと:

  • R2-#4 bcc9950: 起動プロンプト連鎖(Splash→EULA→RSS→TopReload→Push→ATT)を単一 boot covering 化。bootSequenceActive を onDidFinishLaunching でON、終端 InitialATTAction 解決(ATT非提示)または ATT VC dismiss で bootSequenceEnded()→content再評価。プロンプト中・隙間での content_reached 早期/群依存確定を抑止。sim(初回=プロンプト出っぱなし)で早期発火しないこと(負の確認)済。returning-user 経路の正の発火は実機検証へ。
  • R2-#3 55fbe06: native を 1 load = 1 delegate handle(NativeAdLoadHandle)方式へ。各 loader を自分の callback に紐付け、並行 load の誤対応付け・revenueDelegate weak によるILRD欠損・誤 destroy を解消。loader(for:)/deque/didPayRevenue/cap destroy は維持、owner weak で循環断ち。compile確認のみ(sim で MAX native load 不可)→ 実機検証が必須関門。
  • 実機検証手順書 ab_test_device_verification.md(op 81fc50f): 検証A native描画+ILRD(収益クリティカル・最優先)/B オンボーディング完走後content_reached/C AB funnel全体(seed or 一時active)/D 群A=現行どおり(#11)。観測は GA4 DebugView(-FIRDebugEnabled)。

気づき/判断:

  • 「TestFlight 必須」は誤り(Youの指摘)。広告SDKが device-only なだけで、Xcode→実機直挿し(Debug でも実広告配信・ILRD発火)で native 検証は完結。Release 構成で実機ビルドすれば本番経路(seedなし・最適化ON・DEBUG除外)も同時確認できる。TestFlight は最終配布の関門であって修正検証の必須要件ではない。
  • R2-#3 は revenue クリティカルでブラインド改修リスク高。描画は loader(for:) 経由(MiddleAppLovinMaxNativeAdCell.swift:57)で最新 .loader は不使用と確認したうえで実装。検証A 不合格ならリリース不可。

現在地: Codex round2 の must-fix を全 code-complete(ios master へ push 済み)。S3 config は緊急停止のまま(active=false / v7 / ttl14日)=本番中立。5.5.3 据え置きで全修正積載。残るは実機検証(You が Xcode→実機)と、任意の Codex round3 / R2 low。

次回やること: 実機検証(手順書 ab_test_device_verification.md の A〜D)→ 合格で 5.5.3 提出→承認→リリース→ S3 点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ ga4 MCP 接続のうえ GA4 着弾検証+事前テスト①〜⑧。任意で Codex round3(R2修正群の最終確認)。

11:30 - 起動時広告A/B 点火→Codex実装レビューで重大バグ発覚→緊急停止→high/medium修正群(5.5.3据え置き)

テーマ: enrollment窓を決めて**点火(active=true)**まで実施した後、トムソン通知HTML公開・5.5.3バージョンバンプ・実装コードのCodexレビュー(gods-talk ab-test-implementation-review)を回したところ、実コードに重大バグが多数見つかり、緊急停止して順次修正した。本番は中立(active=false)に戻し、5.5.3 提出も検証完了まで保留。

時系列でやったこと:

  • 点火(段階B): ab_test_config.json を active=true / config_version=2 にして s3://footballnext/ab_test_config.json へ配置(op 851ee7a)。enrollment窓6/22開始なので週末は挙動不変の設計。
  • トムソン要確認3項目を HTML化→thomsons S3 公開(起動時広告A-Bテスト_点火前最終確認_20260619.html)。Youより「確認は任されている」=叩き台確定扱い。
  • iOS版状況の確認(App Store Connect MCP): 現行公開は 5.5.2(6/12ビルド・AB計測コード未収録)。master HEAD にAB実装あり。番号が5.5.2のままだったので 5.5.3 へバンプ(ios 39fee19)。※5.5.3は未提出=番号据え置きで再利用可。
  • シミュレータ pre-flight(段階A健全性): 本番S3取得→パース→保存を実証済(前エントリ)。さらに seed test(-ABSeedTestConfig/-FIRDebugEnabled・要 SWIFT_ACTIVE_COMPILATION_CONDITIONS=DEBUG)で experiment_enrolled→target_launch→content_reached→app_open_ad_decision の GA4 送信(Analytics.logEvent)到達を確認。ga4 MCP は本セッション未接続でDataAPI裏取り不可→DebugView目視はYou側、と整理。
  • Codex 実装レビュー round1(ab-test-implementation-review-001): high 8 / medium 5 / low 2。最重要は #1「showAdIfReadyがcanEnroll/窓を見ずB処置を適用=非参加者にB漏れ・点火済みconfigと相互作用」。
  • 緊急停止: 指摘#1を受け S3 を active=false+config_version+1 に戻す(op …、最終 v6)。5.5.3 提出も保留。
  • high/medium 修正(実機検証なしで安全な範囲を実装・各 seed 回帰確認):
    • #1 状態分離(isParticipating=既参加&&追跡窓内&&active/fresh|cached・trackingDays=28・beginTargetContextを既参加追跡/新規enrollに分離)3df1b59
    • #2 config単一snapshot+cached実装 fb79e31(※後述 R2-#1 で再設計)
    • #5/#7 callbackをadStateガード+hidden/failed emitを context クローズ前に・watchdogをcontext-scoped 4b5316e
    • #6 impression冪等キーを creative→広告instance×slot 63824d4
    • #3 build範囲 fail-close+dotted版パース、#11 群A旧キー移行、#9(a) SDK未初期化decision補完 6020850
    • #10 割当再作成防止、#8 config検証強化(schema互換/version>0/min<=max)5f6f589
    • native loader再構成(#6/#7 native)・#9(b) は当初「先送り」とした。
  • Codex round2(ab-test-implementation-review-002): round1修正を確認しつつ 新規 high 4 / medium 6 / low 2。最重要 R2-#1「#2のsession全体固定+TTL6hで、cold launch間隔>6hの日次ユーザーが永久に非参加=実験不成立」。また R2-#3 で「revenueDelegateはweak=loader誤対応付けでILRD欠損しうる→nativeは先送り不可」と私のrationale誤りを訂正。R2-#4「起動overlay間gapでcontent_reachedが広告有無=群依存で誤確定(GATE交絡)」。
  • R2 修正群(実装・seed検証・push):
    • R2-#1 snapshotを session固定→context世代方式 に再設計(beginTargetContextがlast-known-goodを1回読みrecordに固定・fetchは次context有効)+既定TTL 6h→14日・seed時は実fetchスキップで決定化 4a9c516、config ttl 14日化 2115035
    • R2-#m2 追跡窓をJST暦日(daysSince<trackingDays)へ・R2-#m4 assignmentVersion fail-close・R2-#m5 厳密JSON型検証(strictBool/Int/Double)+同一version不変性 835e91f
    • R2-#m1 decisionをcontext単位1回(emitAdDecisionOnce)・R2-#m3 非参加化時にuser property clear f32fe12
    • R2-#m6 impression冪等を weak identity box 3e7fac2
    • R2-#2 達成可能分は adStateガードで対応済(残差=MAX callbackがtokenを返さない原理的制約として記録)

気づき/判断:

  • 提案書・実装計画の机上Codexレビューを5ラウンド通しても、**実コードでしか出ない重大バグ(policy/measurementの状態未分離、TTLとsnapshotの相互作用)**が出た。実装レビューは別物として必須。
  • 私の #2(session全体固定)は筋が悪く R2-#1 で日次ユーザーを壊した。context世代方式(record固定+fetchは次context)が正解。
  • native loader は「A/B妥当性に非影響・収益別経路」と判断したが、revenueDelegate weak の指摘で収益欠損あり=点火前ブロッカーへ格上げ。実機検証必須。

現在地: S3 config は active=false / v7 / ttl14日(中立)。5.5.3 据え置きで全修正を積載。残る must-fix は R2-#4(起動overlay gap・オンボーディング連鎖の covering token 化) と R2-#3(native loader再構成・実機/TestFlight検証必須)。

次回やること: R2-#4 を慎重に実装(LaunchManager連鎖に covering token)→ R2-#3 を実機検証とセットで → Codex round3 → 全通過で 5.5.3 提出→承認後リリース→ S3 再点火(active=true・config_version+1)。

08:20 - 起動時広告A/B 点火準備の続き(トムソン確認3項目HTML公開/enrollment窓確定/段階A=不活性config配置/シミュレータpre-flight PASS)

テーマ: BASE実装・GATE凍結・GA4登録の完了を受け、点火に向けた残作業を進めた。トムソン要確認3項目の通知物をHTML化してS3公開、enrollment期間を確定してconfigに設定、不活性config(active=false)を本番S3へ先行配置(段階A)、シミュレータで本番S3取得経路を実証(段階B=点火は保留)。

やったこと:

  • トムソン要確認3項目をHTML化→thomsons S3に公開(起動時広告A-Bテスト_点火前最終確認_20260619.html)。3項目=①円/人コホート(締切6週/追跡28日/ITT)②収益正規化・residual(月平均eCPM・感度0.5–1.5×・普及率70%/旧版上限30%・月次総売上提供依頼)③事前テスト合格しきい値①〜⑧。提案書と同体裁、事業判断レベルに噛み砕き。URL: https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_点火前最終確認_20260619.html(HTTP 200確認)。Youより「トムソン確認は任されているので不要」と判断 → 叩き台のまま確定扱い。
  • enrollment窓を確定(叩き台6週間): enrollment_start_at=1782054000(2026-06-22 月 0:00 JST)/enrollment_end_at=1785682800(2026-08-03 月 0:00 JST・+42日)。JST深夜0時の月曜境界=コホート日が揃う。ab_test_config.json に2キー追加。解析データ成熟の目安は最終enroll日2026-08-02+追跡28日+maturation3日=〜2026-09-02。
  • 段階A(中立)= 不活性config を本番S3へ先行配置: s3://footballnext/ab_test_config.json(バケットfootballnextルート=Const.baseS3Url)。experiment_active=false=全員非参加で挙動不変。初回アップロードはpublic-read ACL未付与で403→--acl public-read付与で再アップロード→HTTP 200取得可に。配置先プロファイルは thomson-ik(footballnextバケットにアクセス可)。
  • シミュレータ pre-flight(段階Aの健全性確認)= PASS: iPhone 17 Pro sim でフルビルド→install→launch。ABConfigLoader.fetch()(AppDelegate:129)が本番S3を取得→パース→検証→UserDefaults保存まで通すことを、保存キー ab_config_app_open_interval_2026q3 の中身で実証(取得1秒)。全項目一致、enrollment窓epochがNSDate(reference-date 2001)として 1782054000/1785682800 にビット一致デコード。active=false なので canEnroll=false=enroll/AB event は出ず挙動不変も確認。これまで未実証だった本番S3取得経路を初めて実証(過去検証は seed 経路のみ)。

気づき/判断:

  • footballnext バケットはオブジェクト単位 public-read ACL運用(teiki等は200・新規アップロードは無ACLだと403)。S3配置時は --acl public-read 必須。
  • 段階Aで確認できるのは「本番S3取得+パース+保存」まで。GA4のAB funnel(experiment_enrolled等)は active=false では出ない(点火か seed test が必要)。
  • 点火しても enrollment窓開始(6/22 0:00 JST)まで誰もenrollしない設計(canEnroll が窓開始前 false)=今点火しても週末は挙動不変、月曜の最初の対象起動から群Bが24h間隔に切替。

点火前チェックリストの到達: ①GATE値叩き台✅ ②GA4 custom定義✅ ③トムソン3項目(叩き台確定・通知物公開)✅ ④enrollment期間決定✅ ⑤段階A配置・pre-flight✅。残: ⑥段階B=点火(experiment_active=true+config_version=2で再アップロード)のみ。Youのgo待ち。

次回やること: 段階B点火(Youのgo時)。点火後 GA4 dimension にデータが乗るか Data API 検証。緊急停止は active=false+版+1 で即可能。

02:32 - 起動時広告A/B 点火準備(GATE値叩き台凍結/S3 config正本/runbook/GA4 custom定義 登録完了)

テーマ: BASE 実装完了後、点火に向けた準備物を整備。GATE値の叩き台を凍結し、S3 配置用 config と手順書を作り、GA4 の custom 定義を全件登録(管理画面操作は You が実施)。

やったこと:

  • 残りGATE値の叩き台を確定・凍結(commit ios 2bae96d / op 8b9fb4c):
    • client 実行時値 → ABProtocol: 許容clock skew=300s・未来clamp=400日・既定TTL=6h。showAdIfReady の時計判定を skew許容+未来clamp回復で精緻化。ABConfigLoader の ttl_seconds を任意化。
    • 解析側値 → 正典 measurement_protocol(mp_2026q3_r1): maturation=3日/円人コホート(締切42日・追跡28日・分母=割当全員ITT)/D7D14単日(JST)/residual(月別平均eCPM・感度0.5–1.5×・旧版売上比率上限30%/普及率≥70%)/収益正規化(gross/USD/対象版・月)/事前テスト合格しきい値①〜⑧/GA4 Cohort再現手順。要トムソン最終確認は3項目(コホート定義・residual/正規化・合格しきい値=事前登録として握るだけ、実装は既定値で動く)。
  • S3配置用 ab_test_config.json(正本)と ab_test_deploy_runbook.md(点火/緊急停止/版番号規則/GA4登録ワークシート/wire format)を新設(commit op b35bf2f)。config 初期値 experiment_active=false=配置しても誤発進しない。ABConfigLoader のパース+検証通過を standalone で確認。
  • MAX native の loader-ad lifecycle を修正(commit ios 8dc3b5a): load毎に差し替わる loader を ad とペアで retain(weak delegate で過去adのrender/impression/revenue callback欠損を解消)。cell は loader(for: ad) で生成元loaderを使う。destroyAd→Swift destroy(_:) 改名対応。フルビルド成功。
  • GA4 custom定義の登録ワークシートを実コードに整合(commit op 22720b0): 実送信 param キーと1対1照合。未送信の config_age_hours は登録不要と明記(spec §4 訂正)。
  • GA4 custom定義を全件登録完了(GA4 Editor権限 取得済→You が管理画面で実施・スクショで検証): カスタムディメンション18件(イベント範囲16+ユーザー範囲2=assigned_group(user)/app_open_ad_interval(user))+カスタム指標3件(time_to_content秒/revenue_usd標準/effective_interval_minutes分)。param名・スコープ・単位すべて一致=計21件 過不足なし。

気づき/判断:

  • GA4 custom定義の作成は接続中の ga4 MCP(Data API=読み取り専用)とADC未設定では自動実行不可。登録ワークシートを用意し You が手動登録、私は Data API で事後検証する分担に。
  • 「トムソン合意が要る」を再整理: 実ブロッカーは GA4 Editor 権限(取得済)だけ。しきい値・コホート定義は事前登録の作法上トムソンに通知/握るが、実装は叩き台既定値で動く。月次総売上は解析フェーズ入力で点火ブロッカーではない。
  • 検証用 seed/NSLog(#if DEBUG+-ABSeedTestConfig 二重ガード)は実ビルドに混入せず S3 なしで funnel 再検証できる唯一の手段なので残す。

点火前チェックリストの到達: ①GATE値叩き台 ✅/②GA4 custom定義登録 ✅。残: ③トムソンへ要確認3項目を通知 ④enrollment期間を決め config に設定 ⑤ab_test_config.json を S3 配置→experiment_active=true+config_version+1 で点火。

次回やること: トムソンへの通知文面づくり/enrollment 期間の決定/S3 配置・点火。点火後 or seed DebugView で GA4 dimension にデータが乗るか Data API 検証。

01:03 - 起動時広告A/B 計測基盤 BASE A〜F をフル実装+シミュレータ検証+コミット

テーマ: 実装計画 rev6/正典 SPEC.md rev6 を元に、起動時広告 削減A/Bテストの計測基盤を実コードで一気に実装(BASE A〜F)。footballnext-ios(GitHub/master)に実装し、フルビルド成功・シミュレータ実機で全ファネル発火を確認・コミット/push まで完了。

やったこと:

  • 新規4ファイル(footballnext-ios/FootballNEXT2015/):
    • ABTest.swift(BASE-A/F core): 群割当 SHA256("experiment_id:install_uuid") 先頭8バイト big-endian % 100→bucket_map([0,50)→A)、永続install UUID(IDFA不使用)、experiment_id単位の原子保存・再計算禁止、enrollment追記、型付きABActivationConfigの全項目検証+last-known-good+canEnroll/config_status+fail-safe interval、JST暦日(cohortDay/daysSince)、verifyTestVectors()。
    • ABLaunchTracker.swift(BASE-C): ForegroundCycle/TargetLaunchContext、write-ahead OpenContextRecord+terminal CAS、at-most-once送信(emission_state pending→attempted)、可視状態機械+content provider前面チェック、presentation watchdog、kill/crash時 previous_launch_unresolved(元属性で送信)。Foundation-only(emit注入式)で standalone テスト可能。
    • ABAdCollector.swift(BASE-E): 全MAX枠 共通 fn_ad_impression/fn_ad_revenue(冪等・ILRD一本化、experiment共通paramのみ)。
    • ABConfigLoader.swift(BASE-F): S3 ab_test_config.json の非同期取得→手動パース→検証付き applyConfig(fail-safe)。
  • 既存14ファイル配線: AppDelegate(lifecycle 4種・群別頻度制御の時計安全化(abs廃止/clock_anomaly)・app_open_ad_decision(BASE-D)・delegate/revenueDelegate配線)、UserData(didDisplay起点の永続キー)、ViewController(content前面provider)、SplashViewFadeoutAction(content可視)、EULA/RSS/Push/ATTの4モーダルVC(overlay covering)、AdView/MaxByAppLovinBannerAd/AppLovinUnderArticleBannerAd/MiddleAppLovinNativeAd(+Cell)(collector接続)。
  • pbxproj に4新規ファイルを手動登録(plutil -lint OK)。
  • 検証: ①ABTest+ABLaunchTracker を standalone swiftc でコンパイル+状態機械テスト全PASS(非参加/enroll+target_launch/content_reached/launch_no_content/kill→unresolved/at-most-once/CAS/割当ベクトル)。②フルビルド成功(iPhone 17 Pro sim・Debug)。③シミュレータ実機起動で experiment_enrolled→target_launch→app_open_ad_decision(not_ready)→content_reached の実発火をログ確認。
  • 正典 AB_TEST_MEASUREMENT_SPEC.md の未決GATEから「SHA256→bucket定数・テストベクトル・新experiment時の再無作為化」を確定へ移動(ABProtocolへ凍結)。
  • footballnext-ios にコミット 4ebdeb6(18ファイル/+1262 −63)→ GitHub master へ push。

気づき:

  • このプロジェクトは Swift の SWIFT_ACTIVE_COMPILATION_CONDITIONS に DEBUG が未設定(GCC側のみ DEBUG=1)。→ Swift の #if DEBUG は通常 Debug ビルドでも全除外。検証用 seed/NSLog は #if DEBUG+起動引数 -ABSeedTestConfig の二重ガードで、通常ビルドには絶対混入しない(今回は SWIFT_ACTIVE_COMPILATION_CONDITIONS=DEBUG 上書きで検証)。副作用として verifyTestVectors() の起動時自己チェックも通常は走らない(standaloneで検証済みのため実害なし)。

本番影響: S3 に ab_test_config.json を置くまで全context非参加=AB event 不発・広告挙動は現行どおり(中立)。config 配置が実験開始トリガー。

次回やること: GATE-値(トムソン合意要の数値含む)を measurement_protocol として凍結 → ab_test_config.json 作成・S3配置 → GA4 custom定義登録 → native loader-ad lifecycle の深いrefactor(pre-test ③/⑦で検証)→ 検証用 seed/NSLog の最終扱い判断。


2026-06-18

23:55 - 起動時広告A/B 実装計画HTMLの作成+Codex 5ラウンドレビュー(gods-talk)→ rev6 で設計レビュー完了(〜06-19 00:00)

テーマ: トムソン承認が出た提案書を受けて、**実装計画(HTML)**を作成し、Codex(gods-talk)で繰り返しレビュー。rev1→rev6 まで反映し、正典 AB_TEST_MEASUREMENT_SPEC.md も同期。コード変更なし(資料・正典・レビュー往復のみ)。

やったこと:

  • 実装計画 起動時広告A-Bテスト_実装計画_20260618.html を新規作成。実際のiOS実装(AppDelegate.swift showAdIfReady()/広告delegate、UserData.setAppSettings() のS3配信、Firebase=GA4、永続UUID未実装)を読んで具体化。
  • gods-talk 新トピック ab-test-implementation-plan-review で 5ラウンドのレビューを往復(依頼書5本+feedback5本)。各ラウンドで指摘を rev に反映:
    • round1(high9): 可視判定をdidHide限定にしない状態機械化、appOpenAd.delegate/revenueDelegate 未配線、LaunchContext一意化、割当原子保存、fail-safe ITT保持、未確定検出、GA4 ad_impression衝突回避、円/人の固定コホート、混合eCPMの層別。
    • round2(high7): 正典が旧仕様のまま→同期、assignment/activation/enrollment 3分離、型付きconfig原子保存、ForegroundCycle/TargetLaunchの60秒gate、presentation pending、unresolvedを成熟後残差率に格下げ、総売上1個ではslot別点推定不可。
    • round3(high7): experiment_id同梱manifest互換規則、context単位config snapshot、背景化outcome確定、target_cohort_day、fallback param配線、収益範囲条件復活、全MAX枠ILRD。→「条件付きGO」。
    • round4(high4): write-ahead OpenContextRecord、3 manifest分割+版番号、canonical revenue event一本化、総売上母集団の演算式(感度変数化)。→「限定付きGO・追加ラウンド不要」。
    • round5(high1): 配送を at-least-once→at-most-once に方針転換(GA4-onlyでexactly-once不可・logEventにack無し)、per-event emission state、medium4件の内部矛盾同期。→「設計レビュー完了。実装PRの受入チェックで確認できる範囲」。
  • HTMLに 3レーンの実装ゲート(BASE着手可 / BASE契約=API確定前に潰す5点 / GATE-値=schema凍結前に固定)を新設。
  • 正典 .devnotes/AB_TEST_MEASUREMENT_SPEC.md を rev6 まで全面同期(write-ahead/at-most-once配送・3 artifact・canonical event・総売上母集団・enroll許可config/kill-switch・param出所分離・MAX native lifecycle・暦日TZ Asia/Tokyo)。

決めたこと(DECISIONS にも転記):

  • 設計レビューはここで完了。次は実装フェーズ(BASE着手+GATE値を measurement_protocol として凍結)。再レビューは必須にせず実装PRの受入チェックで確認。
  • 配送意味論は at-most-once(pending→attempted を先に確定して一度だけ送信、欠損は残差率に残す)。

気づき:

  • Codexはローカルコードを直接読むため、appOpenAd.delegate=self 未配線のような「コードを見ないと出ない」指摘が出た。提案書段階の机上設計では見えなかった穴。
  • 5ラウンドで指摘の質が「設計の穴→境界/原子性→配線漏れ→収束仕上げ」と逓減し、自然に収束した。

次回やること:

  • 実装着手(BASE)。GATE-値(skew/TTL/maturation日数/暦日具体値/residual単価根拠 等)を measurement_protocol として凍結してから本番 event schema を確定。

13:38 - 起動時広告A/B 提案書の作成・Codex2ラウンドレビュー反映・S3公開アップロード

テーマ: 10:32 の測定設計を、トムソンに出せる**提案書(HTML)**に仕上げる。議論で設計を精緻化し、別LLM(Codex)に2ラウンドのレビューを受けて反映、最後に公開アップロードまで。コード変更なし(資料・メモのみ)。

やったこと:

  • 提案書 起動時広告A-Bテスト_提案書_20260618.html を新規作成(既存 review-20260605.html と同じ体裁)。md版は破棄しHTMLのみ。
  • 議論で固めた設計の更新点(提案書に反映):
    • 群B「1日1回」→「24時間に1回」(実装のローリング挙動に一致)。
    • BigQuery不使用=GA4のみ・無料に決定。ab_group 集計のため GA4 を Viewer→Editor へ権限変更をトムソンに依頼、と明記。
    • 起動時広告の枠を実コードで棚卸し(フォーマット4種+ネイティブ生存5系統。Movie/HotKeyword/SnsMovie は休眠。全ネイティブは CoreTableView.dequeAd() 集約)。
    • 表示カウントは「起動時/それ以外」の2系統。ネット内訳は送らず generic +1。
  • Codex レビュー(gods-talk, .gods-talk/ab-test-proposal-review-*)2ラウンドを実施・反映:
    • R1: 2バケツeCPMの単価構成仮定/launch_bounce の交絡・起動秒数で群A機械的不利/GA4のみで有意差は出せない(→実務閾値判断に降格)/imp定義 等。
    • R2: 群Aは「毎回」でなく現行50分間隔(S3実測 app_open_ad_interval=50 確認)/3パターンに「判定保留」追加/content_ready→content_visible(広告の背後発火問題)/主因の因果主張を弱める/即停止表現を現実化/売上範囲を iOS・対象アプリ・対象月に限定/ITT明記/D7D14起点=実験参加日 等。
    • 指標名を launch_bounce →「コンテンツ未到達離脱率」に改称。
  • 提案書を thomsons S3バケットに公開アップロード(profile thomson-ik / ap-northeast-1 / object ACL public-read、既存HTMLと同方式)。秘密情報スキャン済み。URL: https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html
  • 計測仕様の正典 .devnotes/AB_TEST_MEASUREMENT_SPEC.md を新規作成(コンテンツ未到達離脱率の計測フル+確定前提+残りTODO)。

気づき/確定事実:

  • 本番S3 app_open_ad_interval = 50(群Aの基準は「毎回」ではない)。処置差は想定より小さく、期間・最大減収は事前テストで再試算が必要。
  • Firebase/GCP プロジェクトは footballnext-bca77(Owner=info@thomsons.jp、You は Editor)。GA4 property は Viewer のみ=カスタムディメンション登録不可。BQエクスポート有効化は Owner 権限が要り Sandbox(課金なし)でブロック → GA4のみ方針の裏付け。

次回やること:

  • .devnotes/AB_TEST_MEASUREMENT_SPEC.md の TODO(willDisplay計測・App Openコールバック・rolling24hのabsバグ・固定コホート・収益式・設定取得SLA 等)を埋める。
  • トムソンの提案レスポンス待ち → 承認後に実装着手。

10:32 - 起動時広告 削減A/Bの測定設計(トムソン提案に向けた机上検討)

テーマ: review-20260605.html(多角レビュー)を起点に、トムソン側との議論をどう前に進めるかを検討。起動時広告(App Open Ad、総売上の約25%=ヒヤリング値)を「どこまで減らせるか/減らすと得か損か」を実測で判断する50/50無作為A/Bの測定設計を、コードの現状確認込みで詰めた。コード変更はなし(設計のみ)。

前提(トムソンとのやりとりで判明):

  • トムソンは「記事内広告がウザがられてる」認識が強い。You は「起動時広告への不満も大きい」と主張。→ どちらが主因かは片面ずつ絞って実測すれば決着がつく。
  • トムソンは「起動時広告を減らして満足度を測れない」と渋る。→ You が「離脱率(行動)で測れる」と返した。これが今回の設計の出発点。
  • 起動時広告の売上 ≈ 総売上の25%(ヒヤリング済み)。全消しではなく「絞るダイヤル」として扱う方針。
  • You は一開発者(決定権はトムソン)だが仕様提案は可能。提案は「測定設計込みのパッケージ」にして通しやすくする。

コードの現状確認(footballnext-ios / FootballNEXT2015):

  • ①パターン確定: 起動時広告は AppDelegate.swift:241 で showAdIfReady() をアプリが明示的に呼ぶ(SDK自動表示ではない)→ if で囲むだけでA/B群分けが成立。
  • 時間間隔スロットルは既に実装済み: showAdIfReady()(:546)内に appOpenAdInterval(分)による表示間隔ガード+直近60s background復帰の抑制あり。0で実質毎回、1440で1日1回。
  • GA4イベント配線済み: app_open_ad_show {interval_minutes} / app_open_ad_suppress {reason: interval/recent_background/not_ready} / user property app_open_ad_interval。
  • サーバ設定チャネルが存在: 起動時に S3 JSON(Const.baseS3Url/201811)→ app_setting → UserData.setAppSettings(UserData.swift:184)で UserDefaults に流し込む作り。app_open_ad_interval はキー名整合あり=全ユーザー一律ならアプリ更新なしで間隔変更可能(ただし無作為A/Bではなく前後コホート比較になる)。「ハードコード」認識は半分だけ正しかった。
  • 広告構成はパッチワーク: 起動時広告・記事下バナー(AdView)は AppLovin MAX(MAAdRevenueDelegate.didPayRevenue で表示単位の実収益が取れる)。だが記事一覧の差し込みは自前ローテ(NativeAdManager+AdNativeRate で AdG[Supership AdGeneration]/Zucks/LINE系 default を切替)=MAX外の直SDK。impression本数が一番多い面がMAX外なので、MAXのdidPayRevenueだけでは収益の大半を捕捉できない。
  • didPayRevenue の実装は空(MiddleAppLovinNativeAd.swift:61 等)。ad.revenue を拾えば実額が取れるのに今は何も記録していない。

測定設計(合意した骨子):

  • 方式: 50/50 無作為A/B。バケットをアプリに仕込む(永続UUID→%2)。群A=現状(毎回)/群B=起動時1日1回。ab_group を GA4 user property+各イベントに付与。
  • 判断軸①「お金」: 土台は全SDK共通の impression カウント(AdG/Zucks/MAX全部、表示の瞬間は必ずコードを通るので自前で数えられる)。金額換算は2バケツの eCPM —「起動時(MAX実額)」と「それ以外(=月次総売上−起動時実額 を それ以外impで割った混合平均eCPM)」。各社ダッシュボードを漁らずトムソンの月次総売上1個で導出可能。
    • eCPMが割り算で出る理由=出るのは混合平均だが、A/Bで使うのは群間のimpression“差”の金額で、差の中身の構成比はローテ配分が群無関係ゆえ全体平均と一致 → 平均を掛けても近似で正しい(数値例で確認)。偏りが気になればネット別imp計測+ネット別月次売上で精密化。
    • didPayRevenue は「全部の実額が取れる」ではなく「MAXスライスの実額+月次の答え合わせ」に役割降格。
  • 判断軸②「残り具合」: launch_bounce(起動Xs以内・コンテンツ0で終了)↓ / 週次セッション/人 ↑ / D7・D14継続 ↑。launch_bounce は「率」なので自然減(MAU −15%/年)に埋もれない=起動時広告への一番タイトな指標。
  • 判断ロジック(3パターン): 群Bの[お金/残り] が ①同じ以上/良化→即採用 ②微減/良化→残る分で回収するので採用 ③減/不変→効果なし戻す。最適 interval は多腕 or 階段で後追い。勝った値は S3 で全員展開(再リリース不要)。

仕込む実体(次セッション以降の実装対象):

  1. A/Bバケット割当(永続UUID→%2、ab_group 付与)
  2. launch_bounce イベント新設
  3. 全面・全ネット別の impression カウント(群タグ付き)
  4. didPayRevenue の空メソッドを埋める(MAX実額・補助)/起動時の出し分けは AppDelegate.swift:241 を if で囲む

未確認(次セッションで詰める):

  • S3 app_setting に app_open_ad_interval が現在入っているか/現在値(群Aの基準値の確定)。Const.baseS3Url 実体の中身確認。
  • AdG/Zucks 等の自前ローテ面に「表示された」確実なフック(didDisplay相当 or deque直後)があるか=全面impカウントの可否。
  • 月次総売上をトムソンからどの粒度でもらえるか(総額1個 or ネット別)。

次回: この設計をトムソンへの提案資料にまとめる(You が別セッションで継続予定)。


2026-06-13

00:39 - YouTube プレイヤーの WKWebView 化(割り込み対応・申請前の最終変更)

テーマ: 7channel 由来のナレッジ ios-wkwebview-youtube-inline-playback を footballnext-ios の動画詳細プレイヤーに適用。deprecated な UIWebView 実装(2015年製)を WKWebView 構成に置き換えた。

やったこと:

  • FootballNEXT2015/YoutubePlayerView.swift を全面書き換え(変更はこの1ファイルのみ)
    • UIWebView 継承 → WKWebView を内包する UIView(既存 SnsMoviePlayerView と同じプロジェクト内イディオム)。xib(素の <view> + customClass)と呼び出し側 load()/clear() API は互換のため他ファイル変更なし
    • インライン再生の必須2点セット: allowsInlineMediaPlayback = true + mediaTypesRequiringUserActionForPlayback = []
    • ナレッジ最大のハマりポイント対応: baseURL: nil をやめ、Const.baseURLString(https://fnext.thomsonsapp.com)を baseURL と embed URL の origin= で一致させる構成に
    • embed URL を http:// → https:// 化、enablejsapi=1 / allow="autoplay; encrypted-media; fullscreen" / allowfullscreen 追加
    • target="_blank" リンク無反応対策として WKUIDelegate.createWebViewWith で同一 WebView ロード
    • 自動再生をやめた(autoplay=1 → autoplay=0、ユーザー指示)。タップで再生開始に変更
  • arm64 シミュレータビルドで BUILD SUCCEEDED を確認(DEVELOPER_DIR に Xcode 26.5 指定)
  • footballnext-ios へ選択コミット(a94aa84、YoutubePlayerView.swift のみ)→ origin/master へ push 済み。別件の未コミット変更(push通知系・Podfile 等)は混ぜずに残置

詰まったこと / 気づき:

  • baseURLString は App ではなく Const のメンバー(どちらも App.swift 内に定義があり紛らわしい)。最初 App.baseURLString と書いてコンパイルエラー
  • ナレッジ第4項(埋め込み不可動画のフォールバック、サーバー側 status.embeddable フラグ)はサーバー改修を伴うため今回は見送り

次回やること:

  • You がこの変更を含めて App Store 申請(5.5.1 アーカイブ)
  • 実機での動画詳細画面の再生確認(ビルド確認のみ実施済み)

2026-06-06

17:32 - 定期実行の一元化 + Lambda 大掃除(今後のアプデ前の足場固め)

テーマ: 定期実行の呼び出し元が「cron 専用 EC2」と「AWS Lambda(EventBridge)」に二分されている状態を整理。cron 側へ寄せつつ、稼働実態のない Lambda を一掃した(AWSアカウント thomson-ik / 865107862340 / ap-northeast-1)。

やったこと:

  • TFN_cron(毎分の curl ラッパー Lambda)を cron 専用サーバーへ移管
    • 中身は fnext.thomsonsapp.com の8エンドポイントを叩くだけ(Baseball は既に cron 上で同等処理を実行済み)。
    • 移管手順: 8エンドポイントの200疎通確認(cronサーバーから)→ 実 crontab に8行追加(sleepでずらし配置、FootballNEXTセクションの movieUseSqs 直後)→ cronログで全8ジョブの起動確認(07:36:02)→ EventBridge ルール TFN_cron を disable。
    • サーバー上に ~/crontab_backup_20260606-073553.txt を退避。後日 Lambda 本体+ルールも削除済み。
  • 死んだ/不要な Lambda を段階的に削除(コードは毎回 /tmp にバックアップ後に実行)
    • TFN_cronUpdateOldCurrentData(2016 current データ用・起動0・トリガー無し)
    • TSP_* 5関数(TSP_cron/TSP_crawlArticle/TSP_cron10/TSP_uploadToS3OfIchimens/TSP_sendScheduledNews)+ 対応 EventBridge ルール4本。叩く先 spp.thomsonsapp.com(The Sports Paper)が HTTP 000=死亡を確認。
    • sports-paper-* 4関数 + API Gateway sports-paper(c2770nol99) + テスト残骸 hello-dev-hello/testDetach/lbTest + test-rule。
    • 孤児 EventBridge ルール Per10Minutes / sposhin-rss-crawler-notice-crawl-article(2019 temp.json 指し)。
    • 残存関数の稼働精査(7日/90日 invocation、APIキャッシュ無効も確認=「0回」は本物)→ 完全ゼロの19関数 + 捨て API Gateway 4本(prod-test-crawler / prod-rss-crawler / custom-runtime-php71-demo2 / rss-clawert)を削除。本番 football/baseball の API GW(7isghft565 / a0s3exiz21)と rss-crawler-notice API は現役なので残置。
    • 結果: Lambda 42関数 → 9関数(残9は全て現役か保守ツール)。
  • teiki / article エンドポイントの呼び出し経路を解析
    • footballnext-teiki-notification-articles(24h 897 / 7d 7,095 / 30d 29,322・エラー0・平均162ms)と footballnext-article(24h 126 / 7d 547)は、いずれも API Gateway(7isghft565) → Lambda(DynamoDB READキャッシュ) → MISS時のみ http://fnext.thomsonsapp.com/api4/...(CakePHP Api4Controller) の三段構成。
    • 呼び出し元はアプリ本体(iOS ArticleViewController/TeikiArticleSuiteTableView、Android ArticleDetailActivity/GogaiListViewModel/TeikiNotificationSuiteModel)。teiki は「定期プッシュ通知タップ → 記事一覧」の導線。
    • アプリは既に大半のAPIを https://fnext.thomsonsapp.com に直叩きしており、execute-api 経由は /article/{id} と /footballnext-teiki-notification-articles/{id} の2つ(iOSは加えて /oshirase-v2/{id})の飛び地のみ。原点は HTTPS 200・iOS は ATS 無効なので、直叩き移行の障壁は無し。

詰まったこと / 気づき:

  • CloudWatch GetMetricStatistics の「30日を1バケット集計」は高頻度関数で過少値を返す(rss-crawler が 24h 4320 なのに 30d 10 等)。7日を日次バケットで合算する方式に切替えて正確化。
  • API Gateway の delete-rest-api は連続実行でレート制限(429 / 約30秒に1回)。バックオフ再試行で対応。
  • 削除した footballnext-oshirase-v2(90日invocation 0)を iOS OshiraseViewController.swift:30 がまだ参照していることを後から発見。90日ゼロ=到達不能のデッド画面と判断し、復元せず据え置き(次回アプリ改修でコードごと整理)。

次回やること:

  • API Gateway 飛び地(/article/{id} + /footballnext-teiki-notification-articles/{id})を https://fnext.thomsonsapp.com/api4/... 直叩きへ移行 → 最終的に footballnext API Gateway を撤去。論点は Dynamo READキャッシュの代替(必要なら CakePHP 側キャッシュ)。
  • iOS OshiraseViewController のデッドコード整理(削除済み footballnext-oshirase-v2 参照の除去)。
  • rss-crawler の cron 一元化(RSS発見ロジックのサーバ側エンドポイント化)は引き続き宿題。

2026-06-05

19:50 - large-scale-service-review の試運転(Football NEXT 多角レビュー)+ GA4 接続 + Codex UXオーディット

やったこと:

  • large-scale-service-review スキルを Football NEXT で試運転。複数ソースを集めて多角レビューを実施し、結果を review-20260605.html(HTMLダッシュボード、チャート付き)に出力。
  • データソースを6つ集約して三角測量: ①広告売上推移 FootballNext売上推移.html(13年分、Youが提供)②全レビュー3,987件 ..._AppStore_20260605.json(Youが提供)③ASCレビュー/売上 ④GA4行動データ ⑤GA4コホート ⑥Codexによる広告実装の静的解析。
  • GA4 を接続: Football NEXT は Firebase Analytics 実装済→ GA4 property 211701453(footballnext-bca77) を特定。Google公式 google-analytics-mcp(pipx) を claude mcp add ga4 -s user で登録(ADC流用・property固定)。MAU/セッション/コホート/クラッシュ等を取得。
  • Codex の Build iOS Apps に UXオーディットを依頼(指示書 codex-ios-ux-audit_指示書.md)。実機起動は OneSignal 2.12.0 の arm64 simulator 非対応で失敗したが、静的コード解析で広告挿入ロジックを file:line 付きで特定(findings codex-ios-ux-findings.md)。
  • スキル本体を改善(~/.claude/skills/large-scale-service-review/SKILL.md): 収益モデル先行確定・ローカル成果物の能動スキャン・複数ソース三角測量・カバレッジ可視化を追加。

主要な発見(レビュー結論):

  • 広告ネットワーク売上は 2019ピーク¥17.5M → 2025 ¥5.4M(−69%)、下落が加速(2023 −23%/2024 −29%/2025 −34%)。直近月¥332k。
  • 全レビュー分析: 平均星が2022→2023で崩落(3.0→1.6→1.33)、「広告」言及率 20%→93%。売上下落と時系列一致=広告過多によるデススパイラル。
  • GA4: MAU 26,076→17,609(約2年−32%)、セッション−41%。クラッシュ影響は1%未満で安定性は無問題。94%iOS・ほぼ100%日本。エンゲージ率90%・月35セッションでロイヤル層はヘビー。
  • Codex: App Open Ad のスロットル(頻度ゲート)がコメントアウトされている=広告抑制が外された物証。一覧10件中2枠・コメント5件ごと等の密度も判明。

気づき:

  • 広告型アプリは ASC の proceeds=0 が正常で、実収益はメディエーション/ローカル集計にある。最初に収益モデルを確定しないとデータの在処を外す。
  • AdMob/MAX は別管理者で面別収益は取得不可。効果検証はGA4の離脱シグナルで代替する方針。

次回やること:

  • 改善実装の着手判断(最優先=App Open Ad のスロットル復活)。
  • 任意: OneSignal を監査用ビルドでスタブ化して Codex に実機UX再検証させる。

2026-06-03

17:11 - 試合詳細に「得点者」表示を追加(iOS 5.5.1 + サーバ/フルスタック機能追加)

やったこと

  • サーバ(footballnext-server):
    • API-Football v3 の単一試合レスポンスに events(得点・カード・交代)が同梱されることを実APIで確認 → 追加の RapidAPI コール無しで得点者が取れる。CronController::ovserveFixtures の更新に events_json 保存を追加し、取得&更新の本体を _updateFixtureFromApi($id) に共通化。
    • 終了試合も強制更新できる updateFixture($id) を新設(/cron/updateFixture/{id}、Match Finished スキップ無し)=過去試合のバックフィル手段。
    • V2Controller::fixture()(iOS が叩く /v2/fixture/{id} の実体)に scorers 整形を追加:type=Goal 抽出、team.id で home/away 判定、OG は反転せず素直にマップ、PK/OG 種別、アシスト。当初 V1Controller を誤編集→スモークテストで気づき V2 へ移して V1 は撤回。
    • AfterInstall.sh にモデルスキーマキャッシュ自動クリア(rm -f app/tmp/cache/models/*)を追加。
    • DB(master)の fnlive_fixtures に events_json TEXT NULL を追加(ALTER)。
    • デプロイ後、/v2/fixture で scorers キー・500なし、/cron/ovserveFixtures 200無エラーを確認。直近10試合を updateFixture でバックフィル(全件 scorers=得点数 一致)。
  • iOS(footballnext-ios / 5.5.1):
    • LiveFixture に scorers(表示用 computed property 付き)を追加。
    • 得点者カードを LiveFixtureScorersView(記事テーブルの tableHeaderView・手動レイアウト)で実装:タイトル外出し+白い角丸カード、ホーム左/アウェイ右、live_ball 画像インライン、PK/OG、アシストはボール幅インデント、行間調整。
    • バージョン 5.5.0→5.5.1、MAX Mediation Debugger OFF を同梱。コミット a3793be・push・タグ 5.5.1(規則 v…_byXcode… とは別の素タグ、ユーザー指定)。トムソン確認OK。

詰まったこと / 気づき

  • エンドポイント取り違え: /v2/fixture は V1Controller ではなく V2Controller。スモークテスト(scorers キー欠落)で発覚。手を入れる前に「実際に叩かれるルート」を確認すべし。
  • CakePHP 2.x スキーマキャッシュ: カラム追加後、app/tmp/cache/models/ を消さないと save() が新カラムを黙って捨てる(エラーも出ない)→ AfterInstall で自動クリア。
  • レイアウト試行錯誤: スコアボード用テーブルは storyboard で高さ固定 184・自己サイズ非対応。section 追加や estimatedRowHeight=0 で見切れ・崩壊。最終的にスコアボード側は触らず、記事テーブルのヘッダーに手動レイアウトで載せて解決。
  • OG の team 帰属: API-Football は OG を「得点が入る側(受益チーム)」に既に割り当てている(ブラジル6-2パナマで実検証、home6/away2 がスコアと一致)→ 反転不要。
  • 起動経路: ovserveFixtures はリポジトリ内から呼ばれず、cron 専用サーバの */2 * * * * curl http://fnext.thomsonsapp.com/cron/ovserveFixtures(公開URL→web サーバで実行)で回っていた。

次回やること

  • iOS 5.5.1 を Xcode アーカイブ → App Store 提出。
  • 任意: 方針転換で残った未使用 LiveFixtureScorersCell.xib + 空セルの掃除。

2026-05-30

20:00 - footballnext-ios の近代化(広告SDK全面更新・Zucks/Moloco追加・GMA13移行・GitHubリポジトリ正常化)

やったこと

  • Xcode 26.5 でビルド不可を解消: Reachability pod 3.7.6→3.7.7(pod update Reachability --no-repo-update)。新SDKで private 扱いになった #import <netinet6/in6.h> が 3.7.7 で除去済みのため解決。Ads-Global のスクリプト失敗はビルド中断の巻き添えで、Reachability 修正後に消えた。
  • Zucks ネイティブ広告セルを追加: 記事枠(ZucksNativeAd/ZucksNativeAdCell+xib)とミドル枠(MiddleZucks*)を既存パターン(CoreNativeAd 派生ローダー → NativeAdRow → セル)に倣って実装。AdNativeType.zucks(=15) / AdMiddleNativeType.zucks(=7) 追加、各 NativeAdManager と CoreRow に配線。frameId は記事 _f7758a97de / ミドル _02f98424fe。
  • Moloco を MAX に追加: Podfile に AppLovinMediationMolocoAdapter(→4.6.1.1、MolocoSDKiOS 4.6.1)。アプリ側コード不要(MAX が自動ロード)。Moloco の SKAdNetwork ID は既存で網羅済みだった。
  • 広告SDK/アダプタ一斉最新化(MAX Mediation Debugger 指摘分)をリスク順(低→中→高)に段階更新:
    • AppLovin 13.6.2 / Mintegral 8.1.4 / Pangle 8.1.0.6 / Amazon 5.6.2 / Line(FiveAd) 3.0.1 / Google Mobile Ads 13.3 / InMobi 11.3。
    • FiveAd 2→3 はメジャーだが旧APIが deprecated 維持+警告非エラー設定でコード変更不要。pod 'FiveAd' が直書きロックされていたため pod update AppLovinMediationLineAdapter FiveAd で同時更新。
    • GMA 12→13 は最小 iOS 13 必須のため iOS12→13 化(プロジェクト/Pods/post_install を 13.0 統一)。GADNativeAd→NativeAd(NS_SWIFT_NAME 化)で CoreRow の 3 箇所を修正。削除バナー関数等は能動使用なしで API 破壊ほぼ無し。
    • GMA は 13.3 止まり(GoogleMobileAdsMediationInMobi 11.3.0.0 が GMA ~> 13.3.0 を要求。13.4 は Google 製 InMobi アダプタの追従待ち)。InMobi の AppDelegate 使用(IMSdk init/setLogLevel)は 11.3 でも互換。
  • Liftoff(Vungle): アダプタ 7.6.2.0→7.7.3.0(VungleAds 7.7.3)。「SKAdNetwork Incomplete」は公式XML(155件)と差分し不足2件(apzhy3va96/ln5gz23vtd)を Info.plist に補完。
  • アーカイブ時の「Upload Symbols Failed」警告(AppLovin/DTBiOS/InMobi/Moloco の dSYM 無し)は、ベンダーが dSYM 非同梱のため無害・除去不可と判断(クラッシュ症号化のみ影響、配信に支障なし)。

重大な気づき / 是正

  • operation 配下の footballnext-ios が「GitHub移管前の CodeCommit クローン」だった。GitHub 側は移管時に履歴を書き換えて>50MBバイナリを除去し Pods/ を .gitignore 済みなのに、ローカルは remote=CodeCommit のまま・Pods を丸ごとコミットする旧運用。push しようとして発覚(CodeCommit 認証切れ)。
  • 是正: GitHub ichirokisanuki/footballnext-ios(master) を新規clone → 今回のソース変更のみ(Pods 除外、17ファイル)をパッチ移植 → pod install → push(28f5fe7)。GitHub クローンを operation 配下に再配置し、旧 CodeCommit クローンは ~/Downloads/footballnext-ios-codecommit-backup へ退避。
  • 今回 Pangle バイナリが 124MB=GitHub の 100MB 制限超で、Pods は載せられない/載せない設計の妥当性を再確認。

次回やること

  • 旧 CodeCommit バックアップ(~/Downloads/footballnext-ios-codecommit-backup)の削除(新環境のビルド確認済み)。
  • 実機で MAX Mediation Debugger 再確認(Liftoff が Completed になるか、各SDKが LATEST 表示か)。
  • xcuserstate の追跡解除(.gitignore 済みなのに過去コミットで追跡され続けている)。

2026-05-21

19:33 - footballnext-android の 16KB 対応リリース対応(全面近代化)

やったこと

  • 背景: Google Play から「16KBメモリページサイズ非対応」の指摘(対応期限 2026-05-31)。footballnext-android の前回リリースは2021年10月。
  • 16KB問題の診断: アプリ自体にネイティブコードは無く、.so を持ち込むのは Realm のみと特定。librealm-jni.so が唯一のネイティブlib。Realm Java 10.19.0 が「16KB ELF packaging」対応版(Realm Java は EOL、これが事実上の最終対応版)。
  • ツールチェーン全面近代化: Gradle 6.7.1→8.9、AGP 4.2.0→8.7.3、Kotlin 1.3.50→2.1.0、JDK 8→17+(Android Studio同梱JBR 21使用)、jcenter 除去。compileSdk/targetSdk 30→35、minSdk 19→24。
  • Realm 7.0.8→10.19.0(16KB対応の本丸)。
  • ViewBinding 全面移行: kotlin-android-extensions 廃止に伴い、synthetic を使う43ファイルを ViewBinding 化。
  • 依存刷新: OneSignal 3→5(5.1.38固定)、AdMob 19→23、okhttp 3→4、AndroidX各種、Firebase BoM。
  • 撤去・差し替え: nend SDK 撤去(サービス終了済み)、旧 YouTubeAndroidPlayerApi.jar(提供終了)を android-youtube-player に差し替え。
  • 署名鍵の確定: Dropbox の footballNEXT_key2.jks を keytool 検証。端末上の現行版アプリの証明書 SHA-256 と完全一致 → 正しい署名鍵と確定(Play App Signing 未使用、この鍵で直接署名)。keystore.properties 方式で署名設定を整備。
  • 実機テスト(Huawei YAL-L21 / Android 10)でクラッシュ4件を発見・修正:
    1. Realm 10 の UIスレッド書き込み禁止 → allowWritesOnUiThread(true)
    2. SplashActivity の DialogFragment.show() が onSaveInstanceState 後に落ちる → state ガード
    3. クリック連打防止の窓が3500ms と長く、記事一覧のタップが1回で効かない(既存バグ)→ 600ms
    4. SiteListFragment / TvProgramListFragment がコンストラクタ引数を持ち、復元時に InstantiationException(既存バグ)→ 引数なし+arguments方式
  • 署名済みリリースAAB(versionCode 17 / versionName 3.0.7)生成。16KBアラインメント検証OK(全4ABIで .so LOADセグメント align=16KB、APK/AAB両方で zipalign -P16 整合)。
  • footballnext-android リポジトリに7コミット。

気づき

  • 16KB対応そのものは Realm のバージョン更新のみ。だが「新規リリースを出す」には4年分の近代化が芋づる式に必要だった(jcenter閉鎖で旧構成はビルド不能、AGP8がGradle/Kotlin/JDK更新を強制)。
  • OneSignal 5.8+ は OpenTelemetry/okhttp5/wire を引き込み kotlin-stdlib を2.x系へ強制昇格させる → 5.1.x系最新(5.1.38)に固定して回避。
  • クラッシュ2件(連打防止・コンストラクタ引数Fragment)は2021年版から潜在していた既存バグ。モダンな Fragment ライブラリで顕在化した。

次回やること

  • You: footballnext-android/app/build/outputs/bundle/release/app-release.aab を Play Console にアップロード(内部テスト→製品版推奨)。
  • 動画/コメント/設定画面の手動確認、OneSignalプッシュの実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認。
  • footballnext-android の7コミットを origin へ push するか判断。

2026-05-19

09:33 - エラーログスパイクアラートの原因特定(cake_sessions Duplicate entry)

やったこと

  • 直近のエラーログスパイクアラートメール 3 件(5/16 16:57 / 5/17 07:48 / 5/19 04:51)の原因調査
  • 各時刻の RDS メトリクス確認 → CPU・接続数・IOPS とも正常範囲、5/17 のみ FreeableMemory min 74MB とやや低めだが他は通常
  • CloudWatch Alarms 履歴を辿ってアラート源を特定:
    • footballnext-apache-error-log-all-spike(LogMetrics 名前空間、Apache error log のスパイク監視)
    • 3 件とも同じアラームが ALARM 遷移していた
  • メトリクスフィルタからロググループ FootballNEXT/Apache/error_log を特定
  • 各時刻 ±5 分の Apache error_log を取得 → 3 件とも完全に同じパターン:
    • PHP Fatal: PDOException SQLSTATE[23000] Integrity constraint violation: 1062 Duplicate entry 'xxx' for key 'cake_sessions.PRIMARY'
    • 短時間に数十件発生 → アラート発火
  • referer 解析: すべて fnext.thomsonsapp.com/<不審パス> から
    • /dwcc/, /lottery/, /api.php/, /Public/, /qiantaizhuanyon_chaxun/, /iexchange/webtrader/, /melody/, /appx/, /syn/, /djacgkjs/ ほか
    • 中国語パス・賭博/抽選/取引系・admin パネル探索・PHP scanner の典型パターン
  • ユーザー確認: fnext.thomsonsapp.com は 本番 API ドメイン(捨てるわけにはいかない)
  • 構造の理解: bot が API ドメインに対してパススキャンを並列で打ってきて、同一セッション Cookie を使い回すので CakePHP の DatabaseSession::write() で INSERT INTO cake_sessions が race condition で衝突 → Fatal

決めたこと

  • 緊急性なしと判断しいったん放置。攻撃トラフィック由来でデータ実害なし・RDS への影響も軽微、ただし将来の対応候補として頭の片隅に置く
  • 対応するなら候補:
    • A. ALB or Apache で攻撃パス拒否(短期、即効)
    • B. CakePHP Session 保存先を File or Redis に変更(恒久、副次効果大)
    • C. cake_sessions 書き込みを try-catch で握り潰し(症状対症療法)
    • D. CloudWatch アラームしきい値緩和
    • E. AWS WAF / Cloudflare 導入

詰まったこと / 気づき

  • 「エラーログのスパイク」と聞くと RDS や Web サーバの本格的な異常を疑いがちだが、実態は bot 攻撃由来の Fatal 連発だった
  • fnext.thomsonsapp.com というドメインを当初「正体不明の第三者ドメイン」と勘違い。本番 API ドメインを user が運用してることを最初に確認すべきだった(聞いてしまえば 30 秒だが、聞かないと方向を間違える)
  • CakePHP の DatabaseSession は race condition に弱い設計(INSERT を素直に投げる、ON DUPLICATE KEY UPDATE 等を使わない)。普通のサービスでも稀に Duplicate が発生する可能性

次回やること

  • もし cake_sessions Fatal が頻発するようなら ROADMAP の「いつか」項目を「今月」「今四半期」に格上げする
  • baseballnext 統合後の RDS 状況は引き続き問題なし(5/11 dev-timer #15 経過、本日 5/19 朝の確認でも安定)

2026-05-09

17:45 - dev-timer #14 経由で Step 2 効果検証 / baseballnext 統合方針

やったこと

  • dev-timer #14(5/9 16:00 設定)の通知を受けて作業再開
  • articles インデックス DROP(5/8 02:54 実施分)の Before/After を CloudWatch で比較
    • FreeableMemory: min 55 → 63 MB / avg 137 → 133 MB(ほぼ変化なし、想定通り)
    • CPU avg: 5.4 → 4.9% / max: 60.5 → 58.6%(軽微な改善)
    • ReadIOPS avg: 5.9 → 2.3(-61%) / max: 2193 → 649(-70%) ← 本質的な効果
    • スロークエリ・体感異常なし
  • Step 3 (buffer_pool 1024→768MB 縮小) は baseballnext 統合計画の存在により 保留確定

決めたこと

  • DROP した 4 個(article_site_id / image_width / title / del)は実際に冗長で、本番影響ゼロ。Step 2 成功判定
  • buffer_pool 縮小は baseballnext 統合シナリオ確定後に再評価。今は触らない
  • baseballnext 統合作業は baseballnext-operation プロジェクト側で進行(footballnext-operation はサーバー受け入れ準備状態を維持)

詰まったこと / 気づき

  • 「ReadIOPS が大幅に減る」のはインデックス整理の主効果として理解しておく価値あり。FreeableMemory 改善を期待すると外す
  • gp2 100 IOPS ベースの環境では IOPS 削減はメモリ削減と同じくらい価値がある
  • baseballnext 統合計画の存在で「buffer_pool 縮小」が筋が悪くなったように、設計判断は前提条件次第で逆転する(メモリ縮減 vs 統合余地確保が対立する)

次回やること

  • baseballnext 統合作業の進捗待ち(baseballnext-operation 側)
  • 統合作業中・直後の footballnext RDS のメトリクス観察(FreeableMemory・CPU・IOPS)
  • 統合後にあらためて Step 3 の判断(buffer_pool 1024MB 維持 / 縮小 / medium 昇格)

2026-05-08

02:54 - articles インデックス Step 1+2 一気実行 / impressions 廃止 / baseballnext 統合計画把握

やったこと

  • articles インデックス整理 Step 1 (INVISIBLE) を実施
    • A 群 4 個(article_site_id / image_width / title / del)を ALTER INDEX ... INVISIBLE で隠す
    • 即時切替・即時リバート可能の保険フェーズ
  • 通常は INVISIBLE で 1〜数日観察の流れだが、A 群が冗長 prefix 確定で戻す可能性が極めて低いため 観察期間をスキップして即 Step 2 (DROP INDEX) に進む判断
    • ALTER TABLE articles DROP INDEX ... ALGORITHM=INPLACE, LOCK=NONE で 4 個を 1 文で物理削除(実時間 0.27 秒)
    • 結果: index 499 → 369 MB(-130MB / -26%)、インデックス数 22 → 18
  • impressions テーブルの利用調査
    • INSERT は V1Controller.php:296 1 箇所のみ(他はコメントアウト済み)
    • 読み取りは管理画面 PortalController の集計用 2 箇所のみ(エンドユーザー API からは未参照)
    • ユーザーが INSERT をコメントアウトしてデプロイ → 自分で TRUNCATE 実行
    • 結果: 6,982,221 行 / 730 MB → 0 / 0.03 MB(約 730 MB 解放)
  • DB 全体: 5,006 → 4,145 MB(開始時 9,102 MB から -54%)
  • baseballnext を footballnext RDS に統合する計画があることをユーザーから共有
    • baseballnext RDS(db.t4g.micro / MySQL 8.4.8 / 20GB / Single-AZ)の実態調査
      • 合計 2.24 GB / 90 テーブル
      • articles 632 MB(5/8 08:43 更新 = 現役稼働)、old_articles 522 MB、sns_movie_tweets 472 MB、movies 146 MB ほか
      • SELECT 5.8/秒、INSERT 0.73/秒、UPDATE 0.24/秒、buffer_pool ヒット率 99.775%
      • 「アイドル」ではなく現役稼働中のサービス
    • 統合シナリオ B(t4g.small 維持)で約 +$15/月削減、ただしホット working set が 1.25-1.5GB 規模になりヒット率は要観察
    • C(medium 昇格セット)も選択肢、コスト的には現状(micro+small)と大差なし
  • セッション内で起きた誤認の訂正
    • hot_keyword_rankings の古いデータ削除を「buffer_pool 効率↑」と説明したのは雑だった
    • 正しくは「読まれていないデータは buffer_pool に乗らない → 削除しても FreeableMemory は変わらない」。ストレージ(FreeStorageSpace)には効くが、メモリには効かない
    • 同様に old_articles / impressions の TRUNCATE もメモリには効かなかった(実測でも変化なし)

決めたこと

  • articles インデックス A 群は INVISIBLE 観察期間をスキップして即 DROP(戻す可能性 ≒ 0、CREATE INDEX で再構築可能)
  • impressions の機能を廃止(INSERT 停止 + 全データ廃棄)
  • Step 3 (buffer_pool 1024→768MB 縮小) は 保留。baseballnext 統合計画と相性が悪い
  • baseballnext 統合の実作業は baseballnext-operation プロジェクト側で進める(こちら footballnext-operation は受け入れ側として現状維持)
  • メモリに「直接効く / 間接的に効く / 効かない」の切り分けを今後は混同しない

詰まったこと / 気づき

  • 「アイドルに見えた baseballnext が実は現役」だったように、CloudWatch DatabaseConnections avg だけで稼働判断するのは危険(短時間接続はサンプリングで漏れる)
  • インデックス整理は「メモリには効かないが ReadIOPS には大きく効く」の代表例
  • 設計判断は前提条件(baseballnext 統合の有無)で容易に反転する

次回やること

  • 5/9 16:00 dev-timer #14 で Step 2 効果検証
  • baseballnext 統合計画の進捗(baseballnext-operation 側で進行)と footballnext RDS への影響観察
  • 統合後にあらためて buffer_pool / インスタンスサイズの判断

02:24 - articles OPTIMIZE 結果検証 / hot_keyword_rankings 調査 / メモリスパイク切り分け

やったこと

  • 深夜にユーザーが phpMyAdmin で実行した OPTIMIZE TABLE articles の結果検証
    • data 812 → 263 MB(-549 MB / -68%)
    • index 2010 → 499 MB(-1511 MB / -75%)← インデックス側のページ充填率がかなり悪かった
    • total 2822 → 762 MB(-2060 MB / -73%)
    • data_free 1101 → 7 MB(断片化解消)
    • avg_row_bytes 1448 → 426(妥当な値に戻った)
  • DB 全体 9102 → 5006 MB(-4096 MB / -45%、old_articles 空化分と合算)
  • CloudWatch FreeStorageSpace: 5.86 GB → 8.53 GB(+2.66 GB)
    • テーブル削減 4GB に対して free 増分が小さいのは InnoDB の共有テーブルスペースや一時ファイルが OS に即返却されないため
  • FreeableMemory はほぼ変わらず(OPTIMIZE 後も avg 110-126 MB)
    • buffer_pool ヒット率 99.996% で working set が小さい以上、テーブルが縮んでもメモリ消費は同じ
  • hot_keyword_rankings テーブルの利用調査
    • 7.4 年分(最古 2018-11-25 / 最新 2026-04-09)、全 9,153,398 行のうち直近 1 ヶ月の追加が 495 行のみ(事実上書き込み停止)
    • dir = YYYYMMDD_HHMM フォーマット(10分ごとのスナップショット)、distinct 386,983 件
    • コード参照は すべて「最新 HotKeywordHistory.dir に紐づく rank top2」のみ(V1Controller / S3UploaderController / CronController / CurrentDataComponent / Portal)
    • 過去データは完全に死蔵
  • 19-20 時の FreeableMemory スパイク(min 55MB)の原因切り分け
    • 過去 7 日同時間帯の min は 119-151 MB(特異なパターンなし)
    • 5/7 19:20 のみ 1 分ピンポイントで 148 → 55 MB に急落
    • 同時刻に流していたクエリ: articles + old_articles への AVG(LENGTH(body)) などフルスキャン集計 ×2
    • 結論: 自分の調査クエリが buffer_pool を瞬間的に奪った人為的スパイク。cron 由来の定常パターンではない

決めたこと

  • hot_keyword_rankings の古いデータ整理は判断保留(実行は別途)
  • 19-20 時スパイクは「人為的・cron 由来ではない」と確定。深追いせずクローズ
  • メモリ問題の本質は構造的(db.t4g.small 2GB に MySQL 8.4 + 9GB データ)であり、根本対策はインスタンス昇格に帰着するという再確認

詰まったこと / 気づき

  • 本番 RDS への AVG(LENGTH(...)) FROM 大きいテーブル は瞬間的にメモリを圧迫する。今後は EXPLAIN で I/O 規模を確認するか、リードレプリカ復活後にそちらで実行する
  • OPTIMIZE TABLE は data_free(断片化)だけでなく インデックス側の充填率改善も大きい。articles の index 75% 削減は予想以上だった
  • ストレージ削減は FreeableMemory には直接効かない(buffer_pool ヒット率がもともと高い場合)

次回やること

  • インスタンス昇格 db.t4g.small → medium の段取りを詰める
  • CloudWatch アラーム新設(FreeableMemory < 100MB)
  • articles のインデックス整理は OPTIMIZE で index も縮んだため優先度を下げる(追加効果は数百MB 程度)
  • hot_keyword_rankings のデータ整理(実行可否は別途判断)

2026-05-07

21:47 - RDS (footballnext) FreeableMemory カイゼン調査

やったこと

  • .gitignore にサブプロジェクト3つ (footballnext-ios/, footballnext-android/, footballnext-server/) を追加。3者はそれぞれ独立した Git リポジトリで管理されているため、本リポは指揮塔ポジションとして除外する形にした
  • AWS CLI で RDS の現状把握
    • インスタンス: db.t4g.small / MySQL 8.4.8 / gp2 20GB / Single-AZ / リードレプリカなし
    • パラメータグループ: general-mysql84 (custom)、ステータスが pending-reboot
    • SG: sg-083bace7c7ee9e450 で 3306/tcp が 0.0.0.0/0 開放(PubliclyAccessible=False で実害なしだが要対応)
  • CloudWatch で FreeableMemory 7日間の数値把握
    • Min 55 MB / Avg 141 MB / Max 173 MB ← 黄信号〜赤信号ゾーン
  • パラメータグループの中身を確認し、performance_schema = 1 が user-set かつ pending-reboot 状態で残っていることを発見
    • エンジンデフォルトは 0、過去7日間に再起動イベントなし → 実効値は 0 のまま
    • このまま再起動すると Performance Schema が起動して 200〜400MB 追加で食う「不発弾」状態
  • reset-db-parameter-group で performance_schema を engine-default に戻した(即実行)
  • EC2 (cron インスタンス、52.194.87.168) 経由で RDS に SSH トンネル張って実調査
    • footballnext ユーザーから見える DB は footballnext 1個のみ、9.1 GB
    • database.php に書かれている kowai / search_article は実体なし
    • 大きいテーブル: articles 2.8GB、old_articles 2.0GB、hot_keyword_rankings 1.2GB
    • buffer_pool ヒット率 99.996%(read_requests 152億 vs disk reads 60万)→ working set < 1GB
    • articles の data_free 1.1 GB / data 0.8 GB(135% スカスカ=激しい断片化)
    • articles のインデックス: 22個・合計 2.0 GB、4個は明確に冗長 prefix
  • articles の自動アーカイブ・期間削除処理を footballnext-server/ で grep → 存在しない(コメントアウトのみ)
  • old_articles を空化(ユーザーが phpMyAdmin で TRUNCATE 実行)→ 2,036 MB → 0.34 MB、約 2 GB 解放
  • articles のインデックス削除候補を分析(A群4個: article_site_id / image_width / title / del、いずれも他複合 index の prefix)→ 実行は保留

決めたこと

  • サブプロジェクト 3 リポは本リポから .gitignore で除外
  • performance_schema = 0 を維持
  • old_articles のデータは廃棄、テーブル本体は当面残置(容量影響軽微)
  • articles のインデックス整理は今日は実行せず、調査結果のみ記録

詰まったこと / 気づき

  • 「old_articles の方が行数 3 倍なのに半分のサイズ」謎の答えは InnoDB のページ充填率の差。articles は INSERT/UPDATE/DELETE が活発で data_free が data 本体を上回るほど断片化していた
  • 接続不要で取れる情報(CloudWatch / parameter group)の段階で「performance_schema 不発弾撤去」までできたのは収穫
  • buffer_pool ヒット率が異常に高い → メモリ問題の正解はチューニング側ではなくインスタンス昇格 or DB ダイエット側にある可能性が高い

次回やること

  • articles のインデックス整理(A 群を INVISIBLE → 様子見 → DROP → OPTIMIZE)
  • CloudWatch アラーム新設(FreeableMemory < 100MB)
  • old_articles 空化後の CloudWatch FreeStorageSpace 反映確認

最近のコミット

README

footballnext-operation

概要

(記入予定)

セットアップ

(記入予定)

使い方

(記入予定)