-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
207 lines (184 loc) · 8.8 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Linq;
using Spectre.Console;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using static ElectionComparator.PartyColors;
using ElectionComparator.ElectionData.Format2016;
using ElectionComparator.ElectionData.Format2020;
namespace ElectionComparitor
{
class Program
{
private static bool StateIsValid(string input)
{
var states = new List<string> { "AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY" };
return states.Contains(input.ToUpper());
}
private static decimal CalculatePercentChange(int originalValue, int newValue)
{
decimal change = ((newValue - originalValue) / (decimal) originalValue) * 100;
return Math.Round(change, 2);
}
private static string DetermineWinner(CountyResults county, Elections election)
{
if (county.Elections[election].Results["D"] > county.Elections[election].Results["R"])
{
return "D";
}
return "R";
}
public record PartyElectionData
{
public int Votes { get; set; }
public PercentChange PercentChange { get; set; }
}
public class PercentChange
{
public PartyColorTheme _textColors;
public decimal _value;
public PercentChange(PartyColorTheme textColors, decimal value)
{
_textColors = textColors;
_value = value;
}
public string TextColor => _value > 0 ? _textColors.Positive : _textColors.Negative;
private string Operator => _value > 0 ? "+" : "";
public string FormatttedPercentage => $"[{TextColor}]{Operator}{_value}%[/]";
}
public record CountyRowData
{
public string CountyName { get; set; }
public string CountyTextColor { get; set; }
public int PercentReporting { get; set; }
public PartyElectionData RepublicanData { get; set; }
public PartyElectionData DemocratData { get; set; }
public string WasCountyFlipped { get; set; }
}
public record RawElectionResults
{
public List<ElectionComparator.ElectionData.Format2020.County> RawData2020 { get; init; }
public ElectionResults RawData2016 { get; init; }
}
private static string CountyWasFlipped(string winner2016, string winner2020) => winner2016 != winner2020 ? "Yes" : "No";
private static async Task<RawElectionResults> GetElectionDataFromWeb(string state)
{
var client = new HttpClient();
try
{
var data2020 = await client.GetFromJsonAsync<List<ElectionComparator.ElectionData.Format2020.County>>(
$"https://politics-elex-results.data.api.cnn.io/results/view/2020-county-races-PG-{state}.json"
).ConfigureAwait(false);
var data2016 = await client.GetFromJsonAsync<ElectionResults>(
$"https://data.cnn.com/ELECTION/2016/{state}/county/P_county.json"
).ConfigureAwait(false);
return new RawElectionResults { RawData2016 = data2016, RawData2020 = data2020 };
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to retrieve election data: {ex.Message}");
throw;
}
}
private static CountyRowData CreateCountyRow(CountyResults county)
{
var winner2016 = DetermineWinner(county, Elections.Presidential2016);
var winner2020 = DetermineWinner(county, Elections.Presidential2020);
var countyNameColor = winner2020 == "R" ? RepublicanColors.Neutral : DemocratColors.Neutral;
var rPercentChange = CalculatePercentChange(
county.Elections[Elections.Presidential2016].Results["R"],
county.Elections[Elections.Presidential2020].Results["R"]
);
var dPercentChange = CalculatePercentChange(
county.Elections[Elections.Presidential2016].Results["D"],
county.Elections[Elections.Presidential2020].Results["D"]
);
var data = new CountyRowData
{
CountyName = county.Name,
CountyTextColor = countyNameColor,
PercentReporting = county.PercentReporting,
RepublicanData = new PartyElectionData
{
Votes = county.Elections[Elections.Presidential2020].Results["R"],
PercentChange = new PercentChange(RepublicanColors, rPercentChange)
},
DemocratData = new PartyElectionData
{
Votes = county.Elections[Elections.Presidential2020].Results["D"],
PercentChange = new PercentChange(DemocratColors, dPercentChange)
},
WasCountyFlipped = CountyWasFlipped(winner2016, winner2020)
};
return data;
}
public static int Main(string[] args)
{
var rootCommand = new RootCommand
{
new Option<string>(
"--state",
getDefaultValue: () => "TX",
description: "The two letter state code"),
};
rootCommand.Handler = CommandHandler.Create<string>(async (state) =>
{
if (!StateIsValid(state))
{
Console.Error.WriteLine($"{state} is not a valid US state code.");
Environment.Exit(-1);
}
var rawResults = await GetElectionDataFromWeb(state.ToUpper());
var counties = rawResults.RawData2020.ConvertAll(c => {
var county = new CountyResults
{
Name = c.Name,
PercentReporting = c.PercentReporting
};
county.Elections[Elections.Presidential2020] = new Election();
foreach (var candidate in c.Candidates)
{
county.Elections[Elections.Presidential2020].Results[candidate.Party] = candidate.Votes;
}
return county;
});
foreach (var r in rawResults.RawData2016.Counties)
{
var county = counties.Where(c => c.Name == r.Name).FirstOrDefault();
county.Elections[Elections.Presidential2016] = new Election();
foreach(var candidate in r.Race.Candidates.Where(cand => cand.Party == "D" || cand.Party == "R"))
{
county.Elections[Elections.Presidential2016].Results[candidate.Party] = candidate.Votes;
}
}
var grid = new Table { Border = TableBorder.Rounded };
grid.AddColumn(new TableColumn("[white]County[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]% Reporting[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]Rep. 2020 votes[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]% Change from 2016[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]Dem. 2020 votes[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]% Change from 2016[/]") { NoWrap = true });
grid.AddColumn(new TableColumn("[white]Flipped?[/]") { NoWrap = true });
foreach (var county in counties)
{
var row = CreateCountyRow(county);
grid.AddRow(
$"[{row.CountyTextColor}]{row.CountyName}[/]",
$"[white]{row.PercentReporting}[/]",
$"[{RepublicanColors.Neutral}]{row.RepublicanData.Votes}[/]",
$"{row.RepublicanData.PercentChange.FormatttedPercentage}",
$"[{DemocratColors.Neutral}]{row.DemocratData.Votes}[/]",
$"{row.DemocratData.PercentChange.FormatttedPercentage}",
$"[white]{row.WasCountyFlipped}[/]"
);
}
AnsiConsole.Render(grid);
});
return rootCommand.InvokeAsync(args).Result;
}
}
}