mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 10:11:13 +00:00
[AZ-350] Refactor 03 Phase 2: roadmap + 27 task specs + safety net
Adds Phase 0 (baseline metrics, .gitignore tweaks), Phase 1 (research findings, list-of-changes), and Phase 2 (refactoring roadmap, epic AZ-350, 27 task specs AZ-351..AZ-380, dependency table updates) for the 03-code-quality-refactoring run. Phase 3 (Safety Net) re-verified: 40/40 unit + 5/5 smoke integration pass; documented in test_specs/existing_coverage.md. Coverage % gating deferred to ticket C19 (AZ-372) which adds Coverlet + reportgenerator. Auto-chains to Phase 4 (Execution) via /implement starting at batch 1 (Phase 1 critical fixes). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -24,6 +24,40 @@
|
||||
| AZ-314 DI registration split | AZ-313 | 2 | Done (In Testing) |
|
||||
| AZ-315 Documentation sync | AZ-314 | 2 | In Progress |
|
||||
|
||||
### Step 8 — Refactor 03-code-quality-refactoring (AZ-350 epic)
|
||||
|
||||
Roadmap: `_docs/04_refactoring/03-code-quality-refactoring/analysis/refactoring_roadmap.md` (4 execution phases).
|
||||
|
||||
| Task | C-ID | Title | Phase | Depends On | Points | Status |
|
||||
|------|------|-------|-------|-----------|--------|--------|
|
||||
| AZ-351 | C01 | Fix null logger to DatabaseMigrator | 1 | — | 2 | To Do |
|
||||
| AZ-352 | C02 | Replace empty catch in ExtractTileCoordinatesFromFilename | 1 | — | 2 | To Do |
|
||||
| AZ-363 | C10 | Delete write-only counters in RegionRequestQueue | 1 | — | 1 | To Do |
|
||||
| AZ-356 | C05 | Stub endpoints return 501 | 1 | — | 2 | To Do |
|
||||
| AZ-354 | C04 | Strict CORS by default | 1 | — | 2 | To Do |
|
||||
| AZ-353 | C03 | Sanitize 5xx responses via IExceptionHandler | 1 | — | 3 | To Do |
|
||||
| AZ-359 | C07 | Consolidate RegionService catch ladder | 2 | — | 3 | To Do |
|
||||
| AZ-357 | C06 | Drop tile Version concept; new migration | 2 | — | 5 | To Do |
|
||||
| AZ-362 | C09 | Idempotent POST contract | 2 | AZ-353 | 3 | To Do |
|
||||
| AZ-366 | C13 | Consolidate Haversine + filename parser | 3 | — | 2 | To Do |
|
||||
| AZ-377 | C24 | Consolidate Earth constants + 111000 | 3 | AZ-371 | 2 | To Do |
|
||||
| AZ-368 | C15 | Shared TileCsvWriter | 3 | — | 2 | To Do |
|
||||
| AZ-367 | C14 | Shared TileGridStitcher | 3 | AZ-364 | 3 | To Do |
|
||||
| AZ-369 | C16 | Move inline DTOs out of Program.cs | 3 | — | 2 | To Do |
|
||||
| AZ-365 | C12 | Decompose RouteService.CreateRouteAsync | 3 | — | 5 | To Do |
|
||||
| AZ-364 | C11 | Decompose RouteProcessingService god-class | 3 | AZ-366, AZ-367 (folds in AZ-360) | 5 | To Do |
|
||||
| AZ-360 | C08 | Replace IServiceProvider in RouteProcessingService | 3 | AZ-364 (folded) | 2 | To Do |
|
||||
| AZ-371 | C18 | Magic numbers → ProcessingConfig/MapConfig | 4 | — | 3 | To Do |
|
||||
| AZ-370 | C17 | Status / point-type enums + AC RT2 update | 4 | — | 3 | To Do |
|
||||
| AZ-373 | C20 | Clarify / drop MapsVersion | 4 | AZ-357 | 2 | To Do |
|
||||
| AZ-374 | C21 | Typed HttpClient for Google Maps | 4 | — | 2 | To Do |
|
||||
| AZ-375 | C22 | O(N) existing-tile lookup (HashSet) | 4 | AZ-371 | 2 | To Do |
|
||||
| AZ-376 | C23 | Delete unused FindExistingTileAsync | 4 | — | 1 | To Do |
|
||||
| AZ-378 | C25 | Repo `_logger` fields: delete or use | 4 | — | 1 | To Do |
|
||||
| AZ-379 | C26 | Extract repo SELECT column-list constants | 4 | — | 2 | To Do |
|
||||
| AZ-380 | C27 | Delete CalculatePolygonDiagonalDistance | 4 | — | 1 | To Do |
|
||||
| AZ-372 | C19 | dotnet format + NetAnalyzers + Coverlet | 4 | — | 3 | To Do |
|
||||
|
||||
## Execution Order
|
||||
|
||||
### Step 6
|
||||
@@ -32,15 +66,22 @@
|
||||
3. AZ-289 (integration tests — depends on infra only)
|
||||
4. AZ-290 (non-functional tests — depends on infra only)
|
||||
|
||||
### Step 8 (refactor)
|
||||
### Step 8 (02-coupling-refactoring)
|
||||
1. AZ-310 → AZ-311 (Phase A: route tile endpoints through ITileService)
|
||||
2. AZ-312 → AZ-313 → AZ-314 (Phase B: physical split + consumer + DI rewire)
|
||||
3. AZ-315 (Phase C: docs sync, must be last)
|
||||
|
||||
### Step 8 (03-code-quality-refactoring)
|
||||
Phase 1 (Critical fixes): AZ-351 → AZ-352 → AZ-363 → AZ-356 → AZ-354 → AZ-353
|
||||
Phase 2 (Correctness): AZ-359 → AZ-357 → AZ-362 (AZ-362 needs AZ-353)
|
||||
Phase 3 (Structural cleanup): AZ-366 → AZ-377 → AZ-368 → AZ-367 → AZ-369 → AZ-365 → AZ-364 (folds AZ-360) — AZ-377 needs AZ-371
|
||||
Phase 4 (Typing/config/tooling/polish): AZ-371 → AZ-370 → AZ-373 → AZ-374 → AZ-375 → AZ-376 → AZ-378 → AZ-379 → AZ-380 → AZ-372
|
||||
|
||||
## Total Effort
|
||||
|
||||
Step 6: 6 tasks, 17 story points
|
||||
Step 8 (refactor): 6 tasks, 17 story points
|
||||
Step 8 (02-coupling-refactoring): 6 tasks, 17 story points
|
||||
Step 8 (03-code-quality-refactoring): 27 tasks, ~66 story points
|
||||
|
||||
## Coverage Verification
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Refactor: fix null ILogger passed to DatabaseMigrator at startup
|
||||
|
||||
**Task**: AZ-351_refactor_fix_null_logger_migrator
|
||||
**Name**: Fix null logger to DatabaseMigrator
|
||||
**Description**: Resolve `ILogger<DatabaseMigrator>` directly from DI instead of casting `ILogger<Program>`, which always returns null.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api
|
||||
**Tracker**: AZ-351
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs:82-83` does `app.Services.GetRequiredService<ILogger<Program>>() as ILogger<DatabaseMigrator>`. The cast between unrelated generic instantiations always returns null. `DatabaseMigrator` runs with a null logger, so any migration failure path that depends on logging is silent.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `DatabaseMigrator` receives a real `ILogger<DatabaseMigrator>` instance from DI.
|
||||
- Migration log entries appear in startup output and persist to log sinks.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Replace the `as` cast with `app.Services.GetRequiredService<ILogger<DatabaseMigrator>>()`.
|
||||
- Confirm `DatabaseMigrator`'s constructor logs at least one entry on success and one on failure (already present, just verify).
|
||||
|
||||
### Excluded
|
||||
- Changing `DatabaseMigrator`'s logging strategy or log levels.
|
||||
- Adding migration-related metrics or observability beyond what already exists.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Migrator receives a real logger**
|
||||
Given the post-refactor `Program.cs`
|
||||
When the host starts
|
||||
Then `DatabaseMigrator` is constructed with a non-null `ILogger<DatabaseMigrator>` (verifiable by a unit test or by inspecting startup logs).
|
||||
|
||||
**AC-2: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No DI graph reorder.
|
||||
- No public API change.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: hidden assumption that logger may be null**
|
||||
- *Risk*: `DatabaseMigrator` may have a defensive null-check that masked the bug.
|
||||
- *Mitigation*: keep the null-check during this change; remove it in a follow-up only after we confirm via tests that the live logger is always provided.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C01).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refactor: replace empty catch in ExtractTileCoordinatesFromFilename
|
||||
|
||||
**Task**: AZ-352_refactor_replace_empty_catch_extract_tile_coords
|
||||
**Name**: Remove silent empty catch in tile-coord parser
|
||||
**Description**: Replace the empty `catch { }` in `RouteProcessingService.ExtractTileCoordinatesFromFilename` with a typed catch + warning log.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Services.RouteManagement
|
||||
**Tracker**: AZ-352
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:610-630` swallows every parse/IO exception with `catch { }` and returns `(-1, -1)`. Callers treat this as "tile not stitchable" — the tile silently disappears from the route map. Direct violation of `coderule.mdc` ("Never suppress errors silently").
|
||||
|
||||
## Outcome
|
||||
|
||||
- Malformed filenames produce a visible warning log entry.
|
||||
- Unexpected exception types propagate up the call stack instead of being swallowed.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Replace `catch { }` with `catch (FormatException) { ... } catch (ArgumentException) { ... }` plus warning log via the existing `_logger`.
|
||||
- Let any other exception propagate.
|
||||
- Add a unit test that feeds a malformed filename and asserts on the warning log entry.
|
||||
|
||||
### Excluded
|
||||
- Changing the filename format or the writer side (`StorageConfig.GetTileFilePath`).
|
||||
- Changing the `(-1, -1)` sentinel — that lives until C13 reorganizes the parser/writer pairing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Malformed filename logs a warning**
|
||||
Given a file that does not match the `tile_{ts}_{x}_{y}.jpg` pattern
|
||||
When `ExtractTileCoordinatesFromFilename` is called on it
|
||||
Then the function returns `(-1, -1)` AND a warning log entry is emitted naming the file.
|
||||
|
||||
**AC-2: Unexpected exception propagates**
|
||||
Given a hypothetical unrelated exception (e.g., `IOException`) raised inside the parser
|
||||
When `ExtractTileCoordinatesFromFilename` is called
|
||||
Then the exception is not swallowed and propagates to the caller.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Behavior for valid filenames must be byte-identical.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: existing callers may rely on the swallow-all behavior**
|
||||
- *Risk*: another path in `RouteProcessingService` may pass arbitrary file lists where IO errors are expected.
|
||||
- *Mitigation*: grep all callers; if any expects swallow-all, add explicit handling at that call site.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C02).
|
||||
@@ -0,0 +1,74 @@
|
||||
# Refactor: sanitize 5xx responses via global IExceptionHandler
|
||||
|
||||
**Task**: AZ-353_refactor_sanitize_5xx_responses
|
||||
**Name**: Centralized exception handler with sanitized ProblemDetails
|
||||
**Description**: Replace per-endpoint `try/catch (Exception)` + `Results.Problem(detail: ex.Message)` with a global `IExceptionHandler` that returns sanitized ProblemDetails (correlation ID + generic title) and logs the full exception server-side.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api
|
||||
**Tracker**: AZ-353
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs` has six endpoint handlers (lines 139-143, 170-174, 206-210, 226-230, 245-249, 265-269) that each catch `Exception` and return `Results.Problem(detail: ex.Message, statusCode: 500)`. The `detail` ships the exception message — including stack-trace fragments, file paths, SQL error text, and Google API error bodies — back to the client.
|
||||
|
||||
## Outcome
|
||||
|
||||
- 5xx responses no longer leak internal exception messages.
|
||||
- Server-side logs contain the full exception + correlation ID for each 500.
|
||||
- Per-endpoint try/catch boilerplate is removed.
|
||||
- 37 unit + 5 smoke tests stay green (with assertions on `ProblemDetails.detail` updated).
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add an `IExceptionHandler` (or `UseExceptionHandler` middleware) in `Program.cs`.
|
||||
- Generate a correlation ID per request, include it in both the response body and the server log entry.
|
||||
- Remove the per-endpoint catches that only re-emit `ex.Message`.
|
||||
- Update tests that assert on `ProblemDetails.detail` to assert on the sanitized shape.
|
||||
- Specific 400 paths (e.g., `ArgumentException` in `CreateRoute`) keep their typed handling.
|
||||
|
||||
### Excluded
|
||||
- Changing HTTP status codes for 4xx paths.
|
||||
- Adding new structured error categories (deferred).
|
||||
- Changing the logger sink configuration.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: 5xx body is sanitized**
|
||||
Given any endpoint that throws an unhandled exception
|
||||
When the client receives the 500 response
|
||||
Then `ProblemDetails.detail` does not contain the original exception message; the body has a generic title + correlation ID.
|
||||
|
||||
**AC-2: Server log has the full exception**
|
||||
Given the same scenario as AC-1
|
||||
When the application logs the failure
|
||||
Then the log entry contains the exception type, message, stack trace, and the same correlation ID returned to the client.
|
||||
|
||||
**AC-3: 4xx paths preserved**
|
||||
Given a request that triggers `ArgumentException` in `CreateRoute`
|
||||
When the endpoint runs
|
||||
Then the response is HTTP 400 with the existing typed shape (not 500).
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass (with `ProblemDetails.detail` assertions updated).
|
||||
|
||||
## Constraints
|
||||
|
||||
- HTTP 500 status code preserved for unhandled exceptions.
|
||||
- No new dependencies beyond ASP.NET Core 8 built-ins.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: tests that assert on `detail` text break**
|
||||
- *Risk*: existing unit/integration tests may inspect `ProblemDetails.detail`.
|
||||
- *Mitigation*: update them to assert on the new sanitized shape (title + correlationId) in the same PR.
|
||||
|
||||
**Risk 2: clients depend on the leaky message**
|
||||
- *Risk*: the API has been live; some integrator may parse the message.
|
||||
- *Mitigation*: this is a security improvement; document the change in the OpenAPI spec.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C03).
|
||||
@@ -0,0 +1,66 @@
|
||||
# Refactor: strict CORS by default; explicit opt-in for AllowAnyOrigin
|
||||
|
||||
**Task**: AZ-354_refactor_strict_cors_default
|
||||
**Name**: Strict CORS default + explicit opt-in for permissive policy
|
||||
**Description**: When `CorsConfig:AllowedOrigins` is empty, refuse to start in `Production` and warn loudly in `Development`. Only configure the open policy when the operator opts in via `CorsConfig:AllowAnyOrigin=true`.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api
|
||||
**Tracker**: AZ-354
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs:37-47` falls through to `policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()` when `CorsConfig:AllowedOrigins` is empty. A misconfigured prod deployment silently exposes the entire surface to any origin.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Production deployment with empty `AllowedOrigins` fails to start with a clear error message.
|
||||
- Development deployment with empty `AllowedOrigins` logs a loud warning.
|
||||
- `CorsConfig:AllowAnyOrigin=true` (explicit) is the only path that produces the permissive policy.
|
||||
- 37 unit + 5 smoke tests stay green (test fixtures already specify origins).
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Read `CorsConfig:AllowAnyOrigin` (new boolean flag, default false).
|
||||
- Branch logic in `Program.cs` CORS configuration: empty origins + Production → throw; empty origins + Development → warn; non-empty origins → existing strict policy; explicit `AllowAnyOrigin=true` → existing permissive policy.
|
||||
- Update `appsettings.Development.json` to set explicit origins (or set the new flag) so dev still works out of the box.
|
||||
|
||||
### Excluded
|
||||
- Changing the CORS policy semantics for non-empty `AllowedOrigins`.
|
||||
- Adding per-endpoint CORS overrides.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Production refuses to start without origins**
|
||||
Given `ASPNETCORE_ENVIRONMENT=Production` and empty `CorsConfig:AllowedOrigins` and no `CorsConfig:AllowAnyOrigin=true`
|
||||
When the host attempts to start
|
||||
Then it throws a clear configuration exception naming the missing setting.
|
||||
|
||||
**AC-2: Development warns but starts**
|
||||
Given `ASPNETCORE_ENVIRONMENT=Development` and empty `CorsConfig:AllowedOrigins`
|
||||
When the host starts
|
||||
Then a warning log entry is emitted and the host continues to run.
|
||||
|
||||
**AC-3: Explicit opt-in works**
|
||||
Given `CorsConfig:AllowAnyOrigin=true`
|
||||
When the host starts
|
||||
Then the permissive CORS policy is configured (current behavior).
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Existing test/dev fixtures that specify origins must continue to work without changes (other than appsettings overrides).
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: existing prod env vars don't have origins set**
|
||||
- *Risk*: if any deployed environment relies on the default-permissive behavior, it will break.
|
||||
- *Mitigation*: this is the security fix the change exists to provide. Document in deploy notes / runbook.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C04).
|
||||
@@ -0,0 +1,59 @@
|
||||
# Refactor: stub endpoints return 501 Not Implemented
|
||||
|
||||
**Task**: AZ-356_refactor_stub_endpoints_501
|
||||
**Name**: Stub endpoints respond with HTTP 501
|
||||
**Description**: Change `GetSatelliteTilesByMgrs` and `UploadImage` to return HTTP 501 with a problem-details body, and update OpenAPI metadata accordingly.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api
|
||||
**Tracker**: AZ-356
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs:177-180` (`GetSatelliteTilesByMgrs`) returns 200 OK with an empty payload. `Program.cs:182-185` (`UploadImage`) returns 200 OK with `Success=false`. Clients can't distinguish "stubbed" from "valid empty result" or "real failure".
|
||||
|
||||
## Outcome
|
||||
|
||||
- Both stub endpoints respond with HTTP 501 and a ProblemDetails body indicating "feature not implemented".
|
||||
- OpenAPI document marks both endpoints as not-implemented.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Change handler return statements to `Results.Problem(statusCode: 501, title: "Not implemented", detail: <short description>)`.
|
||||
- Update Swagger / OpenAPI annotations to reflect 501.
|
||||
|
||||
### Excluded
|
||||
- Implementing the underlying functionality (out of scope for this run).
|
||||
- Removing the endpoints (the routes are documented contract surface).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Both stubs return 501**
|
||||
Given a request to `GET /api/satellite/tiles/mgrs` or `POST /api/satellite/upload`
|
||||
When the endpoint executes
|
||||
Then the response is HTTP 501 with a ProblemDetails body.
|
||||
|
||||
**AC-2: OpenAPI marks them not-implemented**
|
||||
Given the generated `swagger.json`
|
||||
When inspected
|
||||
Then the two endpoints declare `501` as a documented response.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass (smoke does not exercise these endpoints).
|
||||
|
||||
## Constraints
|
||||
|
||||
- Endpoints stay registered (route shape preserved); only the response status + body change.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: integrators may have probed the stubs and treated 200 as success**
|
||||
- *Risk*: any caller that received 200 OK with empty body and proceeded as if the operation succeeded will now see 501.
|
||||
- *Mitigation*: this is the fix the change exists to provide. Honest contract over polite-but-wrong success.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C05).
|
||||
@@ -0,0 +1,77 @@
|
||||
# Refactor: drop tile Version concept; latest row wins; new migration
|
||||
|
||||
**Task**: AZ-357_refactor_drop_tile_version
|
||||
**Name**: Eliminate year-based tile versioning; cache by (lat, lon, zoom, tile_size)
|
||||
**Description**: Remove the `Version` filter from tile-cache logic, change repository upsert semantics to (lat, lon, zoom, tile_size), and ship a migration that drops the 5-column unique constraint, replaces it with a 4-column one, and dedupes pre-existing duplicates.
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: None (C20 follows from this change)
|
||||
**Component**: Services.TileDownloader + DataAccess
|
||||
**Tracker**: AZ-357
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.TileDownloader/TileService.cs` uses `var currentVersion = DateTime.UtcNow.Year` and filters cached tiles via `existingTiles.Where(t => t.Version == currentVersion)`. On every Jan 1 UTC the year flips and the cache effectively expires (LF-1 in `discovery/logical_flow_analysis.md`). The `version` concept is unused as a real cache lever.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Tile cache survives year boundaries (cached tiles from prior years remain valid).
|
||||
- Repository lookups return the most recently updated row for each `(lat, lon, zoom, tile_size_meters)` cell.
|
||||
- New rows are upserted on conflict by the 4-column key.
|
||||
- DB unique constraint matches the new key; pre-existing duplicates are deduped (keeping highest `updated_at`).
|
||||
- The `version` column itself is preserved (per `coderule.mdc` — no rename/drop without explicit confirmation).
|
||||
- 37 unit + 5 smoke tests stay green; `migration_test_step1.md` (or equivalent) covers the migration.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Delete `t.Version == currentVersion` filter in `TileService.DownloadAndStoreTilesAsync`.
|
||||
- Stop writing `currentVersion` into `TileEntity.Version` in `BuildTileEntity`.
|
||||
- Update `TileRepository.GetTilesByRegionAsync` and `GetByTileCoordinatesAsync` to deduplicate on the 4-column key, returning the latest row per cell.
|
||||
- Change `TileRepository.InsertAsync`'s `ON CONFLICT` clause to the 4-column key.
|
||||
- Add a new migration SQL file (next number) that drops the 5-column unique constraint, dedupes pre-existing rows, then adds a new 4-column unique constraint.
|
||||
- Add a unit/integration test that fakes `UtcNow` across a year boundary and verifies cache hit.
|
||||
|
||||
### Excluded
|
||||
- Dropping the `version` column from `tiles` (deferred; per `coderule.mdc` no column drops without explicit confirmation).
|
||||
- Touching `MapsVersion` (separate task: AZ-373 / C20).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Cache survives year boundary**
|
||||
Given a row in `tiles` with `version = 2025`
|
||||
When the system queries the same `(lat, lon, zoom, tile_size_meters)` cell with the clock advanced into 2026
|
||||
Then the cached row is returned (not re-downloaded).
|
||||
|
||||
**AC-2: Migration runs cleanly on populated tile data**
|
||||
Given a `tiles` table containing duplicates by the new 4-column key (across different `version` values)
|
||||
When the new migration runs
|
||||
Then duplicates are collapsed to the row with the highest `updated_at`, and the new 4-column unique constraint exists.
|
||||
|
||||
**AC-3: Upsert behaves on the new key**
|
||||
Given two `InsertAsync` calls with identical `(lat, lon, zoom, tile_size_meters)` and different `version` values
|
||||
When both run
|
||||
Then the table contains exactly one row for that cell (the second call updated the first).
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- DB column `version` is preserved (left nullable; new code does not write to it).
|
||||
- HTTP shape of `DownloadTileResponse` preserved (`Version` field still present in the JSON).
|
||||
- No rename of any column.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: production tile table contains duplicates that resolve ambiguously**
|
||||
- *Risk*: if multiple rows share the new 4-column key with the same `updated_at`, the dedupe could pick the wrong row.
|
||||
- *Mitigation*: tie-break on `id` (largest wins) within the dedupe SQL.
|
||||
|
||||
**Risk 2: rollback is hard once the migration runs**
|
||||
- *Risk*: dropped duplicates are gone.
|
||||
- *Mitigation*: migration SQL must be reviewable and tested against a populated copy before prod rollout. Capture pre-migration row counts in the migration log.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C06).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refactor: consolidate 9-way catch ladder in RegionService.ProcessRegionAsync
|
||||
|
||||
**Task**: AZ-359_refactor_consolidate_region_catch_ladder
|
||||
**Name**: Single catch + classifier in region processing
|
||||
**Description**: Replace the nine near-identical catch blocks with a single `try/catch (Exception ex)` that delegates to a `ClassifyRegionFailure` helper.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: None
|
||||
**Component**: Services.RegionProcessing
|
||||
**Tracker**: AZ-359
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RegionProcessing/RegionService.cs:148-197` contains nine catch blocks (TaskCanceledException × 3, OperationCanceledException × 2, RateLimitException, HttpRequestException, Exception × 1) that each build an `errorMessage` and call `HandleProcessingFailureAsync`. Adding a new failure category requires touching all nine.
|
||||
|
||||
## Outcome
|
||||
|
||||
- One catch block; one classification helper.
|
||||
- Same observable failure-path behavior preserved for all current categories (timeout, rate-limit, cancellation, generic).
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Extract `ClassifyRegionFailure(Exception ex, CancellationTokenSource timeoutCts, CancellationToken cancellationToken) : (FailureCategory, string message)`.
|
||||
- Replace the catch ladder with a single `catch (Exception ex)` that calls the helper and then `HandleProcessingFailureAsync`.
|
||||
- Unit-test the classifier directly (cheaper than driving the full processing path).
|
||||
|
||||
### Excluded
|
||||
- Changing the failure categories themselves.
|
||||
- Adding new categories (deferred to future feature work).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Each known exception still classifies correctly**
|
||||
Given each of the previously-handled exception types (TaskCanceledException after timeout, after user cancel, OperationCanceledException, RateLimitException, HttpRequestException, generic Exception)
|
||||
When `ClassifyRegionFailure` is called with that exception + the appropriate token state
|
||||
Then it returns the same human message and category that the old catch block produced.
|
||||
|
||||
**AC-2: HandleProcessingFailureAsync called once**
|
||||
Given any failure
|
||||
When the catch block runs
|
||||
Then `HandleProcessingFailureAsync` is called exactly once.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass; `RegionTests` (timeout + rate-limit coverage) is unchanged.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Same observable behavior for all currently-tested failure categories.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: subtle category drift**
|
||||
- *Risk*: a refactor may change which token (timeout vs user) was the cancellation source.
|
||||
- *Mitigation*: the classifier takes the `timeoutCts` explicitly so it can disambiguate.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C07).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refactor: replace IServiceProvider with IRegionService in RouteProcessingService
|
||||
|
||||
**Task**: AZ-360_refactor_replace_iserviceprovider_routeproc
|
||||
**Name**: Direct IRegionService injection in RouteProcessingService
|
||||
**Description**: Inject `IRegionService` directly into `RouteProcessingService`; remove the `IServiceProvider` field and the per-iteration scope creation.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-364 (C11) — if C11 ships first, the C08 changes happen as part of C11 and this task may close as duplicate.
|
||||
**Component**: Services.RouteManagement
|
||||
**Tracker**: AZ-360
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:18-22, 105-109, 165-169` injects `IServiceProvider`, then creates a new scope and resolves `IRegionService` inside the loop. `IRegionService` is registered as a singleton (verified in `RegionProcessingServiceCollectionExtensions`), so the scope creation is unnecessary and the service-locator pattern hides the real dependency.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `RouteProcessingService` declares `IRegionService` in its constructor.
|
||||
- No `IServiceProvider` field, no per-iteration `using var scope = _serviceProvider.CreateScope();`.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Update constructor parameter list and field.
|
||||
- Update DI registration in `RouteManagementServiceCollectionExtensions` if the order matters.
|
||||
- Remove the two `using var scope` blocks; call `_regionService.<method>` directly.
|
||||
|
||||
### Excluded
|
||||
- Changing `IRegionService`'s registration lifetime.
|
||||
- Other parts of the C11 god-class decomposition.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Constructor declares dependency**
|
||||
Given the post-refactor `RouteProcessingService`
|
||||
When inspected
|
||||
Then the constructor parameter list contains `IRegionService` (not `IServiceProvider`).
|
||||
|
||||
**AC-2: No scope creation in the loop**
|
||||
Given the post-refactor source
|
||||
When grepping for `_serviceProvider.CreateScope()` in `RouteProcessingService.cs`
|
||||
Then zero matches.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- `IRegionService` must remain a singleton for this change to be safe. If a future change makes it scoped, switch to `IServiceScopeFactory`.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: C11 lands first and folds this in**
|
||||
- *Risk*: duplicate work if C11 (AZ-364) ships before C08.
|
||||
- *Mitigation*: C11 task spec explicitly calls out folding C08; this ticket closes as duplicate when that happens.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C08).
|
||||
@@ -0,0 +1,69 @@
|
||||
# Refactor: idempotent POST contract for caller-supplied GUIDs
|
||||
|
||||
**Task**: AZ-362_refactor_idempotent_post_contract
|
||||
**Name**: Return existing resource on duplicate POST instead of 500
|
||||
**Description**: On `INSERT` conflict for a known caller-supplied `Id` in `/api/satellite/request` and `/api/satellite/route`, return the existing resource (200 OK) instead of bubbling a 500.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-353 (C03) — so the new path doesn't traverse the leaky 500 handler.
|
||||
**Component**: Api + Services.RegionProcessing + Services.RouteManagement
|
||||
**Tracker**: AZ-362
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs:187-211` (`RequestRegion`) and `Program.cs:233-250` (`CreateRoute`) accept `request.Id` from the caller and `INSERT` blindly. A retried POST hits a unique-key conflict at the DB and surfaces as 500 with a leaky message (pre-C03). The client cannot determine whether their first POST succeeded.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Two consecutive POSTs with the same `Id` to either endpoint return 200 OK with the existing resource state.
|
||||
- No duplicate background processing is triggered for retried POSTs.
|
||||
- OpenAPI documents the idempotency contract.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- In `RegionService` (and `RouteService`), detect the unique-key violation on insert; if present, fetch and return the existing row instead of throwing.
|
||||
- Update the API handlers to translate the "existing resource" outcome to 200 OK.
|
||||
- Update OpenAPI / Swagger annotations to document the idempotency.
|
||||
- Add a unit/integration test that POSTs the same `Id` twice and asserts on 200 OK both times.
|
||||
|
||||
### Excluded
|
||||
- Changing the `Id` source (caller-supplied stays).
|
||||
- Adding a separate idempotency-key header.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Region POST is idempotent**
|
||||
Given a `POST /api/satellite/request` that creates a region with `Id = X`
|
||||
When the same payload is POSTed again
|
||||
Then both calls return 200 OK with the same region resource and only one background processing job is queued.
|
||||
|
||||
**AC-2: Route POST is idempotent**
|
||||
Given a `POST /api/satellite/route` that creates a route with `Id = X`
|
||||
When the same payload is POSTed again
|
||||
Then both calls return 200 OK with the same route resource.
|
||||
|
||||
**AC-3: OpenAPI documents the contract**
|
||||
Given the generated `swagger.json`
|
||||
When inspected
|
||||
Then the two endpoints describe the idempotency behavior (200 on duplicate `Id`).
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- HTTP shape preserved on success (200 OK with the same resource body).
|
||||
- 500-on-duplicate becomes 200-on-duplicate (a strict improvement).
|
||||
- No DB schema change.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: differing payloads with the same `Id`**
|
||||
- *Risk*: a caller may POST the same `Id` with a *different* body. We must define behavior.
|
||||
- *Mitigation*: for this task, return the existing resource (treating the second POST as a retry). A future ticket can add 409-on-mismatch detection.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C09).
|
||||
@@ -0,0 +1,52 @@
|
||||
# Refactor: delete write-only counters in RegionRequestQueue
|
||||
|
||||
**Task**: AZ-363_refactor_delete_writeonly_counters
|
||||
**Name**: Remove non-atomic write-only counters
|
||||
**Description**: Delete `_totalEnqueued` and `_totalDequeued` fields plus the two `++` lines in `RegionRequestQueue`.
|
||||
**Complexity**: 1 point
|
||||
**Dependencies**: None
|
||||
**Component**: Services.RegionProcessing
|
||||
**Tracker**: AZ-363
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs:12-13, 28, 38` uses `_totalEnqueued++` and `_totalDequeued++` on `int` fields. The increments are not atomic under concurrent producers/consumers, and the fields are never read anywhere in the codebase. Telemetry-via-`++` is both a thread-safety bug and dead code.
|
||||
|
||||
## Outcome
|
||||
|
||||
- The two fields and the two `++` lines are removed.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Delete the field declarations.
|
||||
- Delete the `++` lines from `Enqueue` / `Dequeue`.
|
||||
|
||||
### Excluded
|
||||
- Adding proper `Meter`/`Counter<long>` telemetry (deferred to a future observability ticket).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Fields and increments removed**
|
||||
Given the post-refactor `RegionRequestQueue.cs`
|
||||
When grepped for `_totalEnqueued` or `_totalDequeued`
|
||||
Then zero matches.
|
||||
|
||||
**AC-2: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No public API change (fields are private).
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: future telemetry need**
|
||||
- *Risk*: someone may want enqueue/dequeue counts later.
|
||||
- *Mitigation*: that's a separate, properly-implemented (atomic, read-out) ticket.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C10).
|
||||
@@ -0,0 +1,82 @@
|
||||
# Refactor: decompose RouteProcessingService god-class into 6 collaborators
|
||||
|
||||
**Task**: AZ-364_refactor_decompose_routeprocessing_service
|
||||
**Name**: Split RouteProcessingService into orchestrator + 6 collaborators
|
||||
**Description**: Extract `RouteRegionMatcher`, `RouteCsvWriter`, `RouteSummaryWriter`, `RouteImageRenderer`, `TilesZipBuilder`, `RegionFileCleaner` from `RouteProcessingService`. The hosted service becomes a thin orchestrator. Folds in C08 (replace `IServiceProvider` with `IRegionService`).
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: AZ-366 (C13 — shared Haversine), AZ-367 (C14 — shared stitcher); folds in AZ-360 (C08)
|
||||
**Component**: Services.RouteManagement
|
||||
**Tracker**: AZ-364
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs` (~750 LOC) is a single `BackgroundService` that does queue polling, region matching, CSV parsing, summary writing, image stitching, geofence-rectangle drawing, route-cross drawing, ZIP creation, and per-region cleanup. The file even hosts a public `TileInfo` POCO at the bottom. Six+ responsibilities; the file is hard to navigate, hard to test, and any change touches multiple concerns.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `RouteProcessingService` becomes a thin orchestrator that polls the queue and dispatches to collaborators.
|
||||
- Six new collaborator classes, each with a single responsibility, each unit-testable without a queue.
|
||||
- `TileInfo` lives in its own file under `Services.RouteManagement` (or `Common/DTO`).
|
||||
- `IRegionService` is injected directly (folds in C08).
|
||||
- Same `BackgroundService` lifecycle, same DB writes, same output files (CSV, summary, stitched image, ZIP).
|
||||
- 37 unit + 5 smoke tests stay green; route image output identical for existing scenarios.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Extract:
|
||||
- `RouteRegionMatcher` (pure: route points + completed regions → ordered region list).
|
||||
- `RouteCsvWriter` (writes route_<id>_ready.csv from `IEnumerable<TileInfo>`).
|
||||
- `RouteSummaryWriter` (writes route_<id>_summary.txt; includes the StringBuilder block).
|
||||
- `RouteImageRenderer` (image stitching + cross/border drawing).
|
||||
- `TilesZipBuilder` (ZIP archive creation; resolves entry names).
|
||||
- `RegionFileCleaner` (deletes per-region CSV/summary/stitched files).
|
||||
- Move `TileInfo` to its own file.
|
||||
- Inject `IRegionService` directly (delete `IServiceProvider` field and the two scope blocks).
|
||||
- Add unit tests for each collaborator in isolation.
|
||||
|
||||
### Excluded
|
||||
- Changing the queue mechanism.
|
||||
- Changing the `BackgroundService` lifecycle.
|
||||
- Changing output file formats (CSV header, summary structure, ZIP layout).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Single-responsibility collaborators**
|
||||
Given the post-refactor source
|
||||
When inspected
|
||||
Then each new collaborator class has one public entry point and is independently unit-testable.
|
||||
|
||||
**AC-2: Same outputs for existing scenarios**
|
||||
Given the existing route smoke tests (BasicRouteTests, ExtendedRouteTests)
|
||||
When they run against the post-refactor code
|
||||
Then the produced CSV, summary, stitched image, and ZIP files are identical (byte-for-byte for CSV/summary; pixel-for-pixel for the image).
|
||||
|
||||
**AC-3: No IServiceProvider in RouteProcessingService**
|
||||
Given the post-refactor source
|
||||
When grepping `RouteProcessingService.cs` for `IServiceProvider`
|
||||
Then zero matches.
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Same `BackgroundService` registration (no DI lifetime changes for the hosted service).
|
||||
- Output file paths and contents preserved.
|
||||
- Architecture Vision (`architecture.md` H2) honored — collaborators stay inside `Services.RouteManagement`.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: image output diff after stitcher refactor**
|
||||
- *Risk*: a subtle pixel diff in the stitched image may break the integration test image comparisons.
|
||||
- *Mitigation*: drive C14 (shared stitcher) first; this task plugs into the result.
|
||||
|
||||
**Risk 2: hidden state shared across the 6 concerns**
|
||||
- *Risk*: the god class may share state in ways that don't surface until extracted.
|
||||
- *Mitigation*: extract one collaborator at a time, run tests between each extraction.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C11).
|
||||
@@ -0,0 +1,71 @@
|
||||
# Refactor: decompose RouteService.CreateRouteAsync 165-LOC method
|
||||
|
||||
**Task**: AZ-365_refactor_decompose_route_create_method
|
||||
**Name**: Split CreateRouteAsync into validator + builder + grid + mapper
|
||||
**Description**: Extract `RouteValidator`, `RoutePointGraphBuilder`, `GeofenceGridCalculator`, and `RouteResponseMapper` from `RouteService.CreateRouteAsync`.
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: None
|
||||
**Component**: Services.RouteManagement
|
||||
**Tracker**: AZ-365
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RouteManagement/RouteService.cs:27-211` is a 165-LOC method that does input validation (4 separate rules, ~25 LOC of nested `if` chains), point-graph construction with `GeoUtils.CalculateIntermediatePoints`, route entity persistence, route-points persistence, geofence polygon validation, geofence grid generation, geofence region requests, and response mapping. Five distinct responsibilities in one method.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `CreateRouteAsync` is reduced to orchestration of the four extracted helpers (~30-50 LOC).
|
||||
- Validation aggregates errors instead of short-circuiting on the first.
|
||||
- Each helper is unit-testable in isolation.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Extract `RouteValidator` (all `ArgumentException`-throwing checks; aggregates errors instead of short-circuiting).
|
||||
- Extract `RoutePointGraphBuilder` (interpolation + sequence numbering — pure).
|
||||
- Extract `GeofenceGridCalculator` (NW/SE → list of region centers — promote the existing private method).
|
||||
- Extract `RouteResponseMapper` (entity → DTO; eliminates duplication with `GetRouteAsync`).
|
||||
- Add unit tests for each helper.
|
||||
|
||||
### Excluded
|
||||
- Changing the response shape.
|
||||
- Changing the persistence calls.
|
||||
- Changing the geofence semantics.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: CreateRouteAsync is now an orchestrator**
|
||||
Given the post-refactor source
|
||||
When `CreateRouteAsync` is inspected
|
||||
Then it is reduced to ~30-50 LOC of `_validator.Validate(...)`, `_pointGraphBuilder.Build(...)`, `_geofenceGridCalculator.GenerateRegions(...)`, `_responseMapper.Map(...)` calls (or equivalent).
|
||||
|
||||
**AC-2: Validation aggregates errors**
|
||||
Given an input with multiple validation failures
|
||||
When validated
|
||||
Then all failures are collected and surfaced as a single 400 response (still typed as `ArgumentException` or a typed `ValidationException`).
|
||||
|
||||
**AC-3: Same persistence + same response**
|
||||
Given any input that succeeds today
|
||||
When the post-refactor code runs
|
||||
Then the same DB rows are created and the same response shape is returned.
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass; `RouteServiceTests` is unchanged.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Same persistence calls.
|
||||
- Same response shape (no DTO change).
|
||||
- 400 status preserved for validation failures.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: aggregated validation surfaces multiple errors but tests assert on first**
|
||||
- *Risk*: existing tests may assert on a specific single-error message.
|
||||
- *Mitigation*: update test assertions to allow a list of errors.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C12).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Refactor: consolidate Haversine + tile-coord parsing into Common/Utils
|
||||
|
||||
**Task**: AZ-366_refactor_consolidate_haversine_parser
|
||||
**Name**: Single Haversine + co-located tile-filename parser
|
||||
**Description**: Delete the duplicate Haversine implementation in `RouteProcessingService` and move `ExtractTileCoordinatesFromFilename` next to `StorageConfig.GetTileFilePath`.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Common + Services.RouteManagement
|
||||
**Tracker**: AZ-366
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:596-608` re-implements `GeoUtils.CalculateDistance(GeoPoint, GeoPoint)` as `CalculateDistance(lat1, lon1, lat2, lon2)`. Lines 610-630 host `ExtractTileCoordinatesFromFilename`, a parser tied to the `tile_{ts}_{x}_{y}.jpg` pattern that's *generated* by `StorageConfig.GetTileFilePath` in another assembly. Coupling by string convention only.
|
||||
|
||||
## Outcome
|
||||
|
||||
- One Haversine implementation in the codebase (in `GeoUtils`).
|
||||
- The tile-filename writer and parser live in the same module.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Delete `RouteProcessingService.CalculateDistance(double, double, double, double)`.
|
||||
- Replace its call sites with `GeoUtils.CalculateDistance(GeoPoint a, GeoPoint b)`.
|
||||
- Move `ExtractTileCoordinatesFromFilename` to live next to `StorageConfig.GetTileFilePath` (or onto `StorageConfig` itself as a static method).
|
||||
- Update consumers' `using` directives.
|
||||
|
||||
### Excluded
|
||||
- Changing the filename pattern.
|
||||
- Changing the `GeoUtils.CalculateDistance` algorithm.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: One Haversine**
|
||||
Given the post-refactor source
|
||||
When grepped for `Math.Sin\(.*lat\)` (or any Haversine-like pattern)
|
||||
Then matches are confined to `GeoUtils.cs`.
|
||||
|
||||
**AC-2: Writer and parser co-located**
|
||||
Given the post-refactor source
|
||||
When `StorageConfig.GetTileFilePath` and `ExtractTileCoordinatesFromFilename` are inspected
|
||||
Then they live in the same file or class.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No public API change beyond moving a static method.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: the parser's empty-catch behavior was already changed by C02**
|
||||
- *Risk*: ordering matters — if C13 ships before C02 the empty catch still exists.
|
||||
- *Mitigation*: implement C02 first (already in Phase 1); C13 inherits the cleaned-up parser.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C13).
|
||||
@@ -0,0 +1,63 @@
|
||||
# Refactor: extract shared TileGridStitcher for region+route image generation
|
||||
|
||||
**Task**: AZ-367_refactor_extract_tile_grid_stitcher
|
||||
**Name**: Shared image stitcher with overlay primitives
|
||||
**Description**: Extract `TileGridStitcher` (+ `DrawCross` and `DrawRectangleBorder` overlay primitives) from `RegionService` and `RouteProcessingService`.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-364 (C11 — route-side caller is restructured at the same time)
|
||||
**Component**: Common (or new Imaging project) + Services.RegionProcessing + Services.RouteManagement
|
||||
**Tracker**: AZ-367
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RegionProcessing/RegionService.cs:240-321` and `SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:453-570` both implement "place tiles in a grid by (TileX, TileY) and overlay markers". Basic placement loop, min/max calculation, and `Image.LoadAsync<Rgb24>` per tile are duplicated. Differences are only the overlays (region: red cross at center; route: yellow geofence rectangles + red crosses at route points).
|
||||
|
||||
## Outcome
|
||||
|
||||
- One `TileGridStitcher` class in `Common` (or a new `SatelliteProvider.Imaging` project).
|
||||
- Region and route image generation paths both use the stitcher.
|
||||
- Output images are pixel-for-pixel identical for existing test scenarios.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add `TileGridStitcher` with `Task<Image<Rgb24>> StitchAsync(IEnumerable<TilePlacement> tiles, CancellationToken ct)`.
|
||||
- Add overlay primitives: `DrawCross(Image, Point, Color, ArmLength)` and `DrawRectangleBorder(Image, Rect, Color, Thickness)` exposed as instance methods.
|
||||
- Replace the duplicate stitcher logic in `RegionService` and (post-C11) the `RouteImageRenderer` collaborator.
|
||||
- Add unit tests for the stitcher with synthetic tiles.
|
||||
|
||||
### Excluded
|
||||
- Changing the SixLabors.ImageSharp version.
|
||||
- Adding new overlay shapes beyond cross + rectangle border.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Single stitcher used by both consumers**
|
||||
Given the post-refactor source
|
||||
When grepped for the per-tile placement loop pattern
|
||||
Then matches are confined to `TileGridStitcher`.
|
||||
|
||||
**AC-2: Pixel-identical outputs**
|
||||
Given the existing region and route smoke-test scenarios
|
||||
When the post-refactor code runs
|
||||
Then the stitched output images are pixel-for-pixel identical to the pre-refactor outputs.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- ImageSharp 3.1.11 dependency preserved.
|
||||
- Output image format (PNG/JPG) unchanged.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: subtle pixel diff after extraction**
|
||||
- *Risk*: refactoring the placement loop may change rounding / interpolation behavior.
|
||||
- *Mitigation*: keep the original arithmetic exactly; rely on the integration tests' image comparison as a guard.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C14).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Refactor: extract shared TileCsvWriter
|
||||
|
||||
**Task**: AZ-368_refactor_extract_tile_csv_writer
|
||||
**Name**: Shared CSV writer for tile lists
|
||||
**Description**: Extract `TileCsvWriter` from `RegionService` and `RouteProcessingService`.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Common + Services.RegionProcessing + Services.RouteManagement
|
||||
**Tracker**: AZ-368
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.RegionProcessing/RegionService.cs:323-334` and `SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:388-404` both write the same CSV header (`latitude,longitude,file_path`) with the same ordering rule (`OrderByDescending(t.Latitude).ThenBy(t.Longitude)`) and the same `F6` numeric format. Two near-identical writers.
|
||||
|
||||
## Outcome
|
||||
|
||||
- One `TileCsvWriter` class in `Common`.
|
||||
- Region and route CSV-writing paths both use it.
|
||||
- Output bytes byte-for-byte identical to pre-refactor.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add `TileCsvWriter` with `Task WriteAsync(string path, IEnumerable<TileRecord> tiles, CancellationToken ct)`.
|
||||
- Add a `TileRecord` record (or use an existing minimal DTO).
|
||||
- Replace the duplicate writers in `RegionService` and `RouteProcessingService` (or its post-C11 `RouteCsvWriter` collaborator).
|
||||
- Unit-test the writer.
|
||||
|
||||
### Excluded
|
||||
- Changing the CSV header or column order.
|
||||
- Changing the numeric format (`F6` stays).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Single writer used by both consumers**
|
||||
Given the post-refactor source
|
||||
When grepped for the CSV header `latitude,longitude,file_path`
|
||||
Then matches are confined to `TileCsvWriter`.
|
||||
|
||||
**AC-2: Output bytes identical**
|
||||
Given the existing region/route scenarios
|
||||
When the post-refactor code runs
|
||||
Then the produced CSV files are byte-for-byte identical.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- CSV header, column order, numeric format unchanged.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: line-ending drift on Windows**
|
||||
- *Risk*: a refactor may swap `\n` for `\r\n` and break byte-identical comparison.
|
||||
- *Mitigation*: use `StreamWriter` with `NewLine = "\n"` (matching current behavior).
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C15).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Refactor: move inline DTOs from Program.cs to Common/DTO
|
||||
|
||||
**Task**: AZ-369_refactor_move_inline_dtos
|
||||
**Name**: Relocate inline DTOs and Swagger filter
|
||||
**Description**: Move six DTOs from `Program.cs` to `SatelliteProvider.Common/DTO/`; move `ParameterDescriptionFilter` to `SatelliteProvider.Api/Swagger/`.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api + Common
|
||||
**Tracker**: AZ-369
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Api/Program.cs:272-353` declares six DTOs (`GetSatelliteTilesResponse`, `SatelliteTile`, `UploadImageRequest`, `SaveResult`, `DownloadTileResponse`, `RequestRegionRequest`) and one Swagger filter (`ParameterDescriptionFilter`) at the bottom of the API host file. SRP: the host file should only wire endpoints; data shapes belong in `Common/DTO/`.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `Program.cs` no longer declares any DTOs or Swagger filters.
|
||||
- Six DTOs live in `SatelliteProvider.Common/DTO/`.
|
||||
- `ParameterDescriptionFilter` lives in `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs`.
|
||||
- Public OpenAPI shape unchanged; only namespaces change.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Move each DTO to its own file under `SatelliteProvider.Common/DTO/`.
|
||||
- Move `ParameterDescriptionFilter` to `SatelliteProvider.Api/Swagger/`.
|
||||
- Update `using` directives in `Program.cs` and any tests that consume the DTOs.
|
||||
|
||||
### Excluded
|
||||
- Changing the DTO field names, types, or order.
|
||||
- Changing the OpenAPI metadata.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Program.cs is endpoint-only**
|
||||
Given the post-refactor `Program.cs`
|
||||
When inspected
|
||||
Then it contains no top-level type declarations beyond endpoint wiring.
|
||||
|
||||
**AC-2: OpenAPI shape unchanged**
|
||||
Given the generated `swagger.json`
|
||||
When diffed against the pre-refactor version
|
||||
Then no fields are added, removed, or reordered.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- DTO names and shapes preserved.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: System.Text.Json source-gen sees a namespace change**
|
||||
- *Risk*: STJ `[JsonSerializable]` attributes (if any) may need updating.
|
||||
- *Mitigation*: grep for any `JsonSerializable` referencing the moved types and update.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C16).
|
||||
@@ -0,0 +1,73 @@
|
||||
# Refactor: status / point-type enums + acceptance_criteria.md RT2 update
|
||||
|
||||
**Task**: AZ-370_refactor_status_pointtype_enums
|
||||
**Name**: Introduce RegionStatus + RoutePointType enums; sync AC RT2
|
||||
**Description**: Replace bare-string status / point-type values with enums; persist via Dapper type handler. Update AC RT2 wording to match the 4-value point-type reality.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: None
|
||||
**Component**: Common + Services.RegionProcessing + Services.RouteManagement + Documentation
|
||||
**Tracker**: AZ-370
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
Status and point-type values are bare strings written to and compared from multiple sites: `RegionService.cs:49,90,140,209` ("queued"/"processing"/"completed"/"failed"), `RouteService.cs:66,100` and `RouteProcessingService.cs:138-140` ("start"/"end"/"action"/"intermediate"). Typos at compile time become runtime bugs. Acceptance criterion RT2 in `_docs/00_problem/acceptance_criteria.md` is also out of sync: it says point types are `original` / `intermediate`, but the lived code uses 4 values (`start` / `end` / `action` / `intermediate`).
|
||||
|
||||
## Outcome
|
||||
|
||||
- Two enums in `SatelliteProvider.Common/Enums/`: `RegionStatus { Queued, Processing, Completed, Failed }` and `RoutePointType { Start, End, Action, Intermediate }`.
|
||||
- All status / point-type compare and write sites use the enums.
|
||||
- Dapper persists them as the existing lowercase strings via a registered `EnumStringTypeHandler<T>`.
|
||||
- AC RT2 in `_docs/00_problem/acceptance_criteria.md` lists all 4 point types.
|
||||
- DB column types and stored values are identical; no migration needed.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add the two enums.
|
||||
- Add a generic `EnumStringTypeHandler<T> : SqlMapper.TypeHandler<T>` and register both instantiations at startup.
|
||||
- Replace string-literal compare/write sites with enum values.
|
||||
- Update AC RT2 wording in `_docs/00_problem/acceptance_criteria.md`.
|
||||
- Add unit tests for the type handler (round-trip).
|
||||
|
||||
### Excluded
|
||||
- Migrating existing rows (values are identical strings).
|
||||
- Renaming any column.
|
||||
- Adding any new status or point-type value.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: All compare/write sites use enums**
|
||||
Given the post-refactor source
|
||||
When grepped for `"queued"` / `"processing"` / `"completed"` / `"failed"` / `"start"` / `"end"` / `"action"` / `"intermediate"` as string literals in service code
|
||||
Then matches are confined to enum-value definitions and the type handler.
|
||||
|
||||
**AC-2: DB round-trip preserves values**
|
||||
Given a row with `status = 'completed'` written by the new code
|
||||
When read back via Dapper
|
||||
Then it round-trips to `RegionStatus.Completed`.
|
||||
|
||||
**AC-3: AC RT2 matches the code**
|
||||
Given the post-refactor `_docs/00_problem/acceptance_criteria.md`
|
||||
When RT2 is inspected
|
||||
Then it lists all 4 point types: `start`, `end`, `action`, `intermediate`.
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- DB stored values unchanged (lowercase strings).
|
||||
- No migration.
|
||||
- Enum names match user-confirmed canonical option (α): `Start, End, Action, Intermediate`.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: a third-party tool reads the DB column directly**
|
||||
- *Risk*: external SQL queries comparing to literal strings still work because we kept the lowercase format.
|
||||
- *Mitigation*: type handler emits exactly the same lowercase strings.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C17).
|
||||
@@ -0,0 +1,67 @@
|
||||
# Refactor: move hardcoded magic numbers to ProcessingConfig / MapConfig
|
||||
|
||||
**Task**: AZ-371_refactor_magic_numbers_to_config
|
||||
**Name**: Promote operational constants to config + forward CT in GetTileByLatLon
|
||||
**Description**: Add config-bound replacements for the magic timeouts, intervals, tolerances, retry delays, and tile-size constants. Forward `CancellationToken` from `Program.cs:GetTileByLatLon` into the downloader (LF-2).
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: None
|
||||
**Component**: Common + Services.* (all)
|
||||
**Tracker**: AZ-371
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
Operational levers are baked into source: `RegionService.cs:94` (5 min timeout), `RouteService.cs:15` (200 m point spacing), `RouteProcessingService.cs:22` (5 s polling), `RouteService.cs:154` + `GoogleMapsDownloaderV2.cs:252` (`0.0001` lat/lon tolerance), `GoogleMapsDownloaderV2.cs:18-21` (TILE_SIZE_PIXELS, MAX_RETRY_DELAY_SECONDS, BASE_RETRY_DELAY_SECONDS, ALLOWED_ZOOM_LEVELS), `TileService.cs:152` (TileSizePixels = 256). Plus `Program.cs:GetTileByLatLon` (line 150) does not forward its `CancellationToken` to `DownloadAndStoreSingleTileAsync` (LF-2).
|
||||
|
||||
## Outcome
|
||||
|
||||
- New config keys: `ProcessingConfig.RegionProcessingTimeout`, `ProcessingConfig.RouteProcessingPollInterval`, `ProcessingConfig.MaxRoutePointSpacingMeters`, `ProcessingConfig.LatLonTolerance`, `MapConfig.TileSizePixels`, `MapConfig.AllowedZoomLevels`, `MapConfig.RetryBaseDelaySeconds`, `MapConfig.RetryMaxDelaySeconds`.
|
||||
- All listed magic numbers replaced by config-bound values; defaults match current literals.
|
||||
- `GetTileByLatLon` request cancellation flows into the downloader.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Extend `ProcessingConfig` and `MapConfig` (or equivalent options classes) with the new keys and defaults.
|
||||
- Update `appsettings.json` and `appsettings.Development.json` with the new keys (with the current literal values as defaults).
|
||||
- Replace the magic-number sites with `_processingConfig.<Key>` / `_mapConfig.<Key>` reads.
|
||||
- Forward `CancellationToken ct` in `Program.cs:GetTileByLatLon` into `DownloadAndStoreSingleTileAsync(..., ct)`.
|
||||
|
||||
### Excluded
|
||||
- Changing default values (must match current literals).
|
||||
- Refactoring HTTP retry policy beyond surfacing the delay constants.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: All listed magic numbers moved to config**
|
||||
Given the post-refactor source
|
||||
When grepped for the literal values `5*60*1000`, `200`, `5000`, `0.0001`, `256` in service code
|
||||
Then matches are confined to config defaults / `MapConfig.cs` / `ProcessingConfig.cs`.
|
||||
|
||||
**AC-2: Defaults preserve behavior**
|
||||
Given the post-refactor build with no overrides
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass with no observable behavior change.
|
||||
|
||||
**AC-3: Cancellation flows through GetTileByLatLon**
|
||||
Given a `GET /api/satellite/tiles/latlon` request
|
||||
When the client cancels mid-flight
|
||||
Then the downloader observes the cancellation and aborts the in-progress download.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Default values must match current literals exactly.
|
||||
- No new public API surface.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: `LatLonTolerance` is consumed by both C18 and C22**
|
||||
- *Risk*: ordering — C22 needs `LatLonTolerance` to exist as config.
|
||||
- *Mitigation*: C22 declares C18 as a dependency.
|
||||
|
||||
**Risk 2: forwarding CT may surface previously-hidden hangs**
|
||||
- *Risk*: tests that assumed the request runs to completion despite client cancel may fail.
|
||||
- *Mitigation*: smoke tests don't currently rely on this; investigate any new failures during implementation.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C18).
|
||||
@@ -0,0 +1,70 @@
|
||||
# Refactor: add dotnet format, NetAnalyzers, Coverlet tooling
|
||||
|
||||
**Task**: AZ-372_refactor_format_analyzers_coverage
|
||||
**Name**: Wire formatter, analyzer ruleset, and coverage runner
|
||||
**Description**: Add `Microsoft.CodeAnalysis.NetAnalyzers` and `coverlet.collector`; add a root `.editorconfig` if absent; wire `dotnet format --verify-no-changes` into the test script.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: None (sequenced last in the run so analyzer noise lands on the post-refactor code)
|
||||
**Component**: Tooling (solution root, all `*.csproj`)
|
||||
**Tracker**: AZ-372
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
The repository has no `dotnet format` gate, no Roslyn analyzers beyond defaults, and no Coverlet for coverage. Style drift and easy-to-miss bugs slip through.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `dotnet format --verify-no-changes` succeeds against the post-refactor codebase.
|
||||
- Test runs emit a coverage report via Coverlet.
|
||||
- An initial NetAnalyzers ruleset (warning severity) is active across all projects.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add `Microsoft.CodeAnalysis.NetAnalyzers` package reference to all `*.csproj` (or via `Directory.Build.props`).
|
||||
- Add `coverlet.collector` to the test projects.
|
||||
- Add a root `.editorconfig` (only if absent) with conservative defaults matching existing code style.
|
||||
- Pick an initial analyzer ruleset (CA1001, CA1051, CA2007, CA2227, etc.) at warning severity.
|
||||
- Wire `dotnet format --verify-no-changes` into `scripts/run-tests.sh` (non-blocking warning if it fails to run, blocking if format violations exist).
|
||||
- Run formatter once and commit any whitespace cleanup as a separate batch.
|
||||
|
||||
### Excluded
|
||||
- Promoting any analyzer warning to error severity in this run.
|
||||
- Adopting a third-party style guide.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Formatter is clean**
|
||||
Given the post-refactor codebase
|
||||
When `dotnet format --verify-no-changes` runs
|
||||
Then it exits 0.
|
||||
|
||||
**AC-2: Coverage runs**
|
||||
Given the test projects
|
||||
When `dotnet test --collect:"XPlat Code Coverage"` runs
|
||||
Then a coverage report is produced.
|
||||
|
||||
**AC-3: Analyzers active but non-blocking**
|
||||
Given the build output
|
||||
When inspected
|
||||
Then NetAnalyzers warnings are visible; no warnings have been promoted to errors; build succeeds.
|
||||
|
||||
**AC-4: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No analyzer warning becomes an error in this run.
|
||||
- `.editorconfig` defaults must not force whitespace churn beyond what one initial format pass produces.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: analyzer flood**
|
||||
- *Risk*: a strict ruleset will surface hundreds of warnings on a 3700-LOC codebase.
|
||||
- *Mitigation*: start with a small named ruleset and expand later.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C19).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Refactor: clarify or drop MapsVersion field
|
||||
|
||||
**Task**: AZ-373_refactor_clarify_mapsversion
|
||||
**Name**: Decide MapsVersion semantics post-C06
|
||||
**Description**: Either drop `MapsVersion` from new tile rows (option a) or document it as a free-form provider-tag and keep it for forensics (option b). Decide alongside C06.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-357 (C06)
|
||||
**Component**: Services.TileDownloader + DataAccess + Common
|
||||
**Tracker**: AZ-373
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.TileDownloader/TileService.cs:154` writes `MapsVersion = $"downloaded_{now:yyyy-MM-dd}"` — the field name says "version" but the value is a creation-date label. The actual cache-key version is the `Version` integer (currently the year). C06 removes `Version` from the cache-key logic; `MapsVersion` is now doubly confusing.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `MapsVersion` semantics are explicit (either removed from new writes or documented).
|
||||
- `tiles.maps_version` DB column is preserved (per `coderule.mdc` — no column drops without explicit confirmation).
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Decide between (a) drop from new writes and stop emitting it in `DownloadTileResponse`, or (b) keep as a forensic free-form provider-tag with documentation in `TileMetadata.cs` and the OpenAPI spec.
|
||||
- Implement the chosen option.
|
||||
- Update `_docs/02_document/components/<owner>.md` to reflect the decision.
|
||||
|
||||
### Excluded
|
||||
- Dropping the `tiles.maps_version` column (deferred per `coderule.mdc`).
|
||||
- Renaming the column.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Semantics are explicit**
|
||||
Given the post-refactor source and docs
|
||||
When inspected
|
||||
Then `MapsVersion` is either no longer written by new code (option a) or documented as a free-form provider-tag (option b).
|
||||
|
||||
**AC-2: HTTP shape decision recorded**
|
||||
Given the chosen option
|
||||
When `DownloadTileResponse` is inspected
|
||||
Then either the field is removed (option a) with the OpenAPI spec updated, or the field stays with documentation (option b).
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- DB column kept either way.
|
||||
- No rename.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: option choice needs implementation discretion**
|
||||
- *Risk*: this task documents both options; the implementer must pick one.
|
||||
- *Mitigation*: default to option (a) — drop from writes, keep column; this is the simpler path. If a downstream consumer relies on the field, fall back to option (b).
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C20).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Refactor: register typed HttpClient for Google Maps in DI
|
||||
|
||||
**Task**: AZ-374_refactor_typed_httpclient_googlemaps
|
||||
**Name**: Named HttpClient for GoogleMapsDownloaderV2
|
||||
**Description**: Register a named `HttpClient` ("GoogleMapsTiles") with default headers + timeout, and have `GoogleMapsDownloaderV2` resolve it via `IHttpClientFactory.CreateClient("GoogleMapsTiles")` everywhere.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: Api + Services.TileDownloader
|
||||
**Tracker**: AZ-374
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs:51, 107, 369` calls `_httpClientFactory.CreateClient()` in three different code paths (session-token fetch, single-tile download, batch-tile download retry lambda) and sets the `User-Agent` header per call. The factory pools `HttpMessageHandler`s correctly, but the per-call header setup is duplicated and error-prone.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Single named-client registration in `Program.cs`.
|
||||
- All three downloader paths resolve `CreateClient("GoogleMapsTiles")`.
|
||||
- Same outbound HTTP behavior.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add `services.AddHttpClient("GoogleMapsTiles", c => { c.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT); c.Timeout = TimeSpan.FromSeconds(<existing default>); });` in `Program.cs`.
|
||||
- Replace three `CreateClient()` calls with `CreateClient("GoogleMapsTiles")`.
|
||||
- Remove the per-call `UserAgent` setup.
|
||||
|
||||
### Excluded
|
||||
- Migrating to a typed `HttpClient` subclass (deferred).
|
||||
- Adding Polly or retry policy at the factory level (existing manual retry stays).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Single registration**
|
||||
Given the post-refactor `Program.cs`
|
||||
When inspected
|
||||
Then a single `AddHttpClient("GoogleMapsTiles", ...)` registration exists.
|
||||
|
||||
**AC-2: Downloader uses the named client**
|
||||
Given the post-refactor downloader
|
||||
When grepped for `CreateClient(`
|
||||
Then all matches use the `"GoogleMapsTiles"` name.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Same outbound `User-Agent` header text.
|
||||
- Same timeout (use the existing implicit default until C18 wires this to config).
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: timeout default surprises a slow path**
|
||||
- *Risk*: setting an explicit timeout may cut off a slow Google Maps response that previously hung indefinitely.
|
||||
- *Mitigation*: pick a generous default (e.g., 60 s) matching observed worst case; tune via C18.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C21).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Refactor: O(N) existing-tile lookup via HashSet
|
||||
|
||||
**Task**: AZ-375_refactor_on_existing_tile_lookup
|
||||
**Name**: HashSet-backed existing-tile membership test
|
||||
**Description**: Replace the linear-scan tolerance check in `GoogleMapsDownloaderV2.DownloadTilesGridAsync` with a `HashSet<(int x, int y, int z)>` lookup keyed on integer tile coordinates.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-371 (C18) — the remaining `0.0001` tolerance becomes a config value where it actually applies (geofence polygon check)
|
||||
**Component**: Services.TileDownloader
|
||||
**Tracker**: AZ-375
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs:245-265` does `existingTiles.FirstOrDefault(t => Math.Abs(t.Latitude - tileCenter.Lat) < 0.0001 && Math.Abs(t.Longitude - tileCenter.Lon) < 0.0001 && t.ZoomLevel == zoomLevel)` per grid cell. That's a linear scan per cell — fine for ~16 tiles, quadratic for big regions. The tolerance is also redundant: tile coordinates at a fixed zoom are integers, so an exact tuple compare is correct.
|
||||
|
||||
## Outcome
|
||||
|
||||
- O(N) lookup via `HashSet<(int TileX, int TileY, int Zoom)>`.
|
||||
- The magic `0.0001` tolerance at this site is gone (the other site — geofence polygon at `RouteService.cs:154` — is a real lat/lon tolerance and stays as config).
|
||||
- Behavior identical for any input that already produces correct output.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Compute the integer `(TileX, TileY, Zoom)` for each row in `existingTiles` once, building the HashSet.
|
||||
- Replace the per-cell `FirstOrDefault` with `set.Contains((tileX, tileY, zoom))`.
|
||||
- Remove the unused `0.0001` literal at this site.
|
||||
|
||||
### Excluded
|
||||
- Touching the geofence polygon tolerance check.
|
||||
- Changing how `existingTiles` is fetched.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: HashSet membership replaces FirstOrDefault**
|
||||
Given the post-refactor source
|
||||
When `DownloadTilesGridAsync` is inspected
|
||||
Then it builds a HashSet once and tests membership per cell.
|
||||
|
||||
**AC-2: Magic 0.0001 removed at this site**
|
||||
Given the post-refactor `GoogleMapsDownloaderV2.cs`
|
||||
When grepped for `0.0001`
|
||||
Then matches are confined to non-tile-lookup contexts (or absent).
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- For inputs that produce correct output today, behavior is identical.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: existingTiles' lat/lon don't quantize cleanly back to integer tile coords**
|
||||
- *Risk*: floating-point drift could mean two rows for "the same tile" produce different integer coords.
|
||||
- *Mitigation*: convert via the same lat/lon→tile formula used to write them; if quantization concerns surface, add a regression test.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C22).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refactor: delete unused FindExistingTileAsync
|
||||
|
||||
**Task**: AZ-376_refactor_delete_findexistingtile
|
||||
**Name**: Delete dead FindExistingTileAsync method
|
||||
**Description**: Remove `FindExistingTileAsync` from `ITileRepository` and `TileRepository` — no callers exist and it takes the obsolete `version` argument C06 is removing.
|
||||
**Complexity**: 1 point
|
||||
**Dependencies**: None (verify with one final grep before deletion)
|
||||
**Component**: DataAccess
|
||||
**Tracker**: AZ-376
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.DataAccess/Repositories/ITileRepository.cs` declares `FindExistingTileAsync(latitude, longitude, tileSizeMeters, zoomLevel, version)` and `SatelliteProvider.DataAccess/Repositories/TileRepository.cs:51-76` implements it, but no caller exists in the codebase. Dead code that also takes the obsolete `version` argument C06 is removing.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Method removed from both the interface and the implementation.
|
||||
- `dotnet build` succeeds across all consumers.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Verify with `grep -r "FindExistingTileAsync"` that no caller exists outside docs and the implementation file.
|
||||
- Delete the method declaration from `ITileRepository`.
|
||||
- Delete the implementation from `TileRepository`.
|
||||
- Update `_dependencies_table.md` if needed.
|
||||
|
||||
### Excluded
|
||||
- Replacing it with anything (no caller wants it).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Method gone**
|
||||
Given the post-refactor source
|
||||
When grepped for `FindExistingTileAsync`
|
||||
Then matches are confined to docs (and even those should be cleaned up if they describe the method as live).
|
||||
|
||||
**AC-2: Build succeeds**
|
||||
Given the post-refactor solution
|
||||
When `dotnet build` runs
|
||||
Then it succeeds with zero errors.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Verify dead-code claim with one final grep at implementation time.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: reflection / DI / dynamic dispatch consumes it**
|
||||
- *Risk*: a hidden consumer via reflection.
|
||||
- *Mitigation*: per `coderule.mdc` dead-code rule, scan for reflection / DI registrations that name the method. None are expected; verify before deletion.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C23).
|
||||
@@ -0,0 +1,68 @@
|
||||
# Refactor: consolidate Earth-geometry constants and magic 111000
|
||||
|
||||
**Task**: AZ-377_refactor_consolidate_earth_constants
|
||||
**Name**: Single home for Earth + tile-pixel constants
|
||||
**Description**: Move Earth-geometry constants (`EarthRadiusMeters`, `EarthEquatorialCircumferenceMeters`, `MetersPerDegreeLatitude`) to `GeoUtils`; move `TileSizePixels` to `MapConfig`. Replace duplicate literals at all sites.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-371 (C18 — TileSizePixels move into config)
|
||||
**Component**: Common + DataAccess + Services.TileDownloader
|
||||
**Tracker**: AZ-377
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
Three Earth-related constants drift across the codebase:
|
||||
- `GeoUtils.EARTH_RADIUS = 6378137` (m).
|
||||
- `GoogleMapsDownloaderV2.CalculateTileSizeInMeters: EARTH_CIRCUMFERENCE_METERS = 40075016.686`.
|
||||
- `TileRepository.GetTilesByRegionAsync: EARTH_CIRCUMFERENCE_METERS = 40075016.686` (duplicate).
|
||||
- `TileRepository.GetTilesByRegionAsync: 111000.0` (meters per degree latitude approximation, twice).
|
||||
- `TILE_SIZE_PIXELS = 256` at three sites (`TileRepository:83`, `GoogleMapsDownloaderV2:18`, `TileService:152`).
|
||||
|
||||
## Outcome
|
||||
|
||||
- Earth constants are named `public const`s on `GeoUtils` (or a sibling `GeoConstants` class).
|
||||
- Per-degree-latitude approximation has a single named source.
|
||||
- `TileSizePixels` lives on `MapConfig` (per C18).
|
||||
- All duplicate literal sites use the named constants.
|
||||
- Numerically identical results.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add `public const double EarthRadiusMeters = 6378137d;`, `public const double EarthEquatorialCircumferenceMeters = 40075016.686d;`, `public const double MetersPerDegreeLatitude = 111000d;` (or refine to a more precise value if the existing usage allows).
|
||||
- Replace literal sites with the named constants.
|
||||
- Move `TileSizePixels` to `MapConfig` (depend on C18).
|
||||
|
||||
### Excluded
|
||||
- Switching to a different Earth model (e.g., WGS84 with full geodesic).
|
||||
- Refining constants beyond what's already in code.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: One source per constant**
|
||||
Given the post-refactor source
|
||||
When grepped for `6378137`, `40075016.686`, `111000`, and (after C18) `256`
|
||||
Then matches are confined to the named-constant declarations or `appsettings.json` defaults.
|
||||
|
||||
**AC-2: Numerically identical results**
|
||||
Given the existing region/route scenarios
|
||||
When the post-refactor code runs
|
||||
Then computed distances and tile-size results are byte-for-byte identical (within IEEE 754 tolerance).
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No numerical drift — use the same literal values as before.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: ordering with C18**
|
||||
- *Risk*: `TileSizePixels` move depends on C18 landing first.
|
||||
- *Mitigation*: this ticket declares C18 as a dependency.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C24).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refactor: remove unused _logger fields from repositories (or use them)
|
||||
|
||||
**Task**: AZ-378_refactor_repo_logger_fields
|
||||
**Name**: Repo loggers — delete or use
|
||||
**Description**: Either delete the unused `_logger` injection from each repository or use it for a slow-query warning. Recommended split: use it in `TileRepository.GetTilesByRegionAsync`; delete elsewhere.
|
||||
**Complexity**: 1 point
|
||||
**Dependencies**: None
|
||||
**Component**: DataAccess
|
||||
**Tracker**: AZ-378
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.DataAccess/Repositories/TileRepository.cs:11`, `RegionRepository.cs:11`, and `RouteRepository.cs` each accept and store `ILogger<TRepo>` but never read the field. Dead injection adds DI cost and noise without value.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Repositories that keep `_logger` actually use it (e.g., slow-query warning).
|
||||
- Repositories that don't keep it have the field, parameter, and DI registration removed.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Recommended: `TileRepository.GetTilesByRegionAsync` measures query duration and emits `_logger.LogWarning` if it exceeds a threshold (e.g., 500 ms — make it a const with a comment).
|
||||
- Delete `_logger` from `RegionRepository`, `RouteRepository`, and any other repository where it isn't used.
|
||||
- Update DI registrations in `Program.cs` for the deleted ones.
|
||||
|
||||
### Excluded
|
||||
- Adding structured query telemetry beyond the slow-query warning.
|
||||
- Promoting the warning to a metric (deferred).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Kept loggers are used**
|
||||
Given the post-refactor source
|
||||
When `_logger` survives in any repository
|
||||
Then it is read at least once in that file.
|
||||
|
||||
**AC-2: Unused loggers are removed**
|
||||
Given each remaining repository
|
||||
When the constructor is inspected
|
||||
Then there is no unused `ILogger<TRepo>` parameter.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No public API change.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: slow-query threshold is arbitrary**
|
||||
- *Risk*: 500 ms may be too tight or too loose.
|
||||
- *Mitigation*: make it a named const with a short comment; tune later as needed.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C25).
|
||||
@@ -0,0 +1,59 @@
|
||||
# Refactor: extract repository SELECT column-list constants
|
||||
|
||||
**Task**: AZ-379_refactor_repo_select_columnlist
|
||||
**Name**: One ColumnList per repository
|
||||
**Description**: Extract a per-repository `private const string ColumnList` and interpolate it into each SELECT.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: None
|
||||
**Component**: DataAccess
|
||||
**Tracker**: AZ-379
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.DataAccess/Repositories/TileRepository.cs` contains the same `id, tile_zoom as TileZoom, tile_x as TileX, ...` column list in 4 SELECTs; `RegionRepository.cs` has 2 such SELECTs; `RouteRepository.cs` similar. Every new column must be added in lockstep across all SELECTs; easy to drift.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Each repository defines `private const string ColumnList = "..."` once and reuses it across all SELECTs.
|
||||
- Generated SQL is byte-for-byte identical.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Per repository: extract the column list once.
|
||||
- Replace each SELECT with `$"SELECT {ColumnList} FROM <table> WHERE ..."`.
|
||||
|
||||
### Excluded
|
||||
- Pulling in `Dapper.Contrib` or a micro-ORM.
|
||||
- Renaming or reordering columns.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: One ColumnList per repository**
|
||||
Given the post-refactor source
|
||||
When each repository is inspected
|
||||
Then it declares `ColumnList` once and references it from every SELECT.
|
||||
|
||||
**AC-2: SQL byte-identical**
|
||||
Given the post-refactor code
|
||||
When SELECT statements are extracted (e.g., via test interception or Dapper logging)
|
||||
Then the generated SQL matches the pre-refactor output.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No new dependencies.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: interpolation introduces an injection vector**
|
||||
- *Risk*: `$"..."` interpolation looks like a SQL-injection foot-gun.
|
||||
- *Mitigation*: `ColumnList` is a `const` defined in source; not user input. Standard Dapper parameterization stays for actual values.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C26).
|
||||
@@ -0,0 +1,58 @@
|
||||
# Refactor: delete GeoUtils.CalculatePolygonDiagonalDistance dead alias
|
||||
|
||||
**Task**: AZ-380_refactor_delete_polygon_diagonal
|
||||
**Name**: Delete dead alias method
|
||||
**Description**: Remove `GeoUtils.CalculatePolygonDiagonalDistance` — pure alias of `CalculateDistance` with no callers.
|
||||
**Complexity**: 1 point
|
||||
**Dependencies**: None (verify with one final grep before deletion)
|
||||
**Component**: Common
|
||||
**Tracker**: AZ-380
|
||||
**Epic**: AZ-350
|
||||
|
||||
## Problem
|
||||
|
||||
`SatelliteProvider.Common/Utils/GeoUtils.cs:129-132` defines `CalculatePolygonDiagonalDistance(GeoPoint nw, GeoPoint se)` which simply returns `CalculateDistance(nw, se)`. Pure alias, no callers in the codebase. Adds API surface for nothing.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Method removed.
|
||||
- `dotnet build` succeeds across all consumers.
|
||||
- 37 unit + 5 smoke tests stay green.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Verify with `grep -r "CalculatePolygonDiagonalDistance"` that no caller exists outside the implementation.
|
||||
- Delete the method.
|
||||
|
||||
### Excluded
|
||||
- Replacing it with anything (no caller wants it).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Method gone**
|
||||
Given the post-refactor source
|
||||
When grepped for `CalculatePolygonDiagonalDistance`
|
||||
Then matches are confined to docs (which should be cleaned up if they describe the method as live).
|
||||
|
||||
**AC-2: Build succeeds**
|
||||
Given the post-refactor solution
|
||||
When `dotnet build` runs
|
||||
Then it succeeds with zero errors.
|
||||
|
||||
**AC-3: Tests stay green**
|
||||
Given the post-refactor build
|
||||
When `scripts/run-tests.sh --smoke` runs
|
||||
Then all 37 unit + 5 smoke scenarios pass.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Verify dead-code claim with one final grep at implementation time.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: reflection / DI / dynamic dispatch consumes it**
|
||||
- *Risk*: hidden consumer via reflection.
|
||||
- *Mitigation*: scan reflection / DI / config for the method name. None are expected; verify before deletion.
|
||||
|
||||
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C27).
|
||||
Reference in New Issue
Block a user