1概要
何をするアプリか / なぜ必要か / 誰のためか / 主な機能
どんなアプリか
TripDataHub は、貨物・旅客のパイロットが自分の乗務スケジュールをタイムゾーン込みで正しく把握するための iOS アプリです(旧称 BidProSchedule、バンドル ID は今も com.sfune.BidProSchedule)。使い方はシンプルで、パイロットは社内スケジューリングシステム CrewAccess からスケジュール PDF を書き出し、iOS の共有シートから本アプリに渡すだけ。あとはアプリが PDF を解析・保存し、乗務の時系列ビュー(Timeline)と、iPad では 8 週間の Bid Period カレンダーとして表示します。
本アプリは UPS・航空会社・雇用主・労働組合・スケジュール提供元のいずれとも無関係で、航空会社のシステムに直接つなぐこともしません。データの取り込みは すべて手動・ユーザー起点 です。スクレイパーでも、代替スケジューリングシステムでも、記録の正本(system of record)でもありません。あくまでパイロットが正規にアクセスできるデータの上に重ねる、個人用のユーティリティです。
解決する課題
CrewAccess が出すスケジュールは PDF で、UTC とローカル時刻が混在した生の形式です。そのため「これからの 2 週間が実際どうなるのか」を時系列で一覧する手段がなく、次のフライトまでのカウントダウンもなければ、同僚の予定を見る方法もありません。TripDataHub はこの PDF を、次の 4 つに変換します。
- 一目で読める、タイムゾーン正確な乗務の時系列ビュー(Timeline)。
- Bid Period 全体を俯瞰できる カレンダー(iPad)。割り当てられたトリップに加え、手動管理のリザーブ・コールアウト・個人予定も並べて表示。
- 「空港まであと何時間か」を頭で計算せずに済む 次のフライトまでのカウントダウン(ウィジェット+ライブアクティビティ)。
- 信頼できる同僚との 読み取り専用のスケジュール共有(相互承認制)。
対象ユーザー
- CrewAccess を使う貨物・旅客のパイロット(App Store 上では「brown-tail cargo pilots」と位置づけ)。
- 複数の母基地(現在 ANC / SDF・SDFZ / ONT / MIA)をまたぐ、リザーブ中心・タイムゾーン中心の運用をこなす人。
- 同僚に自分の予定を読み取り専用で共有したいパイロット。
主な機能
- CrewAccess PDF の取り込み — 共有拡張で PDF を受け取り、PDFKit でテキスト抽出 → トリップ/レグ/勤務の構造化 JSON に変換。
- Timeline タブ — トリップ・フライト・デッドヘッド・レイオーバー(ホテル情報つき)・手動の運用イベントを、空港ローカル時刻で時系列に表示。
- Calendar タブ(iPad) — 8 週間の Bid Period グリッド。トリップ、運用イベント(RSV / LCO / HOT / RCID / CQ)、個人予定(通勤・医療・予約・その他)を「運用 > Bid > 個人」の優先順でレイヤー表示。
- 次のフライトまでのカウントダウン — ホーム画面ウィジェット(T-12h〜T-6h)とライブアクティビティ/ダイナミックアイランド(T-6h〜T+6h)。STD を過ぎても未出発なら自動で「Delayed」表示に。
- フレンド共有 — GEMS ID の相互承認による、CloudKit Public Database 経由の読み取り専用共有。
- TripBoard「OpenTime」閲覧 — 任意・読み取り専用。ユーザー自身の BidPro / TripBoard 認証情報が必要で、App Review デモモードでは無効。
- LogTen 互換エクスポート。
- CloudKit によるデバイス間同期 — 取り込んだスケジュールを同期(競合は local-wins、削除はトゥームストーン方式)。
現在のステータス
- Version 1.2.1(build 68) — 2026-06-13 時点で最新(
mainの当日コミット)。v2.xタグの扱いについては「現状」を参照。 - iOS 17.0 以上。iPhone(縦向き中心)と iPad(横向き最適化)で UI を分けて提供。
- v1 のコア機能(取り込み・Timeline・Calendar・カウントダウン・フレンド共有・手動イベント・LogTen エクスポート・App Review デモモード)はすべて実装・出荷済み。
- 直近の開発はフレンド共有/プロフィールの CloudKit 同期の安定化が中心で、その仕上げが v1.2.0 / v1.2.1 リリース。
2アーキテクチャ
ビルド構成 / データの流れ / 保存先 / CloudKit / 守るべき不変条件
ビルドターゲット
すべて project.yml から XcodeGen で生成しています。
- TripDataHub — 本体アプリ(SwiftUI / iOS 17.0+ / Swift 5.9 / Team
54WZM2U9MK/ バンドルcom.sfune.BidProSchedule)。 - TripDataCountdownWidgetExtension — カウントダウン用のホーム画面ウィジェット+ライブアクティビティ。
- TripDataShareActionExtension — CrewAccess PDF を受け取る共有シート拡張。
- TripDataHubTests / TripDataHubUITests — ユニットテストと UI テスト。
ソース構成(TripDataHub/)
Models/ — データ型
TripModels.swift(PayPeriodSchedule / TripLeg / OpenTimeTrip / FriendConnection)を中心に、CalendarModels、FlightCountdownSupport、FriendScheduleMatching、ProfileModels / ProfileSnapshot、DeviceScheduleModels、RosterParsingModels などが並びます。
Services/ — 業務ロジック・解析・同期
AppViewModel.swift— 状態管理とオーケストレーションの中核(かなり巨大)。CrewAccessPDFImportService.swift+PDFTripParser.swift— PDFKit によるテキスト抽出と、CrewAccess スケジュールの行単位パース。- CloudKit 同期系 —
CrewAccessImportCloudKitService/DeviceScheduleCloudKitService/FriendScheduleCloudKitService/GEMSVerificationCloudKitService/ProfileCloudKitService。 IATATimeZoneResolver(空港→タイムゾーン)、BidPeriodService(Bid / Pay Period の境界計算)、DomicileSupport(母基地の扱い)。TripBoardSyncService/TripBoardAuthService— OpenTime 取得(任意・ユーザー認証)。FlightCountdownCoordinator/NextReportNotificationService— カウントダウンと通知。TripScheduleSnapshotEncoder(Web ビューア/共有向けシリアライズ)、AppGroupImportHandoff(共有拡張からの受け渡し)、AppReviewDemo(審査用デモモード)。
ViewModels/ ・ Views/ ・ その他
- ViewModels/ — MVVM ルートの
AppViewModel、BrowserViewModel、プレビュー用データ。 - Views/ — Timeline / Calendar(iPad 専用は
Views/iPad/)/ OpenTime / Settings / Profile / Friends などの SwiftUI 画面と、共有描画ヘルパー(TimelineRowViews/CalendarSupport/MaterialIconView)。 - TripDataHubApp.swift — エントリポイント。ディープリンク
tripdatahub://import?manifest=<id>を処理。
データの流れ ① 取り込みパイプライン
- ユーザーが PDF を共有 → 共有拡張 が
public.pdfを受け取り、ファイルとマニフェスト JSON を App Group コンテナ にコピーし、本体アプリへディープリンク。 - 本体の
AppGroupImportHandoffがマニフェストと PDF を読み込む。 CrewAccessPDFImportService(PDFKit)でテキスト抽出 →PDFTripParserが行単位でトリップ・勤務・レグ・ホテル・クルーの JSON に変換。CrewAccessTripSummaryStoreが JSON をDocuments/CrewAccessImports/{date}_{tripId}.jsonに保存。このローカル JSON が正本(INV-006)。- 起動時に
reconcileCrewAccessSchedulesWithImportFiles()がファイル群からメモリ上の状態を再構築。 - CloudKit 同期(後述)が、生の取り込みファイルと解析済みスナップショットの両方をミラーし、複数端末で利用可能に。
データの流れ ② Timeline / Calendar の描画
PayPeriodScheduleが 1 ペイ期間分のトリップを保持し、TripLegが個々のフライト区間(出所は CrewAccess または OpenTime)。- Timeline — レグを
pairing(トリップ ID)でまとめ、空港ローカル時刻でカード描画。iPhone / iPad で共通の行コンポーネントを使用(INV-005)。 - Calendar(iPad) —
CalendarSupportがTripLegを正規化し、Bid Period 開始からの日付インデックスを UTC で 計算(INV-009)。8 週間グリッドを「運用 > Bid > 個人」のレイヤーで描画(INV-010)。
保存先
- ローカル JSON(
Documents/CrewAccessImports/)— 取り込みスケジュールの正本。 - App Group 共有コンテナ — 共有拡張↔本体の受け渡しとウィジェット用データ。
- Keychain — TripBoard の認証情報。
- UserDefaults / ローカルキャッシュ —
ScheduleCacheService経由。
外部サービス
- TripBoard(OpenTime) —
tripboard.bidproplus.com/fltops-portal.ups.com。任意・読み取り専用で、ユーザー自身の認証情報が必須。自動ログインや MFA 回避は一切なし。デモモードでは無効。 - 独自のクルーシステム API は使わず、CrewAccess の自動スクレイピングもしません(CrewAccess は Zscaler + MFA の背後にあるため、取り込みは手動 PDF 共有のみ)。
CloudKit の使い方
コンテナは iCloud.com.sfune.TimelineSchedule、Public Database のみ(プライベート DB・CKShare は不使用)。背景は docs/ADR/ADR-001-public-cloudkit-phase1.md を参照。
| レコード型 | 役割 |
|---|---|
TDHGEMSVerification | 管理者が書き込む GEMS ID ↔ 生年月日ハッシュの照合表(全ユーザーが読み取り)。 |
TDHVerifiedUser | GEMS 認証に初めて成功したときにユーザーが作成。 |
TDHFriendLink | 相互承認した 2 ユーザーのペアリング記録。 |
TDHSharedSchedule | 友人に読み取り専用で公開する、解析済みスケジュール。 |
TripScheduleSnapshot | Web ビューア向けにシリアライズしたスケジュール。 |
TDHDeviceScheduleSnapshot | ユーザーごとに 1 件。端末間同期用(local-wins)。 |
TDHCrewAccessImportFile | 取り込みトリップごとに 1 件。生 JSON の同期、削除はトゥームストーン。 |
同期の設計
- 自分の端末間 —
DeviceScheduleCloudKitServiceがTDHDeviceScheduleSnapshotを送受信。local-wins で、ローカルのupdatedAtが新しければリモートを採用しない(INV-007)。起動時は ローカルが上書きされる前に 取り込みファイルを取得・整合させる必要があり、これを誤ると新しい端末が自分のデータを消してしまう。 - 取り込みファイル同期 —
CrewAccessImportCloudKitServiceがトリップ単位の生 JSON を同期。削除はレコードのハード削除ではなくトゥームストーン(deletedAt)で表現し、他端末側でローカルファイルを消す(INV-008)。 - フレンド共有 — GEMS 認証・友人リンク承認・共有スケジュール/プロフィールの送受信を、それぞれの CloudKit サービスが担当。
CloudKit の Development 環境と Production 環境のスキーマずれが、フレンド共有まわりの不具合の 常連の原因 です。新しいレコード型やフィールドは、TestFlight / App Store ビルドで動く前に Production へ手動デプロイ しておく必要があります(docs/CLOUDKIT.md)。
カウントダウン / ライブアクティビティ
ウィジェット拡張は App Group 経由で共有スケジュールを読み取ります。ホーム画面ウィジェットは T-12h〜T-6h、ライブアクティビティ(iOS 16.1+)は T-6h〜T+6h をロック画面・ダイナミックアイランドで表示し、STD を過ぎても未出発なら「Delayed」に切り替わります。制御は FlightCountdownCoordinator / FlightCountdownSupport。
守るべき不変条件(Invariants)
変更時に必ず守るべき土台のルールです。全文は docs/INVARIANTS.md。
| ID | ルール |
|---|---|
| INV-001 | 内部計算の基準は常に UTC。Calendar.current / TimeZone.current は使用禁止。 |
| INV-002 | タイムゾーン変換は必ず明示的に行う(フォーマッタ・カレンダーに timeZone を明示)。 |
| INV-003 | Bid / Pay Period の所属判定は UTC 深夜ではなく 03:00 LDT(母基地ローカル時刻)境界で行う。 |
| INV-004 | 母基地(ANC / SDF / ONT / MIA。SDFZ は SDF に正規化)が LDT 変換のタイムゾーンを決める。 |
| INV-005 | 共通概念(Timeline 行、レイオーバーカード)は iPhone / iPad で UI・機能を揃える。 |
| INV-006 | ディスク上の取り込み JSON が正本。CloudKit は冗長な同期層にすぎない。 |
| INV-007 | 端末間同期は local-wins。 |
| INV-008 | 端末間の削除はハード削除でなくトゥームストーンで行う。 |
| INV-009 | カレンダーの日付インデックスはローカル startOfDay でなく UTC で計算。 |
| INV-010 | カレンダーのレイヤーは厳密に「運用 > Bid > 個人」。運用イベントは Bid / 個人に現れず、個人イベントは運用 / Timeline に現れない。 |
| INV-011 | Timeline は「トリップ + フライト + デッドヘッド + 手動運用イベント」のみ(Bid / 個人レイヤーは除外)。 |
docs/ADR/* / docs/CLOUDKIT.md / docs/BID_PERIODS.md / docs/TERMINOLOGY.md は参照のみで全文未確認。スキーマのフィールド一覧やタイムゾーンの具体例は、これらを直接参照してください。
3現状
現行バージョン / 本番機能 / 開発の重心 / watchOS / 技術的負債 / テスト
現行バージョン
- Version 1.2.1 / build 68 —
project.yml(全 3 ターゲット)で確認済み。最新コミット681ecba "Release v1.2.1 build 68"(2026-06-13、当日の出来立てリリース)と一致。 - バンドル ID は
com.sfune.BidProSchedule(製品名は TripDataHub だが、バンドルレベルでは旧称を維持)。 - 最低 iOS 17.0。iPhone / iPad とも対応で UI は別。
v2.x タグに注意
v2.0〜v2.3(2026 年 2〜4 月)は、BidProSchedule から TripDataHub への改名・1.x 採番への移行より 前 の古いレガシータグです(1.0.0 への改名は 2026-03-16)。1.2.1 より新しいわけでは なく、現行バージョンとしては無視してください。
CHANGELOG.md / RELEASE_NOTES.md は v1.0.0(2026-03-16)止まりで、v1.0.1〜v1.2.1 は未更新(v1.1 のみドラフトあり)。正確なリリースノートが要る場合は git log から再構築が必要。また build 68 の App Store Connect 上のステータス(公開中 / 審査中 / TestFlight のみ)はリポジトリからは判別できません。
本番に載っている機能
- CrewAccess PDF の取り込み(共有拡張 → App Group → PDFKit 抽出 → 行ベース解析)。
- Timeline タブ(トリップ・フライト・デッドヘッド・レイオーバー・手動運用イベント)。
- iPad の 8 週間 Bid Period カレンダー(運用 / Bid / 個人のレイヤー)。
- 次のフライトのカウントダウン(ウィジェット T-12h→T-6h + ライブアクティビティ T-6h→T+6h、STD 後は自動「Delayed」)。
- フレンド共有(GEMS ID 相互承認、CloudKit Public DB、CKShare 不使用)。
- 手動の運用イベント(RSV-A/B/C/D・LCO・HOT・RCID・CQ12/CQ6)。
- 手動の個人イベント(通勤・医療・予約・その他)。
- CloudKit 端末間同期(local-wins、トゥームストーン削除)。
- LogTen 互換エクスポート。
- TripBoard「OpenTime」閲覧(任意・ユーザー認証・読み取り専用、起動時自動取得オプションあり)。
- App Review デモモード(
APPSTORE_REVIEWフラグ。GEMS 認証をバイパスし、OpenTime / Friends を隠して合成サンプルデータを使用。PDF 取り込み・Timeline・Calendar は審査でも利用可)。
最近の開発の重心
直近 12 コミットほど(2026-05〜06-13)は フレンド共有 / プロフィールの CloudKit 同期の信頼性向上 に集中しています。具体的には、友人タイムライン同期の修正、友人リクエストの同期リカバリと復元、リンク権限のフォールバック、マッチ時通知、アカウント削除時のリクエスト保持など。2026-06-10 の「プロフィール同期とデモデータの刷新」と、v1.2.0 / v1.2.1(build 67 / 68)が、この一連の作業の安定化・リリースの締めにあたります。
git 上には claude/* や feature/* ブランチが複数ありますが、feature/statistics や refactor/consolidate-dedup-layers 等は main に取り込み済みの完了履歴で、現在進行中の作業ではありません(例外は次の watchOS)。ほかに、マージ済みブランチに対応する古いワークツリーや、未追跡の docs/SIMULATOR_TROUBLESHOOTING.md / TDH-icon-1024.png が作業ツリーに残っています。
watchOS 対応 保留中
完成・検証済みだが、まだ main には統合されていない状態です。
「main に watchOS 対応がある」と思い込まないこと。 現在の作業ツリーには watch ターゲットも関連ファイルも存在しません。一方で 「放棄された」とも思い込まないこと。 完成・検証・テスト済みの実装が feature/watchos に残っています(優先順位の都合で保留にしただけ)。watch 作業が必要になったら、新規実装の前にまず feature/watchos を確認してください。正式な記録は ~/Documents/FNN-Obsidian/20_Projects/TripDataHub/releases/Deferred_Features.md。
調べて分かったこと
- 完全な watchOS MVP が
feature/watchosに存在(単一コミットd08f733「v1.0.3 build 55: watchOS MVP (PR1–PR7)」、2026-05-24)。 git merge-base --is-ancestor feature/watchos mainは NO = 一度もmainにマージされていない。- 分岐元は
b1cf1ff(v1.0.3 build 54)で、現在のmainから 20 コミット遅れ(その後の約 3 週間はフレンド共有強化に費やされた)。 - 現在の
mainには watch 関連ファイルが 一つもない。project.ymlも watch ターゲットを定義していない。
ブランチ側の内容(参考)
TripDataHubWatchApp/(watchOS 10.0、iOS アプリに同梱)— Trip / Reserve / Training / OffDuty の各モード画面とデバッグ用ピッカー。TripDataHubShared/— 共有型(WatchOperationalModeほか各種 Payload)。- WatchConnectivity 転送(iPhone がスナップショットを生成し
updateApplicationContextで送信、Watch 側でコールド起動をまたいで重複排除・永続化)。 - テスト(
WatchSnapshotTestsほか。作成時点で 102/0 合格)と、ブランチ限定のドキュメント(検証チェックリスト・既知の制限)。
復活させるには、feature/watchos を現在の main にリベース/マージし、約 3 週間ぶんのずれ(分岐後に入ったフレンド共有・母基地まわりのモデル/サービス変更を含む)を解消したうえで、ブランチ内の検証チェックリストに沿って再検証する必要があります。
既知の技術的負債
sharedTimelineCards: [WebTimelineCard](TripModels.swift:156-187)に// TODO: Phase B 以降で削除予定のマーク。現状アプリ内では未使用で、未着手の Web ビューア専用。- CloudKit の Dev / Prod ずれ — スキーマ変更は Dashboard から Production へ手動デプロイが必須。両環境の不一致はフレンド共有不具合の 名指しの常習犯(
docs/AI_CONTEXT_INDEX.mdに明記)。 - CHANGELOG / RELEASE_NOTES が古い(v1.0.0 止まり)。v1.1.2〜v1.2.1 のリリースノートが存在しない。
- README.md は分類ルールと BP / PP 日付表だけの参照データで、プロジェクト概要になっていない(「BidProSchedule Notes」見出しのまま)。優先度は低いが要リライト候補。
- Calendar V2 のスコープ項目(疲労オーバーレイ、トリップ編集、ドラッグ&ドロップ、同僚カレンダー重ね合わせ、リアルタイム運用追跡、月表示)は仕様で 明確にスコープ外。「すぐできる改善」として提案しないこと。
- 古いワークツリーや未コミットの画像・ドキュメントが作業ツリーに残置(ブロッカーではなく片付け案件)。
次の作業
ロードマップ以上の明確な将来計画ドキュメントはありません。最も具体的な兆候は、フレンド共有 / プロフィール同期が安定リリース点(v1.2.1 / build 68)に到達したこと。新たな同期不具合が出ない限り、次の重心はロードマップ側の項目(Web タイムラインビューアの実装、カウントダウンの種類追加、Calendar V2 項目)へ移ると見られます。
テストの状況
ユニットテストは次をカバー:カウントダウンのフェーズ/時間/レグ選択、カレンダーの日付インデックス・BP/PP 所属・DST のエッジケース、PDF パーサのリグレッション(ゴールデン JSON)、端末同期の local-wins 挙動、LogTen エクスポート形式、取り込みファイルの CloudKit 同期、GEMS 認証同期(build 32+ で追加の domicile / schemaVersion: 2 を含む)、端末スナップショット同期、取り込み受け渡し、プロフィール同期、友人リクエスト照合、スナップショット符号化、App Group 受け渡し、次レポート時刻の計算。加えて UI テストターゲット(TripDataHubUITests)あり。
4ロードマップ
Planned(着手・確定済み)/ Proposed(仕様上の将来項目)/ Ideas(緩い言及)
CALENDAR_IMPLEMENTATION_SPEC.md / docs/RELEASE_NOTES_v1.1_DRAFT.md / AGENTS.md / コード内 TODO / コミット履歴から整理したもの。
Planned着手・確定済み
- フレンド共有 / プロフィールの CloudKit 同期 の継続的なハードニング(リクエスト照合、アカウント削除後の復元、権限フォールバック、古い通知の修正)。
- 新しいレコード型・フィールドについて、リリース前に Dev / Prod のスキーマ整合を保つ運用。
- 母基地や Bid Period 表を追加した際の、タイムゾーン / DST リグレッションの拡充。
Proposed仕様上の将来項目 / V1 では非対応
Calendar V2(V1 の非ゴール)より:
- カレンダーへの疲労オーバーレイ。
- トリップ編集 UI(現状はカレンダー・タイムラインとも読み取り専用)。
- ドラッグ&ドロップでの再スケジュール。
- 同僚カレンダーの重ね合わせ(現状の読み取り専用フレンドタイムラインの先)。
- リアルタイムの運用 / フライトステータス追跡。
- 月表示(現状の 8 週間グリッドに対して)。
リリースノート v1.1 ドラフト / AGENTS.md より:
- 地政学的な警告エンジン(「later phase」)。
- カウントダウンの種類追加 — Van Time、Report Time、「次の運用イベント」(フライトに限らない)。
- Web タイムラインビューア —
TripScheduleSnapshotを消費する外部ビューア(書き出し側TripScheduleSnapshotEncoderは既にある)。
Ideas緩い言及・未スコープ
- Web ビューアがスナップショットを直接消費するようになったら、未使用の
sharedTimelineCardsを削除(「Phase B 以降で削除予定」)。 - ANC / SDF(Z) / ONT / MIA 以外の母基地追加 —
DomicileSupport・docs/BID_PERIODS.md・BidPeriodServiceTests・日付表の更新が必要。 - README を現在の TripDataHub のブランディング・機能に合わせて更新(今は旧 BidProSchedule のまま)。
github.com/Tony747-G/TripDataHub の未解決 Issue / PR と、docs/AI_CONTEXT_INDEX.md を確認すること。仕様とコミット履歴からの推測より、権威があり最新のロードマップが得られる可能性があります。