"""Enables retrieval of train departure information from Trafikverket API.""" from __future__ import annotations from datetime import datetime from .const import ( DATE_TIME_FORMAT, TRAIN_STOP_REQUIRED_FIELDS, ) from .exceptions import ( MultipleTrainStationsFound, NoTrainAnnouncementFound, NoTrainStationFound, ) from .filters import FieldFilter, FieldSort, FilterOperation, OrFilter, SortOrder from .helpers import ( station_from_xml_node, train_stop_from_xml_node, ) from .models import StationInfoModel, TrainStopModel from .trafikverket import ( TrafikverketBase, ) class TrafikverketTrain(TrafikverketBase): """Class used to communicate with trafikverket's train api.""" version = "1.9" async def async_get_train_station_from_signature( self, signature: str ) -> StationInfoModel: """Retrieve train station based on signature.""" train_stations = await self._api.async_make_request( "TrainStation", "1.5", "rail.infrastructure", [ "AdvertisedLocationName", "LocationSignature", "Advertised", "Deleted", "OfficialLocationName", ], [ FieldFilter(FilterOperation.EQUAL, "LocationSignature", signature), ], ) if len(train_stations) == 0: raise NoTrainStationFound( "Could not find a station with the specified name" ) return await station_from_xml_node(train_stations[0]) async def async_search_train_station(self, location_name: str) -> StationInfoModel: """Retrieve train station id based on name.""" train_stations = await self.async_search_train_stations(location_name) if len(train_stations) == 0: raise NoTrainStationFound( "Could not find a station with the specified name" ) if len(train_stations) > 1: raise MultipleTrainStationsFound( "Found multiple stations with the specified name" ) return train_stations[0] async def async_search_train_stations( self, location_name: str ) -> list[StationInfoModel]: """Search for train stations.""" train_stations = await self._api.async_make_request( "TrainStation", "1.5", "rail.infrastructure", [ "AdvertisedLocationName", "LocationSignature", "Advertised", "Deleted", "OfficialLocationName", ], [ OrFilter( [ FieldFilter( FilterOperation.LIKE, "AdvertisedLocationName", location_name, ), FieldFilter( FilterOperation.LIKE, "OfficialLocationName", location_name, ), ] ), FieldFilter(FilterOperation.EQUAL, "Advertised", "true"), ], ) if len(train_stations) == 0: raise NoTrainStationFound( "Could not find a station with the specified name" ) result = [] for train_station in train_stations: result.append(await station_from_xml_node(train_station)) return result async def async_get_train_stop( self, from_station: StationInfoModel, to_station: StationInfoModel, time_at_location: datetime, product_description: str | None = None, exclude_canceled: bool = False, ) -> TrainStopModel: """Retrieve the train stop.""" date_as_text = time_at_location.strftime(DATE_TIME_FORMAT) filters = [ FieldFilter(FilterOperation.EQUAL, "ActivityType", "Avgang"), FieldFilter( FilterOperation.EQUAL, "LocationSignature", from_station.signature ), FieldFilter( FilterOperation.EQUAL, "AdvertisedTimeAtLocation", date_as_text ), OrFilter( [ FieldFilter( FilterOperation.EQUAL, "ViaToLocation.LocationName", to_station.signature, ), FieldFilter( FilterOperation.EQUAL, "ToLocation.LocationName", to_station.signature, ), ] ), ] if product_description: filters.append( FieldFilter( FilterOperation.LIKE, "ProductInformation.Description", product_description, ) ) if exclude_canceled: filters.append(FieldFilter(FilterOperation.EQUAL, "Canceled", "false")) train_announcements = await self._api.async_make_request( "TrainAnnouncement", self.version, None, TRAIN_STOP_REQUIRED_FIELDS, filters, 1, ) if len(train_announcements) == 0: raise NoTrainAnnouncementFound("No TrainAnnouncement found") train_announcement = train_announcements[0] return await train_stop_from_xml_node(train_announcement) async def async_get_next_train_stops( self, from_station: StationInfoModel, to_station: StationInfoModel, after_time: datetime, product_description: str | None = None, exclude_canceled: bool = False, number_of_stops: int = 1, ) -> list[TrainStopModel]: """Enable retrieval of next departures.""" date_as_text = after_time.strftime(DATE_TIME_FORMAT) filters = [ FieldFilter(FilterOperation.EQUAL, "ActivityType", "Avgang"), FieldFilter( FilterOperation.EQUAL, "LocationSignature", from_station.signature ), FieldFilter( FilterOperation.GREATER_THAN_EQUAL, "AdvertisedTimeAtLocation", date_as_text, ), OrFilter( [ FieldFilter( FilterOperation.EQUAL, "ViaToLocation.LocationName", to_station.signature, ), FieldFilter( FilterOperation.EQUAL, "ToLocation.LocationName", to_station.signature, ), ] ), ] if product_description: filters.append( FieldFilter( FilterOperation.LIKE, "ProductInformation.Description", product_description, ) ) if exclude_canceled: filters.append(FieldFilter(FilterOperation.EQUAL, "Canceled", "false")) sorting = [FieldSort("AdvertisedTimeAtLocation", SortOrder.ASCENDING)] train_announcements = await self._api.async_make_request( "TrainAnnouncement", self.version, None, TRAIN_STOP_REQUIRED_FIELDS, filters, number_of_stops, sorting, ) if len(train_announcements) == 0: raise NoTrainAnnouncementFound("No TrainAnnouncement found") stops = [] for announcement in train_announcements: stops.append(await train_stop_from_xml_node(announcement)) return stops async def async_get_next_train_stop( self, from_station: StationInfoModel, to_station: StationInfoModel, after_time: datetime, product_description: str | None = None, exclude_canceled: bool = False, ) -> TrainStopModel: """Enable retrieval of next departure.""" stops = await self.async_get_next_train_stops( from_station, to_station, after_time, product_description, exclude_canceled, 1, ) return stops[0]