aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/FareService.cs22
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/OtpService.cs3
-rw-r--r--src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs3
-rw-r--r--src/Costasdev.Busurbano.Sources.OpenTripPlannerGql/Queries/PlanConnectionContent.cs214
-rw-r--r--src/frontend/app/api/schema.ts3
-rw-r--r--src/frontend/app/routes/planner.tsx4
7 files changed, 139 insertions, 112 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs b/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
index 823cfa5..7d47383 100644
--- a/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
+++ b/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
@@ -78,8 +78,6 @@ public partial class RoutePlannerController : ControllerBase
var response = await _httpClient.SendAsync(request);
var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<PlanConnectionResponse>>();
- Console.WriteLine(responseBody);
-
if (responseBody is not { IsSuccess: true } || responseBody.Data?.PlanConnection.Edges.Length == 0)
{
LogErrorFetchingRoutes(response.StatusCode, await response.Content.ReadAsStringAsync());
diff --git a/src/Costasdev.Busurbano.Backend/Services/FareService.cs b/src/Costasdev.Busurbano.Backend/Services/FareService.cs
index 0e4fefc..9a11a56 100644
--- a/src/Costasdev.Busurbano.Backend/Services/FareService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/FareService.cs
@@ -20,23 +20,39 @@ public class FareService
var busLegs = legs.Where(l => l.Mode != null && l.Mode.ToUpper() != "WALK").ToList();
// Cash fare logic
- // TODO: In the future, this should depend on the operator/feed
- var cashFare = busLegs.Count * 1.63; // Defaulting to Vitrasa for now
+ double cashFare = 0;
+ foreach (var leg in busLegs)
+ {
+ // TODO: In the future, this should depend on the operator/feed
+ if (leg.FeedId == "vitrasa")
+ {
+ cashFare += 1.63;
+ }
+ else
+ {
+ cashFare += 1.63; // Default fallback
+ }
+ }
// Card fare logic (45-min transfer window)
int cardTicketsRequired = 0;
DateTime? lastTicketPurchased = null;
int tripsPaidWithTicket = 0;
+ string? lastFeedId = null;
foreach (var leg in busLegs)
{
+ // If no ticket purchased, ticket expired (no free transfers after 45 mins), or max trips with ticket reached
+ // Also check if we changed operator (assuming no free transfers between different operators for now)
if (lastTicketPurchased == null ||
(leg.StartTime - lastTicketPurchased.Value).TotalMinutes > 45 ||
- tripsPaidWithTicket >= 3)
+ tripsPaidWithTicket >= 3 ||
+ leg.FeedId != lastFeedId)
{
cardTicketsRequired++;
lastTicketPurchased = leg.StartTime;
tripsPaidWithTicket = 1;
+ lastFeedId = leg.FeedId;
}
else
{
diff --git a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
index 7eba590..8d47225 100644
--- a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
@@ -1,5 +1,4 @@
using System.Globalization;
-using System.Text;
using Costasdev.Busurbano.Backend.Configuration;
using Costasdev.Busurbano.Backend.Helpers;
using Costasdev.Busurbano.Backend.Types.Otp;
@@ -430,6 +429,8 @@ public class OtpService
return new Leg
{
Mode = leg.Mode,
+ FeedId = feedId,
+ RouteId = leg.Route?.GtfsId,
RouteName = leg.Route?.LongName,
RouteShortName = shortName,
RouteLongName = leg.Route?.LongName,
diff --git a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
index b2f4d6a..a4e54d7 100644
--- a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
+++ b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
@@ -23,6 +23,9 @@ public class Itinerary
public class Leg
{
public string? Mode { get; set; } // WALK, BUS, etc.
+ public string? FeedId { get; set; }
+ public string? RouteId { get; set; }
+ public string? TripId { get; set; }
public string? RouteName { get; set; }
public string? RouteShortName { get; set; }
public string? RouteLongName { get; set; }
diff --git a/src/Costasdev.Busurbano.Sources.OpenTripPlannerGql/Queries/PlanConnectionContent.cs b/src/Costasdev.Busurbano.Sources.OpenTripPlannerGql/Queries/PlanConnectionContent.cs
index a4bf8d1..f325336 100644
--- a/src/Costasdev.Busurbano.Sources.OpenTripPlannerGql/Queries/PlanConnectionContent.cs
+++ b/src/Costasdev.Busurbano.Sources.OpenTripPlannerGql/Queries/PlanConnectionContent.cs
@@ -7,30 +7,31 @@ namespace Costasdev.Busurbano.Sources.OpenTripPlannerGql.Queries;
public class PlanConnectionContent : IGraphRequest<PlanConnectionContent.Args>
{
- public record Args(
- double StartLatitude,
- double StartLongitude,
- double EndLatitude,
- double EndLongitude,
- DateTimeOffset ReferenceTime,
- bool ReferenceIsArrival = false
- );
+ public record Args(
+ double StartLatitude,
+ double StartLongitude,
+ double EndLatitude,
+ double EndLongitude,
+ DateTimeOffset ReferenceTime,
+ bool ReferenceIsArrival = false
+ );
- public static string Query(Args args)
- {
- var madridTz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid");
+ public static string Query(Args args)
+ {
+ var madridTz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid");
- // Treat incoming DateTime as Madrid local wall-clock time
- var localMadridTime =
- DateTime.SpecifyKind(args.ReferenceTime.UtcDateTime, DateTimeKind.Unspecified);
+ // Treat incoming DateTime as Madrid local wall-clock time
+ var localMadridTime =
+ DateTime.SpecifyKind(args.ReferenceTime.UtcDateTime, DateTimeKind.Unspecified);
- var offset = madridTz.GetUtcOffset(localMadridTime);
- var actualTimeOfQuery = new DateTimeOffset(localMadridTime, offset);
+ var offset = madridTz.GetUtcOffset(localMadridTime);
- var dateTimeParameter = args.ReferenceIsArrival ? $"latestArrival:\"{actualTimeOfQuery:O}\"" : $"earliestDeparture:\"{actualTimeOfQuery:O}\"";
+ var dateTimeToUse = new DateTimeOffset(args.ReferenceTime.DateTime + offset, offset);
- return string.Create(CultureInfo.InvariantCulture,
- $$"""
+ var dateTimeParameter = args.ReferenceIsArrival ? $"latestArrival:\"{dateTimeToUse:O}\"" : $"earliestDeparture:\"{dateTimeToUse:O}\"";
+
+ return string.Create(CultureInfo.InvariantCulture,
+ $$"""
query Query {
planConnection(
first: 4
@@ -71,6 +72,7 @@ public class PlanConnectionContent : IGraphRequest<PlanConnectionContent.Args>
}
mode
route {
+ gtfsId
shortName
longName
agency {
@@ -133,106 +135,108 @@ public class PlanConnectionContent : IGraphRequest<PlanConnectionContent.Args>
}
}
""");
- }
+ }
}
public class PlanConnectionResponse : AbstractGraphResponse
{
- public PlanConnectionItem PlanConnection { get; set; }
+ public PlanConnectionItem PlanConnection { get; set; }
- public class PlanConnectionItem
- {
- [JsonPropertyName("edges")]
- public Edge[] Edges { get; set; }
- }
+ public class PlanConnectionItem
+ {
+ [JsonPropertyName("edges")]
+ public Edge[] Edges { get; set; }
+ }
- public class Edge
- {
- [JsonPropertyName("node")]
- public Node Node { get; set; }
- }
+ public class Edge
+ {
+ [JsonPropertyName("node")]
+ public Node Node { get; set; }
+ }
- public class Node
- {
- [JsonPropertyName("duration")] public int DurationSeconds { get; set; }
- [JsonPropertyName("start")] public string Start8601 { get; set; }
- [JsonPropertyName("end")] public string End8601 { get; set; }
- [JsonPropertyName("walkTime")] public int WalkSeconds { get; set; }
- [JsonPropertyName("walkDistance")] public double WalkDistance { get; set; }
- [JsonPropertyName("waitingTime")] public int WaitingSeconds { get; set; }
- [JsonPropertyName("legs")] public Leg[] Legs { get; set; }
- }
+ public class Node
+ {
+ [JsonPropertyName("duration")] public int DurationSeconds { get; set; }
+ [JsonPropertyName("start")] public string Start8601 { get; set; }
+ [JsonPropertyName("end")] public string End8601 { get; set; }
+ [JsonPropertyName("walkTime")] public int WalkSeconds { get; set; }
+ [JsonPropertyName("walkDistance")] public double WalkDistance { get; set; }
+ [JsonPropertyName("waitingTime")] public int WaitingSeconds { get; set; }
+ [JsonPropertyName("legs")] public Leg[] Legs { get; set; }
+ }
- public class Leg
- {
- [JsonPropertyName("start")] public ScheduledTimeContainer Start { get; set; }
- [JsonPropertyName("end")] public ScheduledTimeContainer End { get; set; }
- [JsonPropertyName("mode")] public string Mode { get; set; } // TODO: Make enum, maybe
- [JsonPropertyName("route")] public TransitRoute? Route { get; set; }
- [JsonPropertyName("from")] public LegPosition From { get; set; }
- [JsonPropertyName("to")] public LegPosition To { get; set; }
- [JsonPropertyName("stopCalls")] public StopCall[] StopCalls { get; set; }
- [JsonPropertyName("legGeometry")] public LegGeometry LegGeometry { get; set; }
- [JsonPropertyName("steps")] public Step[] Steps { get; set; }
- [JsonPropertyName("headsign")] public string? Headsign { get; set; }
- [JsonPropertyName("distance")] public double Distance { get; set; }
- }
+ public class Leg
+ {
+ [JsonPropertyName("start")] public ScheduledTimeContainer Start { get; set; }
+ [JsonPropertyName("end")] public ScheduledTimeContainer End { get; set; }
+ [JsonPropertyName("mode")] public string Mode { get; set; } // TODO: Make enum, maybe
+ [JsonPropertyName("route")] public TransitRoute? Route { get; set; }
+ [JsonPropertyName("from")] public LegPosition From { get; set; }
+ [JsonPropertyName("to")] public LegPosition To { get; set; }
+ [JsonPropertyName("stopCalls")] public StopCall[] StopCalls { get; set; }
+ [JsonPropertyName("legGeometry")] public LegGeometry LegGeometry { get; set; }
+ [JsonPropertyName("steps")] public Step[] Steps { get; set; }
+ [JsonPropertyName("headsign")] public string? Headsign { get; set; }
+ [JsonPropertyName("distance")] public double Distance { get; set; }
+ }
- public class TransitRoute
- {
- [JsonPropertyName("shortName")] public string ShortName { get; set; }
- [JsonPropertyName("longName")] public string LongName { get; set; }
- [JsonPropertyName("agency")] public AgencyNameContainer Agency { get; set; }
- [JsonPropertyName("color")] public string Color { get; set; }
- [JsonPropertyName("textColor")] public string TextColor { get; set; }
- }
+ public class TransitRoute
+ {
+ [JsonPropertyName("gtfsId")] public string GtfsId { get; set; }
+ [JsonPropertyName("shortName")] public string ShortName { get; set; }
+ [JsonPropertyName("longName")] public string LongName { get; set; }
+ [JsonPropertyName("agency")] public AgencyNameContainer Agency { get; set; }
+ [JsonPropertyName("color")] public string Color { get; set; }
+ [JsonPropertyName("textColor")] public string TextColor { get; set; }
+ }
- public class LegPosition
- {
- [JsonPropertyName("name")] public string Name { get; set; }
- [JsonPropertyName("lat")] public double Latitude { get; set; }
- [JsonPropertyName("lon")] public double Longitude { get; set; }
- [JsonPropertyName("stop")] public StopLocation Stop { get; set; }
- }
+ public class LegPosition
+ {
+ [JsonPropertyName("name")] public string Name { get; set; }
+ [JsonPropertyName("lat")] public double Latitude { get; set; }
+ [JsonPropertyName("lon")] public double Longitude { get; set; }
+ [JsonPropertyName("stop")] public StopLocation Stop { get; set; }
+ }
- public class ScheduledTimeContainer
- {
- [JsonPropertyName("scheduledTime")]
- public string ScheduledTime8601 { get; set; }
- }
+ public class ScheduledTimeContainer
+ {
+ [JsonPropertyName("scheduledTime")]
+ public string ScheduledTime8601 { get; set; }
+ }
- public class AgencyNameContainer
- {
- [JsonPropertyName("name")] public string Name { get; set; }
- }
+ public class AgencyNameContainer
+ {
+ [JsonPropertyName("name")] public string Name { get; set; }
+ }
- public class StopCall
- {
- [JsonPropertyName("stopLocation")]
- public StopLocation StopLocation { get; set; }
- }
+ public class StopCall
+ {
+ [JsonPropertyName("stopLocation")]
+ public StopLocation StopLocation { get; set; }
+ }
- public class StopLocation
- {
- [JsonPropertyName("gtfsId")] public string GtfsId { get; set; }
- [JsonPropertyName("code")] public string Code { get; set; }
- [JsonPropertyName("name")] public string Name { get; set; }
- [JsonPropertyName("lat")] public double Latitude { get; set; }
- [JsonPropertyName("lon")] public double Longitude { get; set; }
- }
+ public class StopLocation
+ {
+ [JsonPropertyName("gtfsId")] public string GtfsId { get; set; }
+ [JsonPropertyName("code")] public string Code { get; set; }
+ [JsonPropertyName("name")] public string Name { get; set; }
+ [JsonPropertyName("lat")] public double Latitude { get; set; }
+ [JsonPropertyName("lon")] public double Longitude { get; set; }
+ [JsonPropertyName("zoneId")] public string? ZoneId { get; set; }
+ }
- public class Step
- {
- [JsonPropertyName("distance")] public double Distance { get; set; }
- [JsonPropertyName("relativeDirection")] public string RelativeDirection { get; set; }
- [JsonPropertyName("streetName")] public string StreetName { get; set; } // TODO: "sidewalk", "path" or actual street name
- [JsonPropertyName("absoluteDirection")] public string AbsoluteDirection { get; set; }
- [JsonPropertyName("lat")] public double Latitude { get; set; }
- [JsonPropertyName("lon")] public double Longitude { get; set; }
- }
+ public class Step
+ {
+ [JsonPropertyName("distance")] public double Distance { get; set; }
+ [JsonPropertyName("relativeDirection")] public string RelativeDirection { get; set; }
+ [JsonPropertyName("streetName")] public string StreetName { get; set; } // TODO: "sidewalk", "path" or actual street name
+ [JsonPropertyName("absoluteDirection")] public string AbsoluteDirection { get; set; }
+ [JsonPropertyName("lat")] public double Latitude { get; set; }
+ [JsonPropertyName("lon")] public double Longitude { get; set; }
+ }
- public class LegGeometry
- {
- [JsonPropertyName("points")] public string? Points { get; set; }
- }
+ public class LegGeometry
+ {
+ [JsonPropertyName("points")] public string? Points { get; set; }
+ }
}
diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts
index 05f3a87..bb2fbcc 100644
--- a/src/frontend/app/api/schema.ts
+++ b/src/frontend/app/api/schema.ts
@@ -134,6 +134,9 @@ export const PlannerStepSchema = z.object({
export const PlannerLegSchema = z.object({
mode: z.string().optional().nullable(),
+ feedId: z.string().optional().nullable(),
+ routeId: z.string().optional().nullable(),
+ tripId: z.string().optional().nullable(),
routeName: z.string().optional().nullable(),
routeShortName: z.string().optional().nullable(),
routeLongName: z.string().optional().nullable(),
diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx
index 44488c8..5968bc2 100644
--- a/src/frontend/app/routes/planner.tsx
+++ b/src/frontend/app/routes/planner.tsx
@@ -587,7 +587,9 @@ const ItineraryDetail = ({
minute: "2-digit",
timeZone: "Europe/Madrid",
})}{" "}
- -{" "}
+ </span>
+ <span>•</span>
+ <span>
{(
(new Date(leg.endTime).getTime() -
new Date(leg.startTime).getTime()) /