szczanieckiej.git

commit 64e1d0e9898caabfd34776758558eb8b81558631

Author: Adam Evyčędo <git@apiote.xyz>

get gtfs updates refactor

 api/api.go | 279 +++++++++++++++++-------------------------
 api/feedsResponse.go | 11 +
 gtfs_rt/main.go | 20 +-
 gtfs_rt/new.go | 30 ++++
 server/handler_vars.go | 2 
 server/router.go | 20 +-
 traffic/access.go | 286 +++++++++++++++++++------------------------
 traffic/feed_info.go | 2 
 traffic/gtfs_rt.go | 91 ++++++++++++++
 traffic/realtime.go | 153 +++++++++++++++++++++++
 traffic/structs.go | 20 +-
 traffic/structs_gen.go | 21 +-


diff --git a/api/api.go b/api/api.go
index 0d8b4dabfb1fc8dacdcf0571161ed7304ff04765..7b7a58aec618f4fee3f974afe59341327978454d 100644
--- a/api/api.go
+++ b/api/api.go
@@ -3,9 +3,8 @@
 // todo(BAF10) direction (0|1) to const (TO|BACK)
 
 import (
-	"apiote.xyz/p/szczanieckiej/gtfs_rt"
-	pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
 	"apiote.xyz/p/szczanieckiej/traffic"
+	"golang.org/x/text/language"
 
 	"encoding/hex"
 	"errors"
@@ -20,13 +19,16 @@ 	"github.com/adrg/strutil/metrics"
 	"github.com/dhconnelly/rtreego"
 )
 
-func convertTrafficAlerts(trafficAlerts []traffic.Alert) []AlertV1 {
+func convertTrafficAlerts(trafficAlerts []traffic.Alert, preferredLanguages []language.Tag) []AlertV1 {
 	alerts := []AlertV1{}
 	for _, alert := range trafficAlerts {
+		headerTag := selectLanguageByTag(alert.Headers, preferredLanguages)
+		descriptionTag := selectLanguageByTag(alert.Descriptions, preferredLanguages)
+		urlTag := selectLanguageByTag(alert.URLs, preferredLanguages)
 		a := AlertV1{
-			Header:      alert.Header,
-			Description: alert.Header,
-			Url:         alert.URL,
+			Header:      alert.Headers[headerTag],
+			Description: alert.Descriptions[descriptionTag],
+			Url:         alert.URLs[urlTag],
 			Cause:       makeAlertCauseV1(alert),
 			Effect:      makeAlertEffectV1(alert),
 		}
@@ -284,10 +286,10 @@ 		Queryables: []QueryableV2{},
 	}
 	for _, item := range items {
 		if stop, ok := item.(traffic.Stop); ok {
-			s := convertTrafficStopV2(stop, context.FeedName)
+			s := convertTrafficStopV2(stop, context.FeedID)
 			success.Queryables = append(success.Queryables, QueryableV2(s))
 		} else if line, ok := item.(traffic.Line); ok {
-			l := convertTrafficLine(line, context.FeedName)
+			l := convertTrafficLine(line, context.FeedID)
 			success.Queryables = append(success.Queryables, QueryableV2(l))
 		} else {
 			// todo error
@@ -312,10 +314,10 @@ 		Queryables: []QueryableV3{},
 	}
 	for _, item := range items {
 		if stop, ok := item.(traffic.Stop); ok {
-			s := convertTrafficStopV2(stop, context.FeedName)
+			s := convertTrafficStopV2(stop, context.FeedID)
 			success.Queryables = append(success.Queryables, QueryableV3(s))
 		} else if line, ok := item.(traffic.Line); ok {
-			l := convertTrafficLineV2(line, context.FeedName)
+			l := convertTrafficLineV2(line, context.FeedID)
 			success.Queryables = append(success.Queryables, QueryableV3(l))
 		} else {
 			// todo error
@@ -516,7 +518,7 @@ 		Locatables: []LocatableV2{},
 	}
 	for _, locatable := range locatables {
 		if stop, ok := locatable.(traffic.Stop); ok {
-			s := convertTrafficStopV2(stop, context.FeedName)
+			s := convertTrafficStopV2(stop, context.FeedID)
 			success.Locatables = append(success.Locatables, LocatableV2(s))
 		} else if vehicle, ok := locatable.(traffic.VehicleStatus); ok {
 			v, err := convertTrafficVehicleV2(vehicle, context, t)
@@ -540,7 +542,7 @@ 		Locatables: []LocatableV3{},
 	}
 	for _, locatable := range locatables {
 		if stop, ok := locatable.(traffic.Stop); ok {
-			s := convertTrafficStopV2(stop, context.FeedName)
+			s := convertTrafficStopV2(stop, context.FeedID)
 			success.Locatables = append(success.Locatables, LocatableV3(s))
 		} else if vehicle, ok := locatable.(traffic.VehicleStatus); ok {
 			v, err := convertTrafficVehicleV3(vehicle, context, t)
@@ -559,7 +561,7 @@ 	return success, nil
 }
 
 func CreateSuccessLine(line traffic.Line, context traffic.Context, t *traffic.Traffic) (LineResponse, error) {
-	l := convertTrafficLine(line, context.FeedName)
+	l := convertTrafficLine(line, context.FeedID)
 	l, err := convertTrafficLineGraphs(line, l, context, t)
 	if err != nil {
 		return LineResponseV1{}, fmt.Errorf("while converting graph: %w", err)
@@ -572,7 +574,7 @@ 	return success, nil
 }
 
 func CreateSuccessLineV2(line traffic.Line, context traffic.Context, t *traffic.Traffic) (LineResponse, error) {
-	l := convertTrafficLineV2(line, context.FeedName)
+	l := convertTrafficLineV2(line, context.FeedID)
 	l, err := convertTrafficLineGraphsV1forLineV2(line, l, context, t)
 	if err != nil {
 		return LineResponseDev{}, fmt.Errorf("while converting graph: %w", err)
@@ -584,10 +586,61 @@
 	return success, nil
 }
 
-func convertVehicle(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV1 {
+func convertVehicleStatusV1(status traffic.DepartureStatus, timeToArrival float64) VehicleStatusV1 {
+	if status == traffic.AT_STOP {
+		return STATUS_AT_STOP
+	} else if timeToArrival < 0 {
+		return STATUS_DEPARTED
+	} else if timeToArrival < 1 {
+		return STATUS_INCOMING
+	}
+	return STATUS_IN_TRANSIT
+}
+
+func convertCongestionLevelV1(level traffic.CongestionLevel) CongestionLevelV1 {
+	switch level {
+	case traffic.CONGESTION_UNKNOWN:
+		return CONGESTION_UNKNOWN
+	case traffic.CONGESTION_SMOOTH:
+		return CONGESTION_SMOOTH
+	case traffic.CONGESTION_STOP_AND_GO:
+		return CONGESTION_STOP_AND_GO
+	case traffic.CONGESTION_SIGNIFICANT:
+		return CONGESTION_SIGNIFICANT
+	case traffic.CONGESTION_SEVERE:
+		return CONGESTION_SEVERE
+	default:
+		return CONGESTION_UNKNOWN
+	}
+}
+
+func convertOccupancyStatusV1(status traffic.OccupancyStatus) OccupancyStatusV1 {
+	switch status {
+	case traffic.OCCUPANCY_UNKNOWN:
+		return OCCUPANCY_UNKNOWN
+	case traffic.OCCUPANCY_EMPTY:
+		return OCCUPANCY_EMPTY
+	case traffic.OCCUPANCY_MANY_AVAILABLE:
+		return OCCUPANCY_MANY_AVAILABLE
+	case traffic.OCCUPANCY_FEW_AVAILABLE:
+		return OCCUPANCY_FEW_AVAILABLE
+	case traffic.OCCUPANCY_STANDING_ONLY:
+		return OCCUPANCY_STANDING_ONLY
+	case traffic.OCCUPANCY_CRUSHED:
+		return OCCUPANCY_CRUSHED
+	case traffic.OCCUPANCY_FULL:
+		return OCCUPANCY_FULL
+	case traffic.OCCUPANCY_NOT_ACCEPTING:
+		return OCCUPANCY_NOT_ACCEPTING
+	default:
+		return OCCUPANCY_UNKNOWN
+	}
+}
+
+func convertVehicle(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV1 {
 	return VehicleV1{
 		Id:           update.VehicleID,
-		Position:     PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)},
+		Position:     PositionV1{Lat: update.Latitude, Lon: update.Longitude},
 		Capabilities: vehicles[update.VehicleID].Capabilities,
 		Speed:        update.Speed,
 		Line:         LineStubV1{Name: line.Name, Kind: makeLineTypeV1(line), Colour: fromColor(line.Colour)},
@@ -597,7 +650,7 @@ 		// todo OccupancyStatus
 	}
 }
 
-func convertVehicleV2(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV2 {
+func convertVehicleV2(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV2 {
 	return VehicleV2{
 		Id:           update.VehicleID,
 		Position:     PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)},
@@ -610,7 +663,7 @@ 		// todo OccupancyStatus
 	}
 }
 
-func convertVehicleV3(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV3 {
+func convertVehicleV3(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV3 {
 	return VehicleV3{
 		Id:           update.VehicleID,
 		Position:     PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)},
@@ -623,19 +676,18 @@ 		// todo OccupancyStatus
 	}
 }
 
-func CreateSuccessDeparturesV1(stop traffic.Stop, departures []traffic.DepartureRealtime,
-	date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) {
+func CreateSuccessDeparturesV1(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) {
 	d := []DepartureV1{}
 	var success DeparturesResponse
 	now := time.Now()
-	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName])
+	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID])
 	if err != nil {
 		return success, err
 	}
 	datetime := time.Date(date.Year(), date.Month(),
 		date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone)
 	for _, trafficDeparture := range departures {
-		zoneAbbr := trafficDeparture.Update.Time.Location().String()
+		zoneAbbr := trafficDeparture.Time.Location().String()
 		stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
 		if err != nil {
 			return success, err
@@ -644,83 +696,49 @@ 		line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t)
 		if err != nil {
 			return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err)
 		}
+		departureTime := traffic.GetTimeWithDelay(trafficDeparture)
 		departure := DepartureV1{
 			Id: stopOrder,
 			Time: TimeV1{
-				DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time),
-				Hour:      uint8(trafficDeparture.Update.Time.Hour()),
-				Minute:    uint8(trafficDeparture.Update.Time.Minute()),
-				Second:    uint8(trafficDeparture.Update.Time.Second()),
+				DayOffset: getDayOffset(datetime, departureTime),
+				Hour:      uint8(departureTime.Hour()),
+				Minute:    uint8(departureTime.Minute()),
+				Second:    uint8(departureTime.Second()),
 				Zone:      zoneAbbr,
 			},
 			Status:     STATUS_IN_TRANSIT,
-			IsRealtime: trafficDeparture.Update.TripUpdate != nil,
+			IsRealtime: trafficDeparture.Update.Delay != nil,
 			Vehicle:    convertVehicle(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign),
 			Boarding:   makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
 		}
-		timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes()
+		timeToArrival := departureTime.Sub(datetime).Minutes()
 		if departure.IsRealtime {
-			if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT {
-				departure.Status = STATUS_AT_STOP
-			} else if timeToArrival < 0 {
-				departure.Status = STATUS_DEPARTED
-			} else if timeToArrival < 1 {
-				departure.Status = STATUS_INCOMING
-			}
-
-			if trafficDeparture.Update.CongestionLevel == nil {
-				departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY {
-				departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO {
-				departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SEVERE
-			}
-
-			if trafficDeparture.Update.OccupancyStatus == nil {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING
-			}
+			departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival)
+			departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel)
+			departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus)
 		}
 		d = append(d, departure)
 	}
 	success = DeparturesResponseV1{
 		Stop:       convertTrafficStop(stop),
 		Departures: d,
-		Alerts:     convertTrafficAlerts(alerts),
+		Alerts:     convertTrafficAlerts(alerts, preferredLanguages),
 	}
 	return success, nil
 }
 
-func CreateSuccessDeparturesV2(stop traffic.Stop, departures []traffic.DepartureRealtime,
-	date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) {
+func CreateSuccessDeparturesV2(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) {
 	d := []DepartureV2{}
 	var success DeparturesResponse
 	now := time.Now()
-	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName])
+	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID])
 	if err != nil {
 		return success, err
 	}
 	datetime := time.Date(date.Year(), date.Month(),
 		date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone)
 	for _, trafficDeparture := range departures {
-		zoneAbbr := trafficDeparture.Update.Time.Location().String()
+		zoneAbbr := trafficDeparture.Time.Location().String()
 		stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
 		if err != nil {
 			return success, err
@@ -729,83 +747,49 @@ 		line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t)
 		if err != nil {
 			return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err)
 		}
+		departureTime := traffic.GetTimeWithDelay(trafficDeparture)
 		departure := DepartureV2{
 			Id: stopOrder,
 			Time: TimeV1{
-				DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time),
-				Hour:      uint8(trafficDeparture.Update.Time.Hour()),
-				Minute:    uint8(trafficDeparture.Update.Time.Minute()),
-				Second:    uint8(trafficDeparture.Update.Time.Second()),
+				DayOffset: getDayOffset(datetime, departureTime),
+				Hour:      uint8(departureTime.Hour()),
+				Minute:    uint8(departureTime.Minute()),
+				Second:    uint8(departureTime.Second()),
 				Zone:      zoneAbbr,
 			},
 			Status:     STATUS_IN_TRANSIT,
-			IsRealtime: trafficDeparture.Update.TripUpdate != nil,
+			IsRealtime: trafficDeparture.Update.Delay != nil,
 			Vehicle:    convertVehicleV2(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign),
 			Boarding:   makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
 		}
-		timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes()
+		timeToArrival := departureTime.Sub(datetime).Minutes()
 		if departure.IsRealtime {
-			if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT {
-				departure.Status = STATUS_AT_STOP
-			} else if timeToArrival < 0 {
-				departure.Status = STATUS_DEPARTED
-			} else if timeToArrival < 1 {
-				departure.Status = STATUS_INCOMING
-			}
-
-			if trafficDeparture.Update.CongestionLevel == nil {
-				departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY {
-				departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO {
-				departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SEVERE
-			}
-
-			if trafficDeparture.Update.OccupancyStatus == nil {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING
-			}
+			departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival)
+			departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel)
+			departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus)
 		}
 		d = append(d, departure)
 	}
 	success = DeparturesResponseV2{
-		Stop:       convertTrafficStopV2(stop, ctx.FeedName),
+		Stop:       convertTrafficStopV2(stop, ctx.FeedID),
 		Departures: d,
-		Alerts:     convertTrafficAlerts(alerts),
+		Alerts:     convertTrafficAlerts(alerts, preferredLanguages),
 	}
 	return success, nil
 }
 
-func CreateSuccessDeparturesV3(stop traffic.Stop, departures []traffic.DepartureRealtime,
-	date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) {
+func CreateSuccessDeparturesV3(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) {
 	d := []DepartureV3{}
 	var success DeparturesResponse
 	now := time.Now()
-	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName])
+	timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID])
 	if err != nil {
 		return success, err
 	}
 	datetime := time.Date(date.Year(), date.Month(),
 		date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone)
 	for _, trafficDeparture := range departures {
-		zoneAbbr := trafficDeparture.Update.Time.Location().String()
+		zoneAbbr := trafficDeparture.Time.Location().String()
 		stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
 		if err != nil {
 			return success, err
@@ -814,66 +798,33 @@ 		line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t)
 		if err != nil {
 			return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err)
 		}
+		departureTime := traffic.GetTimeWithDelay(trafficDeparture)
 		departure := DepartureV3{
 			Id: stopOrder,
 			Time: TimeV1{
-				DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time),
-				Hour:      uint8(trafficDeparture.Update.Time.Hour()),
-				Minute:    uint8(trafficDeparture.Update.Time.Minute()),
-				Second:    uint8(trafficDeparture.Update.Time.Second()),
+				DayOffset: getDayOffset(datetime, departureTime),
+				Hour:      uint8(departureTime.Hour()),
+				Minute:    uint8(departureTime.Minute()),
+				Second:    uint8(departureTime.Second()),
 				Zone:      zoneAbbr,
 			},
 			Status:     STATUS_IN_TRANSIT,
-			IsRealtime: trafficDeparture.Update.TripUpdate != nil,
+			IsRealtime: trafficDeparture.Update.Delay != nil,
 			Vehicle:    convertVehicleV3(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign),
 			Boarding:   makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
 		}
-		timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes()
+		timeToArrival := departureTime.Sub(datetime).Minutes()
 		if departure.IsRealtime {
-			if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT {
-				departure.Status = STATUS_AT_STOP
-			} else if timeToArrival < 0 {
-				departure.Status = STATUS_DEPARTED
-			} else if timeToArrival < 1 {
-				departure.Status = STATUS_INCOMING
-			}
-
-			if trafficDeparture.Update.CongestionLevel == nil {
-				departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY {
-				departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO {
-				departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT
-			} else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION {
-				departure.Vehicle.CongestionLevel = CONGESTION_SEVERE
-			}
-
-			if trafficDeparture.Update.OccupancyStatus == nil {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL
-			} else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS {
-				departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING
-			}
+			departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival)
+			departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel)
+			departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus)
 		}
 		d = append(d, departure)
 	}
 	success = DeparturesResponseDev{
-		Stop:       convertTrafficStopV2(stop, ctx.FeedName),
+		Stop:       convertTrafficStopV2(stop, ctx.FeedID),
 		Departures: d,
-		Alerts:     convertTrafficAlerts(alerts),
+		Alerts:     convertTrafficAlerts(alerts, preferredLanguages),
 	}
 	return success, nil
 }




diff --git a/api/feedsResponse.go b/api/feedsResponse.go
index 3bf3d9b458f05bcd1c1e2875928f14054987027c..062fdf8aa36093d8344aa3fd9faebe61f6e56195 100644
--- a/api/feedsResponse.go
+++ b/api/feedsResponse.go
@@ -35,6 +35,17 @@
 	return descriptionTag, attributionTag
 }
 
+func selectLanguageByTag(messages map[language.Tag]string, preferredLanguages []language.Tag) language.Tag {
+	tags := []language.Tag{}
+	for t := range messages {
+		tags = append(tags, t)
+	}
+
+	matcher := language.NewMatcher(preferredLanguages)
+	_, index, _ := matcher.Match(tags...)
+	return preferredLanguages[index]
+}
+
 func MakeFeedsResponse(feedInfos map[string]traffic.FeedInfo, lastUpdates map[string]time.Time, accept uint, preferredLanguages []language.Tag) (FeedsResponse, error) {
 	switch accept {
 	case 0:




diff --git a/gtfs_rt/main.go b/gtfs_rt/main.go
index f2085b0e496df8c81547a810c3c12c13b46f7f44..7cdedd16593fa6d81c1a671d2ef565d5690886f9 100644
--- a/gtfs_rt/main.go
+++ b/gtfs_rt/main.go
@@ -1,4 +1,4 @@
-package gtfs_rt
+package gtfs_rt // TODO move to traffic
 
 import (
 	pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
@@ -13,7 +13,7 @@
 	"google.golang.org/protobuf/proto"
 )
 
-type Update struct {
+type Update struct { // TODO use traffic/Update
 	VehicleID       string
 	Latitude        float32
 	Longitude       float32
@@ -169,9 +169,10 @@ 	}
 	return nil
 }
 
-func getMessages(previouslyUpdated uint64, feedName string, feedURLs []string) (uint64, error) {
+func GetMessages(previouslyUpdated uint64, feedName string, feedURLs []string) (uint64, []*pb.FeedMessage, error) {
 	now := uint64(time.Now().Unix())
 	lastUpdated := previouslyUpdated
+	updates := cache[feedName]
 	if now-previouslyUpdated > 30 {
 		if cache == nil {
 			cache = map[string][]*pb.FeedMessage{}
@@ -181,27 +182,28 @@ 		for _, url := range feedURLs {
 			client := http.Client{Timeout: 5 * time.Second}
 			response, err := client.Get(url)
 			if err != nil {
-				return lastUpdated, fmt.Errorf("cannot download from ‘%s’: %w", url, err)
+				return lastUpdated, updates, fmt.Errorf("cannot download from ‘%s’: %w", url, err)
 			}
 			message := &pb.FeedMessage{}
 			bytes, err := ioutil.ReadAll(response.Body)
 			if err != nil {
-				return lastUpdated, fmt.Errorf("cannot read response for ‘%s’: %w", url, err)
+				return lastUpdated, updates, fmt.Errorf("cannot read response for ‘%s’: %w", url, err)
 			}
 			if err := proto.Unmarshal(bytes, message); err != nil {
-				return lastUpdated, fmt.Errorf("Failed to parse message: %w", err)
+				return lastUpdated, updates, fmt.Errorf("Failed to parse message: %w", err)
 			}
 			cache[feedName] = append(cache[feedName], message)
 		}
 		lastUpdated = now
+		updates = cache[feedName]
 	}
-	return lastUpdated, nil
+	return lastUpdated, updates, nil
 }
 
 func GetRt(previouslyUpdated uint64, feedUrls []string, feedName string) (map[string]Update, uint64, error) {
 	updates := map[string]Update{}
 
-	lastUpdated, err := getMessages(previouslyUpdated, feedName, feedUrls)
+	lastUpdated, _, err := GetMessages(previouslyUpdated, feedName, feedUrls)
 	if err != nil {
 		return updates, lastUpdated, fmt.Errorf("while getting messages: %w", err)
 	}
@@ -222,7 +224,7 @@
 func GetAlerts(previouslyUpdated uint64, feedUrls []string, feedName string) (Alerts, uint64, error) {
 	alerts := Alerts{}
 
-	lastUpdated, err := getMessages(previouslyUpdated, feedName, feedUrls)
+	lastUpdated, _, err := GetMessages(previouslyUpdated, feedName, feedUrls)
 	if err != nil {
 		return alerts, lastUpdated, fmt.Errorf("while getting messages: %w", err)
 	}




diff --git a/gtfs_rt/new.go b/gtfs_rt/new.go
new file mode 100644
index 0000000000000000000000000000000000000000..78e4cf47c04f4553172808a6f44e0dff0619149b
--- /dev/null
+++ b/gtfs_rt/new.go
@@ -0,0 +1,30 @@
+package gtfs_rt
+
+import (
+	pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
+
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+
+	"google.golang.org/protobuf/proto"
+)
+
+func GetMessages2(feedID string, feedURL string) (*pb.FeedMessage, error) {
+	var message *pb.FeedMessage
+	client := http.Client{Timeout: 5 * time.Second}
+	response, err := client.Get(feedURL)
+	if err != nil {
+		return nil, fmt.Errorf("cannot download from ‘%s’: %w", feedURL, err)
+	}
+	message = new(pb.FeedMessage)
+	bytes, err := io.ReadAll(response.Body)
+	if err != nil {
+		return nil, fmt.Errorf("cannot read response for ‘%s’: %w", feedURL, err)
+	}
+	if err := proto.Unmarshal(bytes, message); err != nil {
+		return nil, fmt.Errorf("Failed to parse message: %w", err)
+	}
+	return message, nil
+}




diff --git a/server/handler_vars.go b/server/handler_vars.go
index 2d04b58d6209de2404c1a0fb82f81389191770f3..09cc1d9afa50591159e1e6925cc65c667298b7ae 100644
--- a/server/handler_vars.go
+++ b/server/handler_vars.go
@@ -131,7 +131,7 @@ func createContext(v AbstractHandlerVars) AbstractHandlerVars {
 	v.setContext(
 		traffic.Context{
 			DataHome: v.getConfig().FeedsPath,
-			FeedName: v.getFeedName(),
+			FeedID:   v.getFeedName(),
 			Version:  v.getVersionCode(),
 		},
 	)




diff --git a/server/router.go b/server/router.go
index ba34211f7a60da274f71223e70d4304b5b39c133..92af52952bf75359bda9d522c766d7ec73276352 100644
--- a/server/router.go
+++ b/server/router.go
@@ -114,7 +114,7 @@ 		}
 
 		context := traffic.Context{
 			DataHome: cfg.FeedsPath,
-			FeedName: feedName,
+			FeedID:   feedName,
 			Version:  versionCode,
 		}
 		stops, err := traffic.GetStopsIn(lb, rt, context, t)
@@ -210,7 +210,7 @@ 		}
 
 		context := traffic.Context{
 			DataHome: cfg.FeedsPath,
-			FeedName: feedName,
+			FeedID:   feedName,
 			Version:  versionCode,
 		}
 
@@ -364,7 +364,7 @@ 	}
 
 	context := traffic.Context{
 		DataHome: cfg.FeedsPath,
-		FeedName: feedName,
+		FeedID:   feedName,
 		Version:  versionCode,
 	}
 
@@ -384,7 +384,7 @@ 	if err != nil {
 		return fmt.Errorf("while getting stop: %w", err)
 	}
 
-	departures, err := traffic.GetDepartures(code, line, cfg.FeedsPath, feedName, versionCode, t, date, departuresType)
+	departures, err := traffic.GetDepartures(code, line, context, t, date, departuresType)
 	if err != nil {
 		if _, ok := err.(traffic_errors.NoSchedule); ok {
 			return ServerError{
@@ -407,7 +407,7 @@ 	}
 
 	acceptLanguage := r.Header.Get("Accept-Language")
 	if acceptLanguage == "" {
-		acceptLanguage, err = traffic.GetLanguage(context, t)
+		acceptLanguage, err = traffic.GetLanguage(context)
 		if err != nil {
 			log.Printf("while gettng default language: %v\n", err)
 			acceptLanguage = "und"
@@ -423,7 +423,7 @@ 			err:   err,
 		}
 	}
 
-	alerts, err := traffic.GetAlerts(string(code), preferredLanguages, context, t)
+	alerts, err := traffic.GetGtfsAlerts(string(code), preferredLanguages, context, t)
 	if err != nil {
 		return fmt.Errorf("while getting alerts: %w", err)
 	}
@@ -431,11 +431,11 @@
 	var success api.DeparturesResponse
 	switch accept {
 	case 0:
-		success, err = api.CreateSuccessDeparturesV3(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept)
+		success, err = api.CreateSuccessDeparturesV3(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages)
 	case 1:
-		success, err = api.CreateSuccessDeparturesV1(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept)
+		success, err = api.CreateSuccessDeparturesV1(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages)
 	case 2:
-		success, err = api.CreateSuccessDeparturesV2(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept)
+		success, err = api.CreateSuccessDeparturesV2(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages)
 	}
 	if err != nil {
 		return fmt.Errorf("while creating departuresSuccess: %w", err)
@@ -464,7 +464,7 @@ 		stopCode := r.Form.Get("stop")
 
 		context := traffic.Context{
 			DataHome: cfg.FeedsPath,
-			FeedName: feedName,
+			FeedID:   feedName,
 			Version:  versionCode,
 		}
 




diff --git a/traffic/access.go b/traffic/access.go
index 7ab8c8550f41157969dd9c69ce8448d92e110a37..a3bd8279211959fd7313aca1212c8f965603f7b2 100644
--- a/traffic/access.go
+++ b/traffic/access.go
@@ -15,7 +15,6 @@ 	"net"
 	"os"
 	"path/filepath"
 	"sort"
-	"strconv"
 	"strings"
 	"time"
 
@@ -48,11 +47,11 @@ 	Calendar       []Schedule
 	DeparturesType DeparturesType
 	Vehicles       Vehicles
 	Feed           Feed
+	Ctx            Context
 
 	Location          *time.Location
 	Datetime          time.Time
 	MinuteB4Datetime  time.Time
-	Midnight          time.Time
 	TodaySchedule     map[string]struct{}
 	YesterdaySchedule map[string]struct{}
 	file              *os.File
@@ -67,6 +66,7 @@ 	FeedInfo   FeedInfo
 }
 
 var lastUpdatedGtfsRt = map[string]uint64{}
+var lastUpdatedGtfsRt2 = map[string]map[string]uint64{}
 
 func isTimeout(err error) bool {
 	var e net.Error
@@ -123,23 +123,10 @@ 	return updates, nil
 }
 
 func calculateGtfsTime(gtfsTime uint, delay int32, date time.Time,
-	timezone *time.Location) (time.Time, error) {
+	timezone *time.Location) time.Time {
 	noon := time.Date(date.Year(), date.Month(), date.Day(), 12, 0, 0, 0,
 		timezone)
-	twelve, _ := time.ParseDuration("-12h")
-	midnight := noon.Add(twelve)
-	departureDuration, err := time.ParseDuration(
-		strconv.FormatInt(int64(gtfsTime), 10) + "m")
-	if err != nil {
-		return midnight, err
-	}
-	delayDuration, err := time.ParseDuration(strconv.FormatInt(int64(delay),
-		10) + "s")
-	if err != nil {
-		return midnight, err
-	}
-	t := midnight.Add(departureDuration).Add(delayDuration)
-	return t, nil
+	return noon.Add(time.Duration(-12) * time.Hour).Add(time.Duration(gtfsTime) * time.Minute).Add(time.Duration(delay) * time.Second)
 }
 
 func loadLocation(input ...interface{}) (interface{}, error) {
@@ -152,15 +139,12 @@
 func loadTime(input ...interface{}) interface{} {
 	result := input[0].(_Result)
 
-	deadzone, _ := time.ParseDuration("-1m")
 	now := time.Now()
 	datetime := time.Date(result.Date.Year(), result.Date.Month(),
 		result.Date.Day(), now.Hour(), now.Minute(), now.Second(), 0,
 		result.Location)
 	result.Datetime = datetime
-	result.MinuteB4Datetime = datetime.Add(deadzone)
-	result.Midnight = time.Date(datetime.Year(), datetime.Month(),
-		datetime.Day(), 0, 0, 0, 0, result.Location)
+	result.MinuteB4Datetime = datetime.Add(time.Duration(-1) * time.Minute)
 	return result
 }
 
@@ -282,7 +266,7 @@ 	result.Trips = trips
 	return result, nil
 }
 
-func getUpdates(input ...interface{}) (interface{}, error) {
+func getDepartures(input ...interface{}) (interface{}, error) {
 	result := input[0].(_Result)
 	departures := []DepartureRealtime{}
 	timedOut := false
@@ -309,7 +293,7 @@ 			}
 		}
 		departures = append(departures, departure)
 	}
-	result.Departures = departures
+	result.Departures = enrichDepartures(departures, result.Datetime, result.DeparturesType, result.Ctx, result.TripsFile, result.Location)
 	result.TripsFile.Close()
 	return result, nil
 }
@@ -326,35 +310,10 @@ 			departureRt.Departure = departure
 			departureRt.Headsign = trip.Headsign
 			departureRt.LineName = trip.LineName
 			departureRt.Order = order
-			departureRt.Update = gtfs_rt.Update{}
+			departureRt.Update = Update{}
 
-			departureTime, err := calculateGtfsTime(departure.Time, 0, date,
+			departureRt.Time = calculateGtfsTime(departure.Time, 0, date,
 				result.Location)
-			if err != nil {
-				return departureRt, err
-			}
-			departureRt.Update.Time = departureTime
-
-			if departureTime.After(result.Midnight) {
-				if result.DeparturesType == DEPARTURES_HYBRID && !timedOut {
-					departureRt.Update, err = getRealtimeOffset(trip.Id,
-						order.Sequence, feed)
-					if err != nil {
-						if isTimeout(err) {
-							timedOut = true
-							finalErr = err
-						} else {
-							log.Printf("while getting realtime departures: %v\n", err)
-						}
-					}
-				}
-				departureTime, err := calculateGtfsTime(departure.Time,
-					departureRt.Update.Delay, date, result.Location)
-				if err != nil {
-					return departureRt, err
-				}
-				departureRt.Update.Time = departureTime
-			}
 			found = true
 			break
 		}
@@ -368,12 +327,20 @@ 	}
 	return departureRt, finalErr
 }
 
+func GetTimeWithDelay(departure DepartureRealtime) time.Time {
+	var delay int
+	if departure.Update.Delay != nil {
+		delay = int(*departure.Update.Delay)
+	}
+	return departure.Time.Add(time.Duration(delay) * time.Nanosecond)
+}
+
 func filterDepartures(input ...interface{}) interface{} {
 	result := input[0].(_Result)
 	departures := []DepartureRealtime{}
 	for _, departure := range result.Departures {
 		if result.DeparturesType == DEPARTURES_FULL ||
-			departure.Update.Time.After(result.MinuteB4Datetime) {
+			GetTimeWithDelay(departure).After(result.MinuteB4Datetime) {
 			departures = append(departures, departure)
 		}
 	}
@@ -397,10 +364,8 @@ }
 
 func sortDepartures(input ...interface{}) interface{} {
 	result := input[0].(_Result)
-
 	sort.Slice(result.Departures, func(i, j int) bool {
-		return result.Departures[i].Update.Time.Before(
-			result.Departures[j].Update.Time)
+		return GetTimeWithDelay(result.Departures[i]).Before(GetTimeWithDelay(result.Departures[j]))
 	})
 
 	return result
@@ -720,24 +685,24 @@ 	}
 	doneChan <- true
 }
 
-func GetDepartures(stopCode string, lineName, dataHome, feedName string,
-	versionCode Validity, traffic *Traffic, date time.Time,
+func GetDepartures(stopCode, lineName string, ctx Context, traffic *Traffic, date time.Time,
 	departuresType DeparturesType) ([]DepartureRealtime, error) {
 
-	codeIndex := traffic.CodeIndexes[feedName][versionCode]
-	calendar := traffic.Calendars[feedName][versionCode]
-	vehicles := traffic.Vehicles[feedName][versionCode]
+	codeIndex := traffic.CodeIndexes[ctx.FeedID][ctx.Version]
+	calendar := traffic.Calendars[ctx.FeedID][ctx.Version]
+	vehicles := traffic.Vehicles[ctx.FeedID][ctx.Version]
 
 	result := _Result{
 		Offset:         codeIndex[stopCode],
 		Filename:       "stops.bare",
 		Date:           date,
 		LineName:       lineName,
-		TimetableHome:  filepath.Join(dataHome, feedName, string(versionCode)),
+		TimetableHome:  filepath.Join(ctx.DataHome, ctx.FeedID, string(ctx.Version)),
 		Calendar:       calendar,
 		DeparturesType: departuresType,
 		Vehicles:       vehicles,
-		Feed:           traffic.Feeds[feedName],
+		Feed:           traffic.Feeds[ctx.FeedID],
+		Ctx:            ctx,
 	}
 
 	r, e := gott.NewResult(result).
@@ -751,7 +716,7 @@ 		Bind(seek).
 		Bind(unmarshalStop).
 		Bind(openTripsFile).
 		Bind(readTrips).
-		Bind(getUpdates).
+		Bind(getDepartures).
 		Map(filterDepartures).
 		Map(filterDeparturesByLine).
 		Map(sortDepartures).
@@ -830,7 +795,7 @@ func getStopByOffset(offset uint, context Context, traffic *Traffic) (Stop, error) { // todo offset should be uint64 everywhere
 	result := _Result{
 		Filename:      "stops.bare",
 		Offset:        offset,
-		TimetableHome: filepath.Join(context.DataHome, context.FeedName, string(context.Version)),
+		TimetableHome: filepath.Join(context.DataHome, context.FeedID, string(context.Version)),
 	}
 	r, e := gott.NewResult(result).
 		Bind(openFile).
@@ -864,7 +829,7 @@ 	}
 }
 
 func getFeedInfo(dataHome string, feedName string,
-	versionCode Validity, traffic *Traffic) (FeedInfo, error) {
+	versionCode Validity) (FeedInfo, error) {
 	result := _Result{
 		Filename:      "feed_info.bare",
 		TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)),
@@ -882,7 +847,7 @@ }
 
 func GetTripsByOffset(offsets []uint, context Context, t *Traffic, filter func(Trip) bool) ([]Trip, error) {
 	trips := []Trip{}
-	file, err := os.Open(filepath.Join(context.DataHome, context.FeedName, string(context.Version), "trips.bare"))
+	file, err := os.Open(filepath.Join(context.DataHome, context.FeedID, string(context.Version), "trips.bare"))
 	if err != nil {
 		return trips, fmt.Errorf("while opening file: %w", err)
 	}
@@ -914,7 +879,7 @@ func GetTripByOffset(offset uint, context Context, t *Traffic) (Trip, error) {
 	result := _Result{
 		Filename:      "trips.bare",
 		Offset:        offset,
-		TimetableHome: filepath.Join(context.DataHome, context.FeedName, string(context.Version)),
+		TimetableHome: filepath.Join(context.DataHome, context.FeedID, string(context.Version)),
 	}
 	r, e := gott.NewResult(result).
 		Bind(openFile).
@@ -929,7 +894,7 @@ 	}
 }
 
 func GetStop(stopCode string, context Context, traffic *Traffic) (Stop, error) {
-	codeIndex := traffic.CodeIndexes[context.FeedName][context.Version]
+	codeIndex := traffic.CodeIndexes[context.FeedID][context.Version]
 	return getStopByOffset(codeIndex[stopCode], context, traffic)
 }
 
@@ -975,21 +940,21 @@ 	return stopStub, nil
 }
 
 func GetLine(name string, context Context, traffic *Traffic) (Line, error) {
-	index := traffic.LineIndexes[context.FeedName][context.Version]
+	index := traffic.LineIndexes[context.FeedID][context.Version]
 	for _, o := range index {
-		cleanedName, err := CleanQuery(name, traffic.Feeds[context.FeedName])
+		cleanedName, err := CleanQuery(name, traffic.Feeds[context.FeedID])
 		if err != nil {
 			return Line{}, err
 		}
 		if o.Name == cleanedName {
-			return getLineByOffset(o.Offsets[0], context.DataHome, context.FeedName, context.Version, traffic)
+			return getLineByOffset(o.Offsets[0], context.DataHome, context.FeedID, context.Version, traffic)
 		}
 	}
 	return Line{}, nil
 }
 
 func GetTrip(id string, context Context, traffic *Traffic) (Trip, error) {
-	tripIndex := traffic.TripIndexes[context.FeedName][context.Version]
+	tripIndex := traffic.TripIndexes[context.FeedID][context.Version]
 	for _, o := range tripIndex {
 		if o.Name == id {
 			return GetTripByOffset(o.Offsets[0], context, traffic)
@@ -1022,7 +987,7 @@ }
 
 func QueryStops(query string, context Context, traffic *Traffic) ([]Stop, error) {
 	stops := []Stop{}
-	nameIndex := traffic.NameIndexes[context.FeedName][context.Version]
+	nameIndex := traffic.NameIndexes[context.FeedID][context.Version]
 	results := fuzzy.FindFrom(query, nameIndex)
 	for _, result := range results {
 		for _, offset := range nameIndex[result.Index].Offsets {
@@ -1038,8 +1003,8 @@ }
 
 func GetStopsNear(location Position, context Context, traffic *Traffic) ([]Stop, error) {
 	stops := []Stop{}
-	positionIndex := traffic.PositionIndexes[context.FeedName][context.Version]
-	codeIndex := traffic.CodeIndexes[context.FeedName][context.Version]
+	positionIndex := traffic.PositionIndexes[context.FeedID][context.Version]
+	codeIndex := traffic.CodeIndexes[context.FeedID][context.Version]
 	spatials := positionIndex.NearestNeighbors(12, rtreego.Point{location.Lat, location.Lon})
 	for _, spatial := range spatials {
 		stop, err := getStopByOffset(codeIndex[spatial.(Stop).Code], context, traffic)
@@ -1051,101 +1016,104 @@ 	}
 	return stops, nil
 }
 
-func GetAlerts(stopCode string, preferredLanguages []language.Tag, context Context, traffic *Traffic) ([]Alert, error) {
-	feed := traffic.Feeds[context.FeedName]
+func GetGtfsAlerts(stopCode string, preferredLanguages []language.Tag, context Context, traffic *Traffic) ([]Alert, error) {
+	return []Alert{}, nil
+	/*
+		feed := traffic.Feeds[context.FeedID]
 
-	alertPositions := map[uint]struct{}{}
+		alertPositions := map[uint]struct{}{}
 
-	gtfsAlerts, lastUpdated, err := gtfs_rt.GetAlerts(lastUpdatedGtfsRt[feed.String()], feed.RealtimeFeeds(), feed.String())
-	if err != nil {
-		if isTimeout(err) {
-			return []Alert{}, nil
-		}
-		return []Alert{}, fmt.Errorf("while geting alerts: %w", err)
-	}
-	lastUpdatedGtfsRt[feed.String()] = lastUpdated
-
-	stop, err := GetStop(stopCode, context, traffic)
-	for _, pos := range gtfsAlerts.ByStop[stop.Id] {
-		alertPositions[pos] = struct{}{}
-	}
-	for _, option := range stop.ChangeOptions {
-		line, err := GetLine(option.LineName, context, traffic)
+		gtfsAlerts, lastUpdated, err := gtfs_rt.GetAlerts(lastUpdatedGtfsRt[feed.String()], feed.RealtimeFeeds(), feed.String())
 		if err != nil {
-			return []Alert{}, fmt.Errorf("while getting line %s: %w", option.LineName, err)
-		}
-		for _, pos := range gtfsAlerts.ByRoute[line.Id] {
-			alertPositions[pos] = struct{}{}
+			if isTimeout(err) {
+				return []Alert{}, nil
+			}
+			return []Alert{}, fmt.Errorf("while geting alerts: %w", err)
 		}
-		for _, pos := range gtfsAlerts.ByType[line.Kind.Value()] {
+		lastUpdatedGtfsRt[feed.String()] = lastUpdated
+
+		stop, err := GetStop(stopCode, context, traffic)
+		for _, pos := range gtfsAlerts.ByStop[stop.Id] {
 			alertPositions[pos] = struct{}{}
 		}
-	}
-	for _, order := range stop.Order {
-		trip, err := GetTripByOffset(order.TripOffset, context, traffic)
-		if err != nil {
-			return []Alert{}, fmt.Errorf("while getting trip at %d: %w", order.TripOffset, err)
+		for _, option := range stop.ChangeOptions {
+			line, err := GetLine(option.LineName, context, traffic)
+			if err != nil {
+				return []Alert{}, fmt.Errorf("while getting line %s: %w", option.LineName, err)
+			}
+			for _, pos := range gtfsAlerts.ByRoute[line.Id] {
+				alertPositions[pos] = struct{}{}
+			}
+			for _, pos := range gtfsAlerts.ByType[line.Kind.Value()] {
+				alertPositions[pos] = struct{}{}
+			}
 		}
-		for _, pos := range gtfsAlerts.ByTrip[trip.Id] {
-			alertPositions[pos] = struct{}{}
+		for _, order := range stop.Order {
+			trip, err := GetTripByOffset(order.TripOffset, context, traffic)
+			if err != nil {
+				return []Alert{}, fmt.Errorf("while getting trip at %d: %w", order.TripOffset, err)
+			}
+			for _, pos := range gtfsAlerts.ByTrip[trip.Id] {
+				alertPositions[pos] = struct{}{}
+			}
 		}
-	}
 
-	alerts := make([]Alert, len(alertPositions))
-	i := 0
-	for pos := range alertPositions {
-		gtfsAlert := gtfsAlerts.Alerts[pos]
+		alerts := make([]Alert, len(alertPositions))
+		i := 0
+		for pos := range alertPositions {
+			gtfsAlert := gtfsAlerts.Alerts[pos]
 
-		includeAlert := false
-		for _, timeRange := range gtfsAlert.TimeRanges {
-			start := time.Unix(int64(timeRange.GetStart()), 0)
-			var end time.Time
-			if timeRange.GetEnd() == 0 {
-				end = time.Date(9999, 12, 31, 23, 59, 59, 0, time.Local)
-			} else {
-				end = time.Unix(int64(timeRange.GetEnd()), 0)
+			includeAlert := false
+			for _, timeRange := range gtfsAlert.TimeRanges {
+				start := time.Unix(int64(timeRange.GetStart()), 0)
+				var end time.Time
+				if timeRange.GetEnd() == 0 {
+					end = time.Date(9999, 12, 31, 23, 59, 59, 0, time.Local)
+				} else {
+					end = time.Unix(int64(timeRange.GetEnd()), 0)
+				}
+				now := time.Now()
+				if !now.Before(start) && !now.After(end) {
+					includeAlert = true
+				}
 			}
-			now := time.Now()
-			if !now.Before(start) && !now.After(end) {
-				includeAlert = true
+			if !includeAlert {
+				continue
+			}
+
+			alerts[i] = Alert{
+				Cause:  int32(gtfsAlert.Cause),  // todo(BAF10)
+				Effect: int32(gtfsAlert.Effect), // todo(BAF10)
+			}
+			alertHeaderLangs := []language.Tag{}
+			for lang := range gtfsAlert.Headers {
+				alertHeaderLangs = append(alertHeaderLangs, lang)
 			}
-		}
-		if !includeAlert {
-			continue
-		}
+			m := language.NewMatcher(alertHeaderLangs)
+			tag, _, _ := m.Match(preferredLanguages...)
+			alerts[i].Header = gtfsAlert.Headers[tag]
 
-		alerts[i] = Alert{
-			Cause:  int32(gtfsAlert.Cause),  // todo(BAF10)
-			Effect: int32(gtfsAlert.Effect), // todo(BAF10)
-		}
-		alertHeaderLangs := []language.Tag{}
-		for lang := range gtfsAlert.Headers {
-			alertHeaderLangs = append(alertHeaderLangs, lang)
-		}
-		m := language.NewMatcher(alertHeaderLangs)
-		tag, _, _ := m.Match(preferredLanguages...)
-		alerts[i].Header = gtfsAlert.Headers[tag]
+			alertDescriptionLangs := []language.Tag{}
+			for lang := range gtfsAlert.Descriptions {
+				alertDescriptionLangs = append(alertDescriptionLangs, lang)
+			}
+			m = language.NewMatcher(alertDescriptionLangs)
+			tag, _, _ = m.Match(preferredLanguages...)
+			alerts[i].Description = gtfsAlert.Descriptions[tag]
 
-		alertDescriptionLangs := []language.Tag{}
-		for lang := range gtfsAlert.Descriptions {
-			alertDescriptionLangs = append(alertDescriptionLangs, lang)
-		}
-		m = language.NewMatcher(alertDescriptionLangs)
-		tag, _, _ = m.Match(preferredLanguages...)
-		alerts[i].Description = gtfsAlert.Descriptions[tag]
+			alertUrlLangs := []language.Tag{}
+			for lang := range gtfsAlert.URLs {
+				alertUrlLangs = append(alertUrlLangs, lang)
+			}
+			m = language.NewMatcher(alertUrlLangs)
+			tag, _, _ = m.Match(preferredLanguages...)
+			alerts[i].URL = gtfsAlert.URLs[tag]
 
-		alertUrlLangs := []language.Tag{}
-		for lang := range gtfsAlert.URLs {
-			alertUrlLangs = append(alertUrlLangs, lang)
+			i++
 		}
-		m = language.NewMatcher(alertUrlLangs)
-		tag, _, _ = m.Match(preferredLanguages...)
-		alerts[i].URL = gtfsAlert.URLs[tag]
 
-		i++
-	}
-
-	return alerts, nil
+		return alerts, nil
+	*/
 }
 
 func GetTimezone(stop Stop, feed Feed) (*time.Location, error) {
@@ -1155,8 +1123,8 @@ 	}
 	return feed.GetLocation(), nil
 }
 
-func GetLanguage(ctx Context, t *Traffic) (string, error) {
-	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedName, ctx.Version, t)
+func GetLanguage(ctx Context) (string, error) {
+	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
 	return feedInfo.Language, err
 }
 
@@ -1184,7 +1152,7 @@ 	return err
 }
 
 func convertVehicle(update gtfs_rt.Update, context Context, t *Traffic) (VehicleStatus, error) {
-	vehicles := t.Vehicles[context.FeedName][context.Version]
+	vehicles := t.Vehicles[context.FeedID][context.Version]
 	tripID := update.TripUpdate.GetTrip().GetTripId()
 	trip, err := GetTrip(tripID, context, t)
 	if err != nil {
@@ -1202,7 +1170,7 @@ }
 
 func GetVehicle(tripID string, context Context, t *Traffic) (VehicleStatus, error) {
 	vehicle := VehicleStatus{}
-	update, err := getRealtimeOffset(tripID, 0, t.Feeds[context.FeedName])
+	update, err := getRealtimeOffset(tripID, 0, t.Feeds[context.FeedID])
 	if err != nil {
 		return vehicle, fmt.Errorf("while getting realtime update: %w", err)
 	}
@@ -1216,8 +1184,8 @@ func GetStopsIn(lb, rt Position, context Context, traffic *Traffic) ([]Stop, error) {
 	// todo limit rect size
 	// todo does it take into account rect 179 -> -179 latitude?
 	stops := []Stop{}
-	positionIndex := traffic.PositionIndexes[context.FeedName][context.Version]
-	codeIndex := traffic.CodeIndexes[context.FeedName][context.Version]
+	positionIndex := traffic.PositionIndexes[context.FeedID][context.Version]
+	codeIndex := traffic.CodeIndexes[context.FeedID][context.Version]
 	rect, err := rtreego.NewRectFromPoints(rtreego.Point{lb.Lat, lb.Lon}, rtreego.Point{rt.Lat, rt.Lon})
 	if err != nil {
 		return stops, fmt.Errorf("while creating a rect: %w", err)
@@ -1236,7 +1204,7 @@
 func GetVehiclesIn(lb, rt Position, context Context, t *Traffic) ([]VehicleStatus, error) {
 	// todo limit rect size
 	vehicles := []VehicleStatus{}
-	updates, err := getRealtimeUpdates(t.Feeds[context.FeedName])
+	updates, err := getRealtimeUpdates(t.Feeds[context.FeedID])
 	if err != nil {
 		return vehicles, err
 	}




diff --git a/traffic/feed_info.go b/traffic/feed_info.go
index b457feeb854f6b22e6d8c810b91561b504886912..ee3703236093274287967fcdbbd8505c11fe07c1 100644
--- a/traffic/feed_info.go
+++ b/traffic/feed_info.go
@@ -53,7 +53,7 @@ 			log.Printf("while parsing date for %s: %v", id, err)
 			continue
 		}
 
-		feedInfo, err := getFeedInfo(cfg.FeedsPath, id, versionCode, t)
+		feedInfo, err := getFeedInfo(cfg.FeedsPath, id, versionCode)
 		if err != nil {
 			log.Printf("while getting feed info for %s: %v", id, err)
 			continue




diff --git a/traffic/gtfs_rt.go b/traffic/gtfs_rt.go
new file mode 100644
index 0000000000000000000000000000000000000000..107582df7a5d1ace6311c74ada2fa8510ef8401b
--- /dev/null
+++ b/traffic/gtfs_rt.go
@@ -0,0 +1,91 @@
+package traffic
+
+import (
+	"apiote.xyz/p/szczanieckiej/gtfs_rt"
+	pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
+
+	"fmt"
+	"time"
+)
+
+func makeTimetableRelationshipFromTripTimetable(r pb.TripDescriptor_ScheduleRelationship) TimetableRelationship {
+	switch r {
+	case pb.TripDescriptor_ADDED:
+		return TRIP_ADDED
+	case pb.TripDescriptor_CANCELED:
+		return TRIP_CANCELED
+	/* TODO update pb schema
+	case pb.TripDescriptor_DELETED:
+		return TRIP_DELETED*/
+	default:
+		return TRIP_SCHEDULED
+	}
+}
+
+func makeTimetableRelationshipFromStopTrip(r pb.TripUpdate_StopTimeUpdate_ScheduleRelationship) TimetableRelationship {
+	switch r {
+	case pb.TripUpdate_StopTimeUpdate_NO_DATA:
+		return NO_DATA
+	case pb.TripUpdate_StopTimeUpdate_SKIPPED:
+		return STOP_SKIPPED
+	default:
+		return TRIP_SCHEDULED
+	}
+}
+
+func getGtfsRtData(entities []*pb.FeedEntity) error {
+	updates = map[string]Update{}
+	for _, entity := range entities {
+		t := entity.TripUpdate
+		// TODO v := entity.Vehicle
+		// TODO a := entity.Alert
+		if t != nil {
+			u := updates[*t.Trip.TripId]
+			u.StopSequence = t.StopTimeUpdate[0].GetStopSequence()
+			u.StopID = t.StopTimeUpdate[0].GetStopId()
+			if arrival := t.StopTimeUpdate[0].GetArrival(); arrival != nil {
+				u.Delay = arrival.Delay
+			}
+			if u.StopSequence == 0 && *u.Delay < 0 {
+				*u.Delay = 0
+			}
+
+			stopTripRelationship := t.StopTimeUpdate[0].GetScheduleRelationship()
+			if stopTripRelationship == pb.TripUpdate_StopTimeUpdate_SCHEDULED {
+				tripTimetableRelationship := t.Trip.GetScheduleRelationship()
+				u.TimetableRelationship = makeTimetableRelationshipFromTripTimetable(tripTimetableRelationship)
+			} else {
+				u.TimetableRelationship = makeTimetableRelationshipFromStopTrip(stopTripRelationship)
+			}
+
+			updates[*t.Trip.TripId] = u
+		}
+	}
+
+	return nil
+}
+
+func getGtfsRealtimeUpdates(tripID string, sequence int, ctx Context) (Update, error) {
+	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
+	if err != nil {
+		return Update{}, fmt.Errorf("while getting feedInfo: %w\n", err)
+	}
+
+	now := uint64(time.Now().Unix())
+	lastUpdated := lastUpdatedGtfsRt2[ctx.FeedID]
+	if passed := now - lastUpdated["updates"]; passed > 30 {
+		message, err := gtfs_rt.GetMessages2(ctx.FeedID, feedInfo.RealtimeFeeds["updates"])
+		if err != nil {
+			return Update{}, fmt.Errorf("while getting messages: %w\n", err)
+		}
+		lastUpdatedGtfsRt2[ctx.FeedID]["updates"] = now
+
+		err = getGtfsRtData(message.Entity)
+		if err != nil {
+			return Update{}, fmt.Errorf("while converting to Update: %v\n", err)
+		}
+	}
+
+	update := updates[tripID]
+	return update, nil
+}




diff --git a/traffic/realtime.go b/traffic/realtime.go
new file mode 100644
index 0000000000000000000000000000000000000000..5cfe2dd50682df9e763c0b092618d320f669637b
--- /dev/null
+++ b/traffic/realtime.go
@@ -0,0 +1,153 @@
+package traffic
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"time"
+
+	"git.sr.ht/~sircmpwn/go-bare"
+	"golang.org/x/text/language"
+)
+
+type TimetableRelationship uint
+
+const (
+	TRIP_SCHEDULED TimetableRelationship = iota
+	TRIP_CANCELED
+	TRIP_DELETED
+	TRIP_ADDED
+	STOP_SKIPPED
+	NO_DATA
+)
+
+type Update struct {
+	StopSequence          uint32
+	StopID                string
+	Delay                 *int32                // seconds, if nil -> unknown
+	TimetableRelationship TimetableRelationship // TODO better name
+	Status                DepartureStatus
+	CongestionLevel       CongestionLevel
+	OccupancyStatus       OccupancyStatus
+	VehicleID             string
+	Latitude              float64
+	Longitude             float64
+	Speed                 float32
+	// TODO …
+}
+
+type CongestionLevel uint
+
+const (
+	CONGESTION_UNKNOWN CongestionLevel = iota
+	CONGESTION_SMOOTH
+	CONGESTION_STOP_AND_GO
+	CONGESTION_SIGNIFICANT
+	CONGESTION_SEVERE
+)
+
+type OccupancyStatus uint
+
+const (
+	OCCUPANCY_UNKNOWN OccupancyStatus = iota
+	OCCUPANCY_EMPTY
+	OCCUPANCY_MANY_AVAILABLE
+	OCCUPANCY_FEW_AVAILABLE
+	OCCUPANCY_STANDING_ONLY
+	OCCUPANCY_CRUSHED
+	OCCUPANCY_FULL
+	OCCUPANCY_NOT_ACCEPTING
+)
+
+type DepartureStatus uint
+
+const (
+	AT_STOP DepartureStatus = iota
+	DEPARTED
+	INCOMING
+)
+
+type Alerts struct {
+	ByRoute  map[string][]uint
+	ByTrip   map[string][]uint
+	ByType   map[uint][]uint
+	ByStop   map[string][]uint
+	ByAgency map[string][]uint
+	Alerts   []Alert
+}
+
+type Alert struct {
+	TimeRanges   [][2]time.Time
+	Headers      map[language.Tag]string
+	Descriptions map[language.Tag]string
+	URLs         map[language.Tag]string
+	Cause        int32 // todo(BAF10)
+	Effect       int32 // todo(BAF10)
+}
+
+var updates map[string]Update
+var alerts Alerts
+
+// TODO vehicles cache
+
+func getTripID(tripsFile *os.File, offset int64) (string, error) {
+	_, err := tripsFile.Seek(offset, 0)
+	if err != nil {
+		return "", fmt.Errorf("while seeking: %w", err)
+	}
+	trip := Trip{}
+	err = bare.UnmarshalReader(tripsFile, &trip)
+	if err != nil {
+		return "", fmt.Errorf("while unmarshalling: %w", err)
+	}
+	return trip.Id, nil
+}
+
+func enrichDepartures(departures []DepartureRealtime, datetime time.Time, departuresType DeparturesType, ctx Context, tripsFile *os.File, timezone *time.Location) []DepartureRealtime {
+	enrichedDepartures := make([]DepartureRealtime, len(departures))
+
+	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
+	if err != nil {
+		log.Printf("while getting feedInfo: %v\n", err)
+		return departures
+	}
+
+	var enrichMethod func(string, int, Context) (Update, error)
+	if _, ok := feedInfo.RealtimeFeeds["updates"]; ok {
+		enrichMethod = getGtfsRealtimeUpdates
+	} else {
+		// TODO enrichMethod = getLuaRealtieUpdates
+	}
+	midnight := time.Date(datetime.Year(), datetime.Month(),
+		datetime.Day(), 0, 0, 0, 0, timezone)
+	if departuresType == DEPARTURES_HYBRID {
+		for i, departure := range departures {
+			if departure.Time.After(midnight) {
+				tripID, err := getTripID(tripsFile, int64(departure.Order.TripOffset))
+				if err != nil {
+					log.Printf("while getting trip id for %s -> %s (%v): %v\n", departure.LineName, departure.Headsign, departure.Time, err)
+					continue
+				}
+				update, err := enrichMethod(tripID, departure.Order.Sequence, ctx)
+				if err != nil {
+					if isTimeout(err) {
+						break
+					} else {
+						log.Printf("while enriching departure %s -> %s (%v): %v\n", departure.LineName, departure.Headsign, departure.Time, err)
+					}
+				} else {
+					enrichedDepartures[i] = departure.WithUpdate(update)
+				}
+			}
+		}
+	}
+	return departures
+}
+
+func GetAlerts(languages []language.Tag /*, feed*/) []Alert {
+	return []Alert{}
+}
+
+func GetVehiclePositions(position Position) []VehicleStatus {
+	return []VehicleStatus{}
+}




diff --git a/traffic/structs.go b/traffic/structs.go
index d24c3178859578d155d3195779e29f751a4677ec..f063919590af4ca7350d53fc0eeb11e99a82fc4c 100644
--- a/traffic/structs.go
+++ b/traffic/structs.go
@@ -1,8 +1,6 @@
 package traffic
 
 import (
-	"apiote.xyz/p/szczanieckiej/gtfs_rt"
-
 	//"image/color"
 	"strings"
 	"time"
@@ -75,11 +73,17 @@ 	StopOffset uint64
 }*/
 
 type DepartureRealtime struct {
+	Time      time.Time
 	Departure Departure
 	Headsign  string
 	LineName  string
 	Order     StopOrder
-	Update    gtfs_rt.Update
+	Update    Update
+}
+
+func (d DepartureRealtime) WithUpdate(update Update) DepartureRealtime {
+	d.Update = update
+	return d
 }
 
 /*type Trip struct {
@@ -210,14 +214,6 @@ 	Zone     string
 	OnDemand bool
 }
 
-type Alert struct {
-	Header      string
-	Description string
-	URL         string
-	Cause       int32 // todo(BAF10)
-	Effect      int32 // todo(BAF10)
-}
-
 // type ID string
 type Validity string // 20060102_20060102
 func (v Validity) Start() string {
@@ -288,6 +284,6 @@ }
 
 type Context struct {
 	DataHome string
-	FeedName string
+	FeedID   string
 	Version  Validity
 }




diff --git a/traffic/structs_gen.go b/traffic/structs_gen.go
index 24ffd86ac14bc285c20de14dd93c79a732ecaba6..8a4db233185b074d8a53b47fdeb7a8297547a9ba 100644
--- a/traffic/structs_gen.go
+++ b/traffic/structs_gen.go
@@ -178,16 +178,17 @@ 	return bare.Marshal(t)
 }
 
 type FeedInfo struct {
-	Name         string            `bare:"name"`
-	Website      string            `bare:"website"`
-	Language     string            `bare:"language"`
-	ValidSince   string            `bare:"validSince"`
-	ValidTill    string            `bare:"validTill"`
-	QrHost       string            `bare:"qrHost"`
-	QrLocation   QRLocation        `bare:"qrLocation"`
-	QrSelector   string            `bare:"qrSelector"`
-	Attributions map[string]string `bare:"attributions"`
-	Descriptions map[string]string `bare:"descriptions"`
+	Name          string            `bare:"name"`
+	Website       string            `bare:"website"`
+	Language      string            `bare:"language"`
+	ValidSince    string            `bare:"validSince"`
+	ValidTill     string            `bare:"validTill"`
+	QrHost        string            `bare:"qrHost"`
+	QrLocation    QRLocation        `bare:"qrLocation"`
+	QrSelector    string            `bare:"qrSelector"`
+	Attributions  map[string]string `bare:"attributions"`
+	Descriptions  map[string]string `bare:"descriptions"`
+	RealtimeFeeds map[string]string `bare:"realtimeFeeds"`
 }
 
 func (t *FeedInfo) Decode(data []byte) error {