mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 19:11:14 +00:00
89260d0ec4
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>
35 lines
1.4 KiB
C#
35 lines
1.4 KiB
C#
namespace SatelliteProvider.Common.Utils;
|
|
|
|
// AZ-368 / C15: shared CSV writer for tile rows.
|
|
// Both region and route pipelines previously emitted the same CSV (header
|
|
// "latitude,longitude,file_path", same OrderByDescending(Latitude).ThenBy(Longitude)
|
|
// ordering, same F6 numeric format). Two near-identical writers are now one.
|
|
//
|
|
// The class is instance-based (no static, per coderule.mdc — file I/O has side
|
|
// effects). It carries no dependencies, so callers can `new TileCsvWriter()`
|
|
// at use sites without DI bloat.
|
|
public sealed class TileCsvWriter
|
|
{
|
|
public const string Header = "latitude,longitude,file_path";
|
|
|
|
public async Task WriteAsync(string filePath, IEnumerable<TileCsvRow> rows, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(filePath);
|
|
ArgumentNullException.ThrowIfNull(rows);
|
|
|
|
var ordered = rows.OrderByDescending(r => r.Latitude).ThenBy(r => r.Longitude).ToList();
|
|
|
|
await using var writer = new StreamWriter(filePath);
|
|
await writer.WriteLineAsync(Header.AsMemory(), cancellationToken);
|
|
|
|
foreach (var row in ordered)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
var line = $"{row.Latitude:F6},{row.Longitude:F6},{row.FilePath}";
|
|
await writer.WriteLineAsync(line.AsMemory(), cancellationToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed record TileCsvRow(double Latitude, double Longitude, string FilePath);
|