Files
satellite-provider/_docs/03_implementation/batch_12_report.md
T
Oleksandr Bezdieniezhnykh 330bccd724 [AZ-366] Refactor C13: consolidate Haversine + tile-coord parsing
RouteProcessingService.CalculateDistance(double, double, double, double)
re-implemented Haversine using EARTH_RADIUS=6371000 alongside the
canonical GeoUtils.CalculateDistance(GeoPoint, GeoPoint) which uses
EARTH_RADIUS=6378137. Two implementations of the same formula for the
same problem.

Separately, ExtractTileCoordinatesFromFilename in RouteProcessingService
parsed the tile_{z}_{x}_{y}_{ts}.jpg filename pattern that's *generated*
by StorageConfig.GetTileFilePath in another assembly — the writer and
parser were coupled by string convention only and a format change in
one would silently break the other.

Both fixes:

(a) Deleted the duplicate Haversine in RouteProcessingService. The
single call site (region-matching nearest-neighbor OrderBy) now uses
GeoUtils.CalculateDistance with GeoPoint instances. The constant
difference is monotonic-equivalent for OrderBy purposes — same region
is picked.

(b) Added static StorageConfig.TryExtractTileCoordinates(string, out
int, out int): bool — pure parser, co-located with GetTileFilePath so
the inverse-pair invariant is structural, not by-convention.
RouteProcessingService.ExtractTileCoordinatesFromFilename becomes a
thin wrapper that delegates to the helper and emits the existing
warning log on malformed input — the AZ-352 tests for warning behavior
all still pass.

Verification:
- 77/77 unit tests green (was 71 → +6 new StorageConfigTests including
  a writer/parser round-trip test for AC-2).
- Smoke + full integration suite green.
- AC-1 grep verification: Math.Sin/EARTH_RADIUS patterns are now
  confined to GeoUtils.cs only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:56:46 +03:00

6.1 KiB

Batch 12 Report — Refactor 03 Phase 3 (start)

Date: 2026-05-10 Epic: AZ-350 (03-code-quality-refactoring) Status: Complete, pushed

Scope (1 task / 2 SP)

ID C-ID Title Points Component
AZ-366 C13 Consolidate Haversine + tile-coord parsing into Common/Utils 2 Common + Services.RouteManagement

First task of Phase 3 (Structural cleanup). Single-task batch — keeps the review surface small while the previous Phase 2 changes settle.

Changes

Production

  • MODIFIED SatelliteProvider.Common/Configs/StorageConfig.cs
    • Added static bool TryExtractTileCoordinates(string filePath, out int tileX, out int tileY). Pure parser — no I/O, no logging, no exceptions for malformed input (caller decides). Co-located with GetTileFilePath which is the writer side, so format changes can never drift between the two ends. This is the structural fix called for in AC-2.
    • The method is static because it's a pure string-parsing computation with no side effects, satisfying the coderule.mdc static-method test ("pure, self-contained computations").
  • MODIFIED SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs
    • Deleted private static double CalculateDistance(double lat1, double lon1, double lat2, double lon2) (the duplicate Haversine using EARTH_RADIUS = 6371000). Replaced its single call site with GeoUtils.CalculateDistance(GeoPoint, GeoPoint) (which uses EARTH_RADIUS = 6378137).
    • ExtractTileCoordinatesFromFilename(string) is now a thin wrapper: it delegates parsing to StorageConfig.TryExtractTileCoordinates and emits the existing warning log when parsing fails. Public/internal contract preserved (callers see the same (int TileX, int TileY) signature, same (-1, -1) sentinel, same warning log behavior on malformed input).
    • Updated the warning message text to tile_<zoom>_<x>_<y>_<timestamp> (matches what GetTileFilePath actually writes; the old message said tile_<timestamp>_<x>_<y> which was wrong — the format is tile_{zoom}_{x}_{y}_{ts}.jpg).

Tests

  • NEW SatelliteProvider.Tests/StorageConfigTests.cs — 6 unit tests for the new helper:
    • TryExtractTileCoordinates_ValidFilename_ReturnsTrue_AZ366 — happy path with full directory path.
    • TryExtractTileCoordinates_FilenameWithoutDirectory_ReturnsTrue_AZ366 — bare filename.
    • TryExtractTileCoordinates_NonTilePrefix_ReturnsFalseAndSentinel_AZ366 — wrong prefix.
    • TryExtractTileCoordinates_NonNumericCoords_ReturnsFalseAndSentinel_AZ366 — coords not parseable.
    • TryExtractTileCoordinates_NullPath_ThrowsArgumentNullException_AZ366 — null contract.
    • GetTileFilePath_RoundTrip_ParserRecoversOriginalCoordinates_AZ366_AC2 — proves writer/parser inverse pair. This is the structural assertion behind AC-2: writing then parsing recovers the original coordinates, which can only stay true if both methods live in the same module.
  • PRESERVED all 4 existing RouteProcessingServiceTests.ExtractTileCoordinatesFromFilename_*_AC1/AC2 tests (added in batch 7 for AZ-352). They still pass against the now-thin wrapper, proving behavior preservation through the refactor.

Verification

  • Unit tests: 77 / 77 passing (was 71 → +6 new StorageConfigTests).
  • Integration smoke + full suite: green. Container exits 0. The route-tile workflow (ExtractTileCoordinatesFromFilename + region-matching distance) is exercised end-to-end by every route test that requests maps.
  • AC-1 grep verification: rg 'Math\.Sin.*Math\.PI|earthRadius|EarthRadius|EARTH_RADIUS' --type=cs returns matches only in SatelliteProvider.Common/Utils/GeoUtils.cs. No Haversine implementation lives anywhere else in the codebase.

Acceptance criteria coverage

AC Evidence
AC-1 One Haversine implementation in the codebase Grep for Math.Sin.*Math.PI and EARTH_RADIUS returns matches only in GeoUtils.cs. The local RouteProcessingService.CalculateDistance (which used 6371000) has been deleted; its single call site now uses GeoUtils.CalculateDistance.
AC-2 Writer and parser co-located Both GetTileFilePath and TryExtractTileCoordinates live as methods on StorageConfig in SatelliteProvider.Common/Configs/StorageConfig.cs. New unit test GetTileFilePath_RoundTrip_ParserRecoversOriginalCoordinates_AZ366_AC2 proves the inverse-pair invariant.
AC-3 Tests stay green 77 unit + smoke + full integration green; 4 pre-existing AZ-352 tests for ExtractTileCoordinatesFromFilename still pass against the thin-wrapper version.

Behavior preservation notes

  • Earth radius constant change: RouteProcessingService.CalculateDistance used 6_371_000 (mean Earth radius), GeoUtils.CalculateDistance uses 6_378_137 (WGS84 equatorial radius). Absolute distances differ by ~0.1%. The single call site uses the value only for OrderBy(...).FirstOrDefault() to pick the nearest region — both formulas are monotonic in actual distance, so the chosen region is the same for any input. No region-matching test asserts on a specific distance value.
  • Public API: StorageConfig adds one new static method. GetTileFilePath is unchanged. RouteProcessingService.ExtractTileCoordinatesFromFilename keeps the same signature and same observable behavior (returns (-1, -1) and logs a warning on malformed input).
  • Warning log message text changed: tile_<timestamp>_<x>_<y>tile_<zoom>_<x>_<y>_<timestamp> (matches GetTileFilePath's actual output format tile_{z}_{x}_{y}_{ts}.jpg). The old text was misleading. No test asserted on the message text — the existing AZ-352 tests check that the warning contains the offending filename, not the format hint.

Up next

  • Batch 13 (per the user-selected sequence): Remaining Phase 3 — natural next is AZ-368 (C15, TileCsvWriter, 2 SP). After C15 lands, C13+C15 together complete the foundational extracts that C11/C12 depend on.
  • Cumulative review next fires after batches 10+11+12 (K=3 trigger). AZ-357 + AZ-362 + AZ-366 are the three batches in the window — running the cumulative review now is the natural gate before starting batch 13.