Fix normal basic mayor panse aggregation
This commit is contained in:
@@ -1488,7 +1488,8 @@ public sealed class ChannelScheduleEngine
|
|||||||
private static bool IsNormalPanseMapTemplate(FormatTemplateDefinition template)
|
private static bool IsNormalPanseMapTemplate(FormatTemplateDefinition template)
|
||||||
{
|
{
|
||||||
return template.RecommendedChannel == BroadcastChannel.Normal &&
|
return template.RecommendedChannel == BroadcastChannel.Normal &&
|
||||||
string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal);
|
(string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal) ||
|
||||||
|
template.Name.StartsWith("판세_기초단체장", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeRegionKey(string value)
|
private static string NormalizeRegionKey(string value)
|
||||||
|
|||||||
@@ -1425,7 +1425,7 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
};
|
};
|
||||||
|
|
||||||
var candidateSlotCount = ResolveBroadcastCandidateSlotCount(template, cut, snapshot, sceneVariables);
|
var candidateSlotCount = ResolveBroadcastCandidateSlotCount(template, cut, snapshot, sceneVariables);
|
||||||
if (candidateSlotCount > 0 && !IsTopPanseTemplate(template) && !IsNormalPanseMapTemplate(template))
|
if (candidateSlotCount > 0 && !IsTopPanseTemplate(template) && !IsNormalPanseTemplate(template))
|
||||||
{
|
{
|
||||||
ClearCandidateSlotValues(values, candidateSlotCount, template, sceneVariables);
|
ClearCandidateSlotValues(values, candidateSlotCount, template, sceneVariables);
|
||||||
}
|
}
|
||||||
@@ -1478,6 +1478,12 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return FilterValuesForScene(values, sceneVariables, template);
|
return FilterValuesForScene(values, sceneVariables, template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsNormalBasicMayorPanseTemplate(template))
|
||||||
|
{
|
||||||
|
ApplyNormalBasicMayorPanseValues(values, template, snapshot, templateFolderPath, sceneVariables);
|
||||||
|
return FilterValuesForScene(values, sceneVariables, template);
|
||||||
|
}
|
||||||
|
|
||||||
if (ScheduleTemplatePolicy.IsStaticHistoricalTrendFormat(template.Name))
|
if (ScheduleTemplatePolicy.IsStaticHistoricalTrendFormat(template.Name))
|
||||||
{
|
{
|
||||||
return FilterValuesForScene(values, sceneVariables, template);
|
return FilterValuesForScene(values, sceneVariables, template);
|
||||||
@@ -1667,6 +1673,35 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplyNormalBasicMayorPanseValues(
|
||||||
|
IDictionary<string, string> values,
|
||||||
|
FormatTemplateDefinition template,
|
||||||
|
ElectionDataSnapshot snapshot,
|
||||||
|
string templateFolderPath,
|
||||||
|
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
||||||
|
{
|
||||||
|
var rows = BuildNormalBasicMayorPanseSummaries(snapshot);
|
||||||
|
var slotCount = ResolveNormalBasicMayorPanseSlotCount(sceneVariables);
|
||||||
|
var totalPlaces = ResolveNormalBasicMayorPanseTotalPlaces(snapshot);
|
||||||
|
var totalDisplay = FormattableString.Invariant($"총 {FormatCount(totalPlaces)}곳");
|
||||||
|
|
||||||
|
SetAliases(values, totalDisplay, "총", "총01", "총1");
|
||||||
|
SetAliases(values, snapshot.RegionName, "시도명", "시도명01", "시도명1");
|
||||||
|
|
||||||
|
for (var slot = 1; slot <= slotCount; slot++)
|
||||||
|
{
|
||||||
|
var row = slot <= rows.Length ? rows[slot - 1] : default;
|
||||||
|
var partyLabel = string.IsNullOrWhiteSpace(row.Party) ? string.Empty : row.Party;
|
||||||
|
var colorParty = string.IsNullOrWhiteSpace(row.ColorParty) ? partyLabel : row.ColorParty;
|
||||||
|
var rateDisplay = FormatRate(row.Rate);
|
||||||
|
var graphColorPath = ResolvePartyGraphColorAssetPath(templateFolderPath, template.Name, colorParty);
|
||||||
|
|
||||||
|
SetAliases(values, partyLabel, $"정당명{slot:00}", $"정당명{slot}");
|
||||||
|
SetAliases(values, rateDisplay, $"득표율{slot:00}", $"득표율{slot}");
|
||||||
|
SetOptionalAliases(values, graphColorPath, $"그래프{slot:00}", $"그래프{slot}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static PanseSummary[] BuildNormalPanseMapSummaries(ElectionDataSnapshot snapshot)
|
private static PanseSummary[] BuildNormalPanseMapSummaries(ElectionDataSnapshot snapshot)
|
||||||
{
|
{
|
||||||
var counts = snapshot.Candidates
|
var counts = snapshot.Candidates
|
||||||
@@ -1679,6 +1714,33 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NormalBasicMayorPanseSummary[] BuildNormalBasicMayorPanseSummaries(ElectionDataSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var totalPlaces = ResolveNormalBasicMayorPanseTotalPlaces(snapshot);
|
||||||
|
var counts = snapshot.Candidates
|
||||||
|
.Where(candidate => !IsPanseSummaryCandidate(candidate))
|
||||||
|
.GroupBy(ResolveNormalPanseMapPartyKey, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.Ordinal);
|
||||||
|
|
||||||
|
return NormalPanseMapPartySlots
|
||||||
|
.Select(slot =>
|
||||||
|
{
|
||||||
|
var count = counts.TryGetValue(slot.ColorParty, out var value) ? value : 0;
|
||||||
|
var rate = totalPlaces <= 0
|
||||||
|
? 0d
|
||||||
|
: Math.Round(count * 100d / totalPlaces, 1, MidpointRounding.AwayFromZero);
|
||||||
|
return new NormalBasicMayorPanseSummary(slot.Label, slot.ColorParty, count, rate);
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ResolveNormalBasicMayorPanseTotalPlaces(ElectionDataSnapshot snapshot)
|
||||||
|
{
|
||||||
|
return snapshot.TotalExpectedVotes > 0
|
||||||
|
? snapshot.TotalExpectedVotes
|
||||||
|
: snapshot.Candidates.Count(candidate => !IsPanseSummaryCandidate(candidate));
|
||||||
|
}
|
||||||
|
|
||||||
private static PanseSummary[] BuildTopPanseSummaries(
|
private static PanseSummary[] BuildTopPanseSummaries(
|
||||||
FormatTemplateDefinition template,
|
FormatTemplateDefinition template,
|
||||||
ElectionDataSnapshot snapshot)
|
ElectionDataSnapshot snapshot)
|
||||||
@@ -2175,6 +2237,23 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return maxSlot > 0 ? maxSlot : DefaultNormalPanseMapSlotCount;
|
return maxSlot > 0 ? maxSlot : DefaultNormalPanseMapSlotCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int ResolveNormalBasicMayorPanseSlotCount(IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
||||||
|
{
|
||||||
|
var maxSlot = 0;
|
||||||
|
foreach (var variableName in sceneVariables.Keys)
|
||||||
|
{
|
||||||
|
if ((TryParseSimpleIndexedSlot(variableName, "득표율", out var slot) ||
|
||||||
|
TryParseSimpleIndexedSlot(variableName, "그래프", out slot) ||
|
||||||
|
TryParseSimpleIndexedSlot(variableName, "정당명", out slot)) &&
|
||||||
|
slot > maxSlot)
|
||||||
|
{
|
||||||
|
maxSlot = slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxSlot > 0 ? maxSlot : DefaultNormalPanseMapSlotCount;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryParseSimpleIndexedSlot(string variableName, string prefix, out int slot)
|
private static bool TryParseSimpleIndexedSlot(string variableName, string prefix, out int slot)
|
||||||
{
|
{
|
||||||
slot = 0;
|
slot = 0;
|
||||||
@@ -2303,6 +2382,11 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return BuildNormalPanseMapStyleColorUpdates(template, snapshot, templateFolderPath, sceneVariables);
|
return BuildNormalPanseMapStyleColorUpdates(template, snapshot, templateFolderPath, sceneVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsNormalBasicMayorPanseTemplate(template))
|
||||||
|
{
|
||||||
|
return Array.Empty<KarismaStyleColorUpdate>();
|
||||||
|
}
|
||||||
|
|
||||||
var orderedCandidates = GetOrderedCandidates(template, cut, snapshot, sceneVariables);
|
var orderedCandidates = GetOrderedCandidates(template, cut, snapshot, sceneVariables);
|
||||||
if (orderedCandidates.Length == 0)
|
if (orderedCandidates.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -2402,7 +2486,7 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
string t3CutPath,
|
string t3CutPath,
|
||||||
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
||||||
{
|
{
|
||||||
if (IsNormalPanseMapTemplate(template))
|
if (IsNormalPanseTemplate(template))
|
||||||
{
|
{
|
||||||
return (Array.Empty<KarismaVisibilityUpdate>(), Array.Empty<KarismaVisibilityUpdate>());
|
return (Array.Empty<KarismaVisibilityUpdate>(), Array.Empty<KarismaVisibilityUpdate>());
|
||||||
}
|
}
|
||||||
@@ -2649,7 +2733,7 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
string t3CutPath,
|
string t3CutPath,
|
||||||
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
||||||
{
|
{
|
||||||
if (IsNormalPanseMapTemplate(template))
|
if (IsNormalPanseTemplate(template))
|
||||||
{
|
{
|
||||||
return (Array.Empty<KarismaVisibilityUpdate>(), Array.Empty<KarismaVisibilityUpdate>());
|
return (Array.Empty<KarismaVisibilityUpdate>(), Array.Empty<KarismaVisibilityUpdate>());
|
||||||
}
|
}
|
||||||
@@ -2798,6 +2882,11 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return BuildTopPanseCounterNumberKeyUpdates(template, snapshot, sceneVariables);
|
return BuildTopPanseCounterNumberKeyUpdates(template, snapshot, sceneVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsNormalBasicMayorPanseTemplate(template))
|
||||||
|
{
|
||||||
|
return BuildNormalBasicMayorPanseCounterNumberKeyUpdates(snapshot, sceneVariables);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsNormalPanseMapTemplate(template))
|
if (IsNormalPanseMapTemplate(template))
|
||||||
{
|
{
|
||||||
return BuildNormalPanseMapCounterNumberKeyUpdates(snapshot, sceneVariables);
|
return BuildNormalPanseMapCounterNumberKeyUpdates(snapshot, sceneVariables);
|
||||||
@@ -3011,6 +3100,43 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<KarismaCounterNumberKeyUpdate> BuildNormalBasicMayorPanseCounterNumberKeyUpdates(
|
||||||
|
ElectionDataSnapshot snapshot,
|
||||||
|
IReadOnlyDictionary<string, KarismaSceneVariableDefinition> sceneVariables)
|
||||||
|
{
|
||||||
|
var rows = BuildNormalBasicMayorPanseSummaries(snapshot);
|
||||||
|
var slotCount = ResolveNormalBasicMayorPanseSlotCount(sceneVariables);
|
||||||
|
if (slotCount <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<KarismaCounterNumberKeyUpdate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var updates = new List<KarismaCounterNumberKeyUpdate>(slotCount);
|
||||||
|
for (var slot = 1; slot <= slotCount; slot++)
|
||||||
|
{
|
||||||
|
var rate = slot <= rows.Length ? NormalizeRateForBroadcast(rows[slot - 1].Rate) : 0d;
|
||||||
|
var matched = false;
|
||||||
|
foreach (var variableName in sceneVariables.Keys)
|
||||||
|
{
|
||||||
|
if (!TryParseSimpleIndexedSlot(variableName, "득표율", out var parsedSlot) ||
|
||||||
|
parsedSlot != slot)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.Add(new KarismaCounterNumberKeyUpdate(variableName, 1, rate));
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matched && sceneVariables.Count == 0)
|
||||||
|
{
|
||||||
|
updates.Add(new KarismaCounterNumberKeyUpdate($"득표율{slot:00}", 1, rate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<KarismaCounterNumberKeyUpdate> BuildTurnoutCounterNumberKeyUpdates(
|
private static IReadOnlyList<KarismaCounterNumberKeyUpdate> BuildTurnoutCounterNumberKeyUpdates(
|
||||||
FormatTemplateDefinition template,
|
FormatTemplateDefinition template,
|
||||||
ElectionDataSnapshot snapshot,
|
ElectionDataSnapshot snapshot,
|
||||||
@@ -5304,7 +5430,7 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return variableName is "선거구명" or "시도명" or "개표율" or "투표율" or "전국투표율" or
|
return variableName is "선거구명" or "시도명" or "개표율" or "투표율" or "전국투표율" or "총" or
|
||||||
"기준시" or "기준시01" or "기준시02" or "유권자수" or "유권자수01" or "투표자수" or "투표자수01" or "유확당" ||
|
"기준시" or "기준시01" or "기준시02" or "유권자수" or "유권자수01" or "투표자수" or "투표자수01" or "유확당" ||
|
||||||
NormalPanseMapRegions.Contains(variableName, StringComparer.Ordinal) ||
|
NormalPanseMapRegions.Contains(variableName, StringComparer.Ordinal) ||
|
||||||
IsBottomWinnerBallotNumberVariableName(variableName) ||
|
IsBottomWinnerBallotNumberVariableName(variableName) ||
|
||||||
@@ -5314,6 +5440,7 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
MatchesIndexedVariable(variableName, "개표율") ||
|
MatchesIndexedVariable(variableName, "개표율") ||
|
||||||
MatchesIndexedVariable(variableName, "투표율") ||
|
MatchesIndexedVariable(variableName, "투표율") ||
|
||||||
MatchesIndexedVariable(variableName, "전국투표율") ||
|
MatchesIndexedVariable(variableName, "전국투표율") ||
|
||||||
|
MatchesIndexedVariable(variableName, "총") ||
|
||||||
MatchesIndexedVariable(variableName, "사진") ||
|
MatchesIndexedVariable(variableName, "사진") ||
|
||||||
MatchesIndexedVariable(variableName, "순위") ||
|
MatchesIndexedVariable(variableName, "순위") ||
|
||||||
IsSpecialRankVariableName(variableName) ||
|
IsSpecialRankVariableName(variableName) ||
|
||||||
@@ -6157,6 +6284,17 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal);
|
string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsNormalBasicMayorPanseTemplate(FormatTemplateDefinition template)
|
||||||
|
{
|
||||||
|
return template.RecommendedChannel == BroadcastChannel.Normal &&
|
||||||
|
template.Name.StartsWith("판세_기초단체장", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNormalPanseTemplate(FormatTemplateDefinition template)
|
||||||
|
{
|
||||||
|
return IsNormalPanseMapTemplate(template) || IsNormalBasicMayorPanseTemplate(template);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsPanseEducationTemplate(string templateName)
|
private static bool IsPanseEducationTemplate(string templateName)
|
||||||
{
|
{
|
||||||
return string.Equals(templateName, "판세_교육감", StringComparison.Ordinal);
|
return string.Equals(templateName, "판세_교육감", StringComparison.Ordinal);
|
||||||
@@ -7199,6 +7337,8 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable
|
|||||||
|
|
||||||
private readonly record struct PanseSummary(string Party, int Count);
|
private readonly record struct PanseSummary(string Party, int Count);
|
||||||
|
|
||||||
|
private readonly record struct NormalBasicMayorPanseSummary(string Party, string ColorParty, int Count, double Rate);
|
||||||
|
|
||||||
private readonly record struct NormalPansePartySlot(string Label, string ColorParty);
|
private readonly record struct NormalPansePartySlot(string Label, string ColorParty);
|
||||||
|
|
||||||
private sealed record SceneUpdatePayload(
|
private sealed record SceneUpdatePayload(
|
||||||
|
|||||||
@@ -1136,6 +1136,12 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsNormalBasicMayorPanseTemplate(template))
|
||||||
|
{
|
||||||
|
var panseOptions = await GetScheduleDistrictOptionsAsync(electionType, template, cancellationToken).ConfigureAwait(false);
|
||||||
|
return CreateScheduleRegionGroupOptions(panseOptions, electionType).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
if (ShouldUseTurnoutPhotoRegionLevelOptions(template))
|
if (ShouldUseTurnoutPhotoRegionLevelOptions(template))
|
||||||
{
|
{
|
||||||
return await GetTurnoutPhotoRegionLevelOptionsAsync(turnoutPhotoMode, cancellationToken).ConfigureAwait(false);
|
return await GetTurnoutPhotoRegionLevelOptionsAsync(turnoutPhotoMode, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -1769,10 +1775,12 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsNormalPanseMapTemplate(template))
|
if (IsNormalPanseMapTemplate(template) || IsNormalBasicMayorPanseTemplate(template))
|
||||||
{
|
{
|
||||||
return CreateNormalPanseMapScheduleSnapshotAsync(
|
return CreateNormalPanseMapScheduleSnapshotAsync(
|
||||||
electionType,
|
electionType,
|
||||||
|
template,
|
||||||
|
station,
|
||||||
regionTargets,
|
regionTargets,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -4379,12 +4387,16 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
|
|
||||||
private async Task<ElectionDataSnapshot> CreateNormalPanseMapScheduleSnapshotAsync(
|
private async Task<ElectionDataSnapshot> CreateNormalPanseMapScheduleSnapshotAsync(
|
||||||
string electionType,
|
string electionType,
|
||||||
|
FormatTemplateDefinition template,
|
||||||
|
BroadcastStationProfile station,
|
||||||
IReadOnlyList<ScheduleRegionTarget> regionTargets,
|
IReadOnlyList<ScheduleRegionTarget> regionTargets,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var isBasicMayorPanse = IsNormalBasicMayorPanseTemplate(template);
|
||||||
|
var panseLabel = isBasicMayorPanse ? "기초단체장 판세" : "전국 판세 지도";
|
||||||
if (!SupportsApiDistrictOptions(electionType))
|
if (!SupportsApiDistrictOptions(electionType))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"{electionType} 전국 판세 지도는 현재 SBS API 연동 대상이 아닙니다.");
|
throw new InvalidOperationException($"{electionType} {panseLabel}는 현재 SBS API 연동 대상이 아닙니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedTargets = regionTargets
|
var selectedTargets = regionTargets
|
||||||
@@ -4394,7 +4406,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
if (selectedTargets.Length == 0)
|
if (selectedTargets.Length == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("전국 판세 지도 대상 선거구가 없습니다.");
|
throw new InvalidOperationException($"{panseLabel} 대상 선거구가 없습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var districtOptions = selectedTargets
|
var districtOptions = selectedTargets
|
||||||
@@ -4410,7 +4422,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
if (refreshResults.Count == 0)
|
if (refreshResults.Count == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("전국 판세 지도용 개표 데이터가 없습니다.");
|
throw new InvalidOperationException($"{panseLabel}용 개표 데이터가 없습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetMap = selectedTargets
|
var targetMap = selectedTargets
|
||||||
@@ -4427,7 +4439,7 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
if (leaders.Length == 0)
|
if (leaders.Length == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("전국 판세 지도에 반영할 1위 후보 데이터가 없습니다.");
|
throw new InvalidOperationException($"{panseLabel}에 반영할 1위 후보 데이터가 없습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalVotes = refreshResults.Sum(result => Math.Max(0, result.TotalExpectedVotes));
|
var totalVotes = refreshResults.Sum(result => Math.Max(0, result.TotalExpectedVotes));
|
||||||
@@ -4437,21 +4449,28 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
var countedRate = totalVotes <= 0
|
var countedRate = totalVotes <= 0
|
||||||
? refreshResults.Select(result => result.CountedRate ?? 0).DefaultIfEmpty(0).Max()
|
? refreshResults.Select(result => result.CountedRate ?? 0).DefaultIfEmpty(0).Max()
|
||||||
: Math.Round(countedVotes * 100d / totalVotes, 1, MidpointRounding.AwayFromZero);
|
: Math.Round(countedVotes * 100d / totalVotes, 1, MidpointRounding.AwayFromZero);
|
||||||
var history = ResolvePreElectionHistoryRecords(electionType, "전국", "전국");
|
var regionName = isBasicMayorPanse
|
||||||
|
? ResolveCouncilSeatAggregateRegionLabel(station, selectedTargets)
|
||||||
|
: "전국";
|
||||||
|
var districtName = isBasicMayorPanse && selectedTargets.Length == 1
|
||||||
|
? selectedTargets[0].DisplayName
|
||||||
|
: regionName;
|
||||||
|
var totalPlaces = selectedTargets.Length;
|
||||||
|
var history = ResolvePreElectionHistoryRecords(electionType, regionName, districtName);
|
||||||
|
|
||||||
return new ElectionDataSnapshot
|
return new ElectionDataSnapshot
|
||||||
{
|
{
|
||||||
BroadcastPhase = BroadcastPhase.Counting,
|
BroadcastPhase = BroadcastPhase.Counting,
|
||||||
ElectionType = electionType,
|
ElectionType = electionType,
|
||||||
DistrictName = "전국",
|
DistrictName = districtName,
|
||||||
DistrictCode = string.Empty,
|
DistrictCode = isBasicMayorPanse && selectedTargets.Length == 1 ? selectedTargets[0].DistrictCode : string.Empty,
|
||||||
RegionName = "전국",
|
RegionName = regionName,
|
||||||
ElectionDistrictName = "전국",
|
ElectionDistrictName = isBasicMayorPanse && selectedTargets.Length == 1 ? selectedTargets[0].DistrictName : regionName,
|
||||||
Candidates = leaders,
|
Candidates = leaders,
|
||||||
TotalExpectedVotes = totalVotes,
|
TotalExpectedVotes = isBasicMayorPanse ? totalPlaces : totalVotes,
|
||||||
TurnoutVotes = turnoutVotes,
|
TurnoutVotes = isBasicMayorPanse ? totalPlaces : turnoutVotes,
|
||||||
CountedVotesFromApi = countedVotes,
|
CountedVotesFromApi = isBasicMayorPanse ? leaders.Length : countedVotes,
|
||||||
RemainingVotesFromApi = remainingVotes,
|
RemainingVotesFromApi = isBasicMayorPanse ? Math.Max(0, totalPlaces - leaders.Length) : remainingVotes,
|
||||||
CountedRateFromApi = countedRate,
|
CountedRateFromApi = countedRate,
|
||||||
ReceivedAt = refreshResults.Select(result => result.ReceivedAt).DefaultIfEmpty(DateTimeOffset.Now).Max(),
|
ReceivedAt = refreshResults.Select(result => result.ReceivedAt).DefaultIfEmpty(DateTimeOffset.Now).Max(),
|
||||||
HistoricalTurnoutHistory = history.TurnoutHistory,
|
HistoricalTurnoutHistory = history.TurnoutHistory,
|
||||||
@@ -5035,6 +5054,12 @@ public sealed class DataViewModel : ObservableObject, IDataRefreshGate, IDisposa
|
|||||||
string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal);
|
string.Equals(template.Name, "판세_광역단체장", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsNormalBasicMayorPanseTemplate(FormatTemplateDefinition? template)
|
||||||
|
{
|
||||||
|
return template?.RecommendedChannel == BroadcastChannel.Normal &&
|
||||||
|
template.Name.StartsWith("판세_기초단체장", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsPanseEducationTemplate(FormatTemplateDefinition template)
|
private static bool IsPanseEducationTemplate(FormatTemplateDefinition template)
|
||||||
{
|
{
|
||||||
return string.Equals(template.Name, "판세_교육감", StringComparison.Ordinal);
|
return string.Equals(template.Name, "판세_교육감", StringComparison.Ordinal);
|
||||||
|
|||||||
Reference in New Issue
Block a user