mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-06 14:42:09 +00:00
fix(dashboard): resolve region zones from full state names (SH-348)
Locations store State as either a two-letter postal code or a full state name (the location list filter expands codes to both forms via UsStateCodes.ExpandStorageValues, and dev fixtures carry State = "indiana"). DashboardRegions.Resolve only matched the two-letter key, so every location stored as a full name fell into Unmapped/Other and the East/Central/West/ California bars undercounted. Normalise the stored value through a new UsStateCodes.ToCode, which accepts a code or a full name and returns the canonical postal code, before the zone lookup. Extend the region test with full-name storage forms and add an invariant asserting every US state and its full-name form resolves to a canonical bucket.
This commit is contained in:
parent
d4afcced87
commit
22308f21f6
3 changed files with 53 additions and 4 deletions
|
|
@ -309,11 +309,17 @@ public class DashboardServiceTests
|
||||||
ctx.Locations.AddRange(
|
ctx.Locations.AddRange(
|
||||||
new Locations { Id = 1, State = "NY" },
|
new Locations { Id = 1, State = "NY" },
|
||||||
new Locations { Id = 2, State = "ca" },
|
new Locations { Id = 2, State = "ca" },
|
||||||
new Locations { Id = 3, State = "XX" });
|
new Locations { Id = 3, State = "XX" },
|
||||||
|
new Locations { Id = 4, State = "indiana" },
|
||||||
|
new Locations { Id = 5, State = "NEW YORK" },
|
||||||
|
new Locations { Id = 6, State = "California" });
|
||||||
ctx.workOrders.AddRange(
|
ctx.workOrders.AddRange(
|
||||||
new WorkOrder { AccountId = 1, LocationId = 1 },
|
new WorkOrder { AccountId = 1, LocationId = 1 },
|
||||||
new WorkOrder { AccountId = 1, LocationId = 2 },
|
new WorkOrder { AccountId = 1, LocationId = 2 },
|
||||||
new WorkOrder { AccountId = 1, LocationId = 3 },
|
new WorkOrder { AccountId = 1, LocationId = 3 },
|
||||||
|
new WorkOrder { AccountId = 1, LocationId = 4 },
|
||||||
|
new WorkOrder { AccountId = 1, LocationId = 5 },
|
||||||
|
new WorkOrder { AccountId = 1, LocationId = 6 },
|
||||||
new WorkOrder { AccountId = 1, LocationId = null });
|
new WorkOrder { AccountId = 1, LocationId = null });
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
@ -322,6 +328,27 @@ public class DashboardServiceTests
|
||||||
|
|
||||||
result.Items.Select(row => row.Region).Should().Equal(
|
result.Items.Select(row => row.Region).Should().Equal(
|
||||||
"East", "Central", "West", "California", "Unmapped/Other");
|
"East", "Central", "West", "California", "Unmapped/Other");
|
||||||
result.Items.Select(row => row.WorkOrderCount).Should().Equal(1, 0, 0, 1, 2);
|
// NY + "NEW YORK" -> East; "indiana" -> Central; "ca" + "California" -> California;
|
||||||
|
// "XX" + unassigned location -> Unmapped/Other. Full-name storage forms must not
|
||||||
|
// fall through to Unmapped/Other (SH-348 review).
|
||||||
|
result.Items.Select(row => row.WorkOrderCount).Should().Equal(2, 1, 0, 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Resolve_MapsEveryUsStateAndItsFullNameToACanonicalBucket()
|
||||||
|
{
|
||||||
|
foreach (var code in UsStateCodes.All)
|
||||||
|
{
|
||||||
|
var byCode = DashboardRegions.Resolve(code);
|
||||||
|
byCode.Should().NotBe(
|
||||||
|
"Unmapped/Other",
|
||||||
|
$"state code {code} must belong to a canonical region");
|
||||||
|
|
||||||
|
var fullName = UsStateCodes.ExpandStorageValues(new[] { code })
|
||||||
|
.First(value => value != code);
|
||||||
|
DashboardRegions.Resolve(fullName).Should().Be(
|
||||||
|
byCode,
|
||||||
|
$"the full-name storage form of {code} must resolve to the same region as the code");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ public static class DashboardRegions
|
||||||
|
|
||||||
public static string Resolve(string? state)
|
public static string Resolve(string? state)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(state))
|
var code = UsStateCodes.ToCode(state);
|
||||||
|
if (code is null)
|
||||||
return "Unmapped/Other";
|
return "Unmapped/Other";
|
||||||
|
|
||||||
return StateToRegion.TryGetValue(state.Trim().ToUpperInvariant(), out var region)
|
return StateToRegion.TryGetValue(code, out var region)
|
||||||
? region
|
? region
|
||||||
: "Unmapped/Other";
|
: "Unmapped/Other";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,29 @@ namespace SeaHaven.Services.Helpers
|
||||||
["WY"] = "WYOMING"
|
["WY"] = "WYOMING"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, string> CodesByName =
|
||||||
|
Names.ToDictionary(pair => pair.Value, pair => pair.Key, StringComparer.Ordinal);
|
||||||
|
|
||||||
public static IReadOnlySet<string> All { get; } = new HashSet<string>(Names.Keys, StringComparer.Ordinal);
|
public static IReadOnlySet<string> All { get; } = new HashSet<string>(Names.Keys, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalises a stored state value to its two-letter postal code. Accepts either a
|
||||||
|
/// postal code ("IN") or a full state name ("Indiana"), case-insensitively, matching
|
||||||
|
/// the storage forms produced by <see cref="ExpandStorageValues"/>. Returns null when
|
||||||
|
/// the value is blank or is not a recognised US state.
|
||||||
|
/// </summary>
|
||||||
|
public static string? ToCode(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var normalized = value.Trim().ToUpperInvariant();
|
||||||
|
if (All.Contains(normalized))
|
||||||
|
return normalized;
|
||||||
|
|
||||||
|
return CodesByName.TryGetValue(normalized, out var code) ? code : null;
|
||||||
|
}
|
||||||
|
|
||||||
public static IReadOnlyCollection<string> ExpandStorageValues(IEnumerable<string> codes)
|
public static IReadOnlyCollection<string> ExpandStorageValues(IEnumerable<string> codes)
|
||||||
{
|
{
|
||||||
return codes
|
return codes
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue