# JR四国データモデル契約(素案) Status: Phase 1.5 contract only この文書は、既存APIレスポンスや画面処理を一括移行するものではない。駅DBとアプリの責任分界を先に固定し、将来のRepository/API整理でidentityを再び混同しないための境界である。 ## Physical Station Physical Stationは、現実の一つの駅施設・旅客駅を表す概念である。物理駅ID(`PhysicalStationId`)はBackendの駅情報DBまたは合意済みdomain masterが発行する値を使う。 アプリは駅番号から物理駅IDを算出・発明しない。Backendから対応付けが届かない間は、contract上の`physicalStationId`を`null`として扱う。 ## Station Stop Station Stop(`StationStopId`)は、路線・方向・運行上の文脈を持つline-specificな停車点である。同じPhysical Stationに複数のStation Stopが存在し得る。 `Y00`と`T28`が同じ高松の別路線文脈を表す場合、番号を一つの物理駅IDとして扱わず、物理駅、停車点、番号、路線を別フィールドで保持する。 ## Station Number Station Numberは、路線またはデータソースが付与した表示・検索用のlegacy referenceである。`M12`のように複数の路線・駅で再利用される可能性があるため、単独でlong-lived canonical IDにしない。 `adaptLegacyStationNumber()`は、legacy numberを`StationIdentity`の境界へ載せるだけで、`physicalStationId`を埋めない。`lookupLegacyStations()`は曖昧な候補を全件返し、呼び出し側が路線・Backend mappingを確認する。 実データの全駅変換、既存の`StationNumber`検索、WebView注入処理はこのフェーズでは変更しない。 ## Train Identity 列車番号はidentityの一部であり、単独のcanonical keyではない。最低限、次の値を組み合わせる。 - `trainNumber` - `serviceDate` - `lineId`(不明な場合は`null`だが、省略しない) - `source`(API・namespace) 必要に応じて`operatorCode`や`serviceVariant`を追加する。`trainIdentityKey()`はこれらを複合化するため、同じ列車番号でも日付、路線、sourceが違えば別keyになる。 ## Calendar Date Calendar Dateは通常の暦日(`YYYY-MM-DD`)である。タイムゾーンを暗黙にしたJavaScript `Date`をidentityの代わりに保存しない。 ## Service Date Service Dateは鉄道の営業日・運行日である。アプリの表示上の暦日と一致しない場合がある。契約ヘルパーの既定境界は04:00で、00:00〜03:59のwall clockは前日のService Date、04:00以降は当日のService Dateとする。この境界はBackendとの最終合意で変更可能なpolicyであり、各処理が独自に判定してはいけない。 ## Wall Clock Time Wall Clock Timeは日常の`00:00`〜`23:59`である。24時台表記をこの型へ混ぜない。`parseWallClockTime()`は`24:00`を受け付けない。 ## Service Minute Service Minuteは、extended railway timeを整数分で表すpure valueである。JavaScript `Date`へ直接変換しない。 ```text 00:00 = 0 23:59 = 1439 24:00 = 1440 24:30 = 1470 25:15 = 1515 ``` `parseServiceMinute()`は上記のextended表記を扱う。一方、通常のwall clockを現在のService Day軸へ写像する`serviceMinuteFromWallClock()`では、既定04:00境界の`00:30`は`1470`になる。つまり「文字列として00:30を読む」ことと「営業日軸へ写像する」ことを分離する。 ## Legacy dataとの関係 既存の`Y00`、`T28`、`M12`などの処理は直ちに変更しない。新しいAPI層やRepositoryは、legacy referenceを境界で受け取り、Backendが返す`PhysicalStationId`・`StationStopId`との対応をadapterで明示する。対応がないデータを推測で結合しない。 既存の運行情報・列車位置データは、今回追加した型へ一括変換せず、fixtureと境界関数から段階的に移行する。 ## Backendとの責任分界 Backend/domain masterが責任を持つもの: - Physical Stationのcanonical ID発行と同一物理駅の統合 - Station Stop、路線、駅番号、営業日ルールの正式な対応表 - 列車番号・Service Date・路線・sourceを含むidentity情報 - schema version、`fetchedAt`、sourceの仕様 アプリが責任を持つもの: - contractに沿った型境界とlegacy adapter - 欠損・曖昧なidentityを推測でcanonical化しないこと - Service Minuteを`Date`へ押し込まずに表示・比較すること - Backend contract変更時のfixture、parser、release compatibility確認 Backendがcanonical mappingを提供するまでは、アプリの`physicalStationId: null`は未解決状態として保持する。 ## Official Position OfficialPositionは、JR公式走行位置の完全一致tupleと、Backendが発行したUUIDを持つcanonical Position metadataである。アプリ側のlookup identityは、pos、line、pos_numの3値をこの順で保持する。pos_numは文字列であり、109、0279、ABCを数値化しない。 Backend responseは次のNative型へ変換する。 - positionId / pos / line / posNum - positionType / platform / track / note - enrichmentStatus / firstSeenAt / lastSeenAt / revision Position metadataの解決はCurrentPositionの取得・更新とは別の処理として扱う。起動時は検証済みのOfficialPosition metadataをAsyncStorageの永続ウォームキャッシュから先に返し、次回pollでAPIを再取得する。session memory cacheとin-flight dedupeを併用し、一覧表示などの多数tupleではbounded concurrencyを使う。404はunresolvedとしてmetadataだけを欠落させ、走行位置・遅延・列車番号などを失敗させない。ネットワーク障害時も同様にCurrentPositionの表示を維持する。 Resolverのアプリ向け入口は一括解決に絞り、tupleの重複除去はResolverが所有する。未使用の管理用APIを追加せず、通信のobservabilityと上記の失敗時の表示契約を維持する。 CurrentTrainのsnapshotは、同じ列車番号でも`Line`を持つ在線観測を行単位で保持する。路線コンテキストがある画面では駅番号・路線の対応を選択条件にし、一般ユーザー向け表示は候補を一件に絞る。管理者向け表示だけは候補を全件表示し、行ごとのOfficialPosition metadata(platform / trackを含む)を使う。路線コンテキストがないfallbackでは、複数行のどれかへ推測で結び付けず、直前のline-specificな候補を保持する。 すべてのNative環境でOfficialPosition Backend resolverをcanonical sourceとして使う。production release / production betaはproduction Backend、experimentalはbeta Backendへ環境configで切り替える。自動fallbackは行わず、旧n8n Position masterのadapter・書き込み経路はNativeから削除する。管理権限がありUUIDとuserIDが揃った場合のみ、環境対応Frontendのcanonical編集URLをgeneralWebViewで開く。