ShiftScheduler/Services/TransportApiService.cs
Copilot 73abfac988
Implement caching for transport connection results with IMemoryCache (#26)
* Initial plan

* Implement caching for transport connection results with IMemoryCache

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Change cache duration from minutes to days

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Implement CachedTransportService design with interface separation

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Implement persistent file-based cache to survive application restarts

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Revert persistent file-based cache implementation, restore IMemoryCache usage

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Code cleanup

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>
Co-authored-by: Claudio Schaad <c.schaad@pog.ch>
2025-08-25 21:15:13 +02:00

133 lines
No EOL
5.2 KiB
C#

using System.Text.Json;
using ShiftScheduler.Shared;
namespace ShiftScheduler.Services
{
public class TransportApiService(HttpClient httpClient, TransportConfiguration config) : ITransportApiService
{
public async Task<TransportConnection?> GetConnectionAsync(DateTime shiftStartTime)
{
// To allow connections that arrive after shift starts, we search from earlier time
// and request more connections to cover the full range
var searchDate = shiftStartTime.ToString("yyyy-MM-dd");
var searchTime = shiftStartTime.AddMinutes(config.MaxLateArrivalMinutes).ToString("HH:mm");
var url = $"{config.ApiBaseUrl}/connections?from={Uri.EscapeDataString(config.StartStation)}&to={Uri.EscapeDataString(config.EndStation)}&date={searchDate}&time={searchTime}&isArrivalTime=1&limit=5";
var response = await httpClient.GetStringAsync(url);
var apiResponse = JsonSerializer.Deserialize<TransportApiResponse>(response, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (apiResponse?.Connections.Count > 0)
{
var allConnections = apiResponse.Connections.Select(MapToTransportConnection).ToList();
return TransportConnectionCalculator.FindBestConnection(
allConnections,
shiftStartTime,
config.SafetyBufferMinutes,
config.MaxEarlyArrivalMinutes,
config.MaxLateArrivalMinutes);
}
return null;
}
private TransportConnection MapToTransportConnection(TransportApiConnection? apiConnection)
{
if (apiConnection == null)
return new TransportConnection();
return new TransportConnection
{
DepartureTime = apiConnection.From?.Departure ?? string.Empty,
ArrivalTime = apiConnection.To?.Arrival ?? string.Empty,
Duration = apiConnection.Duration,
Platform = apiConnection.From?.Platform ?? string.Empty,
Sections = apiConnection.Sections.Select(s => new TransportSection
{
Journey = s.Journey != null ? new TransportJourney
{
Name = s.Journey.Name,
Category = s.Journey.Category,
Number = s.Journey.Number
} : null,
Departure = s.Departure != null ? new TransportCheckpoint
{
Station = s.Departure.Station != null ? new TransportStation
{
Name = s.Departure.Station.Name,
Id = s.Departure.Station.Id
} : null,
Departure = s.Departure.Departure,
Arrival = s.Departure.Arrival,
Platform = s.Departure.Platform
} : null,
Arrival = s.Arrival != null ? new TransportCheckpoint
{
Station = s.Arrival.Station != null ? new TransportStation
{
Name = s.Arrival.Station.Name,
Id = s.Arrival.Station.Id
} : null,
Departure = s.Arrival.Departure,
Arrival = s.Arrival.Arrival,
Platform = s.Arrival.Platform
} : null
}).ToList()
};
}
}
// API Response models for OpenData CH Transport
public class TransportApiResponse
{
public List<TransportApiConnection> Connections { get; set; } = new();
}
public class TransportApiConnection
{
public TransportApiCheckpoint? From { get; set; }
public TransportApiCheckpoint? To { get; set; }
public string Duration { get; set; } = string.Empty;
public List<TransportApiSection> Sections { get; set; } = new();
public override string ToString()
{
return $"{From} - {To}";
}
}
public class TransportApiCheckpoint
{
public TransportApiStation? Station { get; set; }
public string Departure { get; set; } = string.Empty;
public string Arrival { get; set; } = string.Empty;
public string Platform { get; set; } = string.Empty;
public override string ToString()
{
return $"{Station?.Name}: {Departure} - {Arrival}";
}
}
public class TransportApiStation
{
public string Name { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
}
public class TransportApiSection
{
public TransportApiJourney? Journey { get; set; }
public TransportApiCheckpoint? Departure { get; set; }
public TransportApiCheckpoint? Arrival { get; set; }
}
public class TransportApiJourney
{
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string Number { get; set; } = string.Empty;
}
}