Add MCP server
This commit is contained in:
@@ -38,6 +38,16 @@ type SearchJourneysResult struct {
|
||||
KnowledgeBaseResults []any
|
||||
}
|
||||
|
||||
// SearchJourneyOptions contains per-request options for journey search
|
||||
type SearchJourneyOptions struct {
|
||||
DisableSolidarityTransport bool
|
||||
DisableOrganizedCarpool bool
|
||||
DisableCarpoolOperators bool
|
||||
DisableTransit bool
|
||||
DisableFleetVehicles bool
|
||||
DisableKnowledgeBase bool
|
||||
}
|
||||
|
||||
// SearchJourneys performs the business logic for journey search
|
||||
func (h *ApplicationHandler) SearchJourneys(
|
||||
ctx context.Context,
|
||||
@@ -46,6 +56,8 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
destinationGeo *geojson.Feature,
|
||||
passengerID string,
|
||||
solidarityTransportExcludeDriver string,
|
||||
solidarityExcludeGroupId string,
|
||||
options *SearchJourneyOptions,
|
||||
) (*SearchJourneysResult, error) {
|
||||
var (
|
||||
// Results
|
||||
@@ -64,6 +76,19 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
if departureGeo != nil && destinationGeo != nil && !departureDateTime.IsZero() {
|
||||
searched = true
|
||||
|
||||
// Default options if not provided
|
||||
if options == nil {
|
||||
options = &SearchJourneyOptions{}
|
||||
}
|
||||
|
||||
// Check solution type configurations (global config AND per-request options)
|
||||
solidarityTransportEnabled := h.config.GetBool("modules.journeys.solutions.solidarity_transport.enabled") && !options.DisableSolidarityTransport
|
||||
organizedCarpoolEnabled := h.config.GetBool("modules.journeys.solutions.organized_carpool.enabled") && !options.DisableOrganizedCarpool
|
||||
carpoolOperatorsEnabled := h.config.GetBool("modules.journeys.solutions.carpool_operators.enabled") && !options.DisableCarpoolOperators
|
||||
transitEnabled := h.config.GetBool("modules.journeys.solutions.transit.enabled") && !options.DisableTransit
|
||||
fleetVehiclesEnabled := h.config.GetBool("modules.journeys.solutions.fleet_vehicles.enabled") && !options.DisableFleetVehicles
|
||||
knowledgeBaseEnabled := h.config.GetBool("modules.journeys.solutions.knowledge_base.enabled") && !options.DisableKnowledgeBase
|
||||
|
||||
// SOLIDARITY TRANSPORT
|
||||
var err error
|
||||
drivers, err = h.services.GetAccountsInNamespacesMap([]string{"solidarity_drivers", "organized_carpool_drivers"})
|
||||
@@ -74,36 +99,58 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
protodep, _ := transformers.GeoJsonToProto(departureGeo)
|
||||
protodest, _ := transformers.GeoJsonToProto(destinationGeo)
|
||||
|
||||
log.Debug().Time("departure time", departureDateTime).Msg("calling driver journeys with ...")
|
||||
|
||||
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, &gen.GetDriverJourneysRequest{
|
||||
Departure: protodep,
|
||||
Arrival: protodest,
|
||||
DepartureDate: timestamppb.New(departureDateTime),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys")
|
||||
} else {
|
||||
solidarityTransportResults = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) {
|
||||
for _, dj := range res.DriverJourneys {
|
||||
if a, ok := drivers[dj.DriverId].Data["archived"]; ok {
|
||||
if archived, ok := a.(bool); ok {
|
||||
if archived {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if dj.DriverId == solidarityTransportExcludeDriver {
|
||||
continue
|
||||
}
|
||||
if !yield(dj) {
|
||||
return
|
||||
// Get driver IDs to exclude based on group_id (drivers who already have bookings in this group)
|
||||
excludedDriverIds := make(map[string]bool)
|
||||
if solidarityExcludeGroupId != "" {
|
||||
bookingsResp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(ctx, &gen.GetSolidarityTransportBookingsRequest{
|
||||
StartDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
EndDate: timestamppb.New(time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC)),
|
||||
})
|
||||
if err == nil {
|
||||
for _, booking := range bookingsResp.Bookings {
|
||||
if booking.GroupId == solidarityExcludeGroupId {
|
||||
excludedDriverIds[booking.DriverId] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if solidarityTransportEnabled {
|
||||
log.Debug().Time("departure time", departureDateTime).Msg("calling driver journeys with ...")
|
||||
|
||||
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, &gen.GetDriverJourneysRequest{
|
||||
Departure: protodep,
|
||||
Arrival: protodest,
|
||||
DepartureDate: timestamppb.New(departureDateTime),
|
||||
})
|
||||
sort.Slice(solidarityTransportResults, func(i, j int) bool {
|
||||
return solidarityTransportResults[i].DriverDistance < solidarityTransportResults[j].DriverDistance
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys")
|
||||
} else {
|
||||
solidarityTransportResults = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) {
|
||||
for _, dj := range res.DriverJourneys {
|
||||
if a, ok := drivers[dj.DriverId].Data["archived"]; ok {
|
||||
if archived, ok := a.(bool); ok {
|
||||
if archived {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if dj.DriverId == solidarityTransportExcludeDriver {
|
||||
continue
|
||||
}
|
||||
// Skip drivers who already have bookings in the same group
|
||||
if excludedDriverIds[dj.DriverId] {
|
||||
continue
|
||||
}
|
||||
if !yield(dj) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
sort.Slice(solidarityTransportResults, func(i, j int) bool {
|
||||
return solidarityTransportResults[i].DriverDistance < solidarityTransportResults[j].DriverDistance
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get departure and destination addresses from properties
|
||||
@@ -119,124 +166,134 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
}
|
||||
}
|
||||
|
||||
radius := float64(5)
|
||||
// ORGANIZED CARPOOL
|
||||
organizedCarpoolResultsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(ctx, &carpoolproto.DriverJourneysRequest{
|
||||
DepartureLat: departureGeo.Point().Lat(),
|
||||
DepartureLng: departureGeo.Point().Lon(),
|
||||
ArrivalLat: destinationGeo.Point().Lat(),
|
||||
ArrivalLng: destinationGeo.Point().Lon(),
|
||||
DepartureDate: timestamppb.New(departureDateTime),
|
||||
DepartureAddress: &departureAddress,
|
||||
ArrivalAddress: &destinationAddress,
|
||||
DepartureRadius: &radius,
|
||||
ArrivalRadius: &radius,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpools")
|
||||
} else {
|
||||
organizedCarpoolResults = organizedCarpoolResultsRes.DriverJourneys
|
||||
sort.Slice(organizedCarpoolResults, func(i, j int) bool {
|
||||
return *organizedCarpoolResults[i].Distance < *organizedCarpoolResults[j].Distance
|
||||
if organizedCarpoolEnabled {
|
||||
radius := float64(5)
|
||||
organizedCarpoolResultsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(ctx, &carpoolproto.DriverJourneysRequest{
|
||||
DepartureLat: departureGeo.Point().Lat(),
|
||||
DepartureLng: departureGeo.Point().Lon(),
|
||||
ArrivalLat: destinationGeo.Point().Lat(),
|
||||
ArrivalLng: destinationGeo.Point().Lon(),
|
||||
DepartureDate: timestamppb.New(departureDateTime),
|
||||
DepartureAddress: &departureAddress,
|
||||
ArrivalAddress: &destinationAddress,
|
||||
DepartureRadius: &radius,
|
||||
ArrivalRadius: &radius,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpools")
|
||||
} else {
|
||||
organizedCarpoolResults = organizedCarpoolResultsRes.DriverJourneys
|
||||
sort.Slice(organizedCarpoolResults, func(i, j int) bool {
|
||||
return *organizedCarpoolResults[i].Distance < *organizedCarpoolResults[j].Distance
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
// CARPOOL OPERATORS
|
||||
carpools := make(chan *geojson.FeatureCollection)
|
||||
go h.services.InteropCarpool.Search(carpools, *departureGeo, *destinationGeo, departureDateTime)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for c := range carpools {
|
||||
carpoolResults = append(carpoolResults, c)
|
||||
}
|
||||
}()
|
||||
if carpoolOperatorsEnabled {
|
||||
carpools := make(chan *geojson.FeatureCollection)
|
||||
go h.services.InteropCarpool.Search(carpools, *departureGeo, *destinationGeo, departureDateTime)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for c := range carpools {
|
||||
carpoolResults = append(carpoolResults, c)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// TRANSIT
|
||||
transitch := make(chan *transitous.Itinerary)
|
||||
go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
||||
defer close(transitch)
|
||||
response, err := h.services.TransitRouting.PlanWithResponse(ctx, &transitous.PlanParams{
|
||||
FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
|
||||
ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
|
||||
Time: datetime,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
|
||||
return
|
||||
}
|
||||
for _, i := range response.Itineraries {
|
||||
transitch <- &i
|
||||
}
|
||||
}(transitch, departureGeo, destinationGeo, &departureDateTime)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
paris, _ := time.LoadLocation("Europe/Paris")
|
||||
requestedDay := departureDateTime.In(paris).Truncate(24 * time.Hour)
|
||||
if transitEnabled {
|
||||
transitch := make(chan *transitous.Itinerary)
|
||||
go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
||||
defer close(transitch)
|
||||
response, err := h.services.TransitRouting.PlanWithResponse(ctx, &transitous.PlanParams{
|
||||
FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
|
||||
ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
|
||||
Time: datetime,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
|
||||
return
|
||||
}
|
||||
for _, i := range response.Itineraries {
|
||||
transitch <- &i
|
||||
}
|
||||
}(transitch, departureGeo, destinationGeo, &departureDateTime)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
paris, _ := time.LoadLocation("Europe/Paris")
|
||||
requestedDay := departureDateTime.In(paris).Truncate(24 * time.Hour)
|
||||
|
||||
for itinerary := range transitch {
|
||||
// Only include journeys that start on the requested day (in Paris timezone)
|
||||
if !itinerary.StartTime.IsZero() && !itinerary.EndTime.IsZero() {
|
||||
log.Info().
|
||||
Time("startTime", itinerary.StartTime).
|
||||
Time("endTime", itinerary.EndTime).
|
||||
Str("startTimezone", itinerary.StartTime.Location().String()).
|
||||
Str("endTimezone", itinerary.EndTime.Location().String()).
|
||||
Str("startTimeRFC3339", itinerary.StartTime.Format(time.RFC3339)).
|
||||
Str("endTimeRFC3339", itinerary.EndTime.Format(time.RFC3339)).
|
||||
Msg("Journey search - received transit itinerary from Transitous")
|
||||
|
||||
startInParis := itinerary.StartTime.In(paris)
|
||||
startDay := startInParis.Truncate(24 * time.Hour)
|
||||
|
||||
// Check if journey starts on the requested day
|
||||
if startDay.Equal(requestedDay) {
|
||||
transitResults = append(transitResults, itinerary)
|
||||
} else {
|
||||
for itinerary := range transitch {
|
||||
// Only include journeys that start on the requested day (in Paris timezone)
|
||||
if !itinerary.StartTime.IsZero() && !itinerary.EndTime.IsZero() {
|
||||
log.Info().
|
||||
Str("requestedDay", requestedDay.Format("2006-01-02")).
|
||||
Str("startDay", startDay.Format("2006-01-02")).
|
||||
Msg("Journey search - filtered out transit journey (not on requested day)")
|
||||
Time("startTime", itinerary.StartTime).
|
||||
Time("endTime", itinerary.EndTime).
|
||||
Str("startTimezone", itinerary.StartTime.Location().String()).
|
||||
Str("endTimezone", itinerary.EndTime.Location().String()).
|
||||
Str("startTimeRFC3339", itinerary.StartTime.Format(time.RFC3339)).
|
||||
Str("endTimeRFC3339", itinerary.EndTime.Format(time.RFC3339)).
|
||||
Msg("Journey search - received transit itinerary from Transitous")
|
||||
|
||||
startInParis := itinerary.StartTime.In(paris)
|
||||
startDay := startInParis.Truncate(24 * time.Hour)
|
||||
|
||||
// Check if journey starts on the requested day
|
||||
if startDay.Equal(requestedDay) {
|
||||
transitResults = append(transitResults, itinerary)
|
||||
} else {
|
||||
log.Info().
|
||||
Str("requestedDay", requestedDay.Format("2006-01-02")).
|
||||
Str("startDay", startDay.Format("2006-01-02")).
|
||||
Msg("Journey search - filtered out transit journey (not on requested day)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
// VEHICLES
|
||||
vehiclech := make(chan fleetsstorage.Vehicle)
|
||||
go h.vehicleRequest(vehiclech, departureDateTime.Add(-24*time.Hour), departureDateTime.Add(168*time.Hour))
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for vehicle := range vehiclech {
|
||||
vehicleResults = append(vehicleResults, vehicle)
|
||||
}
|
||||
slices.SortFunc(vehicleResults, sorting.VehiclesByDistanceFrom(*departureGeo))
|
||||
}()
|
||||
if fleetVehiclesEnabled {
|
||||
vehiclech := make(chan fleetsstorage.Vehicle)
|
||||
go h.vehicleRequest(vehiclech, departureDateTime.Add(-24*time.Hour), departureDateTime.Add(168*time.Hour))
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for vehicle := range vehiclech {
|
||||
vehicleResults = append(vehicleResults, vehicle)
|
||||
}
|
||||
slices.SortFunc(vehicleResults, sorting.VehiclesByDistanceFrom(*departureGeo))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// KNOWLEDGE BASE
|
||||
departureGeoSearch, _ := h.services.Geography.GeoSearch(departureGeo)
|
||||
kbData := h.config.Get("knowledge_base")
|
||||
if kb, ok := kbData.([]any); ok {
|
||||
for _, sol := range kb {
|
||||
if solution, ok := sol.(map[string]any); ok {
|
||||
if g, ok := solution["geography"]; ok {
|
||||
if geography, ok := g.([]any); ok {
|
||||
for _, gg := range geography {
|
||||
if geog, ok := gg.(map[string]any); ok {
|
||||
if layer, ok := geog["layer"].(string); ok {
|
||||
code := geog["code"]
|
||||
geo, err := h.services.Geography.Find(layer, fmt.Sprintf("%v", code))
|
||||
if err == nil {
|
||||
geog["geography"] = geo
|
||||
geog["name"] = geo.Properties.MustString("nom")
|
||||
}
|
||||
if strings.Compare(fmt.Sprintf("%v", code), departureGeoSearch[layer].Properties.MustString("code")) == 0 {
|
||||
knowledgeBaseResults = append(knowledgeBaseResults, solution)
|
||||
break
|
||||
if knowledgeBaseEnabled {
|
||||
departureGeoSearch, _ := h.services.Geography.GeoSearch(departureGeo)
|
||||
kbData := h.config.Get("knowledge_base")
|
||||
if kb, ok := kbData.([]any); ok {
|
||||
for _, sol := range kb {
|
||||
if solution, ok := sol.(map[string]any); ok {
|
||||
if g, ok := solution["geography"]; ok {
|
||||
if geography, ok := g.([]any); ok {
|
||||
for _, gg := range geography {
|
||||
if geog, ok := gg.(map[string]any); ok {
|
||||
if layer, ok := geog["layer"].(string); ok {
|
||||
code := geog["code"]
|
||||
geo, err := h.services.Geography.Find(layer, fmt.Sprintf("%v", code))
|
||||
if err == nil {
|
||||
geog["geography"] = geo
|
||||
geog["name"] = geo.Properties.MustString("nom")
|
||||
}
|
||||
if strings.Compare(fmt.Sprintf("%v", code), departureGeoSearch[layer].Properties.MustString("code")) == 0 {
|
||||
knowledgeBaseResults = append(knowledgeBaseResults, solution)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user