diff --git a/docs/route-recommendations.md b/docs/route-recommendations.md new file mode 100644 index 0000000000..1688a370af --- /dev/null +++ b/docs/route-recommendations.md @@ -0,0 +1,23 @@ +# 経路の既定選択 + +StationAPI の `connectedRoutes` は、区間ごとの `recommendedGroupId` と経路全体の +`totalSeconds` を返します。アプリはこの系統で種別の表示と駅一覧の取得を行います。 +評価した快速を各停へ置き換える処理は行いません。 + +推奨 ID が種別一覧に存在しない経路は選択対象から除きます。ID のない旧 API では +従来どおり各停を優先します。追加した二つのフィールドの検証エラーだけを検出し、 +そのフィールドを除いた問い合わせを一度再送します。通信エラーや他の検証エラーは +そのまま返します。対応状況は接続先のクライアント単位で保持します。 + +直通経路の他種別は手動で選択できます。これらは API が評価した全経路の後ろに +並べます。並べ替え時も、他種別に推奨系統の順位を流用しません。 + +公開順は StationAPI、MobileApp の順を推奨します。GraphQL の数値 ID に使う +`UInt32` はコード生成で `number` に対応させています。 + +乗換経路では区間ごとの路線と種別を乗車順に保持します。同一路線で各停から快速へ +乗り換える場合も、後半の系統を路線 ID の重複として削除しません。種別一覧の補足と +キーワード検索にも後半の種別を含めます。通常の一系統の路線一覧の重複排除は維持します。 +乗換経路の補足では、同一系統の区間でも路線名を乗車順に並べます。同じ会社の +複数路線を会社名へまとめず、再登場する路線名も残します。通常の種別では、 +従来の会社別表示を維持します。 diff --git a/src/@types/graphql.d.ts b/src/@types/graphql.d.ts index 287f2f06da..7bc2ab784a 100644 --- a/src/@types/graphql.d.ts +++ b/src/@types/graphql.d.ts @@ -25,6 +25,7 @@ export type Scalars = { Boolean: { input: boolean; output: boolean }; Int: { input: number; output: number }; Float: { input: number; output: number }; + UInt32: { input: number; output: number }; }; export type Company = { @@ -50,6 +51,18 @@ export enum CompanyType { SemiMajor = 'SemiMajor', } +export type ConnectedRoute = { + __typename: 'ConnectedRoute'; + legs: Maybe>; + totalSeconds: Scalars['Int']['output']; +}; + +export enum ConnectedRouteSort { + ArrivalTime = 'ArrivalTime', + Recommended = 'Recommended', + TransferCount = 'TransferCount', +} + export type EstimatedArrivalPage = { __typename: 'EstimatedArrivalPage'; routes: Maybe>; @@ -65,7 +78,7 @@ export type EstimatedArrivalStop = { __typename: 'EstimatedArrivalStop'; cumulativeMinutes: Maybe; departureCumulativeMinutes: Maybe; - stationGroupId: Maybe; + stationGroupId: Maybe; stationId: Maybe; stopsHere: Maybe; }; @@ -140,7 +153,7 @@ export enum OperationStatus { export type Query = { __typename: 'Query'; - connectedRoutes: Array; + connectedRoutes: Array; estimateArrivalTimes: EstimatedArrivalPage; line: Maybe; lineGroupListStations: Array; @@ -162,12 +175,15 @@ export type Query = { export type QueryConnectedRoutesArgs = { fromStationGroupId: Scalars['Int']['input']; + sortBy: InputMaybe; toStationGroupId: Scalars['Int']['input']; + viaLineId: InputMaybe; }; export type QueryEstimateArrivalTimesArgs = { directionId: InputMaybe; fromStationId: Scalars['Int']['input']; + legs: InputMaybe>; toStationId: Scalars['Int']['input']; viaLineIds: InputMaybe>; }; @@ -259,16 +275,33 @@ export type QueryStationsNearbyArgs = { export type QueryTrainRouteArgs = { fromStationId: Scalars['Int']['input']; + legs: InputMaybe>; lineGroupId: InputMaybe; + model: InputMaybe; toStationId: Scalars['Int']['input']; }; export type Route = { __typename: 'Route'; - id: Maybe; + id: Maybe; stops: Maybe>; }; +export type RouteLeg = { + __typename: 'RouteLeg'; + fromStation: Maybe; + recommendedGroupId: Scalars['Int']['output']; + stationGroupIds: Maybe>; + toStation: Maybe; + trainTypes: Maybe>; +}; + +export type RouteLegInput = { + fromStationId: Scalars['Int']['input']; + lineGroupId: Scalars['Int']['input']; + toStationId: Scalars['Int']['input']; +}; + export type RoutePage = { __typename: 'RoutePage'; nextPageToken: Maybe; @@ -286,7 +319,7 @@ export type Station = { address: Maybe; closedAt: Maybe; distance: Maybe; - groupId: Maybe; + groupId: Maybe; hasTrainTypes: Maybe; id: Maybe; latitude: Maybe; @@ -318,7 +351,7 @@ export type StationNested = { address: Maybe; closedAt: Maybe; distance: Maybe; - groupId: Maybe; + groupId: Maybe; hasTrainTypes: Maybe; id: Maybe; latitude: Maybe; @@ -368,6 +401,11 @@ export enum TrainDirection { Outbound = 'Outbound', } +export enum TrainRouteModel { + Estimated = 'Estimated', + Legacy = 'Legacy', +} + export type TrainRouteResponse = { __typename: 'TrainRouteResponse'; segments: Maybe>; @@ -375,6 +413,8 @@ export type TrainRouteResponse = { export type TrainRouteSegment = { __typename: 'TrainRouteSegment'; + arrivalCumulativeMinutes: Maybe; + departureCumulativeMinutes: Maybe; distanceFromPrevious: Maybe; maxAcceleration: Maybe; maxDeceleration: Maybe; @@ -387,7 +427,7 @@ export type TrainType = { __typename: 'TrainType'; color: Maybe; direction: Maybe; - groupId: Maybe; + groupId: Maybe; id: Maybe; kind: Maybe; line: Maybe; @@ -418,7 +458,7 @@ export type TrainTypeNested = { __typename: 'TrainTypeNested'; color: Maybe; direction: Maybe; - groupId: Maybe; + groupId: Maybe; id: Maybe; kind: Maybe; line: Maybe; @@ -2549,6 +2589,7 @@ export type GetLineStationsQueryVariables = Exact<{ export type GetLineStationsQuery = { lineStations: Array<{ __typename: 'Station'; + trackDistanceFromPrevious: number | null | undefined; id: number | null | undefined; groupId: number | null | undefined; name: string | null | undefined; @@ -3437,6 +3478,7 @@ export type GetLineGroupStationsQueryVariables = Exact<{ export type GetLineGroupStationsQuery = { lineGroupStations: Array<{ __typename: 'Station'; + trackDistanceFromPrevious: number | null | undefined; id: number | null | undefined; groupId: number | null | undefined; name: string | null | undefined; @@ -3880,6 +3922,7 @@ export type GetStationsByIdsQueryVariables = Exact<{ export type GetStationsByIdsQuery = { stations: Array<{ __typename: 'Station'; + trackDistanceFromPrevious: number | null | undefined; id: number | null | undefined; groupId: number | null | undefined; name: string | null | undefined; @@ -4514,174 +4557,2265 @@ export type TrainTypeRouteFieldsFragment = { | undefined; }; -export type GetRouteTypesLightQueryVariables = Exact<{ +export type GetConnectedRoutesQueryVariables = Exact<{ fromStationGroupId: Scalars['Int']['input']; toStationGroupId: Scalars['Int']['input']; - pageSize: InputMaybe; - pageToken: InputMaybe; viaLineId: InputMaybe; }>; -export type GetRouteTypesLightQuery = { - routeTypes: { - __typename: 'RouteTypePage'; - nextPageToken: string | null | undefined; - trainTypes: +export type GetConnectedRoutesQuery = { + connectedRoutes: Array<{ + __typename: 'ConnectedRoute'; + totalSeconds: number; + legs: | Array<{ - __typename: 'TrainType'; - id: number | null | undefined; - typeId: number | null | undefined; - groupId: number | null | undefined; - name: string | null | undefined; - nameRoman: string | null | undefined; - color: string | null | undefined; - kind: TrainTypeKind | null | undefined; - line: - | { - __typename: 'LineNested'; + __typename: 'RouteLeg'; + recommendedGroupId: number; + stationGroupIds: Array | null | undefined; + trainTypes: + | Array<{ + __typename: 'TrainType'; id: number | null | undefined; - color: string | null | undefined; - lineType: LineType | null | undefined; - nameShort: string | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; nameRoman: string | null | undefined; - nameRomanIpa: string | null | undefined; - lineSymbols: - | Array<{ - __typename: 'LineSymbol'; - color: string | null | undefined; - shape: string | null | undefined; - symbol: string | null | undefined; - }> - | null - | undefined; - station: + color: string | null | undefined; + kind: TrainTypeKind | null | undefined; + line: | { - __typename: 'StationNested'; + __typename: 'LineNested'; id: number | null | undefined; - groupId: number | null | undefined; - name: string | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameShort: string | null | undefined; nameRoman: string | null | undefined; nameRomanIpa: string | null | undefined; - nameChinese: string | null | undefined; - nameKorean: string | null | undefined; - hasTrainTypes: boolean | null | undefined; - stationNumbers: + lineSymbols: | Array<{ - __typename: 'StationNumber'; - lineSymbol: string | null | undefined; - lineSymbolColor: string | null | undefined; - lineSymbolShape: string | null | undefined; - stationNumber: string | null | undefined; + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; }> | null | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + typeId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + } + | null + | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameShort: string | null | undefined; + nameEnglishShort: string | null | undefined; + } + | null + | undefined; } | null | undefined; - trainType: - | { - __typename: 'TrainTypeNested'; - typeId: number | null | undefined; - name: string | null | undefined; - nameRoman: string | null | undefined; - } - | null - | undefined; - company: - | { - __typename: 'Company'; + lines: + | Array<{ + __typename: 'LineNested'; id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; nameShort: string | null | undefined; - nameEnglishShort: string | null | undefined; - } + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + typeId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + } + | null + | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameShort: string | null | undefined; + nameEnglishShort: string | null | undefined; + } + | null + | undefined; + }> | null | undefined; - } + }> | null | undefined; - lines: - | Array<{ - __typename: 'LineNested'; + fromStation: + | { + __typename: 'Station'; id: number | null | undefined; - color: string | null | undefined; - lineType: LineType | null | undefined; - nameShort: string | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; nameRoman: string | null | undefined; - nameRomanIpa: string | null | undefined; - lineSymbols: + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + threeLetterCode: string | null | undefined; + latitude: number | null | undefined; + longitude: number | null | undefined; + prefectureId: number | null | undefined; + hasTrainTypes: boolean | null | undefined; + stopCondition: StopCondition | null | undefined; + nameTtsSegments: | Array<{ - __typename: 'LineSymbol'; - color: string | null | undefined; - shape: string | null | undefined; - symbol: string | null | undefined; + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; }> | null | undefined; - station: + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + line: | { - __typename: 'StationNested'; + __typename: 'LineNested'; id: number | null | undefined; - groupId: number | null | undefined; - name: string | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; nameRoman: string | null | undefined; - nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; nameChinese: string | null | undefined; nameKorean: string | null | undefined; - hasTrainTypes: boolean | null | undefined; - stationNumbers: + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: | Array<{ - __typename: 'StationNumber'; - lineSymbol: string | null | undefined; - lineSymbolColor: string | null | undefined; - lineSymbolShape: string | null | undefined; - stationNumber: string | null | undefined; + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; }> | null | undefined; } | null | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; trainType: | { __typename: 'TrainTypeNested'; + id: number | null | undefined; typeId: number | null | undefined; + groupId: number | null | undefined; name: string | null | undefined; + nameKatakana: string | null | undefined; nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; } | null | undefined; - company: - | { - __typename: 'Company'; - id: number | null | undefined; - nameShort: string | null | undefined; - nameEnglishShort: string | null | undefined; - } - | null - | undefined; - }> + } | null | undefined; - }> - | null - | undefined; - }; -}; - -export type GetStationTrainTypesLightQueryVariables = Exact<{ - stationId: Scalars['Int']['input']; -}>; - -export type GetStationTrainTypesLightQuery = { - stationTrainTypes: Array<{ - __typename: 'TrainType'; - id: number | null | undefined; - typeId: number | null | undefined; - groupId: number | null | undefined; - name: string | null | undefined; - nameRoman: string | null | undefined; - color: string | null | undefined; - kind: TrainTypeKind | null | undefined; + toStation: + | { + __typename: 'Station'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + threeLetterCode: string | null | undefined; + latitude: number | null | undefined; + longitude: number | null | undefined; + prefectureId: number | null | undefined; + hasTrainTypes: boolean | null | undefined; + stopCondition: StopCondition | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }>; +}; + +export type GetConnectedRoutesSortedQueryVariables = Exact<{ + fromStationGroupId: Scalars['Int']['input']; + toStationGroupId: Scalars['Int']['input']; + viaLineId: InputMaybe; + sortBy: ConnectedRouteSort; +}>; + +export type GetConnectedRoutesSortedQuery = { + connectedRoutes: Array<{ + __typename: 'ConnectedRoute'; + totalSeconds: number; + legs: + | Array<{ + __typename: 'RouteLeg'; + recommendedGroupId: number; + stationGroupIds: Array | null | undefined; + trainTypes: + | Array<{ + __typename: 'TrainType'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + color: string | null | undefined; + kind: TrainTypeKind | null | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameShort: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + typeId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + } + | null + | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameShort: string | null | undefined; + nameEnglishShort: string | null | undefined; + } + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameShort: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + typeId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + } + | null + | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameShort: string | null | undefined; + nameEnglishShort: string | null | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + fromStation: + | { + __typename: 'Station'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + threeLetterCode: string | null | undefined; + latitude: number | null | undefined; + longitude: number | null | undefined; + prefectureId: number | null | undefined; + hasTrainTypes: boolean | null | undefined; + stopCondition: StopCondition | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + } + | null + | undefined; + toStation: + | { + __typename: 'Station'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + threeLetterCode: string | null | undefined; + latitude: number | null | undefined; + longitude: number | null | undefined; + prefectureId: number | null | undefined; + hasTrainTypes: boolean | null | undefined; + stopCondition: StopCondition | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: string | null | undefined; + lineSymbolShape: string | null | undefined; + stationNumber: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + line: + | { + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + lines: + | Array<{ + __typename: 'LineNested'; + id: number | null | undefined; + color: string | null | undefined; + lineType: LineType | null | undefined; + nameFull: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameShort: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + status: OperationStatus | null | undefined; + transportType: TransportType | null | undefined; + company: + | { + __typename: 'Company'; + id: number | null | undefined; + nameEnglishShort: string | null | undefined; + nameKatakana: string | null | undefined; + nameShort: string | null | undefined; + } + | null + | undefined; + lineSymbols: + | Array<{ + __typename: 'LineSymbol'; + color: string | null | undefined; + shape: string | null | undefined; + symbol: string | null | undefined; + }> + | null + | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + hasTrainTypes: boolean | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + stationNumbers: + | Array<{ + __typename: 'StationNumber'; + lineSymbol: string | null | undefined; + lineSymbolColor: + | string + | null + | undefined; + lineSymbolShape: + | string + | null + | undefined; + stationNumber: + | string + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + trainType: + | { + __typename: 'TrainTypeNested'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameKatakana: string | null | undefined; + nameRoman: string | null | undefined; + nameRomanIpa: string | null | undefined; + nameChinese: string | null | undefined; + nameKorean: string | null | undefined; + color: string | null | undefined; + direction: TrainDirection | null | undefined; + kind: TrainTypeKind | null | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: + | TtsAlphabet + | null + | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: + | string + | null + | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + } + | null + | undefined; + nameTtsSegments: + | Array<{ + __typename: 'TtsSegment'; + alphabet: TtsAlphabet | null | undefined; + fallbackText: string | null | undefined; + lang: string | null | undefined; + pronunciation: string | null | undefined; + separator: string | null | undefined; + surface: string | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + } + | null + | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }>; +}; + +export type GetStationTrainTypesLightQueryVariables = Exact<{ + stationId: Scalars['Int']['input']; +}>; + +export type GetStationTrainTypesLightQuery = { + stationTrainTypes: Array<{ + __typename: 'TrainType'; + id: number | null | undefined; + typeId: number | null | undefined; + groupId: number | null | undefined; + name: string | null | undefined; + nameRoman: string | null | undefined; + color: string | null | undefined; + kind: TrainTypeKind | null | undefined; line: | { __typename: 'LineNested'; @@ -4840,6 +6974,101 @@ export type GetTrainRouteQuery = { }; }; +export type GetConnectedTrainRouteQueryVariables = Exact<{ + fromStationId: Scalars['Int']['input']; + toStationId: Scalars['Int']['input']; + legs: Array | RouteLegInput; +}>; + +export type GetConnectedTrainRouteQuery = { + trainRoute: { + __typename: 'TrainRouteResponse'; + segments: + | Array<{ + __typename: 'TrainRouteSegment'; + distanceFromPrevious: number | null | undefined; + maxAcceleration: number | null | undefined; + maxDeceleration: number | null | undefined; + maxSpeed: number | null | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + trackDistanceFromPrevious: number | null | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }; +}; + +export type GetEstimatedTrainRouteQueryVariables = Exact<{ + fromStationId: Scalars['Int']['input']; + toStationId: Scalars['Int']['input']; + lineGroupId: InputMaybe; +}>; + +export type GetEstimatedTrainRouteQuery = { + trainRoute: { + __typename: 'TrainRouteResponse'; + segments: + | Array<{ + __typename: 'TrainRouteSegment'; + distanceFromPrevious: number | null | undefined; + maxAcceleration: number | null | undefined; + maxDeceleration: number | null | undefined; + maxSpeed: number | null | undefined; + arrivalCumulativeMinutes: number | null | undefined; + departureCumulativeMinutes: number | null | undefined; + station: + | { + __typename: 'StationNested'; + trackDistanceFromPrevious: number | null | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }; +}; + +export type GetEstimatedConnectedTrainRouteQueryVariables = Exact<{ + fromStationId: Scalars['Int']['input']; + toStationId: Scalars['Int']['input']; + legs: Array | RouteLegInput; +}>; + +export type GetEstimatedConnectedTrainRouteQuery = { + trainRoute: { + __typename: 'TrainRouteResponse'; + segments: + | Array<{ + __typename: 'TrainRouteSegment'; + distanceFromPrevious: number | null | undefined; + maxAcceleration: number | null | undefined; + maxDeceleration: number | null | undefined; + maxSpeed: number | null | undefined; + arrivalCumulativeMinutes: number | null | undefined; + departureCumulativeMinutes: number | null | undefined; + station: + | { + __typename: 'StationNested'; + id: number | null | undefined; + groupId: number | null | undefined; + trackDistanceFromPrevious: number | null | undefined; + } + | null + | undefined; + }> + | null + | undefined; + }; +}; + export type EstimateArrivalTimesQueryVariables = Exact<{ fromStationId: Scalars['Int']['input']; toStationId: Scalars['Int']['input']; @@ -4872,3 +7101,33 @@ export type EstimateArrivalTimesQuery = { | undefined; }; }; + +export type EstimateConnectedRouteArrivalTimesQueryVariables = Exact<{ + fromStationId: Scalars['Int']['input']; + toStationId: Scalars['Int']['input']; + legs: Array | RouteLegInput; +}>; + +export type EstimateConnectedRouteArrivalTimesQuery = { + estimateArrivalTimes: { + __typename: 'EstimatedArrivalPage'; + routes: + | Array<{ + __typename: 'EstimatedArrivalRoute'; + id: number | null | undefined; + stops: + | Array<{ + __typename: 'EstimatedArrivalStop'; + stationId: number | null | undefined; + stationGroupId: number | null | undefined; + cumulativeMinutes: number | null | undefined; + departureCumulativeMinutes: number | null | undefined; + stopsHere: boolean | null | undefined; + }> + | null + | undefined; + }> + | null + | undefined; + }; +}; diff --git a/src/hooks/useDestinationSelection.ts b/src/hooks/useDestinationSelection.ts index 69bae0f161..c8f55d3b33 100644 --- a/src/hooks/useDestinationSelection.ts +++ b/src/hooks/useDestinationSelection.ts @@ -25,9 +25,9 @@ import { concatLegStations, filterRideableRoutes, getStationWithMatchingLine, - pickDefaultTrainType, pickInitialRouteTrainType, pickLegStations, + pickLegTrainType, } from '~/utils/routeSearch'; import { useLazyGraphQLQuery } from './useLazyGraphQLQuery'; @@ -163,7 +163,7 @@ export const useDestinationSelection = (): UseDestinationSelectionResult => { const results = await Promise.all( (route.legs ?? []).map(async (leg) => { - const legTrainType = pickDefaultTrainType(leg.trainTypes); + const legTrainType = pickLegTrainType(leg); if (!legTrainType?.groupId || !leg.fromStation || !leg.toStation) { return { stations: [], error: undefined }; } @@ -371,9 +371,7 @@ export const useDestinationSelection = (): UseDestinationSelectionResult => { const route = trainType.id != null ? transferRouteById.get(trainType.id) : undefined; const lineGroupIds = route - ? (route.legs ?? []).map( - (leg) => pickDefaultTrainType(leg.trainTypes)?.groupId - ) + ? (route.legs ?? []).map((leg) => pickLegTrainType(leg)?.groupId) : [trainType.groupId]; for (const lineGroupId of lineGroupIds) { if (lineGroupId == null) continue; diff --git a/src/lib/gql.ts b/src/lib/gql.ts index 35e7ef7a12..a8438e6e28 100644 --- a/src/lib/gql.ts +++ b/src/lib/gql.ts @@ -13,6 +13,7 @@ import { STAGING_API_URL, } from 'react-native-dotenv'; import { isDevApp } from '~/utils/isDevApp'; +import { createRouteRecommendationRequest } from './graphql/routeRecommendations'; import { rememberTrackDistancesFromResponse } from './trackDistances'; const assertUrl = (name: string, value?: string | null): string => { @@ -37,6 +38,10 @@ const uri = (() => { })(); const graphQLClient = new GraphQLClient(uri); +const routeRecommendationRequest = createRouteRecommendationRequest( + (document: DocumentNode, variables?: object) => + graphQLClient.request(document, variables as Variables) +); // Apollo InMemoryCache (cache-first) 相当: 一度取得したクエリは同一セッション中 // 再フェッチしない。強制的に再取得したい箇所は queryClient.removeQueries で @@ -78,15 +83,13 @@ export const gqlRequest = ( document: DocumentNode, variables?: object ): Promise => - graphQLClient - .request(document, variables as Variables) - .then((data) => { - // 駅の並びを返す問い合わせから、隣り合う駅のあいだの線路の長さを覚える。 - // 呼び出し側が並べ替える前の、API の並びのまま受け取れるのはここだけなので - // (src/lib/trackDistances.ts) - rememberTrackDistancesFromResponse(data); - return data; - }); + routeRecommendationRequest(document, variables).then((data) => { + // 駅の並びを返す問い合わせから、隣り合う駅のあいだの線路の長さを覚える。 + // 呼び出し側が並べ替える前の、API の並びのまま受け取れるのはここだけなので + // (src/lib/trackDistances.ts) + rememberTrackDistancesFromResponse(data); + return data; + }); // Apollo Client の client.query 互換ファサード。フック外からの命令的フェッチ用。 // queryClient.fetchQuery 経由なのでキャッシュ済みなら再フェッチしない。 diff --git a/src/lib/graphql/queries.ts b/src/lib/graphql/queries.ts index af4144c0b6..da448dfa0a 100644 --- a/src/lib/graphql/queries.ts +++ b/src/lib/graphql/queries.ts @@ -466,7 +466,9 @@ export const GET_CONNECTED_ROUTES = gql` toStationGroupId: $toStationGroupId viaLineId: $viaLineId ) { + totalSeconds legs { + recommendedGroupId trainTypes { ...TrainTypeRouteFields } @@ -501,7 +503,9 @@ export const GET_CONNECTED_ROUTES_SORTED = gql` viaLineId: $viaLineId sortBy: $sortBy ) { + totalSeconds legs { + recommendedGroupId trainTypes { ...TrainTypeRouteFields } diff --git a/src/lib/graphql/routeRecommendations.test.ts b/src/lib/graphql/routeRecommendations.test.ts new file mode 100644 index 0000000000..0b8addead9 --- /dev/null +++ b/src/lib/graphql/routeRecommendations.test.ts @@ -0,0 +1,70 @@ +import { parse, print } from 'graphql'; +import { createRouteRecommendationRequest } from './routeRecommendations'; + +const document = parse( + 'query GetConnectedRoutes { connectedRoutes { totalSeconds legs { recommendedGroupId stationGroupIds } } }' +); +afterEach(() => jest.clearAllMocks()); +it('retries metadata validation failures once and remembers support for this client', async () => { + const request = jest + .fn() + .mockRejectedValueOnce({ + response: { + errors: [ + { + message: + 'Cannot query field "recommendedGroupId" on type "RouteLeg".', + }, + ], + }, + }) + .mockResolvedValue({ connectedRoutes: [] }); + const run = createRouteRecommendationRequest(request); + const variables = { fromStationGroupId: 1 }; + await run(document, variables); + expect(request).toHaveBeenCalledTimes(2); + expect(print(request.mock.calls[1][0])).not.toMatch( + /recommendedGroupId|totalSeconds/ + ); + expect(request.mock.calls[1][1]).toBe(variables); + await run(document, variables); + expect(print(request.mock.calls[2][0])).not.toMatch( + /recommendedGroupId|totalSeconds/ + ); +}); +it.each([ + new Error('offline'), + { + response: { + errors: [ + { message: 'Cannot query field "stationGroupIds" on type "RouteLeg".' }, + ], + }, + }, + { + response: { + errors: [ + { message: 'Cannot query field "totalSeconds".' }, + { message: 'Invalid variable' }, + ], + }, + }, +])('preserves unrelated errors', async (error) => { + const request = jest.fn().mockRejectedValue(error); + await expect( + createRouteRecommendationRequest(request)(document) + ).rejects.toBe(error); + expect(request).toHaveBeenCalledTimes(1); +}); +it('does not retry unrelated operations', async () => { + const error = { + response: { errors: [{ message: 'Cannot query field "totalSeconds".' }] }, + }; + const request = jest.fn().mockRejectedValue(error); + await expect( + createRouteRecommendationRequest(request)( + parse('query Other { totalSeconds }') + ) + ).rejects.toBe(error); + expect(request).toHaveBeenCalledTimes(1); +}); diff --git a/src/lib/graphql/routeRecommendations.ts b/src/lib/graphql/routeRecommendations.ts new file mode 100644 index 0000000000..46a94c130c --- /dev/null +++ b/src/lib/graphql/routeRecommendations.ts @@ -0,0 +1,53 @@ +import { type DocumentNode, Kind, visit } from 'graphql'; + +type Request = (document: DocumentNode, variables?: object) => Promise; + +/** Retry only additive route metadata validation failures against an older API. */ +export const createRouteRecommendationRequest = (request: Request): Request => { + let metadataSupported = true; + const legacyDocuments = new WeakMap(); + const legacy = (document: DocumentNode): DocumentNode => { + let cached = legacyDocuments.get(document); + if (!cached) { + cached = visit(document, { + Field: (field) => + ['recommendedGroupId', 'totalSeconds'].includes(field.name.value) + ? null + : undefined, + }); + legacyDocuments.set(document, cached); + } + return cached; + }; + return async (document: DocumentNode, variables?: object): Promise => { + const operation = document.definitions.find( + (definition) => definition.kind === Kind.OPERATION_DEFINITION + ); + const name = + operation?.kind === Kind.OPERATION_DEFINITION + ? operation.name?.value + : undefined; + if (name !== 'GetConnectedRoutes' && name !== 'GetConnectedRoutesSorted') { + return request(document, variables); + } + if (!metadataSupported) return request(legacy(document), variables); + try { + return await request(document, variables); + } catch (error) { + const errors = ( + error as { response?: { errors?: { message: string }[] } } + )?.response?.errors; + if ( + !errors?.length || + !errors.every(({ message }) => + /(?:Cannot query field|Unknown field)[ "']+(?:recommendedGroupId|totalSeconds)\b/.test( + message + ) + ) + ) + throw error; + metadataSupported = false; + return request(legacy(document), variables); + } + }; +}; diff --git a/src/utils/routeSearch.test.ts b/src/utils/routeSearch.test.ts index 78f9b9c231..ab460e05ef 100644 --- a/src/utils/routeSearch.test.ts +++ b/src/utils/routeSearch.test.ts @@ -14,9 +14,11 @@ import { pickDefaultTrainType, pickInitialRouteTrainType, pickLegStationsByGroupIds, + pickLegTrainType, sliceLegStations, sortRouteTrainTypeIndices, } from './routeSearch'; +import { buildTrainTypeRow } from './trainTypeList'; // 文言そのものは翻訳ファイルの責務なので、 // ここでは「どのキーをどの駅名で引くか」だけを検証できるようにモックする @@ -376,6 +378,54 @@ describe('乗換経路', () => { }); }); + it('同一路線で各停から快速に乗り換えると両区間の系統と表示種別を保持する', () => { + const a = station(1, 1, 'A', saikyoLine); + const b = station(2, 2, 'B', saikyoLine); + const c = station(3, 3, 'C', saikyoLine); + const local = { ...saikyoLocal, typeId: 100 } as TrainType; + const rapid = { + ...saikyoRapid, + typeId: 200, + nameRoman: 'Rapid', + } as TrainType; + const route: ConnectedRoute = { + legs: [ + { + trainTypes: [rapid, local], + recommendedGroupId: local.groupId, + fromStation: a, + toStation: b, + }, + { + trainTypes: [local, rapid], + recommendedGroupId: rapid.groupId, + fromStation: b, + toStation: c, + }, + ], + }; + const tt = buildTransferTrainType(route, -1); + if (!tt) throw new Error('Expected a transfer train type'); + expect( + tt.lines?.map((line) => [ + line.id, + line.trainType?.groupId, + line.trainType?.name, + ]) + ).toEqual([ + [saikyoLine.id, local.groupId, '各駅停車'], + [saikyoLine.id, rapid.groupId, '快速'], + ]); + const row = buildTrainTypeRow(tt, saikyoLine, a, c, true); + expect(row.title).toBe('各駅停車'); + expect(row.subtitle).toBe('埼京線 快速'); + expect(row.searchText).toContain('快速'); + expect(row.searchText).toContain('rapid'); + expect(buildTrainTypeRow(tt, saikyoLine, a, c, false).subtitle).toContain( + 'Rapid' + ); + }); + describe('pickInitialRouteTrainType', () => { const pick = (routes: ConnectedRoute[]) => { const { trainTypes, transferRouteById } = buildRouteTrainTypes(routes); @@ -1000,3 +1050,46 @@ describe('経路検索見出しの翻訳キー', () => { } }); }); + +describe('API-evaluated service selection', () => { + const local = { + id: 1, + groupId: 100, + kind: TrainTypeKind.Default, + } as TrainType; + const rapid = { id: 2, groupId: 200, kind: TrainTypeKind.Rapid } as TrainType; + const leg = { trainTypes: [local, rapid], recommendedGroupId: 200 }; + it('uses the evaluated rapid instead of silently substituting a local', () => { + expect(pickLegTrainType(leg)).toBe(rapid); + expect(pickLegTrainType({ trainTypes: [local, rapid] })).toBe(local); + expect(pickLegTrainType({ ...leg, recommendedGroupId: 999 })).toBeNull(); + expect( + filterRideableRoutes([{ legs: [{ ...leg, recommendedGroupId: 999 }] }]) + ).toEqual([]); + }); + it('keeps manually selectable sibling services after every ranked route', () => { + const routes = [ + { legs: [leg] }, + { + legs: [ + { + trainTypes: [{ ...rapid, id: 3, groupId: 300 }], + recommendedGroupId: 300, + }, + ], + }, + ]; + const built = buildRouteTrainTypes(routes); + expect(built.trainTypes.map((tt) => tt.groupId)).toEqual([200, 300, 100]); + expect( + pickInitialRouteTrainType( + routes, + built.trainTypes, + built.transferRouteById + ) + ).toBe(rapid); + expect( + sortRouteTrainTypeIndices(built.trainTypes, routes, [...routes].reverse()) + ).toEqual([1, 0, 2]); + }); +}); diff --git a/src/utils/routeSearch.ts b/src/utils/routeSearch.ts index 9e07b6d0f8..07a8b2749b 100644 --- a/src/utils/routeSearch.ts +++ b/src/utils/routeSearch.ts @@ -60,6 +60,7 @@ export const computeCurrentStationInRoutes = ( /** connectedRoutes の 1 区間 */ export type ConnectedRouteLeg = { + recommendedGroupId?: number | null; trainTypes: TrainType[] | null | undefined; fromStation?: Station | null; toStation?: Station | null; @@ -69,6 +70,7 @@ export type ConnectedRouteLeg = { /** connectedRoutes の 1 経路(API の順位順に並ぶ) */ export type ConnectedRoute = { + totalSeconds?: number | null; legs: ConnectedRouteLeg[] | null | undefined; }; @@ -85,6 +87,14 @@ export const pickDefaultTrainType = ( return findLocalType(selectable) ?? selectable[0] ?? null; }; +/** Use the service evaluated by the API; retain local preference for legacy APIs. */ +export const pickLegTrainType = (leg: ConnectedRouteLeg): TrainType | null => + leg.recommendedGroupId == null + ? pickDefaultTrainType(leg.trainTypes) + : (leg.trainTypes?.find( + (type) => type.groupId === leg.recommendedGroupId + ) ?? null); + /** * 乗車に使える経路だけを残す。乗換のない経路は種別が 1 つでもあれば使える。 * 乗換のある経路は、全区間の乗降駅と種別が揃っていないと駅をつなげないので除く @@ -96,12 +106,12 @@ export const filterRideableRoutes = ( ): ConnectedRoute[] => routes.filter((route) => { const legs = route.legs ?? []; - if (legs.length === 1) return !!pickDefaultTrainType(legs[0].trainTypes); + if (legs.length === 1) return !!pickLegTrainType(legs[0]); return ( legs.length > 1 && legs.every( (leg) => - !!pickDefaultTrainType(leg.trainTypes)?.groupId && + !!pickLegTrainType(leg)?.groupId && !!leg.fromStation && !!leg.toStation ) @@ -273,23 +283,27 @@ export const buildTransferTrainType = ( id: number ): TrainType | null => { const legs = route.legs ?? []; - const legTrainTypes = legs.map((leg) => pickDefaultTrainType(leg.trainTypes)); + const legTrainTypes = legs.map((leg) => pickLegTrainType(leg)); const [firstTrainType] = legTrainTypes; if (!firstTrainType) return null; const lines = legs.flatMap((leg, index) => { const trainType = legTrainTypes[index]; - return [leg.fromStation?.line, leg.toStation?.line] - .filter((line): line is NonNullable => !!line) - .map((line) => ({ - ...line, - trainType: (trainType?.lines?.find((l) => l.id === line.id) - ?.trainType ?? { - typeId: trainType?.typeId, - name: trainType?.name, - nameRoman: trainType?.nameRoman, - }) as Line['trainType'], - })); + return uniqBy( + [leg.fromStation?.line, leg.toStation?.line].filter( + (line): line is NonNullable => !!line + ), + 'id' + ).map((line) => ({ + ...line, + trainType: (trainType?.lines?.find((l) => l.id === line.id) + ?.trainType ?? { + groupId: trainType?.groupId, + typeId: trainType?.typeId, + name: trainType?.name, + nameRoman: trainType?.nameRoman, + }) as Line['trainType'], + })); }); return { @@ -297,7 +311,8 @@ export const buildTransferTrainType = ( id, line: (legs.at(-1)?.toStation?.line ?? firstTrainType.line) as TrainType['line'], - lines: uniqBy(lines, 'id') as unknown as TrainType['lines'], + // Deduplicate endpoints within each leg, preserving repeated lines across rides. + lines: lines as unknown as TrainType['lines'], }; }; @@ -317,7 +332,8 @@ export const isTransferRouteTrainType = ( /** * 行き先を選んだときに既定で選ぶ種別。API の順位が最も高い経路が乗換のある経路なら - * その経路を表す種別、乗換のない経路なら直通の種別から各停(無ければ先頭)を選ぶ。 + * その経路を表す種別、乗換のない経路なら API が評価した推奨系統を選ぶ。 + * 推奨 ID のない旧 API では各停を優先する。 * 直通に各停が無いとき、後ろの乗換経路の「各駅停車」を拾わないよう乗換経路は除いて探す * @param routes 乗車に使える経路(API の順位順) * @param trainTypes buildRouteTrainTypes の種別 @@ -332,13 +348,16 @@ export const pickInitialRouteTrainType = ( trainTypes.find( (tt) => tt.id != null && transferRouteById.get(tt.id) === routes[0] ) ?? - pickDefaultTrainType( - trainTypes.filter((tt) => !isTransferRouteTrainType(tt)) - ); + (routes[0]?.legs?.length === 1 && routes[0].legs[0].recommendedGroupId != null + ? pickLegTrainType(routes[0].legs[0]) + : pickDefaultTrainType( + trainTypes.filter((tt) => !isTransferRouteTrainType(tt)) + )); /** * 経路検索の結果を種別一覧に並べる種別へまとめる。乗換のない経路は区間で乗れる - * 種別をそのまま、乗換のある経路は経路ごとに 1 種別として、API の順位順に並べる + * 推奨系統で、乗換のある経路は経路ごとに 1 種別として、API の順位順に並べる。 + * 直通経路の手動選択用の他種別は、全推奨経路の後ろに並べる * @param routes 乗車に使える経路(API の順位順) * @returns 種別一覧に並べる種別と、経路を表す種別の id から経路を引く表 */ @@ -351,11 +370,26 @@ export const buildRouteTrainTypes = ( const trainTypes: TrainType[] = []; const transferRouteById = new Map(); const seenGroupIds = new Set(); + const manualTypes: TrainType[] = []; routes.forEach((route, index) => { const legs = route.legs ?? []; if (legs.length === 1) { - for (const trainType of legs[0].trainTypes ?? []) { + const leg = legs[0]; + const preferred = pickLegTrainType(leg); + if (leg.recommendedGroupId != null) { + if (!preferred) return; + manualTypes.push( + ...(leg.trainTypes ?? []).filter( + (tt) => tt.groupId !== preferred.groupId + ) + ); + } + for (const trainType of leg.recommendedGroupId == null + ? (leg.trainTypes ?? []) + : preferred + ? [preferred] + : []) { // groupId の無い種別は選んでも駅リストを引けないので並べない if (trainType.groupId == null) continue; if (seenGroupIds.has(trainType.groupId)) continue; @@ -372,6 +406,11 @@ export const buildRouteTrainTypes = ( transferRouteById.set(id, route); }); + for (const type of manualTypes) { + if (type.groupId == null || seenGroupIds.has(type.groupId)) continue; + seenGroupIds.add(type.groupId); + trainTypes.push(type); + } return { trainTypes, transferRouteById }; }; @@ -389,6 +428,7 @@ const connectedRouteKey = (route: ConnectedRoute): string => [ leg.fromStation?.id, leg.toStation?.id, + leg.recommendedGroupId, (leg.stationGroupIds ?? []).join(','), (leg.trainTypes ?? []).map((tt) => tt.groupId).join(','), ].join(':') @@ -419,6 +459,7 @@ export const sortRouteTrainTypeIndices = ( // buildRouteTrainTypes と同じく、乗換のない経路は種別の系統で、乗換のある経路は // 経路ごとに 1 つ数える。同じ系統が複数の経路に出るときは先に来た経路の順位を使う const rankByKey = new Map(); + const manualKeys: string[] = []; for (const sortedRoute of sortedRoutes) { const index = indicesByKey.get(connectedRouteKey(sortedRoute))?.shift(); if (index == null) continue; @@ -427,6 +468,13 @@ export const sortRouteTrainTypeIndices = ( for (const trainType of legs[0].trainTypes ?? []) { const key = `group:${trainType.groupId}`; if (trainType.groupId == null || rankByKey.has(key)) continue; + if ( + legs[0].recommendedGroupId != null && + trainType.groupId !== legs[0].recommendedGroupId + ) { + manualKeys.push(key); + continue; + } rankByKey.set(key, rankByKey.size); } continue; @@ -434,6 +482,10 @@ export const sortRouteTrainTypeIndices = ( rankByKey.set(`route:${transferRouteTrainTypeId(index)}`, rankByKey.size); } + for (const key of manualKeys) { + if (!rankByKey.has(key)) rankByKey.set(key, rankByKey.size); + } + const rankOf = (trainType: TrainType) => rankByKey.get( isTransferRouteTrainType(trainType) diff --git a/src/utils/trainTypeList.test.ts b/src/utils/trainTypeList.test.ts index 256a3c6ad4..8357bd41d4 100644 --- a/src/utils/trainTypeList.test.ts +++ b/src/utils/trainTypeList.test.ts @@ -355,3 +355,113 @@ describe('buildTrainTypeRow', () => { expect(row.viaLines.map((l) => l.id)).toEqual([fukutoshin.id, tokyu.id]); }); }); + +describe('transfer rows preserve repeated lines and ride order', () => { + it.each([true, false])( + 'keeps individual line names and repeated segments within a service (ja=%s)', + (ja) => { + const service = { + typeId: 2, + groupId: 200, + name: '快速', + nameRoman: 'Rapid', + }; + const boardingLine = createLine(9); + const tojo = createLine(1, { + nameShort: '東上線', + nameRoman: 'Tojo Line', + company: { id: 11, nameShort: '東武', nameEnglishShort: 'Tobu' }, + trainType: service, + } as Partial); + const ogose = createLine(2, { + nameShort: '越生線', + nameRoman: 'Ogose Line', + company: tojo.company, + trainType: service, + } as Partial); + const metro = createLine(3, { + nameShort: '副都心線', + nameRoman: 'Fukutoshin Line', + company: { id: 20, nameShort: 'メトロ', nameEnglishShort: 'Metro' }, + trainType: service, + } as Partial); + + // Both all-one-company deduplication and mixed-company abbreviation + // used to lose this route's individual segments in the subtitle. + for (const viaLines of [ + [tojo, ogose, tojo], + [tojo, ogose, tojo, metro], + ]) { + const trainType = { + id: -1, + name: '各駅停車', + nameRoman: 'Local', + lines: [boardingLine, ...viaLines], + } as TrainType; + const row = buildTrainTypeRow(trainType, boardingLine, null, null, ja); + const expectedNames = ja + ? '東上線 越生線 東上線' + : 'Tojo Line, Ogose Line, Tojo Line'; + const lastName = + viaLines.length === 4 ? (ja ? ' 副都心線' : ', Fukutoshin Line') : ''; + expect(row.subtitle).toBe( + `${expectedNames}${lastName} ${ja ? '快速' : 'Rapid'}` + ); + expect(row.viaLines).toEqual(viaLines); + expect(row.searchText).toContain('越生線'); + expect(row.searchText).toContain('ogose line'); + + // A normal service still uses the existing compact company display. + if (viaLines.length === 4) { + const normalRow = buildTrainTypeRow( + { ...trainType, id: 1 }, + boardingLine, + null, + null, + ja + ); + expect(normalRow.subtitle).toBe( + ja ? '東武線 副都心線 直通' : 'Via Tobu Line, Fukutoshin Line' + ); + } + } + } + ); + + it('keeps later rides when the destination line appears earlier in the route', () => { + const local = { + typeId: 1, + groupId: 100, + name: '各駅停車', + nameRoman: 'Local', + }; + const rapid = { typeId: 2, groupId: 200, name: '快速', nameRoman: 'Rapid' }; + const lines = [ + createLine(1, { trainType: local } as Partial), + createLine(2, { trainType: local } as Partial), + createLine(1, { trainType: rapid } as Partial), + createLine(2, { trainType: local } as Partial), + ]; + const tt = { + id: -1, + name: '各駅停車', + nameRoman: 'Local', + lines, + } as TrainType; + const row = buildTrainTypeRow( + tt, + lines[0], + null, + createDestination(lines[3]), + true + ); + expect( + row.viaLines.map((line) => [line.id, line.trainType?.groupId]) + ).toEqual([ + [2, 100], + [1, 200], + [2, 100], + ]); + expect(row.subtitle).toBe('路線2 各駅停車\n路線1 快速\n路線2 各駅停車'); + }); +}); diff --git a/src/utils/trainTypeList.ts b/src/utils/trainTypeList.ts index 7b931ca304..a5e491bdfa 100644 --- a/src/utils/trainTypeList.ts +++ b/src/utils/trainTypeList.ts @@ -1,5 +1,6 @@ import uniqBy from 'lodash/uniqBy'; import type { Line, Station, StationNested, TrainType } from '~/@types/graphql'; +import { isTransferRouteTrainType } from './routeSearch'; /** * 各種別が始発(出発駅)で乗車する起点路線を lines から特定する。 @@ -186,7 +187,12 @@ export const buildTrainTypeRow = ( destination: Station | null | undefined, ja: boolean ): TrainTypeRow => { - const lines = uniqBy(trainType.lines ?? [], 'id'); + const transfer = isTransferRouteTrainType(trainType); + // Synthetic transfer lines already follow the rides; repeated line IDs may + // carry different services and must remain in the display and search text. + const lines = transfer + ? (trainType.lines ?? []) + : uniqBy(trainType.lines ?? [], 'id'); // 経由路線は乗車駅で実際に乗車する路線から先を出したいので、種別ごとに乗車路線を // 求めて起点にする(経路の途中駅から乗る場合、終端路線は乗車路線と一致しない)。 @@ -196,14 +202,24 @@ export const buildTrainTypeRow = ( selectedLine, destination ); - const viaLines = getViaLines(lines, boardingLine, destination); + const viaLines = transfer + ? lines.slice(1) + : getViaLines(lines, boardingLine, destination); const title = (ja ? trainType.name : trainType.nameRoman) ?? ''; - // 同じ種別の路線をグループ化(連続していなくても同じtypeIdなら同一グループ) + // 乗換経路は隣り合う同一系統・種別だけをまとめ、乗車区間の順序を保つ。 + // 通常の種別は従来どおり同じ typeId の路線をまとめる。 const groupedViaLines = viaLines.reduce((groups, l) => { const typeId = l.trainType?.typeId; - const existingGroup = - typeId !== null && typeId !== undefined + const last = groups.at(-1); + const groupId = l.trainType?.groupId; + const existingGroup = transfer + ? groupId != null && + last?.[0]?.trainType?.groupId === groupId && + last[0]?.trainType?.typeId === typeId + ? last + : undefined + : typeId != null ? groups.find((g) => g[0]?.trainType?.typeId === typeId) : undefined; if (existingGroup) { @@ -216,21 +232,29 @@ export const buildTrainTypeRow = ( const isSingleGroup = groupedViaLines.length <= 1; - const subtitle = isSingleGroup - ? ja - ? `${formatLineNames(viaLines, ja)}${viaLines.length ? ' 直通' : ''}` - : viaLines.length - ? `Via ${formatLineNames(viaLines, ja)}` - : '' - : groupedViaLines - .map((group) => { - const names = formatLineNames(group, ja); - const typeName = ja - ? (group[0]?.trainType?.name ?? '') - : (group[0]?.trainType?.nameRoman ?? ''); - return typeName ? `${names} ${typeName}` : names; - }) - .join('\n'); + const subtitle = + !transfer && isSingleGroup + ? ja + ? `${formatLineNames(viaLines, ja)}${viaLines.length ? ' 直通' : ''}` + : viaLines.length + ? `Via ${formatLineNames(viaLines, ja)}` + : '' + : groupedViaLines + .map((group) => { + // Transfer subtitles describe the ride sequence, so company + // abbreviations and name deduplication would hide route segments. + const names = transfer + ? group + .map((line) => (ja ? line.nameShort : line.nameRoman)) + .filter(Boolean) + .join(ja ? ' ' : ', ') + : formatLineNames(group, ja); + const typeName = ja + ? (group[0]?.trainType?.name ?? '') + : (group[0]?.trainType?.nameRoman ?? ''); + return typeName ? `${names} ${typeName}` : names; + }) + .join('\n'); // カードの配色・路線記号・ナンバリングは全種別で選択中の路線に揃える。行ごとに // 乗車路線を推定して切り替えると、同じ駅から同じように乗るのに種別ごとに色と記号が @@ -250,6 +274,12 @@ export const buildTrainTypeRow = ( trainType.nameRoman ?? '', subtitle, ...viaLines.flatMap((l) => [l.nameShort ?? '', l.nameRoman ?? '']), + ...(transfer + ? viaLines.flatMap((l) => [ + l.trainType?.name ?? '', + l.trainType?.nameRoman ?? '', + ]) + : []), ].join(' ') ); diff --git a/utils/codegen.ts b/utils/codegen.ts index 8b187284de..38070d827d 100644 --- a/utils/codegen.ts +++ b/utils/codegen.ts @@ -15,6 +15,7 @@ const config: CodegenConfig = { 'src/@types/graphql.d.ts': { plugins: ['typescript', 'typescript-operations'], config: { + scalars: { UInt32: { input: 'number', output: 'number' } }, avoidOptionals: true, preResolveTypes: true, skipTypename: false,