diff options
Diffstat (limited to 'src/Enmarcha.Backend')
8 files changed, 208 insertions, 4 deletions
diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs index 13fb430..a887c89 100644 --- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs +++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs @@ -130,7 +130,7 @@ public partial class ArrivalsController : ControllerBase arrivals.Add(arrival); } - await _pipeline.ExecuteAsync(new ArrivalsContext + var context = new ArrivalsContext { StopId = id, StopCode = stop.Code, @@ -138,7 +138,9 @@ public partial class ArrivalsController : ControllerBase Arrivals = arrivals, NowLocal = nowLocal, StopLocation = new Position { Latitude = stop.Lat, Longitude = stop.Lon } - }); + }; + + await _pipeline.ExecuteAsync(context); var feedId = id.Split(':')[0]; @@ -167,7 +169,8 @@ public partial class ArrivalsController : ControllerBase ContrastHelper.GetBestTextColour(r.Color ?? fallbackColor) : r.TextColor })], - Arrivals = [.. arrivals.Where(a => a.Estimate.Minutes >= timeThreshold)] + Arrivals = [.. arrivals.Where(a => a.Estimate.Minutes >= timeThreshold)], + Usage = context.Usage }); } diff --git a/src/Enmarcha.Backend/Data/vitrasa_stops_p95.csv b/src/Enmarcha.Backend/Data/vitrasa_stops_p95.csv new file mode 100644 index 0000000..8091fd9 --- /dev/null +++ b/src/Enmarcha.Backend/Data/vitrasa_stops_p95.csv @@ -0,0 +1,60 @@ +codigo +14264 +8630 +6940 +14206 +14333 +8750 +5610 +6930 +8610 +8840 +5800 +1310 +5820 +5630 +6300 +2780 +8820 +8460 +8520 +8540 +8470 +14892 +8040 +8480 +6570 +8450 +6960 +5300 +6860 +5790 +5540 +20102 +6620 +14337 +5650 +1360 +20193 +8500 +1920 +20111 +20075 +5560 +5570 +7000 +5530 +2820 +5620 +5680 +5520 +6550 +1260 +1280 +8770 +2830 +14123 +14163 +14168 +2020 +8060 diff --git a/src/Enmarcha.Backend/Enmarcha.Backend.csproj b/src/Enmarcha.Backend/Enmarcha.Backend.csproj index 1591e7c..de6489e 100644 --- a/src/Enmarcha.Backend/Enmarcha.Backend.csproj +++ b/src/Enmarcha.Backend/Enmarcha.Backend.csproj @@ -35,7 +35,7 @@ </ItemGroup> <ItemGroup> - <None Update="Data\xunta_fares.csv"> + <None Update="Data\*.csv"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> diff --git a/src/Enmarcha.Backend/Program.cs b/src/Enmarcha.Backend/Program.cs index 8599a5c..f4b39ff 100644 --- a/src/Enmarcha.Backend/Program.cs +++ b/src/Enmarcha.Backend/Program.cs @@ -128,6 +128,7 @@ builder.Services.AddSingleton<LineFormatterService>(); builder.Services.AddScoped<IArrivalsProcessor, VitrasaRealTimeProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, CorunaRealTimeProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, SantiagoRealTimeProcessor>(); +builder.Services.AddScoped<IArrivalsProcessor, VigoUsageProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, FilterAndSortProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, NextStopsProcessor>(); diff --git a/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs b/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs index 4f49afe..6d8c2c0 100644 --- a/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs +++ b/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs @@ -23,6 +23,7 @@ public class ArrivalsContext public Position? StopLocation { get; set; } public required List<Arrival> Arrivals { get; set; } + public List<BusStopUsagePoint>? Usage { get; set; } public required DateTime NowLocal { get; set; } } diff --git a/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs b/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs new file mode 100644 index 0000000..f5c7664 --- /dev/null +++ b/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs @@ -0,0 +1,121 @@ +using System.Text.Json; +using CsvHelper; +using CsvHelper.Configuration; +using Enmarcha.Backend.Types.Arrivals; +using Microsoft.Extensions.Caching.Memory; +using System.Globalization; + +namespace Enmarcha.Backend.Services.Processors; + +public class VigoUsageProcessor : IArrivalsProcessor +{ + private readonly HttpClient _httpClient; + private readonly IMemoryCache _cache; + private readonly ILogger<VigoUsageProcessor> _logger; + private readonly IHostEnvironment _environment; + private readonly FeedService _feedService; + private static readonly HashSet<string> _vigoStopsWhitelist = []; + private static bool _whitelistLoaded = false; + private static readonly object _lock = new(); + + public VigoUsageProcessor( + HttpClient httpClient, + IMemoryCache cache, + ILogger<VigoUsageProcessor> logger, + IHostEnvironment environment, + FeedService feedService) + { + _httpClient = httpClient; + _cache = cache; + _logger = logger; + _environment = environment; + _feedService = feedService; + + LoadWhitelist(); + } + + private void LoadWhitelist() + { + if (_whitelistLoaded) return; + + lock (_lock) + { + if (_whitelistLoaded) return; + + try + { + var path = Path.Combine(_environment.ContentRootPath, "Data", "vitrasa_stops_p95.csv"); + if (File.Exists(path)) + { + using var reader = new StreamReader(path); + using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + csv.Read(); + csv.ReadHeader(); + while (csv.Read()) + { + var code = csv.GetField("codigo"); + if (!string.IsNullOrWhiteSpace(code)) + { + _vigoStopsWhitelist.Add(code.Trim()); + } + } + _logger.LogInformation("Loaded {Count} Vigo stops for usage data whitelist", _vigoStopsWhitelist.Count); + } + else + { + _logger.LogWarning("Vigo stops whitelist CSV not found at {Path}", path); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading Vigo stops whitelist"); + } + finally + { + _whitelistLoaded = true; + } + } + } + + public async Task ProcessAsync(ArrivalsContext context) + { + if (!context.StopId.StartsWith("vitrasa:")) return; + + var normalizedCode = _feedService.NormalizeStopCode("vitrasa", context.StopCode); + if (!_vigoStopsWhitelist.Contains(normalizedCode)) return; + + var cacheKey = $"vigo_usage_{normalizedCode}"; + if (_cache.TryGetValue(cacheKey, out List<BusStopUsagePoint>? cachedUsage)) + { + context.Usage = cachedUsage; + return; + } + + try + { + using var activity = Telemetry.Source.StartActivity("FetchVigoUsage"); + var url = $"https://datos.vigo.org/vci_api_app/api2.jsp?tipo=TRANSPORTE_PARADA_HORAS_USO¶da={normalizedCode}"; + var response = await _httpClient.GetAsync(url); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + var usage = JsonSerializer.Deserialize<List<BusStopUsagePoint>>(json); + + if (usage != null) + { + _cache.Set(cacheKey, usage, TimeSpan.FromDays(7)); + context.Usage = usage; + } + } + else + { + _logger.LogWarning("Failed to fetch usage data for stop {StopCode}, status: {Status}", normalizedCode, response.StatusCode); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching usage data for Vigo stop {StopCode}", normalizedCode); + } + } +} diff --git a/src/Enmarcha.Backend/Types/Arrivals/BusStopUsagePoint.cs b/src/Enmarcha.Backend/Types/Arrivals/BusStopUsagePoint.cs new file mode 100644 index 0000000..edb08f4 --- /dev/null +++ b/src/Enmarcha.Backend/Types/Arrivals/BusStopUsagePoint.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Enmarcha.Backend.Types.Arrivals; + +public class BusStopUsagePoint +{ + [JsonPropertyName("h")] + public required int Hour { get; set; } + + [JsonPropertyName("t")] + public required int Total { get; set; } + + [JsonPropertyName("d")] + public required int DayOfWeek { get; set; } +} diff --git a/src/Enmarcha.Backend/Types/Arrivals/StopArrivalsResponse.cs b/src/Enmarcha.Backend/Types/Arrivals/StopArrivalsResponse.cs index 4d2f481..ddc4535 100644 --- a/src/Enmarcha.Backend/Types/Arrivals/StopArrivalsResponse.cs +++ b/src/Enmarcha.Backend/Types/Arrivals/StopArrivalsResponse.cs @@ -18,4 +18,7 @@ public class StopArrivalsResponse [JsonPropertyName("arrivals")] public List<Arrival> Arrivals { get; set; } = []; + + [JsonPropertyName("usage")] + public List<BusStopUsagePoint>? Usage { get; set; } } |
