@dra2020/district-analytics 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/district-analytics.js +1315 -1205
- package/package.json +6 -3
- package/.prettierrc +0 -5
- package/dist/_api.d.ts +0 -27
- package/dist/_data.d.ts +0 -130
- package/dist/analyze.d.ts +0 -4
- package/dist/cli.js +0 -12091
- package/dist/cli.js.map +0 -1
- package/dist/cohesive.d.ts +0 -4
- package/dist/compact.d.ts +0 -5
- package/dist/constants.d.ts +0 -6
- package/dist/district-analytics.js.map +0 -1
- package/dist/equal.d.ts +0 -4
- package/dist/geofeature.d.ts +0 -3
- package/dist/index.d.ts +0 -2
- package/dist/minority.d.ts +0 -3
- package/dist/political.d.ts +0 -8
- package/dist/preprocess.d.ts +0 -2
- package/dist/report.d.ts +0 -15
- package/dist/settings.d.ts +0 -5
- package/dist/src/_api.d.ts +0 -27
- package/dist/src/_data.d.ts +0 -130
- package/dist/src/analyze.d.ts +0 -4
- package/dist/src/cohesive.d.ts +0 -4
- package/dist/src/compact.d.ts +0 -5
- package/dist/src/constants.d.ts +0 -6
- package/dist/src/equal.d.ts +0 -4
- package/dist/src/geofeature.d.ts +0 -3
- package/dist/src/index.d.ts +0 -2
- package/dist/src/minority.d.ts +0 -3
- package/dist/src/political.d.ts +0 -8
- package/dist/src/preprocess.d.ts +0 -2
- package/dist/src/report.d.ts +0 -15
- package/dist/src/settings.d.ts +0 -5
- package/dist/src/types.d.ts +0 -110
- package/dist/src/utils.d.ts +0 -28
- package/dist/src/valid.d.ts +0 -8
- package/dist/test/_cli.d.ts +0 -5
- package/dist/types.d.ts +0 -110
- package/dist/utils.d.ts +0 -28
- package/dist/valid.d.ts +0 -8
- package/jestconfig.json +0 -14
- package/lib/HelloWorld.d.ts +0 -3
- package/lib/HelloWorld.js +0 -11
- package/lib/_api.js +0 -91
- package/lib/_api.js.map +0 -1
- package/lib/_cli.js +0 -434
- package/lib/_cli.js.map +0 -1
- package/lib/_data.js +0 -425
- package/lib/_data.js.map +0 -1
- package/lib/analyze.d.ts +0 -3
- package/lib/analyze.js +0 -69
- package/lib/analyze.js.map +0 -1
- package/lib/api.d.ts +0 -34
- package/lib/api.js +0 -117
- package/lib/api.js.map +0 -1
- package/lib/cli.d.ts +0 -1
- package/lib/cli.js +0 -386
- package/lib/cli.js.map +0 -1
- package/lib/cohesive.d.ts +0 -4
- package/lib/cohesive.js +0 -132
- package/lib/cohesive.js.map +0 -1
- package/lib/compact.d.ts +0 -4
- package/lib/compact.js +0 -183
- package/lib/compact.js.map +0 -1
- package/lib/constants.js +0 -367
- package/lib/constants.js.map +0 -1
- package/lib/data.js +0 -188
- package/lib/data.js.map +0 -1
- package/lib/equal.d.ts +0 -4
- package/lib/equal.js +0 -59
- package/lib/equal.js.map +0 -1
- package/lib/features.js +0 -19
- package/lib/features.js.map +0 -1
- package/lib/geofeature.js +0 -112
- package/lib/geofeature.js.map +0 -1
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -11
- package/lib/index.js.map +0 -1
- package/lib/minority.d.ts +0 -3
- package/lib/minority.js +0 -61
- package/lib/minority.js.map +0 -1
- package/lib/political.d.ts +0 -7
- package/lib/political.js +0 -88
- package/lib/political.js.map +0 -1
- package/lib/preprocess.d.ts +0 -4
- package/lib/preprocess.js +0 -101
- package/lib/preprocess.js.map +0 -1
- package/lib/report.d.ts +0 -14
- package/lib/report.js +0 -817
- package/lib/report.js.map +0 -1
- package/lib/sample.d.ts +0 -1
- package/lib/sample.js +0 -32
- package/lib/sample.js.map +0 -1
- package/lib/scorecard.d.ts +0 -4
- package/lib/scorecard.js +0 -237
- package/lib/scorecard.js.map +0 -1
- package/lib/settings.d.ts +0 -5
- package/lib/settings.js +0 -18
- package/lib/settings.js.map +0 -1
- package/lib/types.d.ts +0 -125
- package/lib/types.js +0 -7
- package/lib/types.js.map +0 -1
- package/lib/utils.d.ts +0 -20
- package/lib/utils.js +0 -223
- package/lib/utils.js.map +0 -1
- package/lib/valid.d.ts +0 -5
- package/lib/valid.js +0 -230
- package/lib/valid.js.map +0 -1
- package/main.js +0 -4
- package/src/_api.ts +0 -121
- package/src/_data.ts +0 -595
- package/src/analyze.ts +0 -92
- package/src/cohesive.ts +0 -156
- package/src/compact.ts +0 -208
- package/src/constants.ts +0 -371
- package/src/equal.ts +0 -75
- package/src/geofeature.ts +0 -138
- package/src/index.ts +0 -6
- package/src/minority.ts +0 -70
- package/src/political.ts +0 -114
- package/src/preprocess.ts +0 -132
- package/src/report.ts +0 -1022
- package/src/settings.ts +0 -20
- package/src/types.ts +0 -185
- package/src/utils.ts +0 -245
- package/src/valid.ts +0 -275
- package/tsconfig.json +0 -25
- package/tslint.json +0 -3
- package/types/polygon-clipping/index.d.ts +0 -1
- package/webpack.config.js +0 -73
package/src/_data.ts
DELETED
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// DATA ABSTRACTION LAYER
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as T from './types'
|
|
6
|
-
import * as U from './utils';
|
|
7
|
-
import * as S from './settings';
|
|
8
|
-
|
|
9
|
-
import { AnalyticsSession } from './_api';
|
|
10
|
-
import { isConnected, isEmbedded } from './valid';
|
|
11
|
-
import { extractDistrictProperties } from './compact';
|
|
12
|
-
import { fptpWin } from './political'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// DISTRICT STATISTICS
|
|
16
|
-
|
|
17
|
-
// Indexes for statistics fields in Districts
|
|
18
|
-
// NOTE - Not a const, so the number can be determined dynamically
|
|
19
|
-
export enum DistrictField {
|
|
20
|
-
TotalPop, // Display
|
|
21
|
-
PopDevPct, // Display
|
|
22
|
-
bEqualPop, // Display
|
|
23
|
-
bNotEmpty, // Display
|
|
24
|
-
bContiguous, // Display
|
|
25
|
-
bNotEmbedded, // Display
|
|
26
|
-
CountySplits,
|
|
27
|
-
DemVotes,
|
|
28
|
-
RepVotes,
|
|
29
|
-
TwoPartyVote, // Two-party total ()= Dem + Rep) not all votes!
|
|
30
|
-
DemPct, // Display (also the "VPI," by convention")
|
|
31
|
-
RepPct, // Display
|
|
32
|
-
DemSeat,
|
|
33
|
-
TotalVAP, // VAP or CVAP ...
|
|
34
|
-
MinorityPop, // Derived
|
|
35
|
-
WhitePop,
|
|
36
|
-
BlackPop,
|
|
37
|
-
HispanicPop,
|
|
38
|
-
PacificPop,
|
|
39
|
-
AsianPop,
|
|
40
|
-
NativePop,
|
|
41
|
-
WhitePct, // Display
|
|
42
|
-
MinorityPct, // Display
|
|
43
|
-
BlackPct, // Display
|
|
44
|
-
HispanicPct, // Display
|
|
45
|
-
PacificPct, // Display
|
|
46
|
-
AsianPct, // Display
|
|
47
|
-
NativePct // Display
|
|
48
|
-
|
|
49
|
-
// 1 - MORE ...
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// The fields to display in a District Statistics pane
|
|
53
|
-
export const DisplayFields = [
|
|
54
|
-
DistrictField.TotalPop,
|
|
55
|
-
DistrictField.PopDevPct,
|
|
56
|
-
DistrictField.bEqualPop,
|
|
57
|
-
DistrictField.bNotEmpty,
|
|
58
|
-
DistrictField.bContiguous,
|
|
59
|
-
DistrictField.bNotEmbedded,
|
|
60
|
-
DistrictField.DemPct,
|
|
61
|
-
DistrictField.WhitePct,
|
|
62
|
-
DistrictField.MinorityPct,
|
|
63
|
-
DistrictField.BlackPct,
|
|
64
|
-
DistrictField.HispanicPct,
|
|
65
|
-
DistrictField.PacificPct,
|
|
66
|
-
DistrictField.AsianPct,
|
|
67
|
-
DistrictField.NativePct
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
export class Districts {
|
|
71
|
-
_session: AnalyticsSession;
|
|
72
|
-
|
|
73
|
-
_shapes: T.GeoFeatureCollection;
|
|
74
|
-
_geoProperties = {} as T.DistrictProperties;
|
|
75
|
-
|
|
76
|
-
statistics: any[][];
|
|
77
|
-
|
|
78
|
-
constructor(s: AnalyticsSession, ds: T.GeoFeatureCollection) {
|
|
79
|
-
this._session = s;
|
|
80
|
-
|
|
81
|
-
this._shapes = ds;
|
|
82
|
-
this.statistics = this.initStatistics();
|
|
83
|
-
}
|
|
84
|
-
getShape(i: number): T.GeoFeature { return this._shapes.features[i]; }
|
|
85
|
-
getGeoProperties(i: number): any { return this._geoProperties[i]; }
|
|
86
|
-
setGeoProperties(i: number, p: T.DistrictShapeProperties): void { this._geoProperties[i] = p; }
|
|
87
|
-
|
|
88
|
-
numberOfColumns(): number { return U.countEnumValues(DistrictField); }
|
|
89
|
-
// +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
|
|
90
|
-
// state-level values. Real districts are 1–N.
|
|
91
|
-
numberOfRows(): number { return this._session.state.nDistricts + 2; }
|
|
92
|
-
numberOfWorkingDistricts(): number { return this._session.state.nDistricts + 1; }
|
|
93
|
-
|
|
94
|
-
// This is the core statistics 2D matrix:
|
|
95
|
-
// Fields on the outside, districts on the inside
|
|
96
|
-
initStatistics(): any[][] {
|
|
97
|
-
let nRows = this.numberOfRows();
|
|
98
|
-
let nCols = this.numberOfColumns();
|
|
99
|
-
|
|
100
|
-
let outer = U.initArray(nCols, undefined);
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < nCols; i++) {
|
|
103
|
-
outer[i] = U.initArray(nRows, null);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return outer;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// This is the workhorse computational routine!
|
|
110
|
-
//
|
|
111
|
-
// TODO - Optimize for getting multiple properties from the same feature?
|
|
112
|
-
// TODO - Optimize by only re-calc'ing districts that have changed?
|
|
113
|
-
// In this case, special attention to getting county-splits right.
|
|
114
|
-
// TODO - Optimize by asyn'ing this?
|
|
115
|
-
// TODO - Is there a way to do this programmatically off data? Does it matter?
|
|
116
|
-
recalcStatistics(bLog: boolean = false): void {
|
|
117
|
-
// Compute these once per recalc cycle
|
|
118
|
-
let targetSize = this._session.state.totalPop / this._session.state.nDistricts;
|
|
119
|
-
let deviationThreshold = this._session.populationDeviationThreshold();
|
|
120
|
-
let planByDistrict = this._session.plan.byDistrictID();
|
|
121
|
-
let plan = this._session.plan;
|
|
122
|
-
let graph = this._session.graph;
|
|
123
|
-
|
|
124
|
-
// INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
|
|
125
|
-
|
|
126
|
-
let stateTPVote = 0;
|
|
127
|
-
let stateDemVote = 0;
|
|
128
|
-
let stateRepVote = 0;
|
|
129
|
-
|
|
130
|
-
let stateVAPPop = 0;
|
|
131
|
-
let stateWhitePop = 0;
|
|
132
|
-
let stateMinorityPop = 0;
|
|
133
|
-
let stateBlackPop = 0;
|
|
134
|
-
let stateHispanicPop = 0;
|
|
135
|
-
let statePacificPop = 0;
|
|
136
|
-
let stateAsianPop = 0;
|
|
137
|
-
let stateNativePop = 0;
|
|
138
|
-
|
|
139
|
-
// NOTE - These plan-level booleans are set in their respective analytics:
|
|
140
|
-
// - Equal population (bEqualPop)
|
|
141
|
-
// - Complete (bNotEmpty)
|
|
142
|
-
// - Contiguos (bContiguous)
|
|
143
|
-
// - Free of holes (bNotEmbedded)
|
|
144
|
-
|
|
145
|
-
// 2 - MORE ...
|
|
146
|
-
|
|
147
|
-
// Loop over the districts (including the dummy unassigned one)
|
|
148
|
-
for (let i = 0; i < this.numberOfWorkingDistricts(); i++) {
|
|
149
|
-
|
|
150
|
-
// INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
|
|
151
|
-
|
|
152
|
-
let featurePop: number;
|
|
153
|
-
let totalPop: number = 0;
|
|
154
|
-
|
|
155
|
-
let countySplits: number[] = U.initArray(this._session.counties.nCounties, 0)
|
|
156
|
-
|
|
157
|
-
let demVotes: number = 0;
|
|
158
|
-
let repVotes: number = 0;
|
|
159
|
-
|
|
160
|
-
let totalVAP: number = 0;
|
|
161
|
-
let whitePop: number = 0;
|
|
162
|
-
let blackPop: number = 0;
|
|
163
|
-
let hispanicPop: number = 0;
|
|
164
|
-
let pacificPop: number = 0;
|
|
165
|
-
let asianPop: number = 0;
|
|
166
|
-
let nativePop: number = 0;
|
|
167
|
-
|
|
168
|
-
// 3 - MORE ...
|
|
169
|
-
|
|
170
|
-
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
171
|
-
let outerThis = this;
|
|
172
|
-
|
|
173
|
-
// Get the geoIDs assigned to it ...
|
|
174
|
-
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
175
|
-
|
|
176
|
-
// ... loop over them creating district-by-district statistics
|
|
177
|
-
geoIDs.forEach(function (geoID: string): void {
|
|
178
|
-
// Map from geoID to feature index
|
|
179
|
-
let featureID = outerThis._session.features.featureID(geoID);
|
|
180
|
-
let f: T.GeoFeature = outerThis._session.features.featureByIndex(featureID);
|
|
181
|
-
|
|
182
|
-
// ACCUMULATE VALUES
|
|
183
|
-
|
|
184
|
-
// Total population of each feature (used more than once)
|
|
185
|
-
featurePop = outerThis._session.features.fieldForFeature(f, Dataset.CENSUS, FeatureField.TotalPop);
|
|
186
|
-
|
|
187
|
-
// Total district population
|
|
188
|
-
totalPop += featurePop;
|
|
189
|
-
|
|
190
|
-
// Total population by counties w/in a district
|
|
191
|
-
countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
|
|
192
|
-
|
|
193
|
-
// Democratic and Republican vote totals
|
|
194
|
-
demVotes += outerThis._session.features.fieldForFeature(f, Dataset.ELECTION, FeatureField.DemVotes);
|
|
195
|
-
repVotes += outerThis._session.features.fieldForFeature(f, Dataset.ELECTION, FeatureField.RepVotes);
|
|
196
|
-
|
|
197
|
-
// Voting-age demographic breakdowns (or citizen voting-age)
|
|
198
|
-
// Guard againt null/NaN values
|
|
199
|
-
let _totalVAP = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.TotalPop);
|
|
200
|
-
let _whitePop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.WhitePop);
|
|
201
|
-
let _blackPop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.BlackPop);
|
|
202
|
-
let _hispanicPop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.HispanicPop);
|
|
203
|
-
let _pacificPop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.PacificPop);
|
|
204
|
-
let _asianPop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.AsianPop);
|
|
205
|
-
let _nativePop = outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.NativePop);
|
|
206
|
-
|
|
207
|
-
if (_totalVAP) totalVAP += _totalVAP;
|
|
208
|
-
if (_whitePop) whitePop += _whitePop;
|
|
209
|
-
if (_blackPop) blackPop += _blackPop;
|
|
210
|
-
if (_hispanicPop) hispanicPop += _hispanicPop;
|
|
211
|
-
if (_pacificPop) pacificPop += _pacificPop;
|
|
212
|
-
if (_asianPop) asianPop += _asianPop;
|
|
213
|
-
if (_nativePop) nativePop += _nativePop;
|
|
214
|
-
|
|
215
|
-
// TODO - DELETE
|
|
216
|
-
// totalVAP += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.TotalPop);
|
|
217
|
-
// whitePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.WhitePop);
|
|
218
|
-
// blackPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.BlackPop);
|
|
219
|
-
// hispanicPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.HispanicPop);
|
|
220
|
-
// pacificPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.PacificPop);
|
|
221
|
-
// asianPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.AsianPop);
|
|
222
|
-
// nativePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.NativePop);
|
|
223
|
-
|
|
224
|
-
// 4 - MORE ...
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// COMPUTE DERIVED VALUES
|
|
228
|
-
|
|
229
|
-
// Population deviation % and equal population (boolean) by district.
|
|
230
|
-
// Leave the values null for the dummy unassigned district.
|
|
231
|
-
let popDevPct = null;
|
|
232
|
-
let bEqualPop = null;
|
|
233
|
-
if (i > 0) {
|
|
234
|
-
popDevPct = (totalPop - targetSize) / targetSize;
|
|
235
|
-
bEqualPop = (popDevPct <= deviationThreshold);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
239
|
-
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
240
|
-
let totVotes: number;
|
|
241
|
-
let demPct: number = 0;
|
|
242
|
-
let repPct: number = 0;
|
|
243
|
-
let DemSeat: number = 0;
|
|
244
|
-
|
|
245
|
-
totVotes = demVotes + repVotes;
|
|
246
|
-
if (totVotes > 0) {
|
|
247
|
-
demPct = demVotes / totVotes;
|
|
248
|
-
repPct = repVotes / totVotes;
|
|
249
|
-
DemSeat = fptpWin(demPct);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Total minority VAP
|
|
253
|
-
let minorityPop = totalVAP - whitePop
|
|
254
|
-
|
|
255
|
-
// Voting-age demographic proportions (or citizen voting-age)
|
|
256
|
-
let whitePct: number = 0;
|
|
257
|
-
let minorityPct: number = 0;
|
|
258
|
-
let blackPct: number = 0;
|
|
259
|
-
let hispanicPct: number = 0;
|
|
260
|
-
let pacificPct: number = 0;
|
|
261
|
-
let asianPct: number = 0;
|
|
262
|
-
let nativePct: number = 0;
|
|
263
|
-
|
|
264
|
-
if (totalVAP > 0) {
|
|
265
|
-
whitePct = whitePop / totalVAP;
|
|
266
|
-
minorityPct = minorityPop / totalVAP;
|
|
267
|
-
blackPct = blackPop / totalVAP;
|
|
268
|
-
hispanicPct = hispanicPop / totalVAP;
|
|
269
|
-
pacificPct = pacificPop / totalVAP;
|
|
270
|
-
asianPct = asianPop / totalVAP;
|
|
271
|
-
nativePct = nativePop / totalVAP;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// 5 - MORE ...
|
|
275
|
-
|
|
276
|
-
// COMPUTE DISTRICT-LEVEL VALUES
|
|
277
|
-
|
|
278
|
-
// Validations
|
|
279
|
-
let bNotEmpty = (!U.isSetEmpty(geoIDs));
|
|
280
|
-
let bContiguous = null;
|
|
281
|
-
let bNotEmbedded = null;
|
|
282
|
-
// Leave the values null for the dummy unassigned district,
|
|
283
|
-
// and districts that are empty.
|
|
284
|
-
if (i > 0) {
|
|
285
|
-
if (bNotEmpty) {
|
|
286
|
-
bContiguous = isConnected(geoIDs, graph);
|
|
287
|
-
bNotEmbedded = (!isEmbedded(i, planByDistrict[i], plan, graph));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 6 - MORE ...
|
|
292
|
-
|
|
293
|
-
// UPDATE THE DISTRICT STATISTICS
|
|
294
|
-
|
|
295
|
-
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
296
|
-
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
297
|
-
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
298
|
-
|
|
299
|
-
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
300
|
-
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
301
|
-
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
302
|
-
|
|
303
|
-
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
304
|
-
|
|
305
|
-
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
306
|
-
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
307
|
-
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
308
|
-
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
309
|
-
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
310
|
-
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
311
|
-
|
|
312
|
-
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
313
|
-
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
314
|
-
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
315
|
-
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
316
|
-
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
317
|
-
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
318
|
-
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
319
|
-
|
|
320
|
-
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
321
|
-
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
322
|
-
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
323
|
-
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
324
|
-
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
325
|
-
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
326
|
-
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
327
|
-
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
328
|
-
|
|
329
|
-
// 7 - MORE ...
|
|
330
|
-
|
|
331
|
-
// ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
332
|
-
|
|
333
|
-
stateTPVote += totVotes;
|
|
334
|
-
stateDemVote += demVotes;
|
|
335
|
-
stateRepVote += repVotes;
|
|
336
|
-
|
|
337
|
-
stateVAPPop += totalVAP;
|
|
338
|
-
stateWhitePop += whitePop;
|
|
339
|
-
stateMinorityPop += minorityPop
|
|
340
|
-
stateBlackPop += blackPop;
|
|
341
|
-
stateHispanicPop += hispanicPop;
|
|
342
|
-
statePacificPop += pacificPop;
|
|
343
|
-
stateAsianPop += asianPop;
|
|
344
|
-
stateNativePop += nativePop;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// UPDATE STATE STATISTICS
|
|
348
|
-
let summaryRow = this.numberOfRows() - 1;
|
|
349
|
-
|
|
350
|
-
if (stateTPVote > 0) {
|
|
351
|
-
this.statistics[DistrictField.DemPct][summaryRow] = stateDemVote / stateTPVote;
|
|
352
|
-
this.statistics[DistrictField.RepPct][summaryRow] = stateRepVote / stateTPVote;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (stateVAPPop > 0) {
|
|
356
|
-
this.statistics[DistrictField.WhitePct][summaryRow] = stateWhitePop / stateVAPPop;
|
|
357
|
-
this.statistics[DistrictField.MinorityPct][summaryRow] = stateMinorityPop / stateVAPPop;
|
|
358
|
-
this.statistics[DistrictField.BlackPct][summaryRow] = stateBlackPop / stateVAPPop;
|
|
359
|
-
this.statistics[DistrictField.HispanicPct][summaryRow] = stateHispanicPop / stateVAPPop;
|
|
360
|
-
this.statistics[DistrictField.PacificPct][summaryRow] = statePacificPop / stateVAPPop;
|
|
361
|
-
this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
|
|
362
|
-
this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
// NOTE - I did not roll these into district statistics, because creating the
|
|
366
|
-
// district shapes themselves is the big district-by-district activity, these
|
|
367
|
-
// calc's already work, and I'm not going to expose these values. Wrapping
|
|
368
|
-
// the underlying function and exposing it here to illustrate the parallelism
|
|
369
|
-
// with recalcStatistics(). These are called in tandem by doAnalyzeDistricts().
|
|
370
|
-
extractDistrictShapeProperties(bLog: boolean = false): void {
|
|
371
|
-
extractDistrictProperties(this._session, bLog)
|
|
372
|
-
}
|
|
373
|
-
getCountyIndex(geoID: string): number {
|
|
374
|
-
let countyGeoID = U.parseGeoID(geoID)['county'] as string;
|
|
375
|
-
let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
|
|
376
|
-
let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
|
|
377
|
-
|
|
378
|
-
return countyIndex;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// CLASSES, ETC. FOR FEATURE & COUNTY DATA
|
|
384
|
-
|
|
385
|
-
// Types of datasets by feature
|
|
386
|
-
export const enum Dataset {
|
|
387
|
-
CENSUS = "CENSUS",
|
|
388
|
-
VAP = "VAP",
|
|
389
|
-
ELECTION = "ELECTION"
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export type DatasetKeys = {
|
|
393
|
-
CENSUS: string; // A total population Census dataset
|
|
394
|
-
VAP: string; // A voting age (or citizen voting age) dataset
|
|
395
|
-
ELECTION: string; // An election dataset
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export const DatasetDescriptions: any = {
|
|
399
|
-
D16F: "2016 ACS Total Population",
|
|
400
|
-
D16T: "2016 ACS Voting Age Population",
|
|
401
|
-
E16GPR: "2016 Presidential Election",
|
|
402
|
-
D10F: "2010 Census Total Population",
|
|
403
|
-
D10T: "2010 Voting Age Population",
|
|
404
|
-
C16GCO: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
405
|
-
|
|
406
|
-
// TODO - What other potential datasets?
|
|
407
|
-
// MORE ...
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Identifiers of fields for each feature in the datasets
|
|
411
|
-
export const enum FeatureField {
|
|
412
|
-
TotalPop = "Tot",
|
|
413
|
-
WhitePop = "Wh",
|
|
414
|
-
BlackPop = "BlC",
|
|
415
|
-
HispanicPop = "His",
|
|
416
|
-
AsianPop = "AsnC",
|
|
417
|
-
PacificPop = "PacC",
|
|
418
|
-
NativePop = "NatC",
|
|
419
|
-
DemVotes = "D",
|
|
420
|
-
RepVotes = "R",
|
|
421
|
-
TotalVotes = "Tot"
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Wrap data by feature, to abstract the specifics of the internal structure
|
|
425
|
-
export class Features {
|
|
426
|
-
_session: AnalyticsSession;
|
|
427
|
-
|
|
428
|
-
_data: T.GeoFeatureCollection;
|
|
429
|
-
_keys: DatasetKeys;
|
|
430
|
-
|
|
431
|
-
_featureIDs = {} as T.FeaturesByGeoID;
|
|
432
|
-
|
|
433
|
-
constructor(s: AnalyticsSession, data: T.GeoFeatureCollection, keys: DatasetKeys) {
|
|
434
|
-
this._session = s;
|
|
435
|
-
|
|
436
|
-
this._data = data;
|
|
437
|
-
this._keys = keys;
|
|
438
|
-
}
|
|
439
|
-
nFeatures(): number { return this._data.features.length; }
|
|
440
|
-
featureByIndex(i: number): T.GeoFeature { return this._data.features[i]; }
|
|
441
|
-
// TODO - Generalize this
|
|
442
|
-
geoIDForFeature(f: any): string { return f.properties['GEOID10']; }
|
|
443
|
-
fieldForFeature(f: any, dt: Dataset, fk: string): any {
|
|
444
|
-
let dk: string = this._keys[dt];
|
|
445
|
-
|
|
446
|
-
return _getFeatures(f, dk, fk);
|
|
447
|
-
}
|
|
448
|
-
resetDataset(d: Dataset, k: string): void {
|
|
449
|
-
this._keys[d] = k;
|
|
450
|
-
// TODO - Does anything need to be recalc'd now when a dataset is changed?
|
|
451
|
-
}
|
|
452
|
-
mapGeoIDsToFeatureIDs(): void {
|
|
453
|
-
for (let i: number = 0; i < this._session.features.nFeatures(); i++) {
|
|
454
|
-
let f: T.GeoFeature = this._session.features.featureByIndex(i);
|
|
455
|
-
let geoID: string = this._session.features.geoIDForFeature(f);
|
|
456
|
-
|
|
457
|
-
this._featureIDs[geoID] = i;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
featureID(i: string): number { return this._featureIDs[i]; }
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// NOTE - This accessor is cloned from fGetW() in dra-client/restrict.ts
|
|
464
|
-
// f is a direct GeoJSON feature
|
|
465
|
-
// p is a geoID
|
|
466
|
-
function _getFeatures(f: any, datasetKey: string, p: string): any {
|
|
467
|
-
// Shim to load sample data2.json from disk for command-line scaffolding
|
|
468
|
-
if (f.properties && f.properties['datasets']) {
|
|
469
|
-
return f.properties['datasets'][datasetKey][p];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// NOTE - The fGetW() code from dra-client below here ...
|
|
473
|
-
|
|
474
|
-
// Direct property?
|
|
475
|
-
if (f.properties && f.properties[p] !== undefined)
|
|
476
|
-
return f.properties[p];
|
|
477
|
-
|
|
478
|
-
// Joined property?
|
|
479
|
-
let a: any[] = _fGetJoined(f);
|
|
480
|
-
if (a) {
|
|
481
|
-
for (let i: number = 0; i < a.length; i++) {
|
|
482
|
-
let o: any = a[i];
|
|
483
|
-
if (!datasetKey) {
|
|
484
|
-
if (o[p] !== undefined)
|
|
485
|
-
return o[p];
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
if (o['datasets'] && o['datasets'][datasetKey])
|
|
489
|
-
if (o['datasets'][datasetKey][p])
|
|
490
|
-
return o['datasets'][datasetKey][p];
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return undefined;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function _fGetJoined(f: any): any[] {
|
|
498
|
-
return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Wrap data by county, to abstract the specifics of the internal structure
|
|
502
|
-
export class Counties {
|
|
503
|
-
_session: AnalyticsSession;
|
|
504
|
-
|
|
505
|
-
_data: T.GeoFeatureCollection;
|
|
506
|
-
_countyNameLookup: T.FIPSCodeToCountyNameMap;
|
|
507
|
-
|
|
508
|
-
nCounties: number;
|
|
509
|
-
index = {} as T.FIPSToOrdinalMap;
|
|
510
|
-
|
|
511
|
-
constructor(s: AnalyticsSession, data: T.GeoFeatureCollection) {
|
|
512
|
-
this._session = s;
|
|
513
|
-
|
|
514
|
-
this._data = data;
|
|
515
|
-
this.nCounties = this._data.features.length;
|
|
516
|
-
this._countyNameLookup = {};
|
|
517
|
-
}
|
|
518
|
-
// nCounties(): number { return this._data.features.length; }
|
|
519
|
-
countyByIndex(i: number): T.GeoFeature { return this._data.features[i]; }
|
|
520
|
-
propertyForCounty(f: any, pk: string): any { return f.properties[pk]; }
|
|
521
|
-
mapFIPSToName(fips: string, name: string): void { this._countyNameLookup[fips] = name; }
|
|
522
|
-
nameFromFIPS(fips: string): string { return this._countyNameLookup[fips]; }
|
|
523
|
-
indexFromFIPS(fips: string): number { return this.index[fips]; }
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// CLASSES TO ORGANIZE AND/OR ABSTRACT OTHER DATA
|
|
527
|
-
|
|
528
|
-
export class State {
|
|
529
|
-
_session: AnalyticsSession;
|
|
530
|
-
|
|
531
|
-
xx: string;
|
|
532
|
-
nDistricts: number;
|
|
533
|
-
totalPop = 0;
|
|
534
|
-
tooBigFIPS = [] as string[];
|
|
535
|
-
tooBigName = [] as string[];
|
|
536
|
-
expectedSplits = 0;
|
|
537
|
-
expectedAffected = 0;
|
|
538
|
-
|
|
539
|
-
constructor(s: AnalyticsSession, xx: string, n: number) {
|
|
540
|
-
this._session = s;
|
|
541
|
-
|
|
542
|
-
this.xx = xx;
|
|
543
|
-
this.nDistricts = n;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export class Plan {
|
|
548
|
-
_session: AnalyticsSession;
|
|
549
|
-
|
|
550
|
-
// TODO - Do these need to be updated, when the plan changes?
|
|
551
|
-
_planByGeoID: T.PlanByGeoID;
|
|
552
|
-
_planByDistrictID: T.PlanByDistrictID;
|
|
553
|
-
|
|
554
|
-
districtIDs: number[];
|
|
555
|
-
|
|
556
|
-
constructor(s: AnalyticsSession, p: T.PlanByGeoID) {
|
|
557
|
-
this._session = s;
|
|
558
|
-
|
|
559
|
-
this._planByGeoID = p;
|
|
560
|
-
this._planByDistrictID = {} as T.PlanByDistrictID;
|
|
561
|
-
|
|
562
|
-
this.districtIDs = []; // Set when the plan in inverted
|
|
563
|
-
}
|
|
564
|
-
invertPlan(): void {
|
|
565
|
-
this._planByDistrictID = U.invertPlan(this._planByGeoID);
|
|
566
|
-
this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
|
|
567
|
-
}
|
|
568
|
-
initializeDistrict(i: number): void { this._planByDistrictID[i] = new Set(); }
|
|
569
|
-
|
|
570
|
-
byGeoID(): T.PlanByGeoID { return this._planByGeoID; }
|
|
571
|
-
byDistrictID(): T.PlanByDistrictID { return this._planByDistrictID; }
|
|
572
|
-
districtForGeoID(i: string): number { return this._planByGeoID[i]; }
|
|
573
|
-
geoIDsForDistrictID(i: number): Set<string> { return this._planByDistrictID[i]; }
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
export class Graph {
|
|
577
|
-
_session: AnalyticsSession;
|
|
578
|
-
|
|
579
|
-
_graph: T.ContiguityGraph;
|
|
580
|
-
|
|
581
|
-
constructor(s: AnalyticsSession, graph: T.ContiguityGraph) {
|
|
582
|
-
this._session = s;
|
|
583
|
-
|
|
584
|
-
this._graph = graph;
|
|
585
|
-
}
|
|
586
|
-
// TODO - Rework this, when we support MIXED MAPS.
|
|
587
|
-
peerNeighbors(node: string): string[] {
|
|
588
|
-
// Get the neighboring geoIDs connected to a geoID
|
|
589
|
-
// Ignore the lengths of the shared borders (the values), for now
|
|
590
|
-
|
|
591
|
-
return U.getObjectKeys(this._graph[node]);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
package/src/analyze.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// ANALYZE A PLAN
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as T from './types'
|
|
6
|
-
import * as U from './utils';
|
|
7
|
-
import * as S from './settings';
|
|
8
|
-
|
|
9
|
-
import { AnalyticsSession } from './_api';
|
|
10
|
-
|
|
11
|
-
import { doIsComplete, doIsContiguous, doIsFreeOfHoles } from './valid'
|
|
12
|
-
import { doPopulationDeviation, doHasEqualPopulations } from './equal';
|
|
13
|
-
import { doReock, doPolsbyPopper, extractDistrictProperties } from './compact';
|
|
14
|
-
import { doCountySplits, doPlanComplexity } from './cohesive';
|
|
15
|
-
import {
|
|
16
|
-
doSeatsBias, doVotesBias,
|
|
17
|
-
doResponsiveness, doResponsiveDistricts, doEfficiencyGap
|
|
18
|
-
} from './political';
|
|
19
|
-
import { doMajorityMinorityDistricts } from './minority'
|
|
20
|
-
|
|
21
|
-
// Compile district-level info for plan/map-level analytics
|
|
22
|
-
export function doAnalyzeDistricts(s: AnalyticsSession, bLog: boolean = false): void {
|
|
23
|
-
s.districts.recalcStatistics(bLog);
|
|
24
|
-
s.districts.extractDistrictShapeProperties(bLog);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// TODO - I could make this table-driven, but I'm thinking that the explicit
|
|
28
|
-
// calls might make chunking for aync easier.
|
|
29
|
-
// Calculate the analytics & validations and cache the results
|
|
30
|
-
// NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
|
|
31
|
-
export function doAnalyzePlan(s: AnalyticsSession, bLog: boolean = false): void {
|
|
32
|
-
// Get the requested suites, and only execute those tests
|
|
33
|
-
let requestedSuites = s.config['suites'];
|
|
34
|
-
|
|
35
|
-
// Tests in the "Legal" suite, i.e., pass/ fail constraints
|
|
36
|
-
if (requestedSuites.includes(T.Suite.Legal)) {
|
|
37
|
-
s.tests[T.Test.Complete] = doIsComplete(s);
|
|
38
|
-
|
|
39
|
-
s.tests[T.Test.Contiguous] = doIsContiguous(s);
|
|
40
|
-
|
|
41
|
-
s.tests[T.Test.FreeOfHoles] = doIsFreeOfHoles(s);
|
|
42
|
-
|
|
43
|
-
s.tests[T.Test.PopulationDeviation] = doPopulationDeviation(s);
|
|
44
|
-
|
|
45
|
-
// NOTE - I can't check whether a population deviation is legal or not, until
|
|
46
|
-
// the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
|
|
47
|
-
// the given type of district (CD vs. LD). The EqualPopulation test is derived
|
|
48
|
-
// from PopulationDeviation, as part of scorecard or test log preparation.
|
|
49
|
-
|
|
50
|
-
// Create an empty test entry here though ...
|
|
51
|
-
s.tests[T.Test.EqualPopulation] = s.getTest(T.Test.EqualPopulation) as T.TestEntry;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Tests in the "Fair" suite
|
|
55
|
-
if (requestedSuites.includes(T.Suite.Fair)) {
|
|
56
|
-
s.tests[T.Test.SeatsBias] = doSeatsBias(s);
|
|
57
|
-
|
|
58
|
-
s.tests[T.Test.VotesBias] = doVotesBias(s);
|
|
59
|
-
|
|
60
|
-
s.tests[T.Test.Responsiveness] = doResponsiveness(s);
|
|
61
|
-
|
|
62
|
-
s.tests[T.Test.ResponsiveDistricts] = doResponsiveDistricts(s);
|
|
63
|
-
|
|
64
|
-
s.tests[T.Test.EfficiencyGap] = doEfficiencyGap(s);
|
|
65
|
-
|
|
66
|
-
s.tests[T.Test.MajorityMinorityDistricts] = doMajorityMinorityDistricts(s);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
70
|
-
if (requestedSuites.includes(T.Suite.Best)) {
|
|
71
|
-
s.tests[T.Test.Reock] = doReock(s, bLog);
|
|
72
|
-
s.tests[T.Test.PolsbyPopper] = doPolsbyPopper(s, bLog);
|
|
73
|
-
|
|
74
|
-
s.tests[T.Test.CountySplits] = doCountySplits(s);
|
|
75
|
-
|
|
76
|
-
s.tests[T.Test.Complexity] = doPlanComplexity(s);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Enable a Test Log and Scorecard to be generated
|
|
80
|
-
s.bPlanAnalyzed = true;
|
|
81
|
-
s.bPostProcessingDone = false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Derive secondary analytics that are based on primary tests.
|
|
85
|
-
// This concept allows Population Deviation to be a primary numeric test and
|
|
86
|
-
// Equal Population to be secondary pass/fail validation.
|
|
87
|
-
//
|
|
88
|
-
// NOTE - Should this be conditionalized on the test suites requested?
|
|
89
|
-
// Those are encapsulated in reports.ts right now, so not doing that.
|
|
90
|
-
export function doDeriveSecondaryTests(s: AnalyticsSession): void {
|
|
91
|
-
s.tests[T.Test.EqualPopulation] = doHasEqualPopulations(s);
|
|
92
|
-
}
|