[AZ-364] [AZ-360] Refactor C11+C08: decompose RouteProcessingService

Extracts RouteRegionMatcher, RouteCsvWriter, RouteSummaryWriter,
RouteImageRenderer, TilesZipBuilder, RegionFileCleaner from the
~750-LOC RouteProcessingService god-class. Moves TileInfo to its
own file as a sealed record. Replaces IServiceProvider scope-
locator with a direct IRegionService injection (folds AZ-360 / C08).
Updates DI registration and tests.

Tests: 133 / 133 unit + 5 / 5 smoke green; integration suite exit 0.
Pixel-equivalent stitched route image and byte-equivalent CSV /
summary / ZIP outputs verified through the smoke run.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 03:12:49 +03:00
parent 70a0a2c4d5
commit 6f23120c49
16 changed files with 1181 additions and 439 deletions
@@ -0,0 +1,66 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.DataAccess.Models;
namespace SatelliteProvider.Services.RouteManagement;
// AZ-364 / C11: extracted from RouteProcessingService.CleanupRegionFilesAsync.
// Deletes per-region files (CSV, summary, stitched image) once a route has
// successfully consumed them. Each delete is best-effort: a failed delete is
// logged at warning level and does not abort the cleanup of the remaining
// files. Stitched image path is reconstructed from regionId + ReadyDirectory
// because regions historically did not always persist that path on the entity.
public class RegionFileCleaner
{
private readonly StorageConfig _storageConfig;
private readonly ILogger<RegionFileCleaner> _logger;
public RegionFileCleaner(IOptions<StorageConfig> storageConfig, ILogger<RegionFileCleaner> logger)
{
ArgumentNullException.ThrowIfNull(storageConfig);
_storageConfig = storageConfig.Value;
_logger = logger;
}
public Task CleanupAsync(IEnumerable<RegionEntity> regions, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(regions);
foreach (var region in regions)
{
if (cancellationToken.IsCancellationRequested)
break;
if (region == null) continue;
DeleteIfPresent(region.CsvFilePath, "region CSV file");
DeleteIfPresent(region.SummaryFilePath, "region summary file");
var stitchedImagePath = Path.Combine(_storageConfig.ReadyDirectory, $"region_{region.Id}_stitched.jpg");
if (File.Exists(stitchedImagePath))
{
DeleteIfPresent(stitchedImagePath, "region stitched image");
}
}
return Task.CompletedTask;
}
private void DeleteIfPresent(string? path, string label)
{
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
return;
}
try
{
File.Delete(path);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete {Label}: {FilePath}", label, path);
}
}
}