[AZ-368] Refactor C15: extract shared TileCsvWriter

Both RegionService.GenerateCsvFileAsync and
RouteProcessingService.GenerateRouteCsvAsync wrote the same CSV
shape: header "latitude,longitude,file_path", same
OrderByDescending(Latitude).ThenBy(Longitude) ordering, same F6
numeric format. Two near-identical writers with no shared abstraction.

Extracted TileCsvWriter (instance class, no DI dependencies) plus a
TileCsvRow record bridging the per-pipeline DTOs (TileMetadata vs
TileInfo) to a single contract. The header constant, ordering rule,
and StreamWriter lifecycle now live in one place.

Both call sites collapse to a one-line projection plus a delegated
WriteAsync call. Region method becomes static (no longer references
instance state). Route method preserves its existing logger line.

Coverage:
- 7 new unit tests including a byte-for-byte equivalence test that
  writes the same input via both the new TileCsvWriter and the
  inlined-original code path side by side and asserts file bytes
  are identical.
- Integration smoke + full suite green; route + region CSV outputs
  unchanged across all existing scenarios (verified by extended-route
  CSV verification step in the integration suite).
- 84/84 unit tests pass (was 77).

Side improvement: writer now respects CancellationToken mid-loop.
The pre-refactor inline code did not. Strict improvement; consistent
with every other async API in the codebase.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 01:07:51 +03:00
parent 098f905796
commit 89260d0ec4
5 changed files with 273 additions and 21 deletions
@@ -293,17 +293,10 @@ public class RegionService : IRegionService
return outputPath;
}
private async Task GenerateCsvFileAsync(string filePath, List<TileMetadata> tiles, CancellationToken cancellationToken)
private static async Task GenerateCsvFileAsync(string filePath, List<TileMetadata> tiles, CancellationToken cancellationToken)
{
var orderedTiles = tiles.OrderByDescending(t => t.Latitude).ThenBy(t => t.Longitude).ToList();
using var writer = new StreamWriter(filePath);
await writer.WriteLineAsync("latitude,longitude,file_path");
foreach (var tile in orderedTiles)
{
await writer.WriteLineAsync($"{tile.Latitude:F6},{tile.Longitude:F6},{tile.FilePath}");
}
var rows = tiles.Select(t => new TileCsvRow(t.Latitude, t.Longitude, t.FilePath));
await new TileCsvWriter().WriteAsync(filePath, rows, cancellationToken);
}
private async Task GenerateSummaryFileAsync(