@dra2020/district-analytics 1.0.10 → 1.0.11
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/cli.js +1725 -1344
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +1310 -1205
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/_api.d.ts +4 -4
- package/dist/src/_data.d.ts +4 -2
- package/dist/src/analyze.d.ts +1 -1
- package/dist/src/cohesive.d.ts +4 -2
- package/dist/src/equal.d.ts +2 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/minority.d.ts +1 -1
- package/dist/src/political.d.ts +5 -5
- package/dist/src/preprocess.d.ts +1 -1
- package/dist/src/report.d.ts +0 -15
- package/dist/src/results.d.ts +141 -0
- package/dist/src/settings.d.ts +3 -0
- package/dist/src/types.d.ts +24 -11
- package/dist/src/utils.d.ts +4 -3
- package/dist/src/valid.d.ts +3 -3
- package/dist/test/_cli.d.ts +2 -0
- package/package.json +6 -4
- package/dist/_api.d.ts +0 -27
- package/dist/_data.d.ts +0 -130
- package/dist/analyze.d.ts +0 -4
- package/dist/cohesive.d.ts +0 -4
- package/dist/compact.d.ts +0 -5
- package/dist/constants.d.ts +0 -6
- 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/types.d.ts +0 -110
- package/dist/utils.d.ts +0 -28
- package/dist/valid.d.ts +0 -8
|
@@ -108,12 +108,20 @@ return /******/ (function(modules) { // webpackBootstrap
|
|
|
108
108
|
//
|
|
109
109
|
// THE NODE PACKAGE API
|
|
110
110
|
//
|
|
111
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
112
|
+
if (mod && mod.__esModule) return mod;
|
|
113
|
+
var result = {};
|
|
114
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
115
|
+
result["default"] = mod;
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
111
118
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
112
119
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
113
120
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
121
|
+
const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
122
|
+
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
123
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
124
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
117
125
|
class AnalyticsSession {
|
|
118
126
|
constructor(SessionRequest) {
|
|
119
127
|
this.config = {};
|
|
@@ -122,7 +130,6 @@ class AnalyticsSession {
|
|
|
122
130
|
this.bPostProcessingDone = false;
|
|
123
131
|
this.testScales = {};
|
|
124
132
|
this.tests = {};
|
|
125
|
-
this.scorecard = {};
|
|
126
133
|
this.title = SessionRequest['title'];
|
|
127
134
|
this.legislativeDistricts = SessionRequest['legislativeDistricts'];
|
|
128
135
|
this.config = this.processConfig(SessionRequest['config']);
|
|
@@ -135,12 +142,13 @@ class AnalyticsSession {
|
|
|
135
142
|
// NOTE: I've pulled these out of the individual analytics to here. Eventually,
|
|
136
143
|
// we could want them to passed into an analytics session as data, along with
|
|
137
144
|
// everything else. For now, this keeps branching out of the main code.
|
|
138
|
-
|
|
145
|
+
results_1.doConfigureScales(this);
|
|
139
146
|
}
|
|
140
147
|
processConfig(config) {
|
|
141
148
|
// NOTE - Session settings are required:
|
|
142
149
|
// - Analytics suites can be defaulted to all with [], but
|
|
143
150
|
// - Dataset keys must be explicitly specified with 'dataset'
|
|
151
|
+
// TODO - Remove this mechanism. Always run everything.
|
|
144
152
|
let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
|
|
145
153
|
// If the config passed in has no suites = [], use the default suites
|
|
146
154
|
if (U.isArrayEmpty(config['suites'])) {
|
|
@@ -155,10 +163,10 @@ class AnalyticsSession {
|
|
|
155
163
|
// analytics & validations, saving/updating the individual test results.
|
|
156
164
|
analyzePlan(bLog = false) {
|
|
157
165
|
try {
|
|
158
|
-
preprocess_1.doPreprocessData(this);
|
|
166
|
+
preprocess_1.doPreprocessData(this, bLog);
|
|
159
167
|
analyze_1.doAnalyzeDistricts(this, bLog);
|
|
160
168
|
analyze_1.doAnalyzePlan(this, bLog);
|
|
161
|
-
|
|
169
|
+
results_1.doAnalyzePostProcessing(this, bLog);
|
|
162
170
|
}
|
|
163
171
|
catch (_a) {
|
|
164
172
|
console.log("Exception caught by analyzePlan()");
|
|
@@ -166,6 +174,36 @@ class AnalyticsSession {
|
|
|
166
174
|
}
|
|
167
175
|
return true;
|
|
168
176
|
}
|
|
177
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
178
|
+
getPlanAnalytics(bLog = false) {
|
|
179
|
+
return results_2.preparePlanAnalytics(this, bLog);
|
|
180
|
+
}
|
|
181
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
182
|
+
getDistrictStatistics(bLog = false) {
|
|
183
|
+
return results_2.prepareDistrictStatistics(this, bLog);
|
|
184
|
+
}
|
|
185
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
186
|
+
getDiscontiguousDistrictFeatures(bLog = false) {
|
|
187
|
+
// Get the (possibly empty) list of discontiguous district IDs
|
|
188
|
+
let contiguousTest = this.getTest(1 /* Contiguous */);
|
|
189
|
+
let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
|
|
190
|
+
// Convert them into a (possibly empty) list of features
|
|
191
|
+
let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
|
|
192
|
+
if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
|
|
193
|
+
for (let i = 0; i < discontiguousDistrictIDs.length; i++) {
|
|
194
|
+
let poly = this.districts.getShape(i);
|
|
195
|
+
discontiguousDistrictFeatures.features.push(poly);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return discontiguousDistrictFeatures;
|
|
199
|
+
}
|
|
200
|
+
// TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
|
|
201
|
+
// Prepare a scorecard for rendering
|
|
202
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
203
|
+
// prepareScorecard(): any {
|
|
204
|
+
// return doPrepareScorecard(this);
|
|
205
|
+
// }
|
|
206
|
+
// HELPERS USED INTERNALLY
|
|
169
207
|
// Get an individual test, so you can drive UI with the results.
|
|
170
208
|
getTest(testID) {
|
|
171
209
|
// Get the existing test entries
|
|
@@ -180,18 +218,15 @@ class AnalyticsSession {
|
|
|
180
218
|
// Return a pointer to the the test entry for this test
|
|
181
219
|
return this.tests[testID];
|
|
182
220
|
}
|
|
183
|
-
//
|
|
184
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
185
|
-
prepareScorecard() {
|
|
186
|
-
return report_1.doPrepareScorecard(this);
|
|
187
|
-
}
|
|
221
|
+
// TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
|
|
188
222
|
// Prepare test results for rendering
|
|
189
223
|
// NOTE - This assumes that analyzePlan() has been run!
|
|
190
|
-
prepareTestLog() {
|
|
191
|
-
|
|
192
|
-
}
|
|
224
|
+
// prepareTestLog(): any {
|
|
225
|
+
// return doPrepareTestLog(this);
|
|
226
|
+
// }
|
|
227
|
+
// NOTE - Not sure why this has to be up here.
|
|
193
228
|
populationDeviationThreshold() {
|
|
194
|
-
return 1 - this.testScales[4 /* PopulationDeviation */]['
|
|
229
|
+
return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
|
|
195
230
|
}
|
|
196
231
|
}
|
|
197
232
|
exports.AnalyticsSession = AnalyticsSession;
|
|
@@ -211,8 +246,16 @@ exports.AnalyticsSession = AnalyticsSession;
|
|
|
211
246
|
//
|
|
212
247
|
// DATA ABSTRACTION LAYER
|
|
213
248
|
//
|
|
249
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
250
|
+
if (mod && mod.__esModule) return mod;
|
|
251
|
+
var result = {};
|
|
252
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
253
|
+
result["default"] = mod;
|
|
254
|
+
return result;
|
|
255
|
+
};
|
|
214
256
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
215
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
257
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
258
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
216
259
|
const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
|
|
217
260
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
218
261
|
const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
|
|
@@ -251,23 +294,6 @@ var DistrictField;
|
|
|
251
294
|
DistrictField[DistrictField["NativePct"] = 27] = "NativePct"; // Display
|
|
252
295
|
// 1 - MORE ...
|
|
253
296
|
})(DistrictField = exports.DistrictField || (exports.DistrictField = {}));
|
|
254
|
-
// The fields to display in a District Statistics pane
|
|
255
|
-
exports.DisplayFields = [
|
|
256
|
-
DistrictField.TotalPop,
|
|
257
|
-
DistrictField.PopDevPct,
|
|
258
|
-
DistrictField.bEqualPop,
|
|
259
|
-
DistrictField.bNotEmpty,
|
|
260
|
-
DistrictField.bContiguous,
|
|
261
|
-
DistrictField.bNotEmbedded,
|
|
262
|
-
DistrictField.DemPct,
|
|
263
|
-
DistrictField.WhitePct,
|
|
264
|
-
DistrictField.MinorityPct,
|
|
265
|
-
DistrictField.BlackPct,
|
|
266
|
-
DistrictField.HispanicPct,
|
|
267
|
-
DistrictField.PacificPct,
|
|
268
|
-
DistrictField.AsianPct,
|
|
269
|
-
DistrictField.NativePct
|
|
270
|
-
];
|
|
271
297
|
class Districts {
|
|
272
298
|
constructor(s, ds) {
|
|
273
299
|
this._geoProperties = {};
|
|
@@ -276,7 +302,13 @@ class Districts {
|
|
|
276
302
|
this.statistics = this.initStatistics();
|
|
277
303
|
}
|
|
278
304
|
getShape(i) { return this._shapes.features[i]; }
|
|
279
|
-
getGeoProperties(i) {
|
|
305
|
+
getGeoProperties(i) {
|
|
306
|
+
// Make sure the district shape exists & has geo properties
|
|
307
|
+
if (i in this._geoProperties)
|
|
308
|
+
return this._geoProperties[i];
|
|
309
|
+
else
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
280
312
|
setGeoProperties(i, p) { this._geoProperties[i] = p; }
|
|
281
313
|
numberOfColumns() { return U.countEnumValues(DistrictField); }
|
|
282
314
|
// +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
|
|
@@ -299,7 +331,7 @@ class Districts {
|
|
|
299
331
|
// TODO - Optimize for getting multiple properties from the same feature?
|
|
300
332
|
// TODO - Optimize by only re-calc'ing districts that have changed?
|
|
301
333
|
// In this case, special attention to getting county-splits right.
|
|
302
|
-
// TODO - Optimize by
|
|
334
|
+
// TODO - Optimize by async'ing this?
|
|
303
335
|
// TODO - Is there a way to do this programmatically off data? Does it matter?
|
|
304
336
|
recalcStatistics(bLog = false) {
|
|
305
337
|
// Compute these once per recalc cycle
|
|
@@ -308,6 +340,9 @@ class Districts {
|
|
|
308
340
|
let planByDistrict = this._session.plan.byDistrictID();
|
|
309
341
|
let plan = this._session.plan;
|
|
310
342
|
let graph = this._session.graph;
|
|
343
|
+
// TODO - SPLITTING
|
|
344
|
+
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
345
|
+
let nCountyBuckets = this._session.counties.nCounties + 1;
|
|
311
346
|
// INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
|
|
312
347
|
let stateTPVote = 0;
|
|
313
348
|
let stateDemVote = 0;
|
|
@@ -323,7 +358,7 @@ class Districts {
|
|
|
323
358
|
// NOTE - These plan-level booleans are set in their respective analytics:
|
|
324
359
|
// - Equal population (bEqualPop)
|
|
325
360
|
// - Complete (bNotEmpty)
|
|
326
|
-
// -
|
|
361
|
+
// - Contiguous (bContiguous)
|
|
327
362
|
// - Free of holes (bNotEmbedded)
|
|
328
363
|
// 2 - MORE ...
|
|
329
364
|
// Loop over the districts (including the dummy unassigned one)
|
|
@@ -331,7 +366,8 @@ class Districts {
|
|
|
331
366
|
// INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
|
|
332
367
|
let featurePop;
|
|
333
368
|
let totalPop = 0;
|
|
334
|
-
|
|
369
|
+
// TODO - SPLITTING
|
|
370
|
+
let countySplits = U.initArray(nCountyBuckets, 0);
|
|
335
371
|
let demVotes = 0;
|
|
336
372
|
let repVotes = 0;
|
|
337
373
|
let totalVAP = 0;
|
|
@@ -341,156 +377,178 @@ class Districts {
|
|
|
341
377
|
let pacificPop = 0;
|
|
342
378
|
let asianPop = 0;
|
|
343
379
|
let nativePop = 0;
|
|
380
|
+
// NOTE - Only report explicitly found validity issues
|
|
381
|
+
let bNotEmpty = false;
|
|
382
|
+
let bContiguous = true;
|
|
383
|
+
let bNotEmbedded = true;
|
|
384
|
+
let bEqualPop = true;
|
|
344
385
|
// 3 - MORE ...
|
|
345
386
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
346
387
|
let outerThis = this;
|
|
347
|
-
// Get the geoIDs assigned to
|
|
388
|
+
// Get the geoIDs assigned to the district
|
|
389
|
+
// Guard against empty districts
|
|
348
390
|
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
whitePct = whitePop / totalVAP;
|
|
430
|
-
minorityPct = minorityPop / totalVAP;
|
|
431
|
-
blackPct = blackPop / totalVAP;
|
|
432
|
-
hispanicPct = hispanicPop / totalVAP;
|
|
433
|
-
pacificPct = pacificPop / totalVAP;
|
|
434
|
-
asianPct = asianPop / totalVAP;
|
|
435
|
-
nativePct = nativePop / totalVAP;
|
|
436
|
-
}
|
|
437
|
-
// 5 - MORE ...
|
|
438
|
-
// COMPUTE DISTRICT-LEVEL VALUES
|
|
439
|
-
// Validations
|
|
440
|
-
let bNotEmpty = (!U.isSetEmpty(geoIDs));
|
|
441
|
-
let bContiguous = null;
|
|
442
|
-
let bNotEmbedded = null;
|
|
443
|
-
// Leave the values null for the dummy unassigned district,
|
|
444
|
-
// and districts that are empty.
|
|
445
|
-
if (i > 0) {
|
|
446
|
-
if (bNotEmpty) {
|
|
391
|
+
if (geoIDs && (geoIDs.size > 0)) {
|
|
392
|
+
bNotEmpty = true;
|
|
393
|
+
// ... loop over the geoIDs creating district-by-district statistics
|
|
394
|
+
geoIDs.forEach(function (geoID) {
|
|
395
|
+
// Skip water-only features
|
|
396
|
+
if (!(U.isWaterOnly(geoID))) {
|
|
397
|
+
// Map from geoID to feature index
|
|
398
|
+
let featureID = outerThis._session.features.featureID(geoID);
|
|
399
|
+
let f = outerThis._session.features.featureByIndex(featureID);
|
|
400
|
+
// ACCUMULATE VALUES
|
|
401
|
+
// Total population of each feature
|
|
402
|
+
// NOTE - This result is used more than once
|
|
403
|
+
featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
404
|
+
// Total district population
|
|
405
|
+
totalPop += featurePop;
|
|
406
|
+
// TODO - SPLITTING
|
|
407
|
+
// Total population by counties w/in a district,
|
|
408
|
+
// except the dummy unassigned district 0
|
|
409
|
+
if (i > 0)
|
|
410
|
+
countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
|
|
411
|
+
// Democratic and Republican vote totals
|
|
412
|
+
demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
|
|
413
|
+
repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
|
|
414
|
+
// Voting-age demographic breakdowns (or citizen voting-age)
|
|
415
|
+
totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
|
|
416
|
+
whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
|
|
417
|
+
blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
|
|
418
|
+
hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
|
|
419
|
+
pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
|
|
420
|
+
asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
|
|
421
|
+
nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
|
|
422
|
+
// 4 - MORE ...
|
|
423
|
+
}
|
|
424
|
+
// else console.log("Skipping water-only feature in district statistics:", geoID);
|
|
425
|
+
});
|
|
426
|
+
// COMPUTE DERIVED VALUES
|
|
427
|
+
// Population deviation % and equal population (boolean) by district.
|
|
428
|
+
// Don't set the values for the dummy unassigned district.
|
|
429
|
+
let popDevPct = null;
|
|
430
|
+
if (i > 0) {
|
|
431
|
+
popDevPct = (totalPop - targetSize) / targetSize;
|
|
432
|
+
bEqualPop = (popDevPct <= deviationThreshold);
|
|
433
|
+
}
|
|
434
|
+
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
435
|
+
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
436
|
+
let totVotes;
|
|
437
|
+
let demPct = 0;
|
|
438
|
+
let repPct = 0;
|
|
439
|
+
let DemSeat = 0;
|
|
440
|
+
totVotes = demVotes + repVotes;
|
|
441
|
+
if (totVotes > 0) {
|
|
442
|
+
demPct = demVotes / totVotes;
|
|
443
|
+
repPct = repVotes / totVotes;
|
|
444
|
+
DemSeat = political_1.fptpWin(demPct);
|
|
445
|
+
}
|
|
446
|
+
// Total minority VAP
|
|
447
|
+
let minorityPop = totalVAP - whitePop;
|
|
448
|
+
// Voting-age demographic proportions (or citizen voting-age)
|
|
449
|
+
let whitePct = 0;
|
|
450
|
+
let minorityPct = 0;
|
|
451
|
+
let blackPct = 0;
|
|
452
|
+
let hispanicPct = 0;
|
|
453
|
+
let pacificPct = 0;
|
|
454
|
+
let asianPct = 0;
|
|
455
|
+
let nativePct = 0;
|
|
456
|
+
if (totalVAP > 0) {
|
|
457
|
+
whitePct = whitePop / totalVAP;
|
|
458
|
+
minorityPct = minorityPop / totalVAP;
|
|
459
|
+
blackPct = blackPop / totalVAP;
|
|
460
|
+
hispanicPct = hispanicPop / totalVAP;
|
|
461
|
+
pacificPct = pacificPop / totalVAP;
|
|
462
|
+
asianPct = asianPop / totalVAP;
|
|
463
|
+
nativePct = nativePop / totalVAP;
|
|
464
|
+
}
|
|
465
|
+
// 5 - MORE ...
|
|
466
|
+
// COMPUTE DISTRICT-LEVEL VALUES
|
|
467
|
+
// Validations
|
|
468
|
+
// Leave the default values for the dummy unassigned district,
|
|
469
|
+
// and districts that are empty.
|
|
470
|
+
if ((i > 0) && bNotEmpty) {
|
|
447
471
|
bContiguous = valid_1.isConnected(geoIDs, graph);
|
|
448
472
|
bNotEmbedded = (!valid_1.isEmbedded(i, planByDistrict[i], plan, graph));
|
|
449
473
|
}
|
|
474
|
+
// 6 - MORE ...
|
|
475
|
+
{ // UPDATE THE DISTRICT STATISTICS
|
|
476
|
+
// NOTE - These are set below for both non-empty & empty districts:
|
|
477
|
+
// this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
478
|
+
// this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
479
|
+
// this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
480
|
+
// this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
481
|
+
// this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
482
|
+
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
483
|
+
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
484
|
+
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
485
|
+
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
486
|
+
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
487
|
+
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
488
|
+
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
489
|
+
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
490
|
+
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
491
|
+
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
492
|
+
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
493
|
+
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
494
|
+
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
495
|
+
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
496
|
+
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
497
|
+
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
498
|
+
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
499
|
+
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
500
|
+
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
501
|
+
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
502
|
+
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
503
|
+
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
504
|
+
}
|
|
505
|
+
// 7 - MORE ...
|
|
506
|
+
{ // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
507
|
+
stateTPVote += totVotes;
|
|
508
|
+
stateDemVote += demVotes;
|
|
509
|
+
stateRepVote += repVotes;
|
|
510
|
+
stateVAPPop += totalVAP;
|
|
511
|
+
stateWhitePop += whitePop;
|
|
512
|
+
stateMinorityPop += minorityPop;
|
|
513
|
+
stateBlackPop += blackPop;
|
|
514
|
+
stateHispanicPop += hispanicPop;
|
|
515
|
+
statePacificPop += pacificPop;
|
|
516
|
+
stateAsianPop += asianPop;
|
|
517
|
+
stateNativePop += nativePop;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else { // If a district is empty, zero these results (vs. null)
|
|
521
|
+
this.statistics[DistrictField.PopDevPct][i] = 0.0;
|
|
522
|
+
this.statistics[DistrictField.DemVotes][i] = 0;
|
|
523
|
+
this.statistics[DistrictField.RepVotes][i] = 0;
|
|
524
|
+
this.statistics[DistrictField.TwoPartyVote][i] = 0;
|
|
525
|
+
this.statistics[DistrictField.DemPct][i] = 0;
|
|
526
|
+
this.statistics[DistrictField.RepPct][i] = 0;
|
|
527
|
+
this.statistics[DistrictField.DemSeat][i] = 0;
|
|
528
|
+
this.statistics[DistrictField.WhitePop][i] = 0;
|
|
529
|
+
this.statistics[DistrictField.MinorityPop][i] = 0;
|
|
530
|
+
this.statistics[DistrictField.BlackPop][i] = 0;
|
|
531
|
+
this.statistics[DistrictField.HispanicPop][i] = 0;
|
|
532
|
+
this.statistics[DistrictField.PacificPop][i] = 0;
|
|
533
|
+
this.statistics[DistrictField.AsianPop][i] = 0;
|
|
534
|
+
this.statistics[DistrictField.NativePop][i] = 0;
|
|
535
|
+
this.statistics[DistrictField.TotalVAP][i] = 0;
|
|
536
|
+
this.statistics[DistrictField.WhitePct][i] = 0;
|
|
537
|
+
this.statistics[DistrictField.MinorityPct][i] = 0;
|
|
538
|
+
this.statistics[DistrictField.BlackPct][i] = 0;
|
|
539
|
+
this.statistics[DistrictField.HispanicPct][i] = 0;
|
|
540
|
+
this.statistics[DistrictField.PacificPct][i] = 0;
|
|
541
|
+
this.statistics[DistrictField.AsianPct][i] = 0;
|
|
542
|
+
this.statistics[DistrictField.NativePct][i] = 0;
|
|
543
|
+
}
|
|
544
|
+
{ // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
|
|
545
|
+
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
546
|
+
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
547
|
+
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
548
|
+
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
549
|
+
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
550
|
+
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
450
551
|
}
|
|
451
|
-
// 6 - MORE ...
|
|
452
|
-
// UPDATE THE DISTRICT STATISTICS
|
|
453
|
-
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
454
|
-
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
455
|
-
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
456
|
-
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
457
|
-
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
458
|
-
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
459
|
-
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
460
|
-
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
461
|
-
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
462
|
-
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
463
|
-
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
464
|
-
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
465
|
-
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
466
|
-
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
467
|
-
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
468
|
-
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
469
|
-
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
470
|
-
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
471
|
-
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
472
|
-
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
473
|
-
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
474
|
-
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
475
|
-
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
476
|
-
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
477
|
-
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
478
|
-
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
479
|
-
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
480
|
-
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
481
|
-
// 7 - MORE ...
|
|
482
|
-
// ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
483
|
-
stateTPVote += totVotes;
|
|
484
|
-
stateDemVote += demVotes;
|
|
485
|
-
stateRepVote += repVotes;
|
|
486
|
-
stateVAPPop += totalVAP;
|
|
487
|
-
stateWhitePop += whitePop;
|
|
488
|
-
stateMinorityPop += minorityPop;
|
|
489
|
-
stateBlackPop += blackPop;
|
|
490
|
-
stateHispanicPop += hispanicPop;
|
|
491
|
-
statePacificPop += pacificPop;
|
|
492
|
-
stateAsianPop += asianPop;
|
|
493
|
-
stateNativePop += nativePop;
|
|
494
552
|
}
|
|
495
553
|
// UPDATE STATE STATISTICS
|
|
496
554
|
let summaryRow = this.numberOfRows() - 1;
|
|
@@ -524,16 +582,6 @@ class Districts {
|
|
|
524
582
|
}
|
|
525
583
|
}
|
|
526
584
|
exports.Districts = Districts;
|
|
527
|
-
exports.DatasetDescriptions = {
|
|
528
|
-
D16F: "2016 ACS Total Population",
|
|
529
|
-
D16T: "2016 ACS Voting Age Population",
|
|
530
|
-
E16GPR: "2016 Presidential Election",
|
|
531
|
-
D10F: "2010 Census Total Population",
|
|
532
|
-
D10T: "2010 Voting Age Population",
|
|
533
|
-
C16GCO: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
534
|
-
// TODO - What other potential datasets?
|
|
535
|
-
// MORE ...
|
|
536
|
-
};
|
|
537
585
|
// Wrap data by feature, to abstract the specifics of the internal structure
|
|
538
586
|
class Features {
|
|
539
587
|
constructor(s, data, keys) {
|
|
@@ -544,15 +592,18 @@ class Features {
|
|
|
544
592
|
}
|
|
545
593
|
nFeatures() { return this._data.features.length; }
|
|
546
594
|
featureByIndex(i) { return this._data.features[i]; }
|
|
547
|
-
|
|
548
|
-
|
|
595
|
+
geoIDForFeature(f) {
|
|
596
|
+
// GEOIDs will be one of these properties
|
|
597
|
+
let value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
|
|
598
|
+
return value;
|
|
599
|
+
}
|
|
549
600
|
fieldForFeature(f, dt, fk) {
|
|
550
601
|
let dk = this._keys[dt];
|
|
551
602
|
return _getFeatures(f, dk, fk);
|
|
552
603
|
}
|
|
553
604
|
resetDataset(d, k) {
|
|
554
605
|
this._keys[d] = k;
|
|
555
|
-
// TODO - Does anything need to be recalc'd now when a dataset is changed?
|
|
606
|
+
// TODO - RECALC: Does anything need to be recalc'd now when a dataset is changed?
|
|
556
607
|
}
|
|
557
608
|
mapGeoIDsToFeatureIDs() {
|
|
558
609
|
for (let i = 0; i < this._session.features.nFeatures(); i++) {
|
|
@@ -586,12 +637,15 @@ function _getFeatures(f, datasetKey, p) {
|
|
|
586
637
|
return o[p];
|
|
587
638
|
}
|
|
588
639
|
else {
|
|
589
|
-
if (o['datasets'] && o['datasets'][datasetKey])
|
|
590
|
-
|
|
640
|
+
if (o['datasets'] && o['datasets'][datasetKey]) {
|
|
641
|
+
let v = (o['datasets'][datasetKey][p]);
|
|
642
|
+
if ((!(v == null)) && (!(v == undefined)))
|
|
591
643
|
return o['datasets'][datasetKey][p];
|
|
644
|
+
}
|
|
592
645
|
}
|
|
593
646
|
}
|
|
594
647
|
}
|
|
648
|
+
console.log(`${p} value undefined for ${f.properties['GEOID10']}!`);
|
|
595
649
|
return undefined;
|
|
596
650
|
}
|
|
597
651
|
function _fGetJoined(f) {
|
|
@@ -600,13 +654,13 @@ function _fGetJoined(f) {
|
|
|
600
654
|
// Wrap data by county, to abstract the specifics of the internal structure
|
|
601
655
|
class Counties {
|
|
602
656
|
constructor(s, data) {
|
|
657
|
+
this._countyNameLookup = {};
|
|
603
658
|
this.index = {};
|
|
659
|
+
this.totalPopulation = [];
|
|
604
660
|
this._session = s;
|
|
605
661
|
this._data = data;
|
|
606
662
|
this.nCounties = this._data.features.length;
|
|
607
|
-
this._countyNameLookup = {};
|
|
608
663
|
}
|
|
609
|
-
// nCounties(): number { return this._data.features.length; }
|
|
610
664
|
countyByIndex(i) { return this._data.features[i]; }
|
|
611
665
|
propertyForCounty(f, pk) { return f.properties[pk]; }
|
|
612
666
|
mapFIPSToName(fips, name) { this._countyNameLookup[fips] = name; }
|
|
@@ -635,8 +689,24 @@ class Plan {
|
|
|
635
689
|
this._planByDistrictID = {};
|
|
636
690
|
this.districtIDs = []; // Set when the plan in inverted
|
|
637
691
|
}
|
|
692
|
+
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
693
|
+
// for contiguity. Just skip them in aggregating district statistics.
|
|
694
|
+
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
695
|
+
// let newPlan = {} as T.PlanByGeoID;
|
|
696
|
+
// for (let geoID in plan) {
|
|
697
|
+
// // Remove water-only features
|
|
698
|
+
// if (!(U.isWaterOnly(geoID))) {
|
|
699
|
+
// newPlan[geoID] = plan[geoID];
|
|
700
|
+
// }
|
|
701
|
+
// else {
|
|
702
|
+
// console.log("Removing water-only feature", geoID);
|
|
703
|
+
// }
|
|
704
|
+
// }
|
|
705
|
+
// return newPlan;
|
|
706
|
+
// }
|
|
638
707
|
invertPlan() {
|
|
639
|
-
|
|
708
|
+
// TODO - UNASSIGNED
|
|
709
|
+
this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
|
|
640
710
|
this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
|
|
641
711
|
}
|
|
642
712
|
initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
|
|
@@ -646,12 +716,49 @@ class Plan {
|
|
|
646
716
|
geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
|
|
647
717
|
}
|
|
648
718
|
exports.Plan = Plan;
|
|
719
|
+
// Invert a feature assignment structure to sets of ids by district
|
|
720
|
+
function invertPlan(plan, s) {
|
|
721
|
+
let invertedPlan = {};
|
|
722
|
+
// Add a dummy 'unassigned' district
|
|
723
|
+
invertedPlan[S.NOT_ASSIGNED] = new Set();
|
|
724
|
+
// TODO - UNASSIGNED
|
|
725
|
+
// NOTE - The feature assignments coming from DRA do not include unassigned ones.
|
|
726
|
+
// - In the DRA-calling context, there's an analytics session with a reference
|
|
727
|
+
// to the features. Loop over all the features to find the unassigned ones,
|
|
728
|
+
// and add them to the dummy unassigned district explicitly.
|
|
729
|
+
// - In the CLI-calling context, there's no session (yet) but the plan is complete.
|
|
730
|
+
if (!(s == undefined)) {
|
|
731
|
+
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
732
|
+
let f = s.features.featureByIndex(i);
|
|
733
|
+
let geoID = s.features.geoIDForFeature(f);
|
|
734
|
+
// If the feature is NOT explicitly assigned to a district, add the geoID
|
|
735
|
+
// to the dummy unassigned district 0.
|
|
736
|
+
if (!(U.keyExists(geoID, plan)))
|
|
737
|
+
invertedPlan[S.NOT_ASSIGNED].add(geoID);
|
|
738
|
+
// TODO - WATER-ONLY: NOT skipping water-only features here, because we're
|
|
739
|
+
// not skipping them below when they are explicitly assigned in plans. Should
|
|
740
|
+
// we skip them in both places?
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (let geoID in plan) {
|
|
744
|
+
let districtID = plan[geoID];
|
|
745
|
+
// Make sure the set for the districtID exists
|
|
746
|
+
if (!(U.objectContains(invertedPlan, districtID))) {
|
|
747
|
+
invertedPlan[districtID] = new Set();
|
|
748
|
+
}
|
|
749
|
+
// Add the geoID to the districtID's set
|
|
750
|
+
invertedPlan[districtID].add(geoID);
|
|
751
|
+
if (U.isWaterOnly(geoID))
|
|
752
|
+
console.log("Water-only feature still in plan!", geoID);
|
|
753
|
+
}
|
|
754
|
+
return invertedPlan;
|
|
755
|
+
}
|
|
756
|
+
exports.invertPlan = invertPlan;
|
|
649
757
|
class Graph {
|
|
650
758
|
constructor(s, graph) {
|
|
651
759
|
this._session = s;
|
|
652
760
|
this._graph = graph;
|
|
653
761
|
}
|
|
654
|
-
// TODO - Rework this, when we support MIXED MAPS.
|
|
655
762
|
peerNeighbors(node) {
|
|
656
763
|
// Get the neighboring geoIDs connected to a geoID
|
|
657
764
|
// Ignore the lengths of the shared borders (the values), for now
|
|
@@ -688,19 +795,20 @@ function doAnalyzeDistricts(s, bLog = false) {
|
|
|
688
795
|
s.districts.extractDistrictShapeProperties(bLog);
|
|
689
796
|
}
|
|
690
797
|
exports.doAnalyzeDistricts = doAnalyzeDistricts;
|
|
691
|
-
// TODO - I could make this table-driven, but I'm thinking that the explicit
|
|
692
|
-
// calls might make chunking for aync easier.
|
|
693
798
|
// Calculate the analytics & validations and cache the results
|
|
694
799
|
// NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
|
|
800
|
+
// NOTE - I could make this table-driven, but I'm thinking that the explicit
|
|
801
|
+
// calls might make chunking for aync easier.
|
|
695
802
|
function doAnalyzePlan(s, bLog = false) {
|
|
803
|
+
// TODO - Remove this mechanism. Always run all tests
|
|
696
804
|
// Get the requested suites, and only execute those tests
|
|
697
805
|
let requestedSuites = s.config['suites'];
|
|
698
806
|
// Tests in the "Legal" suite, i.e., pass/ fail constraints
|
|
699
807
|
if (requestedSuites.includes(0 /* Legal */)) {
|
|
700
|
-
s.tests[0 /* Complete */] = valid_1.doIsComplete(s);
|
|
701
|
-
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s);
|
|
702
|
-
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s);
|
|
703
|
-
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s);
|
|
808
|
+
s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
|
|
809
|
+
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
|
|
810
|
+
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
|
|
811
|
+
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
|
|
704
812
|
// NOTE - I can't check whether a population deviation is legal or not, until
|
|
705
813
|
// the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
|
|
706
814
|
// the given type of district (CD vs. LD). The EqualPopulation test is derived
|
|
@@ -710,19 +818,21 @@ function doAnalyzePlan(s, bLog = false) {
|
|
|
710
818
|
}
|
|
711
819
|
// Tests in the "Fair" suite
|
|
712
820
|
if (requestedSuites.includes(1 /* Fair */)) {
|
|
713
|
-
s.tests[
|
|
714
|
-
s.tests[
|
|
715
|
-
s.tests[
|
|
716
|
-
s.tests[
|
|
717
|
-
s.tests[
|
|
718
|
-
s.tests[
|
|
821
|
+
s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
|
|
822
|
+
s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
|
|
823
|
+
s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
|
|
824
|
+
s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
|
|
825
|
+
s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
|
|
826
|
+
s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
|
|
719
827
|
}
|
|
720
828
|
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
721
829
|
if (requestedSuites.includes(2 /* Best */)) {
|
|
722
830
|
s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
|
|
723
831
|
s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
|
|
724
|
-
s.tests[7 /*
|
|
725
|
-
s.tests[
|
|
832
|
+
s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
|
|
833
|
+
s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
|
|
834
|
+
s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
|
|
835
|
+
s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
|
|
726
836
|
}
|
|
727
837
|
// Enable a Test Log and Scorecard to be generated
|
|
728
838
|
s.bPlanAnalyzed = true;
|
|
@@ -735,8 +845,8 @@ exports.doAnalyzePlan = doAnalyzePlan;
|
|
|
735
845
|
//
|
|
736
846
|
// NOTE - Should this be conditionalized on the test suites requested?
|
|
737
847
|
// Those are encapsulated in reports.ts right now, so not doing that.
|
|
738
|
-
function doDeriveSecondaryTests(s) {
|
|
739
|
-
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s);
|
|
848
|
+
function doDeriveSecondaryTests(s, bLog = false) {
|
|
849
|
+
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
|
|
740
850
|
}
|
|
741
851
|
exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
742
852
|
|
|
@@ -753,19 +863,237 @@ exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
|
753
863
|
"use strict";
|
|
754
864
|
|
|
755
865
|
//
|
|
756
|
-
//
|
|
866
|
+
// SPLITTING of counties & districts
|
|
757
867
|
//
|
|
868
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
869
|
+
if (mod && mod.__esModule) return mod;
|
|
870
|
+
var result = {};
|
|
871
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
872
|
+
result["default"] = mod;
|
|
873
|
+
return result;
|
|
874
|
+
};
|
|
758
875
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
759
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
876
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
877
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
878
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
879
|
+
// CALCULATE ENHANCED SQRT ENTROPY METRIC
|
|
880
|
+
function doCountySplitting(s, bLog = false) {
|
|
881
|
+
let test = s.getTest(8 /* CountySplitting */);
|
|
882
|
+
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
883
|
+
let countyTotals = s.counties.totalPopulation;
|
|
884
|
+
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
885
|
+
let f = calcCountyFractions(CxD, countyTotals);
|
|
886
|
+
let w = calcCountyWeights(countyTotals);
|
|
887
|
+
let SqEnt_DC = countySplitting(f, w, bLog);
|
|
888
|
+
let CxDreducedC = U.deepCopy(CxD);
|
|
889
|
+
reduceCSplits(CxDreducedC, districtTotals);
|
|
890
|
+
let fReduced = calcCountyFractions(CxDreducedC, countyTotals);
|
|
891
|
+
let wReduced = calcCountyWeights(countyTotals);
|
|
892
|
+
let SqEnt_DCreduced = countySplitting(fReduced, wReduced, bLog);
|
|
893
|
+
test['score'] = SqEnt_DCreduced;
|
|
894
|
+
test['details']['SqEnt_DC'] = SqEnt_DC;
|
|
895
|
+
return test;
|
|
896
|
+
}
|
|
897
|
+
exports.doCountySplitting = doCountySplitting;
|
|
898
|
+
function doDistrictSplitting(s, bLog = false) {
|
|
899
|
+
let test = s.getTest(9 /* DistrictSplitting */);
|
|
900
|
+
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
901
|
+
let countyTotals = s.counties.totalPopulation;
|
|
902
|
+
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
903
|
+
let g = calcDistrictFractions(CxD, districtTotals);
|
|
904
|
+
let x = calcDistrictWeights(districtTotals);
|
|
905
|
+
let SqEnt_CD = districtSplitting(g, x, bLog);
|
|
906
|
+
let CxDreducedD = U.deepCopy(CxD);
|
|
907
|
+
reduceDSplits(CxDreducedD, countyTotals);
|
|
908
|
+
let gReduced = calcDistrictFractions(CxDreducedD, districtTotals);
|
|
909
|
+
let xReduced = calcDistrictWeights(districtTotals);
|
|
910
|
+
let SqEnt_CDreduced = districtSplitting(gReduced, xReduced, bLog);
|
|
911
|
+
test['score'] = SqEnt_CDreduced;
|
|
912
|
+
test['details']['SqEnt_CD'] = SqEnt_CD;
|
|
913
|
+
return test;
|
|
914
|
+
}
|
|
915
|
+
exports.doDistrictSplitting = doDistrictSplitting;
|
|
916
|
+
// HELPERS
|
|
917
|
+
// Loop over all the county-district combos, skipping the virtual district 0
|
|
918
|
+
// and virtual county 0.
|
|
919
|
+
//
|
|
920
|
+
// NOTE - The county-district splits and the county & district totals may all,
|
|
921
|
+
// in general, be fractional/decimal numbers as opposed to integers, due to
|
|
922
|
+
// dissaggregation & re-aggregation. Hence, comparisons need to approximate
|
|
923
|
+
// equality.
|
|
924
|
+
// Consolidate districts (rows) consisting of just one county (column)
|
|
925
|
+
// UP into the dummy district (0).
|
|
926
|
+
function reduceCSplits(CxDreducedC, districtTotals) {
|
|
927
|
+
let nD = CxDreducedC.length;
|
|
928
|
+
let nC = CxDreducedC[0].length;
|
|
929
|
+
for (let j = 1; j < nC; j++) {
|
|
930
|
+
for (let i = 1; i < nD; i++) {
|
|
931
|
+
let split_total = CxDreducedC[i][j];
|
|
932
|
+
if (split_total > 0) {
|
|
933
|
+
if (areRoughlyEqual(split_total, districtTotals[i])) {
|
|
934
|
+
CxDreducedC[0][j] += split_total;
|
|
935
|
+
CxDreducedC[i][j] = 0;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
// Consolidate whole counties (columns) in a district (row) LEFT into the
|
|
942
|
+
// dummy county (0).
|
|
943
|
+
function reduceDSplits(CxDreducedD, countyTotals) {
|
|
944
|
+
let nD = CxDreducedD.length;
|
|
945
|
+
let nC = CxDreducedD[0].length;
|
|
946
|
+
for (let i = 1; i < nD; i++) {
|
|
947
|
+
for (let j = 1; j < nC; j++) {
|
|
948
|
+
let split_total = CxDreducedD[i][j];
|
|
949
|
+
if (split_total > 0) {
|
|
950
|
+
if (areRoughlyEqual(split_total, countyTotals[j])) {
|
|
951
|
+
CxDreducedD[i][0] += split_total;
|
|
952
|
+
CxDreducedD[i][j] = 0;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function calcCountyWeights(countyTotals) {
|
|
959
|
+
let nC = countyTotals.length;
|
|
960
|
+
let cTotal = U.sumArray(countyTotals);
|
|
961
|
+
let w = U.initArray(nC, 0.0);
|
|
962
|
+
for (let j = 0; j < nC; j++) {
|
|
963
|
+
w[j] = countyTotals[j] / cTotal;
|
|
964
|
+
}
|
|
965
|
+
return w;
|
|
966
|
+
}
|
|
967
|
+
function calcDistrictWeights(districtTotals) {
|
|
968
|
+
let nD = districtTotals.length;
|
|
969
|
+
let dTotal = U.sumArray(districtTotals);
|
|
970
|
+
let x = U.initArray(nD, 0.0);
|
|
971
|
+
for (let i = 0; i < nD; i++) {
|
|
972
|
+
x[i] = districtTotals[i] / dTotal;
|
|
973
|
+
}
|
|
974
|
+
return x;
|
|
975
|
+
}
|
|
976
|
+
function calcCountyFractions(CxDreducedD, countyTotals) {
|
|
977
|
+
let nD = CxDreducedD.length;
|
|
978
|
+
let nC = CxDreducedD[0].length;
|
|
979
|
+
let f = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
980
|
+
for (let j = 0; j < nC; j++) {
|
|
981
|
+
for (let i = 0; i < nD; i++) {
|
|
982
|
+
if (countyTotals[j] > 0) {
|
|
983
|
+
f[i][j] = CxDreducedD[i][j] / countyTotals[j];
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
f[i][j] = 0.0;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return f;
|
|
991
|
+
}
|
|
992
|
+
function calcDistrictFractions(CxDreducedC, districtTotals) {
|
|
993
|
+
let nD = CxDreducedC.length;
|
|
994
|
+
let nC = CxDreducedC[0].length;
|
|
995
|
+
let g = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
996
|
+
for (let j = 0; j < nC; j++) {
|
|
997
|
+
for (let i = 0; i < nD; i++) {
|
|
998
|
+
if (districtTotals[i] > 0) {
|
|
999
|
+
g[i][j] = CxDreducedC[i][j] / districtTotals[i];
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
g[i][j] = 0.0;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return g;
|
|
1007
|
+
}
|
|
1008
|
+
// Deal with decimal census "counts" due to disagg/re-agg
|
|
1009
|
+
function areRoughlyEqual(x, y) {
|
|
1010
|
+
let delta = Math.abs(x - y);
|
|
1011
|
+
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
1012
|
+
return result;
|
|
1013
|
+
}
|
|
1014
|
+
// For all districts in a county, sum the split score.
|
|
1015
|
+
function countySplitScore(j, f, numD, bLog = false) {
|
|
1016
|
+
let e = 0.0;
|
|
1017
|
+
for (let i = 0; i < numD; i++) {
|
|
1018
|
+
e += Math.sqrt(f[i][j]);
|
|
1019
|
+
}
|
|
1020
|
+
return e;
|
|
1021
|
+
}
|
|
1022
|
+
// For all counties, sum the weighted county splits.
|
|
1023
|
+
function countySplitting(f, w, bLog = false) {
|
|
1024
|
+
let numC = f[0].length;
|
|
1025
|
+
let numD = f.length;
|
|
1026
|
+
let e = 0.0;
|
|
1027
|
+
for (let j = 0; j < numC; j++) {
|
|
1028
|
+
let splitScore = countySplitScore(j, f, numD, bLog);
|
|
1029
|
+
e += w[j] * splitScore;
|
|
1030
|
+
if (bLog)
|
|
1031
|
+
console.log("County splitting =", j, w[j], splitScore, e);
|
|
1032
|
+
}
|
|
1033
|
+
return U.trim(e, 3);
|
|
1034
|
+
}
|
|
1035
|
+
// For all counties in a district, sum the split score.
|
|
1036
|
+
function districtSplitScore(i, g, numC, bLog = false) {
|
|
1037
|
+
let e = 0.0;
|
|
1038
|
+
for (let j = 0; j < numC; j++) {
|
|
1039
|
+
e += Math.sqrt(g[i][j]);
|
|
1040
|
+
}
|
|
1041
|
+
return e;
|
|
1042
|
+
}
|
|
1043
|
+
// For all districts, sum the weighted district splits.
|
|
1044
|
+
function districtSplitting(g, x, bLog = false) {
|
|
1045
|
+
let numC = g[0].length;
|
|
1046
|
+
let numD = g.length;
|
|
1047
|
+
let e = 0.0;
|
|
1048
|
+
for (let i = 0; i < numD; i++) {
|
|
1049
|
+
let splitScore = districtSplitScore(i, g, numC, bLog);
|
|
1050
|
+
e += x[i] * splitScore;
|
|
1051
|
+
if (bLog)
|
|
1052
|
+
console.log("District split score =", i, x[i], splitScore, e);
|
|
1053
|
+
}
|
|
1054
|
+
return U.trim(e, 3);
|
|
1055
|
+
}
|
|
1056
|
+
// ANALYZE SIMPLE COUNTY & VTD SPLITTING
|
|
1057
|
+
/*
|
|
1058
|
+
|
|
1059
|
+
Sample results for NC 2016 dongressional plan
|
|
1060
|
+
________________________________________________________________________________
|
|
1061
|
+
|
|
1062
|
+
State: NC
|
|
1063
|
+
Census: 2010
|
|
1064
|
+
Total population: 9,535,483
|
|
1065
|
+
Number of districts: 13
|
|
1066
|
+
Target district size: 733,499
|
|
1067
|
+
Number of counties: 100
|
|
1068
|
+
|
|
1069
|
+
Equal Population: 11.24% deviation
|
|
1070
|
+
Compactness: None
|
|
1071
|
+
Proportionality: 27.67% gap
|
|
1072
|
+
Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
|
|
1073
|
+
|
|
1074
|
+
These counties are split unexpectedly:
|
|
1075
|
+
|
|
1076
|
+
• Bladen
|
|
1077
|
+
• Buncombe
|
|
1078
|
+
• Catawba
|
|
1079
|
+
• Cumberland
|
|
1080
|
+
• Durham
|
|
1081
|
+
• Guilford
|
|
1082
|
+
• Iredell
|
|
1083
|
+
• Johnston
|
|
1084
|
+
• Pitt
|
|
1085
|
+
• Rowan
|
|
1086
|
+
• Wilson
|
|
1087
|
+
|
|
1088
|
+
*/
|
|
1089
|
+
function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
1090
|
+
let test = s.getTest(7 /* UnexpectedCountySplits */);
|
|
763
1091
|
// THE THREE VALUES TO DETERMINE FOR A PLAN
|
|
764
1092
|
let unexpectedSplits = 0;
|
|
765
1093
|
let unexpectedAffected = 0;
|
|
766
1094
|
let countiesSplitUnexpectedly = [];
|
|
767
|
-
// FIRST, ANALYZE THE COUNTY
|
|
768
|
-
//
|
|
1095
|
+
// FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
|
|
1096
|
+
// Get the county-district pivot ("splits")
|
|
769
1097
|
let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
|
|
770
1098
|
// countiesByDistrict = countiesByDistrict.slice(1, -1);
|
|
771
1099
|
// Find the single-county districts, i.e., districts NOT split across counties.
|
|
@@ -775,10 +1103,13 @@ function doCountySplits(s) {
|
|
|
775
1103
|
// See if there's only one county partition
|
|
776
1104
|
let nCountiesInDistrict = 0;
|
|
777
1105
|
for (let c = 0; c < s.counties.nCounties; c++) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (
|
|
781
|
-
|
|
1106
|
+
// Guard against empty district
|
|
1107
|
+
if (countiesByDistrict[d]) {
|
|
1108
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
1109
|
+
nCountiesInDistrict += 1;
|
|
1110
|
+
if (nCountiesInDistrict > 1) {
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
782
1113
|
}
|
|
783
1114
|
}
|
|
784
1115
|
}
|
|
@@ -798,11 +1129,14 @@ function doCountySplits(s) {
|
|
|
798
1129
|
let nCountyParts = 0;
|
|
799
1130
|
let subtotal = 0;
|
|
800
1131
|
for (let d = 1; d <= s.state.nDistricts; d++) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1132
|
+
// Guard against empty district
|
|
1133
|
+
if (countiesByDistrict[d]) {
|
|
1134
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
1135
|
+
nPartitionsOverall += 1;
|
|
1136
|
+
nCountyParts += 1;
|
|
1137
|
+
if (!(U.arrayContains(singleCountyDistricts, d))) {
|
|
1138
|
+
subtotal += countiesByDistrict[d][c];
|
|
1139
|
+
}
|
|
806
1140
|
}
|
|
807
1141
|
}
|
|
808
1142
|
}
|
|
@@ -840,48 +1174,21 @@ function doCountySplits(s) {
|
|
|
840
1174
|
countiesSplitUnexpectedly.push(s.counties.nameFromFIPS(fips));
|
|
841
1175
|
}
|
|
842
1176
|
countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
|
|
843
|
-
|
|
844
|
-
test['
|
|
845
|
-
test['details'] =
|
|
846
|
-
'unexpectedSplits': unexpectedSplits,
|
|
847
|
-
'unexpectedAffected': unexpectedAffected,
|
|
848
|
-
'countiesSplitUnexpectedly': countiesSplitUnexpectedly
|
|
849
|
-
};
|
|
1177
|
+
test['score'] = U.trim(unexpectedAffected);
|
|
1178
|
+
test['details']['unexpectedSplits'] = unexpectedSplits;
|
|
1179
|
+
test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
|
|
850
1180
|
return test;
|
|
851
1181
|
}
|
|
852
|
-
exports.
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
//
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
// - The map -- So it can count the features by summary level in the map,
|
|
860
|
-
// as well as the number of BG’s that are split.
|
|
861
|
-
//
|
|
862
|
-
// TODO - Where would the state counts come from? Preprocessed and passed in, or
|
|
863
|
-
// done in a one-time initialization call (which would require a full set of
|
|
864
|
-
// block geo_id's for the state).
|
|
865
|
-
//
|
|
866
|
-
// However, if a map is not yet (fully) simplified, then determining the
|
|
867
|
-
// complexity of a map also requires a preprocessed summary level hierarchy, so
|
|
868
|
-
// you can get the child features (e.g., tracts) of a parent feature (e.g.,
|
|
869
|
-
// a county).
|
|
870
|
-
//
|
|
871
|
-
// NOTE - I have script for producing this hierarchy which we could repurpose.
|
|
872
|
-
//
|
|
873
|
-
// TODO - For mixed map processing--specfically to find the neighbors of a feature
|
|
874
|
-
// that are actually in the map (as opposed to just neighbors at the same
|
|
875
|
-
// summary level in the static graph)--you need a special hierarchy that
|
|
876
|
-
// distinguishes between the 'interior' and 'edge children of a feature.
|
|
877
|
-
//
|
|
878
|
-
// NOTE - The script noted above does this.
|
|
879
|
-
function doPlanComplexity(s) {
|
|
880
|
-
let test = s.getTest(8 /* Complexity */);
|
|
881
|
-
console.log("TODO - Calculating plan complexity ...");
|
|
1182
|
+
exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
|
|
1183
|
+
function doFindSplitVTDs(s, bLog = false) {
|
|
1184
|
+
let test = s.getTest(10 /* VTDSplits */);
|
|
1185
|
+
let splitVTDs = [];
|
|
1186
|
+
// TODO - SPLITTING: Flesh this out, using Terry's virtual VTD's ...
|
|
1187
|
+
test['score'] = splitVTDs.length;
|
|
1188
|
+
test['details']['splitVTDs'] = splitVTDs;
|
|
882
1189
|
return test;
|
|
883
1190
|
}
|
|
884
|
-
exports.
|
|
1191
|
+
exports.doFindSplitVTDs = doFindSplitVTDs;
|
|
885
1192
|
|
|
886
1193
|
|
|
887
1194
|
/***/ }),
|
|
@@ -898,9 +1205,17 @@ exports.doPlanComplexity = doPlanComplexity;
|
|
|
898
1205
|
//
|
|
899
1206
|
// COMPACT
|
|
900
1207
|
//
|
|
1208
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1209
|
+
if (mod && mod.__esModule) return mod;
|
|
1210
|
+
var result = {};
|
|
1211
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1212
|
+
result["default"] = mod;
|
|
1213
|
+
return result;
|
|
1214
|
+
};
|
|
901
1215
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1216
|
+
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
|
|
902
1217
|
const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
|
|
903
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1218
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
904
1219
|
// Measures of compactness compare district shapes to various ideally compact
|
|
905
1220
|
// benchmarks, such as circles. All else equal, more compact districts are better.
|
|
906
1221
|
//
|
|
@@ -988,16 +1303,19 @@ function doReock(s, bLog = false) {
|
|
|
988
1303
|
let scores = [];
|
|
989
1304
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
990
1305
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1306
|
+
// Guard against no shape and no properties
|
|
1307
|
+
if (districtProps) {
|
|
1308
|
+
let a = districtProps[0 /* Area */];
|
|
1309
|
+
let d = districtProps[1 /* Diameter */];
|
|
1310
|
+
let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
|
|
1311
|
+
// Save each district score
|
|
1312
|
+
scores.push(reock);
|
|
1313
|
+
// Echo the results by district
|
|
1314
|
+
if (bLog)
|
|
1315
|
+
console.log("Reock for district", districtID, "=", reock);
|
|
1316
|
+
}
|
|
999
1317
|
}
|
|
1000
|
-
// Populate the test entry
|
|
1318
|
+
// Populate the test entry ... for the shapes that exist!
|
|
1001
1319
|
let averageReock = U.avgArray(scores);
|
|
1002
1320
|
test['score'] = U.trim(averageReock);
|
|
1003
1321
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -1013,16 +1331,19 @@ function doPolsbyPopper(s, bLog = false) {
|
|
|
1013
1331
|
let scores = [];
|
|
1014
1332
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
1015
1333
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1334
|
+
// Guard against no shape and no properties
|
|
1335
|
+
if (districtProps) {
|
|
1336
|
+
let a = districtProps[0 /* Area */];
|
|
1337
|
+
let p = districtProps[2 /* Perimeter */];
|
|
1338
|
+
let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
|
|
1339
|
+
// Save each district score
|
|
1340
|
+
scores.push(polsbyPopper);
|
|
1341
|
+
// Echo the results by district
|
|
1342
|
+
if (bLog)
|
|
1343
|
+
console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
|
|
1344
|
+
}
|
|
1024
1345
|
}
|
|
1025
|
-
// Populate the test entry
|
|
1346
|
+
// Populate the test entry ... for the shapes that exist!
|
|
1026
1347
|
let averagePolsbyPopper = U.avgArray(scores);
|
|
1027
1348
|
test['score'] = U.trim(averagePolsbyPopper);
|
|
1028
1349
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -1032,19 +1353,24 @@ exports.doPolsbyPopper = doPolsbyPopper;
|
|
|
1032
1353
|
// HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
|
|
1033
1354
|
function extractDistrictProperties(s, bLog = false) {
|
|
1034
1355
|
for (let i = 1; i <= s.state.nDistricts; i++) {
|
|
1035
|
-
let j = i - 1; // TODO -
|
|
1356
|
+
let j = i - 1; // TODO - TERRY: How do you get away w/o this?!?
|
|
1036
1357
|
let poly = s.districts.getShape(j);
|
|
1037
|
-
//
|
|
1038
|
-
let
|
|
1039
|
-
let
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1358
|
+
// Guard against no shape for empty districts AND null shapes
|
|
1359
|
+
let polyOptions = { noLatitudeCorrection: true };
|
|
1360
|
+
let bNull = (!Poly.polyNormalize(poly, polyOptions));
|
|
1361
|
+
if (poly && (!bNull)) {
|
|
1362
|
+
// TODO - OPTIMIZE: Bundle these calls?
|
|
1363
|
+
let area = geofeature_1.gfArea(poly);
|
|
1364
|
+
let perimeter = geofeature_1.gfPerimeter(poly);
|
|
1365
|
+
let diameter = geofeature_1.gfDiameter(poly);
|
|
1366
|
+
let props = [0, 0, 0]; // TODO - TERRY?!?
|
|
1367
|
+
props[0 /* Area */] = area;
|
|
1368
|
+
props[1 /* Diameter */] = diameter;
|
|
1369
|
+
props[2 /* Perimeter */] = perimeter;
|
|
1370
|
+
s.districts.setGeoProperties(i, props);
|
|
1371
|
+
if (bLog)
|
|
1372
|
+
console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
|
|
1373
|
+
}
|
|
1048
1374
|
}
|
|
1049
1375
|
}
|
|
1050
1376
|
exports.extractDistrictProperties = extractDistrictProperties;
|
|
@@ -1092,10 +1418,17 @@ exports.extractDistrictProperties = extractDistrictProperties;
|
|
|
1092
1418
|
//
|
|
1093
1419
|
// EQUAL POPULATION
|
|
1094
1420
|
//
|
|
1421
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1422
|
+
if (mod && mod.__esModule) return mod;
|
|
1423
|
+
var result = {};
|
|
1424
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1425
|
+
result["default"] = mod;
|
|
1426
|
+
return result;
|
|
1427
|
+
};
|
|
1095
1428
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1096
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1097
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1098
|
-
function doPopulationDeviation(s) {
|
|
1429
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1430
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
1431
|
+
function doPopulationDeviation(s, bLog = false) {
|
|
1099
1432
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
1100
1433
|
// Compute the min, max, and average district populations,
|
|
1101
1434
|
// excluding the dummy 'unassigned' 0 and N+1 summary "districts."
|
|
@@ -1124,7 +1457,7 @@ function doPopulationDeviation(s) {
|
|
|
1124
1457
|
exports.doPopulationDeviation = doPopulationDeviation;
|
|
1125
1458
|
// NOTE - This validity check is *derived* and depends on population deviation %
|
|
1126
1459
|
// being computed (above) and normalized in test log & scorecard generation.
|
|
1127
|
-
function doHasEqualPopulations(s) {
|
|
1460
|
+
function doHasEqualPopulations(s, bLog = false) {
|
|
1128
1461
|
let test = s.getTest(3 /* EqualPopulation */);
|
|
1129
1462
|
// Get the normalized population deviation %
|
|
1130
1463
|
let popDevTest = s.getTest(4 /* PopulationDeviation */);
|
|
@@ -1162,14 +1495,19 @@ exports.doHasEqualPopulations = doHasEqualPopulations;
|
|
|
1162
1495
|
//
|
|
1163
1496
|
// GEO-FEATURES UTILITIES
|
|
1164
1497
|
//
|
|
1498
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1499
|
+
if (mod && mod.__esModule) return mod;
|
|
1500
|
+
var result = {};
|
|
1501
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1502
|
+
result["default"] = mod;
|
|
1503
|
+
return result;
|
|
1504
|
+
};
|
|
1165
1505
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1166
|
-
const Poly = __webpack_require__(/*! @dra2020/poly */ "@dra2020/poly");
|
|
1506
|
+
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
|
|
1167
1507
|
// CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
|
|
1168
|
-
// TODO -
|
|
1508
|
+
// TODO - TERRY: Confirm Cartesian calculations
|
|
1169
1509
|
function gfArea(poly) {
|
|
1170
1510
|
let area = _polygonArea(poly);
|
|
1171
|
-
// let polyOptions = { noLatitudeCorrection: false } DELETE
|
|
1172
|
-
// let area: number = Poly.polyArea(poly, polyOptions);
|
|
1173
1511
|
return area;
|
|
1174
1512
|
}
|
|
1175
1513
|
exports.gfArea = gfArea;
|
|
@@ -1215,19 +1553,16 @@ function _polygonArea(poly) {
|
|
|
1215
1553
|
}
|
|
1216
1554
|
return a;
|
|
1217
1555
|
}
|
|
1218
|
-
// TODO -
|
|
1556
|
+
// TODO - TERRY: Confirm Cartesian calculations
|
|
1219
1557
|
// The perimeter calculation already just computes cartesian distance if you
|
|
1220
1558
|
// pass in the noLatitudeCorrection flag. You would need to divide by
|
|
1221
1559
|
// Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
|
|
1222
1560
|
function gfPerimeter(poly) {
|
|
1223
1561
|
let perimeter = _polygonPerimeter(poly);
|
|
1224
|
-
// let polyOptions = { noLatitudeCorrection: true } // Cartesian distance
|
|
1225
|
-
// let perimeter: number = Poly.polyPerimeter(poly, polyOptions); DELETE
|
|
1226
1562
|
return perimeter;
|
|
1227
|
-
// return perimeter / Poly.EARTH_RADIUS; DELETE
|
|
1228
1563
|
}
|
|
1229
1564
|
exports.gfPerimeter = gfPerimeter;
|
|
1230
|
-
// TODO -
|
|
1565
|
+
// TODO - TERRY: Confirm Cartesian calculations
|
|
1231
1566
|
// Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
|
|
1232
1567
|
// NOTE: No conversion of degrees to radians!
|
|
1233
1568
|
function _polygonPerimeter(poly) {
|
|
@@ -1239,10 +1574,8 @@ function _polygonPerimeter(poly) {
|
|
|
1239
1574
|
let p = poly[i][0];
|
|
1240
1575
|
for (let j = 0; j < p.length - 1; j++)
|
|
1241
1576
|
perimeter += _distance(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1]);
|
|
1242
|
-
// perimeter += haversine(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1], options); DELETE
|
|
1243
1577
|
if (p.length > 2 && (p[0][0] != p[p.length - 1][0] || p[0][1] != p[p.length - 1][1]))
|
|
1244
1578
|
perimeter += _distance(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1]);
|
|
1245
|
-
// perimeter += haversine(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1], options); DELETE
|
|
1246
1579
|
}
|
|
1247
1580
|
return perimeter;
|
|
1248
1581
|
}
|
|
@@ -1253,7 +1586,7 @@ function _distance(x1, y1, x2, y2) {
|
|
|
1253
1586
|
d = Math.sqrt((dLat * dLat) + (dLon * dLon));
|
|
1254
1587
|
return d;
|
|
1255
1588
|
}
|
|
1256
|
-
// TODO -
|
|
1589
|
+
// TODO - TERRY: Confirm Cartesian calculations
|
|
1257
1590
|
// As I mentioned, the polyCircle code was already just treating the coordinate
|
|
1258
1591
|
// system as Cartesian. I then did polyFromCircle to convert it to a polygon that
|
|
1259
1592
|
// then could be passed to polyArea in order to take into account the projection.
|
|
@@ -1262,9 +1595,6 @@ function _distance(x1, y1, x2, y2) {
|
|
|
1262
1595
|
function gfDiameter(poly) {
|
|
1263
1596
|
let polyOptions = { noLatitudeCorrection: true }; // NO-OP
|
|
1264
1597
|
let circle = Poly.polyToCircle(poly, polyOptions);
|
|
1265
|
-
// let circleArea: number = Poly.polyArea(Poly.polyFromCircle(circle, undefined, polyOptions), polyOptions);
|
|
1266
|
-
// let circleRadius: number = Math.sqrt(circleArea / Math.PI);
|
|
1267
|
-
// let diameter: number = circleRadius * 2; DELETE
|
|
1268
1598
|
let diameter = circle.r * 2;
|
|
1269
1599
|
return diameter;
|
|
1270
1600
|
}
|
|
@@ -1290,6 +1620,7 @@ function __export(m) {
|
|
|
1290
1620
|
}
|
|
1291
1621
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1292
1622
|
__export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
|
|
1623
|
+
__export(__webpack_require__(/*! ./results */ "./src/results.ts"));
|
|
1293
1624
|
__export(__webpack_require__(/*! ./types */ "./src/types.ts"));
|
|
1294
1625
|
|
|
1295
1626
|
|
|
@@ -1308,61 +1639,17 @@ __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
|
|
|
1308
1639
|
// PROTECTS MINORITIES
|
|
1309
1640
|
//
|
|
1310
1641
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
// The TOTAL, WHITE, BLACK, and HISPANIC counts from the Census, aggregated by
|
|
1316
|
-
// district. We *might* also ultimately need TOTAL18, WHITE18, BLACK18, and
|
|
1317
|
-
// HISPANIC18 counts by feature for these analytics.
|
|
1318
|
-
//
|
|
1319
|
-
// The minority population of a feature will probably be calculated as everyone
|
|
1320
|
-
// exceot non-Hispanic Whites:
|
|
1321
|
-
//
|
|
1322
|
-
// MINORITY = TOTAL - (WHITE - HISPANIC)
|
|
1323
|
-
//
|
|
1324
|
-
// That could be calculated as part of preprocessing the Census data, or it
|
|
1325
|
-
// could be computed on the fly. Since it's derived data and the formula might
|
|
1326
|
-
// change, it's probably best to compute it on the fly.
|
|
1327
|
-
//
|
|
1328
|
-
// In addition to the Census extract, these analytics need:
|
|
1329
|
-
// - The # of districts in the map, for determining minority proportionality
|
|
1330
|
-
// - Minorities as a % of the total population; possibly the voting age share
|
|
1331
|
-
//
|
|
1332
|
-
// TODO - Is the # of districts passed as a parameter or inferred from the # in
|
|
1333
|
-
// the map?
|
|
1334
|
-
// TODO - Is minority share preprocessed once and passed as a parameter or
|
|
1335
|
-
// computed in a initialization routine?
|
|
1336
|
-
function doMajorityMinorityDistricts(s) {
|
|
1337
|
-
let test = s.getTest(14 /* MajorityMinorityDistricts */);
|
|
1338
|
-
console.log("TODO - Calculating # of majority-minority districts ...");
|
|
1642
|
+
function doMajorityMinorityDistricts(s, bLog = false) {
|
|
1643
|
+
let test = s.getTest(16 /* MajorityMinorityDistricts */);
|
|
1644
|
+
if (bLog)
|
|
1645
|
+
console.log("TODO - Calculating # of majority-minority districts ...");
|
|
1339
1646
|
return test;
|
|
1340
1647
|
}
|
|
1341
1648
|
exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
1342
|
-
//
|
|
1343
|
-
//
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
// IN ADDITION TO THE MAP (IDEALLY, GEO_IDS INDEXED BY DISTRICT_ID)
|
|
1347
|
-
//
|
|
1348
|
-
// Census data by geo_id - { total population | white | black | hispanic }
|
|
1349
|
-
//
|
|
1350
|
-
// The minority population of a feature will probably be calculated as the # of
|
|
1351
|
-
// non-White Hispanics:
|
|
1352
|
-
//
|
|
1353
|
-
// MINORITY = TOTAL - (WHITE - HISPANIC)
|
|
1354
|
-
//
|
|
1355
|
-
// That could be calculated as part of preprocessing the Census data, or it
|
|
1356
|
-
// could be computed on the fly.
|
|
1357
|
-
//
|
|
1358
|
-
// And probably:
|
|
1359
|
-
// 'districts' - The # of districts for determining minority proportionality.
|
|
1360
|
-
// 'minority_share' - Minorities as a % of the total population
|
|
1361
|
-
//
|
|
1362
|
-
// TODO - Is the # of districts passed as a parameter or inferred from the # in
|
|
1363
|
-
// the map?
|
|
1364
|
-
// TODO - Is minority share preprocessed once and passed as a parameter or
|
|
1365
|
-
// computed in a initialization routine?
|
|
1649
|
+
// Sources for majority-minority info:
|
|
1650
|
+
// - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
|
|
1651
|
+
// TODO - 2020: Update/revise this, when the update comes out in September:
|
|
1652
|
+
// - http://www.ncsl.org/Portals/1/Documents/Redistricting/Redistricting_2010.pdf
|
|
1366
1653
|
|
|
1367
1654
|
|
|
1368
1655
|
/***/ }),
|
|
@@ -1379,10 +1666,17 @@ exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
|
1379
1666
|
//
|
|
1380
1667
|
// FAIR/PROPORTIONAL
|
|
1381
1668
|
//
|
|
1669
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1670
|
+
if (mod && mod.__esModule) return mod;
|
|
1671
|
+
var result = {};
|
|
1672
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1673
|
+
result["default"] = mod;
|
|
1674
|
+
return result;
|
|
1675
|
+
};
|
|
1382
1676
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1383
1677
|
const assert_1 = __webpack_require__(/*! assert */ "assert");
|
|
1384
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1385
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1678
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1679
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
1386
1680
|
// Partisan analytics need the following data:
|
|
1387
1681
|
//
|
|
1388
1682
|
// An "election model" by geo_id, where each item has 4 pieces of data:
|
|
@@ -1402,36 +1696,41 @@ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
|
1402
1696
|
// I'm labelling this general concept a "Voter Preference Index (VPI)," a
|
|
1403
1697
|
// conscious +1 letter play on Cook's "PVI" acronymn.
|
|
1404
1698
|
// MEASURING BIAS & RESPONSIVENESS (NAGLE'S METHOD)
|
|
1405
|
-
function doSeatsBias(s) {
|
|
1406
|
-
let test = s.getTest(
|
|
1407
|
-
|
|
1699
|
+
function doSeatsBias(s, bLog = false) {
|
|
1700
|
+
let test = s.getTest(11 /* SeatsBias */);
|
|
1701
|
+
if (bLog)
|
|
1702
|
+
console.log("TODO - Calculating seats bias ...");
|
|
1408
1703
|
return test;
|
|
1409
1704
|
}
|
|
1410
1705
|
exports.doSeatsBias = doSeatsBias;
|
|
1411
|
-
function doVotesBias(s) {
|
|
1412
|
-
let test = s.getTest(
|
|
1413
|
-
|
|
1706
|
+
function doVotesBias(s, bLog = false) {
|
|
1707
|
+
let test = s.getTest(12 /* VotesBias */);
|
|
1708
|
+
if (bLog)
|
|
1709
|
+
console.log("TODO - Calculating votes bias ...");
|
|
1414
1710
|
return test;
|
|
1415
1711
|
}
|
|
1416
1712
|
exports.doVotesBias = doVotesBias;
|
|
1417
|
-
function doResponsiveness(s) {
|
|
1418
|
-
let test = s.getTest(
|
|
1419
|
-
|
|
1713
|
+
function doResponsiveness(s, bLog = false) {
|
|
1714
|
+
let test = s.getTest(13 /* Responsiveness */);
|
|
1715
|
+
if (bLog)
|
|
1716
|
+
console.log("TODO - Calculating responsiveness ...");
|
|
1420
1717
|
return test;
|
|
1421
1718
|
}
|
|
1422
1719
|
exports.doResponsiveness = doResponsiveness;
|
|
1423
|
-
function doResponsiveDistricts(s) {
|
|
1424
|
-
let test = s.getTest(
|
|
1425
|
-
|
|
1720
|
+
function doResponsiveDistricts(s, bLog = false) {
|
|
1721
|
+
let test = s.getTest(14 /* ResponsiveDistricts */);
|
|
1722
|
+
if (bLog)
|
|
1723
|
+
console.log("TODO - Calculating # of responsive districts ...");
|
|
1426
1724
|
return test;
|
|
1427
1725
|
}
|
|
1428
1726
|
exports.doResponsiveDistricts = doResponsiveDistricts;
|
|
1429
1727
|
// OTHER MEASURES OF PARTISAN BIAS
|
|
1430
|
-
// TODO - This formula might need to be inverted for D vs. R +/-
|
|
1728
|
+
// TODO - PARTISAN: This formula might need to be inverted for D vs. R +/-
|
|
1431
1729
|
// TODO - Normalize the results.
|
|
1432
|
-
function doEfficiencyGap(s) {
|
|
1433
|
-
|
|
1434
|
-
|
|
1730
|
+
function doEfficiencyGap(s, bLog = false) {
|
|
1731
|
+
if (bLog)
|
|
1732
|
+
console.log("TODO - Calculating the efficiency gap ...");
|
|
1733
|
+
let test = s.getTest(15 /* EfficiencyGap */);
|
|
1435
1734
|
// Get partisan statistics by districts.
|
|
1436
1735
|
// Use Democratic votes, seats, and shares by convention.
|
|
1437
1736
|
let DVotes = s.districts.statistics[D.DistrictField.DemVotes];
|
|
@@ -1478,17 +1777,25 @@ exports.fptpWin = fptpWin;
|
|
|
1478
1777
|
//
|
|
1479
1778
|
// PREPROCESS DATA
|
|
1480
1779
|
//
|
|
1780
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1781
|
+
if (mod && mod.__esModule) return mod;
|
|
1782
|
+
var result = {};
|
|
1783
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1784
|
+
result["default"] = mod;
|
|
1785
|
+
return result;
|
|
1786
|
+
};
|
|
1481
1787
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1482
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1788
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1483
1789
|
// NOTE - Do preprocessing separately, so the constructor returns quickly.
|
|
1484
|
-
function doPreprocessData(s) {
|
|
1790
|
+
function doPreprocessData(s, bLog = false) {
|
|
1485
1791
|
// If necessary, do one-time preprocessing
|
|
1486
1792
|
if (!s.bOneTimeProcessingDone) {
|
|
1487
|
-
doPreprocessCountyFeatures(s);
|
|
1488
|
-
doPreprocessCensus(s);
|
|
1489
|
-
doPreprocessElection(s);
|
|
1793
|
+
doPreprocessCountyFeatures(s, bLog);
|
|
1794
|
+
doPreprocessCensus(s, bLog);
|
|
1795
|
+
doPreprocessElection(s, bLog);
|
|
1490
1796
|
s.bOneTimeProcessingDone = true;
|
|
1491
1797
|
}
|
|
1798
|
+
// TODO - UNASSIGNED: Made both the planByGeoID & DistrictID are right
|
|
1492
1799
|
// Invert the plan by district ID
|
|
1493
1800
|
s.plan.invertPlan();
|
|
1494
1801
|
// Create a map of geoIDs to feature IDs
|
|
@@ -1496,7 +1803,7 @@ function doPreprocessData(s) {
|
|
|
1496
1803
|
}
|
|
1497
1804
|
exports.doPreprocessData = doPreprocessData;
|
|
1498
1805
|
// CREATE A FIPS CODE TO COUNTY NAME LOOKUP
|
|
1499
|
-
function doPreprocessCountyFeatures(s) {
|
|
1806
|
+
function doPreprocessCountyFeatures(s, bLog = false) {
|
|
1500
1807
|
for (let i = 0; i < s.counties.nCounties; i++) {
|
|
1501
1808
|
let county = s.counties.countyByIndex(i);
|
|
1502
1809
|
let fips = s.counties.propertyForCounty(county, 'COUNTYFP');
|
|
@@ -1505,29 +1812,58 @@ function doPreprocessCountyFeatures(s) {
|
|
|
1505
1812
|
}
|
|
1506
1813
|
}
|
|
1507
1814
|
// ANALYZE THE CENSUS BY COUNTY
|
|
1508
|
-
function doPreprocessCensus(s) {
|
|
1815
|
+
function doPreprocessCensus(s, bLog = false) {
|
|
1509
1816
|
// The county-splitting analytic needs the following info, using NC as an example:
|
|
1510
1817
|
// '_stateTotal' = The total state population, e.g., 9,535,483 for NC's 2010 Census
|
|
1511
1818
|
// 'totalByCounty' = The total population by county FIPS code
|
|
1819
|
+
// SUM TOTAL POPULATION BY COUNTY
|
|
1512
1820
|
let totalByCounty = {};
|
|
1513
1821
|
// NOTE - This works w/o GEOIDs, because you're looping over all features.
|
|
1514
1822
|
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
1515
1823
|
let f = s.features.featureByIndex(i);
|
|
1516
1824
|
let geoID = s.features.geoIDForFeature(f);
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1825
|
+
// Skip water-only features
|
|
1826
|
+
if (!(U.isWaterOnly(geoID))) {
|
|
1827
|
+
let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
1828
|
+
// Sum total population across the state
|
|
1829
|
+
s.state.totalPop += value;
|
|
1830
|
+
// Get the county FIPS code for the feature
|
|
1831
|
+
let county = U.parseGeoID(geoID)['county'];
|
|
1832
|
+
let countyFIPS = U.getFIPSFromCountyGeoID(county);
|
|
1833
|
+
// If a subtotal for the county doesn't exist, initialize one
|
|
1834
|
+
if (!(U.keyExists(countyFIPS, totalByCounty))) {
|
|
1835
|
+
totalByCounty[countyFIPS] = 0;
|
|
1836
|
+
}
|
|
1837
|
+
// Sum total population by county
|
|
1838
|
+
totalByCounty[countyFIPS] += value;
|
|
1526
1839
|
}
|
|
1527
|
-
//
|
|
1528
|
-
|
|
1840
|
+
// else {
|
|
1841
|
+
// console.log("Skipping water-only feature in Census preprocessing:", geoID);
|
|
1842
|
+
// }
|
|
1529
1843
|
}
|
|
1530
1844
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
1845
|
+
// CREATE A FIPS CODE-ORDINAL MAP
|
|
1846
|
+
// Get the county FIPS codes
|
|
1847
|
+
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
1848
|
+
// Sort the results
|
|
1849
|
+
fipsCodes = fipsCodes.sort();
|
|
1850
|
+
// TODO - SPLITTING
|
|
1851
|
+
// Add a dummy county, for county-district splitting analysis
|
|
1852
|
+
fipsCodes.unshift('000');
|
|
1853
|
+
// Create the ID-ordinal map
|
|
1854
|
+
for (let i in fipsCodes) {
|
|
1855
|
+
s.counties.index[fipsCodes[i]] = Number(i);
|
|
1856
|
+
}
|
|
1857
|
+
// MAKE AN ARRAY OF TOTAL POPULATIONS BY COUNTY INDEX
|
|
1858
|
+
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
1859
|
+
let nCountyBuckets = s.counties.nCounties + 1;
|
|
1860
|
+
let countyTotals = U.initArray(nCountyBuckets, 0);
|
|
1861
|
+
for (let fipsCode in totalByCounty) {
|
|
1862
|
+
let i = s.counties.indexFromFIPS(fipsCode);
|
|
1863
|
+
countyTotals[i] = totalByCounty[fipsCode];
|
|
1864
|
+
}
|
|
1865
|
+
s.counties.totalPopulation = countyTotals;
|
|
1866
|
+
// ANALYZE THE COUNTIES
|
|
1531
1867
|
// 'target_size': 733499, # calc as total / districts
|
|
1532
1868
|
let targetSize = Math.round(s.state.totalPop / s.state.nDistricts);
|
|
1533
1869
|
// Find counties that are bigger than the target district size.
|
|
@@ -1539,18 +1875,13 @@ function doPreprocessCensus(s) {
|
|
|
1539
1875
|
let tooBigName = [];
|
|
1540
1876
|
let expectedSplits = 0;
|
|
1541
1877
|
let expectedAffected = 0;
|
|
1542
|
-
// Create a FIPS code-ordinal map
|
|
1543
|
-
// Get the county FIPS codes
|
|
1544
|
-
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
1545
|
-
// Sort the results
|
|
1546
|
-
fipsCodes = fipsCodes.sort();
|
|
1547
|
-
// Create the ID-ordinal map
|
|
1548
|
-
for (let i in fipsCodes) {
|
|
1549
|
-
s.counties.index[fipsCodes[i]] = Number(i);
|
|
1550
|
-
}
|
|
1551
1878
|
// Loop over the counties
|
|
1552
1879
|
for (let county in fipsCodes) {
|
|
1553
1880
|
let fipsCode = fipsCodes[county];
|
|
1881
|
+
// TODO - SPLITTING
|
|
1882
|
+
// Skip the dummy county
|
|
1883
|
+
if (fipsCode == '000')
|
|
1884
|
+
continue;
|
|
1554
1885
|
let countyAffected = 0;
|
|
1555
1886
|
// Find the number of required splits, assuming target district size.
|
|
1556
1887
|
let rawQuotient = totalByCounty[fipsCode] / (targetSize + 1);
|
|
@@ -1571,34 +1902,428 @@ function doPreprocessCensus(s) {
|
|
|
1571
1902
|
s.state.expectedAffected = expectedAffected;
|
|
1572
1903
|
}
|
|
1573
1904
|
// PREPROCESS ELECTION RESULTS
|
|
1574
|
-
function doPreprocessElection(s) {
|
|
1575
|
-
|
|
1905
|
+
function doPreprocessElection(s, bLog = false) {
|
|
1906
|
+
if (bLog)
|
|
1907
|
+
console.log("TODO - Preprocessing election data ...");
|
|
1576
1908
|
}
|
|
1577
1909
|
|
|
1578
1910
|
|
|
1579
1911
|
/***/ }),
|
|
1580
1912
|
|
|
1581
|
-
/***/ "./src/
|
|
1582
|
-
|
|
1583
|
-
!*** ./src/
|
|
1584
|
-
|
|
1913
|
+
/***/ "./src/results.ts":
|
|
1914
|
+
/*!************************!*\
|
|
1915
|
+
!*** ./src/results.ts ***!
|
|
1916
|
+
\************************/
|
|
1585
1917
|
/*! no static exports found */
|
|
1586
1918
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1587
1919
|
|
|
1588
1920
|
"use strict";
|
|
1589
1921
|
|
|
1590
1922
|
//
|
|
1591
|
-
//
|
|
1592
|
-
// - A test log: a simple enumeration of all analytics & validations w/ raw results
|
|
1593
|
-
// - A scorecard: a structured subset of analytics & validations w/ normalized
|
|
1594
|
-
// results, cateories, and an overall score
|
|
1923
|
+
// TEMPLATES FOR UNFORMATTED ANALYTICS RESULTS
|
|
1595
1924
|
//
|
|
1925
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1926
|
+
if (mod && mod.__esModule) return mod;
|
|
1927
|
+
var result = {};
|
|
1928
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1929
|
+
result["default"] = mod;
|
|
1930
|
+
return result;
|
|
1931
|
+
};
|
|
1932
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1933
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1934
|
+
};
|
|
1596
1935
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1597
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1598
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
1599
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1936
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1937
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
1938
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
1939
|
+
// TODO - DASHBOARD: Delete
|
|
1940
|
+
// import { doAnalyzePostProcessing } from './report'
|
|
1600
1941
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
1601
|
-
|
|
1942
|
+
const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
|
|
1943
|
+
// Example
|
|
1944
|
+
let sampleRequirements = {
|
|
1945
|
+
score: 2 /* Red */,
|
|
1946
|
+
metrics: {
|
|
1947
|
+
complete: 0 /* Green */,
|
|
1948
|
+
contiguous: 2 /* Red */,
|
|
1949
|
+
freeOfHoles: 1 /* Yellow */,
|
|
1950
|
+
equalPopulation: 2 /* Red */
|
|
1951
|
+
},
|
|
1952
|
+
details: {
|
|
1953
|
+
unassignedFeatures: [],
|
|
1954
|
+
emptyDistricts: [],
|
|
1955
|
+
discontiguousDistricts: [2],
|
|
1956
|
+
embeddedDistricts: [],
|
|
1957
|
+
populationDeviation: 0.6748,
|
|
1958
|
+
deviationThreshold: 0.75 / 100
|
|
1959
|
+
},
|
|
1960
|
+
datasets: {
|
|
1961
|
+
census: "2010 Census Total Population"
|
|
1962
|
+
},
|
|
1963
|
+
resources: {
|
|
1964
|
+
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
let sampleCompactness = {
|
|
1968
|
+
score: 60,
|
|
1969
|
+
metrics: {
|
|
1970
|
+
reock: 0.3773,
|
|
1971
|
+
polsby: 0.3815
|
|
1972
|
+
},
|
|
1973
|
+
details: {},
|
|
1974
|
+
datasets: {
|
|
1975
|
+
shapes: "2010 VTD shapes"
|
|
1976
|
+
},
|
|
1977
|
+
resources: {}
|
|
1978
|
+
};
|
|
1979
|
+
let sampleSplitting = {
|
|
1980
|
+
score: 73,
|
|
1981
|
+
metrics: {
|
|
1982
|
+
sqEnt_DCreduced: 1.531,
|
|
1983
|
+
sqEnt_CDreduced: 1.760
|
|
1984
|
+
},
|
|
1985
|
+
details: {
|
|
1986
|
+
countiesSplitUnexpectedly: [
|
|
1987
|
+
"Bladen", "Buncombe", "Catawba", "Cumberland", "Durham", "Guilford", "Iredell", "Johnston", "Pitt", "Rowan", "Wilson"
|
|
1988
|
+
],
|
|
1989
|
+
unexpectedAffected: 0.3096,
|
|
1990
|
+
nSplitVTDs: 12,
|
|
1991
|
+
splitVTDs: []
|
|
1992
|
+
},
|
|
1993
|
+
datasets: {},
|
|
1994
|
+
resources: {}
|
|
1995
|
+
};
|
|
1996
|
+
// TODO - This category is still being fleshed out.
|
|
1997
|
+
let samplePartisan = {
|
|
1998
|
+
score: 100,
|
|
1999
|
+
metrics: {
|
|
2000
|
+
partisanBias: 0.15,
|
|
2001
|
+
responsiveness: 2.0
|
|
2002
|
+
},
|
|
2003
|
+
details: {},
|
|
2004
|
+
datasets: {
|
|
2005
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2006
|
+
},
|
|
2007
|
+
resources: {
|
|
2008
|
+
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
// TODO - This category is still being fleshed out.
|
|
2012
|
+
let sampleMinority = {
|
|
2013
|
+
score: null,
|
|
2014
|
+
metrics: {
|
|
2015
|
+
nBlack37to50: 1,
|
|
2016
|
+
nBlackMajority: 12,
|
|
2017
|
+
nHispanic37to50: 0,
|
|
2018
|
+
nHispanicMajority: 0,
|
|
2019
|
+
nPacific37to50: 0,
|
|
2020
|
+
nPacificMajority: 0,
|
|
2021
|
+
nAsian37to50: 0,
|
|
2022
|
+
nAsianMajority: 0,
|
|
2023
|
+
nNative37to50: 0,
|
|
2024
|
+
nNativeMajority: 0,
|
|
2025
|
+
nMinority37to50: 0,
|
|
2026
|
+
nMinorityMajority: 0,
|
|
2027
|
+
averageDVoteShare: 0.90
|
|
2028
|
+
},
|
|
2029
|
+
details: {
|
|
2030
|
+
vap: true,
|
|
2031
|
+
comboCategories: true
|
|
2032
|
+
},
|
|
2033
|
+
datasets: {
|
|
2034
|
+
vap: "2010 Voting Age Population"
|
|
2035
|
+
},
|
|
2036
|
+
resources: {}
|
|
2037
|
+
};
|
|
2038
|
+
exports.samplePlanAnalytics = {
|
|
2039
|
+
requirements: sampleRequirements,
|
|
2040
|
+
compactness: sampleCompactness,
|
|
2041
|
+
// TODO - Don't show these categories yet
|
|
2042
|
+
splitting: sampleSplitting,
|
|
2043
|
+
partisan: samplePartisan,
|
|
2044
|
+
minority: sampleMinority
|
|
2045
|
+
};
|
|
2046
|
+
function preparePlanAnalytics(s, bLog = false) {
|
|
2047
|
+
if (!(s.bPostProcessingDone)) {
|
|
2048
|
+
doAnalyzePostProcessing(s);
|
|
2049
|
+
}
|
|
2050
|
+
// REQUIREMENTS CATEGORY
|
|
2051
|
+
let paRequirements;
|
|
2052
|
+
{
|
|
2053
|
+
let completeTest = s.getTest(0 /* Complete */);
|
|
2054
|
+
let contiguousTest = s.getTest(1 /* Contiguous */);
|
|
2055
|
+
let freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
|
|
2056
|
+
let equalPopulationTest = s.getTest(3 /* EqualPopulation */);
|
|
2057
|
+
// Combine individual checks into an overall score
|
|
2058
|
+
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
2059
|
+
// requirements/validations, map booleans to tri-states here.
|
|
2060
|
+
let completeMetric = U.mapBooleanToTriState(completeTest['score']);
|
|
2061
|
+
let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
|
|
2062
|
+
let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
|
|
2063
|
+
let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
|
|
2064
|
+
let reqScore = 0 /* Green */;
|
|
2065
|
+
let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
|
|
2066
|
+
if (checks.includes(1 /* Yellow */))
|
|
2067
|
+
reqScore = 1 /* Yellow */;
|
|
2068
|
+
if (checks.includes(2 /* Red */))
|
|
2069
|
+
reqScore = 2 /* Red */;
|
|
2070
|
+
// Get values to support details entries
|
|
2071
|
+
let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
|
|
2072
|
+
let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
|
|
2073
|
+
let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
|
|
2074
|
+
let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
|
|
2075
|
+
let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
|
|
2076
|
+
let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
|
|
2077
|
+
let xx = s.state.xx;
|
|
2078
|
+
// TODO - JSON: Is there a better / easier way to work with the variable?
|
|
2079
|
+
let stateReqsDict = state_reqs_json_1.default;
|
|
2080
|
+
let reqLinkToStateReqs = stateReqsDict[xx];
|
|
2081
|
+
// Populate the category
|
|
2082
|
+
paRequirements = {
|
|
2083
|
+
score: reqScore,
|
|
2084
|
+
metrics: {
|
|
2085
|
+
complete: completeMetric,
|
|
2086
|
+
contiguous: contiguousMetric,
|
|
2087
|
+
freeOfHoles: freeOfHolesMetric,
|
|
2088
|
+
equalPopulation: equalPopulationMetric
|
|
2089
|
+
},
|
|
2090
|
+
details: {
|
|
2091
|
+
unassignedFeatures: unassignedFeaturesDetail,
|
|
2092
|
+
emptyDistricts: emptyDistrictsDetail,
|
|
2093
|
+
discontiguousDistricts: discontiguousDistrictsDetail,
|
|
2094
|
+
embeddedDistricts: embeddedDistrictsDetail,
|
|
2095
|
+
populationDeviation: populationDeviationDetail,
|
|
2096
|
+
deviationThreshold: deviationThresholdDetail
|
|
2097
|
+
},
|
|
2098
|
+
datasets: {
|
|
2099
|
+
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
2100
|
+
},
|
|
2101
|
+
resources: {
|
|
2102
|
+
stateReqs: reqLinkToStateReqs
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
// COMPACTNESS CATEGORY
|
|
2107
|
+
let paCompactness;
|
|
2108
|
+
{
|
|
2109
|
+
let reockWeight = 0.5;
|
|
2110
|
+
let polsbyWeight = 1.0 - reockWeight;
|
|
2111
|
+
let reockTest = s.getTest(5 /* Reock */);
|
|
2112
|
+
let polsbyTest = s.getTest(6 /* PolsbyPopper */);
|
|
2113
|
+
let normalizedReock = reockTest['normalizedScore'];
|
|
2114
|
+
let normalizedPolsby = reockTest['normalizedScore'];
|
|
2115
|
+
let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
|
|
2116
|
+
let reockMetric = U.deepCopy(reockTest['score']);
|
|
2117
|
+
let polsbyMetric = U.deepCopy(polsbyTest['score']);
|
|
2118
|
+
// Populate the category
|
|
2119
|
+
paCompactness = {
|
|
2120
|
+
score: compactnessScore,
|
|
2121
|
+
metrics: {
|
|
2122
|
+
reock: reockMetric,
|
|
2123
|
+
polsby: polsbyMetric
|
|
2124
|
+
},
|
|
2125
|
+
details: {
|
|
2126
|
+
// None at this time
|
|
2127
|
+
},
|
|
2128
|
+
datasets: {
|
|
2129
|
+
shapes: "2010 VTD shapes"
|
|
2130
|
+
// TODO - DATASETS
|
|
2131
|
+
// shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
|
|
2132
|
+
},
|
|
2133
|
+
resources: {
|
|
2134
|
+
// None at this time
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
// SPLITTING CATEGORY
|
|
2139
|
+
let paSplitting;
|
|
2140
|
+
{
|
|
2141
|
+
let unexpectedCountySplittingTest = s.getTest(7 /* UnexpectedCountySplits */);
|
|
2142
|
+
let VTDSplitsTest = s.getTest(10 /* VTDSplits */);
|
|
2143
|
+
let countySplittingTest = s.getTest(8 /* CountySplitting */);
|
|
2144
|
+
let districtSplittingTest = s.getTest(9 /* DistrictSplitting */);
|
|
2145
|
+
let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
|
|
2146
|
+
let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
|
|
2147
|
+
let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
|
|
2148
|
+
let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
|
|
2149
|
+
let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
|
|
2150
|
+
let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
|
|
2151
|
+
let countySplittingNormalized = countySplittingTest['normalizedScore'];
|
|
2152
|
+
let districtSplittingNormalized = districtSplittingTest['normalizedScore'];
|
|
2153
|
+
let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
|
|
2154
|
+
+(S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
|
|
2155
|
+
paSplitting = {
|
|
2156
|
+
score: splittingScore,
|
|
2157
|
+
metrics: {
|
|
2158
|
+
sqEnt_DCreduced: SqEnt_DCreducedMetric,
|
|
2159
|
+
sqEnt_CDreduced: SqEnt_CDreducedMetric
|
|
2160
|
+
// NOTE - The un-reduced raw values
|
|
2161
|
+
// sqEnt_DC : SqEnt_DCMetric,
|
|
2162
|
+
// sqEnt_CD : SqEnt_CDMetric
|
|
2163
|
+
},
|
|
2164
|
+
details: {
|
|
2165
|
+
countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
|
|
2166
|
+
unexpectedAffected: unexpectedAffectedMetric,
|
|
2167
|
+
nSplitVTDs: nVTDSplitsMetric,
|
|
2168
|
+
splitVTDs: splitVTDsDetail
|
|
2169
|
+
},
|
|
2170
|
+
datasets: {
|
|
2171
|
+
// None at this time
|
|
2172
|
+
},
|
|
2173
|
+
resources: {
|
|
2174
|
+
// None at this time
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
// PARTISAN CATEGORY
|
|
2179
|
+
//
|
|
2180
|
+
// TODO - PARTISAN: This category is still being fleshed out. Just an example below.
|
|
2181
|
+
let paPartisan;
|
|
2182
|
+
{
|
|
2183
|
+
paPartisan = {
|
|
2184
|
+
score: 100,
|
|
2185
|
+
metrics: {
|
|
2186
|
+
partisanBias: 0.15,
|
|
2187
|
+
responsiveness: 2.0
|
|
2188
|
+
},
|
|
2189
|
+
details: {},
|
|
2190
|
+
datasets: {
|
|
2191
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2192
|
+
},
|
|
2193
|
+
resources: {
|
|
2194
|
+
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
// MINORITY CATEGORY
|
|
2199
|
+
//
|
|
2200
|
+
// TODO - MINORITY: This category is still being fleshed out. Just an example below.
|
|
2201
|
+
let paMinority;
|
|
2202
|
+
{
|
|
2203
|
+
paMinority = {
|
|
2204
|
+
score: null,
|
|
2205
|
+
metrics: {
|
|
2206
|
+
nBlack37to50: 1,
|
|
2207
|
+
nBlackMajority: 12,
|
|
2208
|
+
nHispanic37to50: 0,
|
|
2209
|
+
nHispanicMajority: 0,
|
|
2210
|
+
nPacific37to50: 0,
|
|
2211
|
+
nPacificMajority: 0,
|
|
2212
|
+
nAsian37to50: 0,
|
|
2213
|
+
nAsianMajority: 0,
|
|
2214
|
+
nNative37to50: 0,
|
|
2215
|
+
nNativeMajority: 0,
|
|
2216
|
+
nMinority37to50: 0,
|
|
2217
|
+
nMinorityMajority: 0,
|
|
2218
|
+
averageDVoteShare: 0.90
|
|
2219
|
+
},
|
|
2220
|
+
details: {
|
|
2221
|
+
vap: true,
|
|
2222
|
+
comboCategories: true
|
|
2223
|
+
},
|
|
2224
|
+
datasets: {
|
|
2225
|
+
vap: "2010 Voting Age Population"
|
|
2226
|
+
},
|
|
2227
|
+
resources: {}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
// PLAN ANALYTICS
|
|
2231
|
+
let pa = {
|
|
2232
|
+
requirements: paRequirements,
|
|
2233
|
+
compactness: paCompactness,
|
|
2234
|
+
// TODO - Not implemented yet
|
|
2235
|
+
splitting: paSplitting,
|
|
2236
|
+
partisan: paPartisan,
|
|
2237
|
+
minority: paMinority
|
|
2238
|
+
};
|
|
2239
|
+
return pa;
|
|
2240
|
+
}
|
|
2241
|
+
exports.preparePlanAnalytics = preparePlanAnalytics;
|
|
2242
|
+
// Example
|
|
2243
|
+
exports.sampleDistrictStatistics = {
|
|
2244
|
+
table: [
|
|
2245
|
+
// District 0 is the dummy unassigned district
|
|
2246
|
+
[0, 0, 0, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2247
|
+
[1, 653133, -0.0950, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.4177, 0.5823, 0.8631, 0.1369, 0.0734, 0.0360, 0.0009, 0.0235, 0.0064],
|
|
2248
|
+
[2, 620961, -0.1396, 0 /* Green */, 2 /* Red */, 2 /* Red */, 0 /* Green */, 0.8820, 0.1180, 0.3129, 0.6871, 0.6169, 0.0391, 0.0013, 0.0310, 0.0099],
|
|
2249
|
+
[3, 971777, 0.3465, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.7261, 0.2739, 0.5174, 0.4826, 0.1745, 0.1572, 0.0020, 0.1531, 0.0090],
|
|
2250
|
+
[4, 863420, 0.1964, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.8957, 0.1043, 0.1734, 0.8266, 0.6489, 0.1348, 0.0020, 0.0496, 0.0127],
|
|
2251
|
+
[5, 805029, 0.1155, 0 /* Green */, 2 /* Red */, 0 /* Green */, 1 /* Yellow */, 0.5743, 0.4257, 0.6587, 0.3413, 0.2494, 0.0363, 0.0012, 0.0536, 0.0081],
|
|
2252
|
+
[6, 824741, 0.1428, 0 /* Green */, 2 /* Red */, 0 /* Green */, 2 /* Red */, 0.5341, 0.4659, 0.7045, 0.2955, 0.1619, 0.0526, 0.0018, 0.0782, 0.0090],
|
|
2253
|
+
[7, 549714, -0.2383, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0.5025, 0.4975, 0.6906, 0.3094, 0.2468, 0.0319, 0.0013, 0.0258, 0.0111],
|
|
2254
|
+
[8, 484777, -0.3283, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0.4105, 0.5895, 0.8370, 0.1630, 0.1074, 0.0316, 0.0013, 0.0197, 0.0077],
|
|
2255
|
+
// District N+1 is the dummy state-summary district
|
|
2256
|
+
[9, 721694, 0.6748, 0 /* Green */, 2 /* Red */, 2 /* Red */, 2 /* Red */, 0.6293, 0.3707, 0.5722, 0.4278, 0.2925, 0.0729, 0.0015, 0.0618, 0.0093]
|
|
2257
|
+
],
|
|
2258
|
+
details: {},
|
|
2259
|
+
datasets: {
|
|
2260
|
+
shapes: "2010 VTD shapes",
|
|
2261
|
+
census: "2010 Census Total Population",
|
|
2262
|
+
vap: "2010 Voting Age Population",
|
|
2263
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2264
|
+
},
|
|
2265
|
+
resources: {}
|
|
2266
|
+
};
|
|
2267
|
+
// Create a DistrictStatistics instance, deep copying the underlying values.
|
|
2268
|
+
function prepareDistrictStatistics(s, bLog = false) {
|
|
2269
|
+
if (!(s.bPostProcessingDone)) {
|
|
2270
|
+
doAnalyzePostProcessing(s);
|
|
2271
|
+
}
|
|
2272
|
+
let dsTable = [];
|
|
2273
|
+
for (let i = 0; i < s.districts.numberOfRows(); i++) {
|
|
2274
|
+
let rawRow = [
|
|
2275
|
+
i,
|
|
2276
|
+
s.districts.statistics[D.DistrictField.TotalPop][i],
|
|
2277
|
+
s.districts.statistics[D.DistrictField.PopDevPct][i],
|
|
2278
|
+
s.districts.statistics[D.DistrictField.bEqualPop][i],
|
|
2279
|
+
s.districts.statistics[D.DistrictField.bNotEmpty][i],
|
|
2280
|
+
s.districts.statistics[D.DistrictField.bContiguous][i],
|
|
2281
|
+
s.districts.statistics[D.DistrictField.bNotEmbedded][i],
|
|
2282
|
+
s.districts.statistics[D.DistrictField.DemPct][i],
|
|
2283
|
+
s.districts.statistics[D.DistrictField.RepPct][i],
|
|
2284
|
+
s.districts.statistics[D.DistrictField.WhitePct][i],
|
|
2285
|
+
s.districts.statistics[D.DistrictField.MinorityPct][i],
|
|
2286
|
+
s.districts.statistics[D.DistrictField.BlackPct][i],
|
|
2287
|
+
s.districts.statistics[D.DistrictField.HispanicPct][i],
|
|
2288
|
+
s.districts.statistics[D.DistrictField.PacificPct][i],
|
|
2289
|
+
s.districts.statistics[D.DistrictField.AsianPct][i],
|
|
2290
|
+
s.districts.statistics[D.DistrictField.NativePct][i]
|
|
2291
|
+
];
|
|
2292
|
+
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
2293
|
+
// requirements/validations, map booleans to tri-states here.
|
|
2294
|
+
rawRow[3 /* bEqualPop */] = U.mapBooleanToTriState(rawRow[3 /* bEqualPop */]);
|
|
2295
|
+
rawRow[4 /* bNotEmpty */] = U.mapBooleanToTriState(rawRow[4 /* bNotEmpty */]);
|
|
2296
|
+
rawRow[5 /* bContiguous */] = U.mapBooleanToTriState(rawRow[5 /* bContiguous */]);
|
|
2297
|
+
rawRow[6 /* bNotEmbedded */] = U.mapBooleanToTriState(rawRow[6 /* bNotEmbedded */]);
|
|
2298
|
+
let readyRow = U.deepCopy(rawRow);
|
|
2299
|
+
dsTable.push(readyRow);
|
|
2300
|
+
}
|
|
2301
|
+
let dsDetails = {
|
|
2302
|
+
// None at this time
|
|
2303
|
+
};
|
|
2304
|
+
let dsDatasets = {
|
|
2305
|
+
shapes: "2010 VTD shapes",
|
|
2306
|
+
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
2307
|
+
vap: U.deepCopy(s.config['descriptions']['VAP']),
|
|
2308
|
+
election: U.deepCopy(s.config['descriptions']['ELECTION'])
|
|
2309
|
+
};
|
|
2310
|
+
let dsResources = {
|
|
2311
|
+
// None at this time
|
|
2312
|
+
};
|
|
2313
|
+
let ds = {
|
|
2314
|
+
table: dsTable,
|
|
2315
|
+
details: dsDetails,
|
|
2316
|
+
datasets: dsDatasets,
|
|
2317
|
+
resources: dsResources
|
|
2318
|
+
};
|
|
2319
|
+
return ds;
|
|
2320
|
+
}
|
|
2321
|
+
exports.prepareDistrictStatistics = prepareDistrictStatistics;
|
|
2322
|
+
// META-DATA FOR TESTS/ANALYTICS
|
|
2323
|
+
//
|
|
2324
|
+
// NOTE - This structure is a vestige of having created a metadata-driven
|
|
2325
|
+
// scorecard w/in district-analytics at first. It works for creating the
|
|
2326
|
+
// unstyled results structures, so it isn't a high priority to rationalize.
|
|
1602
2327
|
var TestType;
|
|
1603
2328
|
(function (TestType) {
|
|
1604
2329
|
TestType[TestType["PassFail"] = 0] = "PassFail";
|
|
@@ -1610,7 +2335,6 @@ const completeDefn = {
|
|
|
1610
2335
|
name: "Complete",
|
|
1611
2336
|
normalize: false,
|
|
1612
2337
|
externalType: TestType.PassFail,
|
|
1613
|
-
detailsFn: doPrepareCompleteDetails,
|
|
1614
2338
|
suites: [0 /* Legal */]
|
|
1615
2339
|
};
|
|
1616
2340
|
const contiguousDefn = {
|
|
@@ -1618,7 +2342,6 @@ const contiguousDefn = {
|
|
|
1618
2342
|
name: "Contiguous",
|
|
1619
2343
|
normalize: false,
|
|
1620
2344
|
externalType: TestType.PassFail,
|
|
1621
|
-
detailsFn: doPrepareContiguousDetails,
|
|
1622
2345
|
suites: [0 /* Legal */]
|
|
1623
2346
|
};
|
|
1624
2347
|
const freeOfHolesDefn = {
|
|
@@ -1626,7 +2349,6 @@ const freeOfHolesDefn = {
|
|
|
1626
2349
|
name: "Free of Holes",
|
|
1627
2350
|
normalize: false,
|
|
1628
2351
|
externalType: TestType.PassFail,
|
|
1629
|
-
detailsFn: doPrepareFreeOfHolesDetails,
|
|
1630
2352
|
suites: [0 /* Legal */]
|
|
1631
2353
|
};
|
|
1632
2354
|
const equalPopulationDefn = {
|
|
@@ -1634,7 +2356,6 @@ const equalPopulationDefn = {
|
|
|
1634
2356
|
name: "Equal Population",
|
|
1635
2357
|
normalize: false,
|
|
1636
2358
|
externalType: TestType.PassFail,
|
|
1637
|
-
detailsFn: doPrepareEqualPopulationDetails,
|
|
1638
2359
|
suites: [0 /* Legal */]
|
|
1639
2360
|
};
|
|
1640
2361
|
const populationDeviationDefn = {
|
|
@@ -1642,7 +2363,6 @@ const populationDeviationDefn = {
|
|
|
1642
2363
|
name: "Population Deviation",
|
|
1643
2364
|
normalize: true,
|
|
1644
2365
|
externalType: TestType.Percentage,
|
|
1645
|
-
detailsFn: doPreparePopulationDeviationDetails,
|
|
1646
2366
|
suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
|
|
1647
2367
|
};
|
|
1648
2368
|
const reockDefn = {
|
|
@@ -1650,7 +2370,6 @@ const reockDefn = {
|
|
|
1650
2370
|
name: "Reock",
|
|
1651
2371
|
normalize: true,
|
|
1652
2372
|
externalType: TestType.Number,
|
|
1653
|
-
detailsFn: doPrepareReockDetails,
|
|
1654
2373
|
suites: [2 /* Best */]
|
|
1655
2374
|
};
|
|
1656
2375
|
const polsbyPopperDefn = {
|
|
@@ -1658,24 +2377,45 @@ const polsbyPopperDefn = {
|
|
|
1658
2377
|
name: "Polsby-Popper",
|
|
1659
2378
|
normalize: true,
|
|
1660
2379
|
externalType: TestType.Number,
|
|
1661
|
-
detailsFn: doPreparePolsbyPopperDetails,
|
|
1662
2380
|
suites: [2 /* Best */]
|
|
1663
2381
|
};
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
normalize:
|
|
2382
|
+
// TODO - SPLITTING
|
|
2383
|
+
const unexpectedCountySplitsDefn = {
|
|
2384
|
+
ID: 7 /* UnexpectedCountySplits */,
|
|
2385
|
+
name: "Unexpected County Splits",
|
|
2386
|
+
normalize: false,
|
|
1669
2387
|
externalType: TestType.Percentage,
|
|
1670
|
-
|
|
2388
|
+
suites: [2 /* Best */]
|
|
2389
|
+
};
|
|
2390
|
+
// TODO - SPLITTING
|
|
2391
|
+
const VTDSplitsDefn = {
|
|
2392
|
+
ID: 10 /* VTDSplits */,
|
|
2393
|
+
name: "VTD Splits",
|
|
2394
|
+
normalize: false,
|
|
2395
|
+
externalType: TestType.Number,
|
|
2396
|
+
suites: [2 /* Best */]
|
|
2397
|
+
};
|
|
2398
|
+
// TODO - SPLITTING
|
|
2399
|
+
const countySplittingDefn = {
|
|
2400
|
+
ID: 8 /* CountySplitting */,
|
|
2401
|
+
name: "County Splitting",
|
|
2402
|
+
normalize: true,
|
|
2403
|
+
externalType: TestType.Number,
|
|
2404
|
+
suites: [2 /* Best */]
|
|
2405
|
+
};
|
|
2406
|
+
// TODO - SPLITTING
|
|
2407
|
+
const districtSplittingDefn = {
|
|
2408
|
+
ID: 9 /* DistrictSplitting */,
|
|
2409
|
+
name: "District Splitting",
|
|
2410
|
+
normalize: true,
|
|
2411
|
+
externalType: TestType.Number,
|
|
1671
2412
|
suites: [2 /* Best */]
|
|
1672
2413
|
};
|
|
1673
2414
|
const efficiencyGapDefn = {
|
|
1674
|
-
ID:
|
|
2415
|
+
ID: 15 /* EfficiencyGap */,
|
|
1675
2416
|
name: "Efficiency Gap",
|
|
1676
2417
|
normalize: false,
|
|
1677
2418
|
externalType: TestType.Percentage,
|
|
1678
|
-
detailsFn: doPrepareEfficiencyGapDetails,
|
|
1679
2419
|
suites: [1 /* Fair */]
|
|
1680
2420
|
};
|
|
1681
2421
|
// All the tests that have been defined (can be reported on)
|
|
@@ -1687,72 +2427,13 @@ const testDefns = {
|
|
|
1687
2427
|
[4 /* PopulationDeviation */]: populationDeviationDefn,
|
|
1688
2428
|
[5 /* Reock */]: reockDefn,
|
|
1689
2429
|
[6 /* PolsbyPopper */]: polsbyPopperDefn,
|
|
1690
|
-
// TODO -
|
|
1691
|
-
[7 /*
|
|
1692
|
-
[
|
|
1693
|
-
/*
|
|
1694
|
-
|
|
1695
|
-
//
|
|
1696
|
-
|
|
1697
|
-
catName: "Map Validations",
|
|
1698
|
-
catTests: [
|
|
1699
|
-
{ testID: 0 /* Complete */ },
|
|
1700
|
-
{ testID: 1 /* Contiguous */ },
|
|
1701
|
-
{ testID: 2 /* FreeOfHoles */ },
|
|
1702
|
-
{ testID: 3 /* EqualPopulation */ }
|
|
1703
|
-
],
|
|
1704
|
-
catNumeric: false,
|
|
1705
|
-
catWeight: undefined,
|
|
1706
|
-
catPrepareFn: doPrepareValidSection
|
|
1707
|
-
};
|
|
1708
|
-
const fairCategory = {
|
|
1709
|
-
// TODO - Change this label
|
|
1710
|
-
catName: "Is the map fair?",
|
|
1711
|
-
catTests: [
|
|
1712
|
-
{
|
|
1713
|
-
testID: 13 /* EfficiencyGap */,
|
|
1714
|
-
testWeight: 100
|
|
1715
|
-
}
|
|
1716
|
-
],
|
|
1717
|
-
catNumeric: true,
|
|
1718
|
-
catWeight: undefined,
|
|
1719
|
-
catPrepareFn: doPrepareFairSection
|
|
1720
|
-
};
|
|
1721
|
-
// TODO - Decide on the relative weights of these tests!
|
|
1722
|
-
// NOTE: 'testWeights' are simply relative, i.e., each normalized score is
|
|
1723
|
-
// multiplied by the associated 'testWeight', and the sum of those is divided
|
|
1724
|
-
// by the total weight. Weights don't have to add to 100.
|
|
1725
|
-
const bestCategory = {
|
|
1726
|
-
catName: "Traditional Districting Principles",
|
|
1727
|
-
catTests: [
|
|
1728
|
-
{
|
|
1729
|
-
testID: 4 /* PopulationDeviation */,
|
|
1730
|
-
testWeight: 10
|
|
1731
|
-
},
|
|
1732
|
-
{
|
|
1733
|
-
testID: 5 /* Reock */,
|
|
1734
|
-
testWeight: 25
|
|
1735
|
-
},
|
|
1736
|
-
{
|
|
1737
|
-
testID: 6 /* PolsbyPopper */,
|
|
1738
|
-
testWeight: 25
|
|
1739
|
-
}
|
|
1740
|
-
// TODO - Re-enable, when the metric is implemented
|
|
1741
|
-
// {
|
|
1742
|
-
// testID: T.Test.CountySplits,
|
|
1743
|
-
// testWeight: 50
|
|
1744
|
-
// }
|
|
1745
|
-
],
|
|
1746
|
-
catNumeric: true,
|
|
1747
|
-
catWeight: undefined,
|
|
1748
|
-
catPrepareFn: doPrepareBestSection
|
|
1749
|
-
};
|
|
1750
|
-
// The overall scorecard definition
|
|
1751
|
-
const scorecardDefn = {
|
|
1752
|
-
[0 /* Legal */]: validCategory,
|
|
1753
|
-
// TODO - NIY
|
|
1754
|
-
// [T.Suite.Fair]: fairCategory,
|
|
1755
|
-
[2 /* Best */]: bestCategory
|
|
2430
|
+
// TODO - SPLITTING
|
|
2431
|
+
[7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
|
|
2432
|
+
[10 /* VTDSplits */]: VTDSplitsDefn,
|
|
2433
|
+
[8 /* CountySplitting */]: countySplittingDefn,
|
|
2434
|
+
[9 /* DistrictSplitting */]: districtSplittingDefn,
|
|
2435
|
+
// TODO - More tests ...
|
|
2436
|
+
[15 /* EfficiencyGap */]: efficiencyGapDefn
|
|
1756
2437
|
};
|
|
1757
2438
|
// NORMALIZE RAW ANALYTICS
|
|
1758
2439
|
// Raw numeric analytics, such as population deviation, compactness, etc. are
|
|
@@ -1767,19 +2448,29 @@ function doConfigureScales(s) {
|
|
|
1767
2448
|
const LDLimit = 10.00 / 100; // Deviation threshold for LD's
|
|
1768
2449
|
const CDGoodEnough = 0.20 / 100;
|
|
1769
2450
|
const LDGoodEnough = (CDGoodEnough / CDLimit) * LDLimit;
|
|
1770
|
-
const
|
|
2451
|
+
const popDevScale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
1771
2452
|
// const scale = [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
1772
|
-
s.testScales[4 /* PopulationDeviation */] = {
|
|
1773
|
-
s.testScales[5 /* Reock */] = {
|
|
1774
|
-
s.testScales[6 /* PolsbyPopper */] = {
|
|
1775
|
-
const
|
|
1776
|
-
|
|
2453
|
+
s.testScales[4 /* PopulationDeviation */] = { scale: popDevScale, bInvertRaw: true };
|
|
2454
|
+
s.testScales[5 /* Reock */] = { scale: [0.25, 0.50] };
|
|
2455
|
+
s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
|
|
2456
|
+
const nDistricts = s.state.nDistricts;
|
|
2457
|
+
const nCounties = s.counties.nCounties;
|
|
2458
|
+
// TODO - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
|
|
2459
|
+
// number of county splits seems too stringent, empirically.
|
|
2460
|
+
const allowableCountySplitsMultiplier = 1.5;
|
|
2461
|
+
const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
|
|
2462
|
+
const countySplittingThreshold = ((nAllowableSplits * 1.71) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
|
|
2463
|
+
const countySplittingScale = [1.0, countySplittingThreshold];
|
|
2464
|
+
s.testScales[8 /* CountySplitting */] = { scale: countySplittingScale, bInvertScaled: true };
|
|
2465
|
+
const districtSplittingThreshold = 1.5;
|
|
2466
|
+
const districtSplittingScale = [1.0, districtSplittingThreshold];
|
|
2467
|
+
s.testScales[9 /* DistrictSplitting */] = { scale: districtSplittingScale, bInvertScaled: true };
|
|
1777
2468
|
// TODO - More analytics ...
|
|
1778
2469
|
}
|
|
1779
2470
|
exports.doConfigureScales = doConfigureScales;
|
|
1780
2471
|
// Postprocess analytics - Normalize numeric results and derive secondary tests.
|
|
1781
2472
|
// Do this after analytics have been run and before preparing a test log or scorecard.
|
|
1782
|
-
function doAnalyzePostProcessing(s) {
|
|
2473
|
+
function doAnalyzePostProcessing(s, bLog = false) {
|
|
1783
2474
|
// Normalize the raw scores for all the numerics tests
|
|
1784
2475
|
let testResults = U.getNumericObjectKeys(testDefns);
|
|
1785
2476
|
for (let testID of testResults) {
|
|
@@ -1787,642 +2478,18 @@ function doAnalyzePostProcessing(s) {
|
|
|
1787
2478
|
let testResult = s.getTest(testID);
|
|
1788
2479
|
let rawScore = testResult['score'];
|
|
1789
2480
|
let normalizedScore;
|
|
1790
|
-
|
|
1791
|
-
normalizedScore = U.normalize(rawScore, testScale, testInvertp);
|
|
2481
|
+
normalizedScore = U.normalize(rawScore, s.testScales[testID]);
|
|
1792
2482
|
testResult['normalizedScore'] = normalizedScore;
|
|
1793
2483
|
// Add the scale used to normalize the raw score to the details
|
|
1794
|
-
testResult['details']['scale'] =
|
|
2484
|
+
testResult['details']['scale'] = s.testScales[testID].scale;
|
|
1795
2485
|
}
|
|
1796
2486
|
}
|
|
1797
2487
|
// Derive secondary tests
|
|
1798
|
-
analyze_1.doDeriveSecondaryTests(s);
|
|
2488
|
+
analyze_1.doDeriveSecondaryTests(s, bLog);
|
|
1799
2489
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
1800
2490
|
s.bPostProcessingDone = true;
|
|
1801
2491
|
}
|
|
1802
2492
|
exports.doAnalyzePostProcessing = doAnalyzePostProcessing;
|
|
1803
|
-
// Prepare a structured but unformatted scorecard, from the test results
|
|
1804
|
-
function doGenerateScorecard(s) {
|
|
1805
|
-
if (!(s.bPostProcessingDone)) {
|
|
1806
|
-
doAnalyzePostProcessing(s);
|
|
1807
|
-
}
|
|
1808
|
-
// Create a new scorecard
|
|
1809
|
-
let scorecard = {};
|
|
1810
|
-
// Filter the defined scorecard categories by the requested test suites
|
|
1811
|
-
let categories = U.getNumericObjectKeys(scorecardDefn);
|
|
1812
|
-
let suitesRequested = s.config['suites'];
|
|
1813
|
-
categories = categories.filter(x => suitesRequested.includes(x));
|
|
1814
|
-
// ... and initialize each one in the new scorecard
|
|
1815
|
-
for (let c of categories) {
|
|
1816
|
-
scorecard[c] = {};
|
|
1817
|
-
scorecard[c]['catName'] = scorecardDefn[c]['catName'];
|
|
1818
|
-
scorecard[c]['catTests'] = {};
|
|
1819
|
-
// scorecard[c]['catScore'] = undefined;
|
|
1820
|
-
}
|
|
1821
|
-
// For each scorecard category
|
|
1822
|
-
for (let c of categories) {
|
|
1823
|
-
// Grab the scorecard category definition
|
|
1824
|
-
let { catName, catTests, catNumeric } = scorecardDefn[c];
|
|
1825
|
-
let numericCategoryScore = 0;
|
|
1826
|
-
let totalWeight = 0;
|
|
1827
|
-
let booleanCategoryScore = true;
|
|
1828
|
-
// Process the results for each test result in the category
|
|
1829
|
-
for (let testDefn of catTests) {
|
|
1830
|
-
// Get the config info for the test
|
|
1831
|
-
let testID = testDefn['testID'];
|
|
1832
|
-
// ... and the actual test result
|
|
1833
|
-
let testResult = s.getTest(testID);
|
|
1834
|
-
// Create a new test entry for the scorecard
|
|
1835
|
-
let testReport = U.deepCopy(testResult);
|
|
1836
|
-
// Add the name
|
|
1837
|
-
testReport['name'] = testDefns[testID]['name'];
|
|
1838
|
-
if (catNumeric) {
|
|
1839
|
-
// Normalize raw numeric scores ... moved to FIRST PASS above
|
|
1840
|
-
// Accumulate a category score
|
|
1841
|
-
let normalizedScore = testReport['normalizedScore'];
|
|
1842
|
-
numericCategoryScore += normalizedScore * testDefn['testWeight'];
|
|
1843
|
-
totalWeight += testDefn['testWeight'];
|
|
1844
|
-
}
|
|
1845
|
-
else {
|
|
1846
|
-
// AND together pass/fail tests into a category score
|
|
1847
|
-
if (!testReport['score']) {
|
|
1848
|
-
booleanCategoryScore = false;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
scorecard[c]['catTests'][testID] = testReport;
|
|
1852
|
-
}
|
|
1853
|
-
// Set the category score
|
|
1854
|
-
if (catNumeric) {
|
|
1855
|
-
scorecard[c]['catScore'] = Math.round(numericCategoryScore / totalWeight);
|
|
1856
|
-
}
|
|
1857
|
-
else {
|
|
1858
|
-
scorecard[c]['catScore'] = booleanCategoryScore;
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
// TODO - Compute an overall score from the category weights
|
|
1862
|
-
return scorecard;
|
|
1863
|
-
}
|
|
1864
|
-
// Prepare a formatted scorecard suitable for rendering
|
|
1865
|
-
function doPrepareScorecard(s) {
|
|
1866
|
-
// Initialize the output format
|
|
1867
|
-
let text = { data: [] };
|
|
1868
|
-
let blocks = text.data;
|
|
1869
|
-
// If the plan as already been analyzed, prepare a scorecard
|
|
1870
|
-
if (s.bPlanAnalyzed) {
|
|
1871
|
-
// Create and cache a new, unformatted scorecard
|
|
1872
|
-
s.scorecard = doGenerateScorecard(s);
|
|
1873
|
-
// Create a scorecard header
|
|
1874
|
-
blocks.push({ variant: 'h4', text: `Analysis - NC 2019 Special Edition` });
|
|
1875
|
-
blocks.push({ variant: 'body1', text: `In response to the recent court ruling in North Carolina and the court's requirement for transparency, we are pleased to provide the general public with early access to this base set of redistricting analytics. Stay tuned for more updates!` });
|
|
1876
|
-
blocks.push({ variant: 'body1', text: `For more details, see our blog post on Medium.` });
|
|
1877
|
-
// Prepare each scorecard category
|
|
1878
|
-
blocks.push({ variant: 'beginExpansion', text: `Overall Plan` });
|
|
1879
|
-
let categories = U.getNumericObjectKeys(s.scorecard);
|
|
1880
|
-
for (let c of categories) {
|
|
1881
|
-
let sectionPrepareFn = scorecardDefn[c]['catPrepareFn'];
|
|
1882
|
-
let sectionText = sectionPrepareFn(s, c);
|
|
1883
|
-
blocks.push(...sectionText);
|
|
1884
|
-
}
|
|
1885
|
-
// blocks.push({ variant: 'body1', text: `` });
|
|
1886
|
-
// blocks.push({ variant: 'body1', text: `There is much more analysis coming of county splitting, partisan fairness, and the opportunity for minority representation! For now, you can glean a lot from the district statistics below.` });
|
|
1887
|
-
blocks.push({ variant: 'endExpansion' });
|
|
1888
|
-
// Report district statistics
|
|
1889
|
-
blocks.push({ variant: 'beginExpansion', text: `Individual Districts` });
|
|
1890
|
-
let districtStatisticsText = doPrepareDistrictStatistics(s);
|
|
1891
|
-
blocks.push(...districtStatisticsText);
|
|
1892
|
-
blocks.push({ variant: 'endExpansion' });
|
|
1893
|
-
// Report what datasets were used
|
|
1894
|
-
let c = s.config['datasets']["CENSUS" /* CENSUS */];
|
|
1895
|
-
let v = s.config['datasets']["VAP" /* VAP */];
|
|
1896
|
-
let e = s.config['datasets']["ELECTION" /* ELECTION */];
|
|
1897
|
-
blocks.push({ variant: 'beginExpansion', text: `About the Data` });
|
|
1898
|
-
blocks.push({ variant: 'body1', text: `These are the 4 datasets used in this analysis:` });
|
|
1899
|
-
// TODO - Get the shape "dataset" from dra-client
|
|
1900
|
-
blocks.push({ variant: 'body1', text: `* Shapes: 2010 VTD shapes` });
|
|
1901
|
-
blocks.push({ variant: 'body1', text: `* Census: ${D.DatasetDescriptions[c]}` });
|
|
1902
|
-
blocks.push({ variant: 'body1', text: `* VAP: ${D.DatasetDescriptions[v]}` });
|
|
1903
|
-
blocks.push({ variant: 'body1', text: `* Elections: ${D.DatasetDescriptions[e]}` });
|
|
1904
|
-
blocks.push({ variant: 'endExpansion' });
|
|
1905
|
-
blocks.push({ variant: 'body1', text: `` });
|
|
1906
|
-
blocks.push({
|
|
1907
|
-
"variant": "link",
|
|
1908
|
-
"text": "Questions or comments?",
|
|
1909
|
-
"label": "analytics@davesredistricting.org",
|
|
1910
|
-
"link": "mailto:analytics@davesredistricting.org"
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
// Otherwise, return a blank scorecard
|
|
1914
|
-
return text;
|
|
1915
|
-
}
|
|
1916
|
-
exports.doPrepareScorecard = doPrepareScorecard;
|
|
1917
|
-
function doPrepareDistrictStatistics(s) {
|
|
1918
|
-
let text = { data: [] };
|
|
1919
|
-
let blocks = text.data;
|
|
1920
|
-
blocks.push({ variant: 'beginTable' });
|
|
1921
|
-
blocks.push({ variant: 'row', cells: ['ID', 'Total', 'Δ%', 'OK?', '*', 'Dem', 'Rep', 'White', 'Minority', 'Black', 'Hispanic', 'Pacific', 'Asian', 'Native'] });
|
|
1922
|
-
for (let d = 0; d < s.districts.numberOfRows(); d++) {
|
|
1923
|
-
let tot = s.districts.statistics[D.DistrictField.TotalPop][d];
|
|
1924
|
-
if (tot == 0)
|
|
1925
|
-
blocks.push({ variant: 'row', cells: [String(d), '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-'] });
|
|
1926
|
-
else {
|
|
1927
|
-
tot = Math.round(tot);
|
|
1928
|
-
let dev = fractionToPercentage(s.districts.statistics[D.DistrictField.PopDevPct][d]);
|
|
1929
|
-
let bEq = s.districts.statistics[D.DistrictField.bEqualPop][d];
|
|
1930
|
-
let bC = s.districts.statistics[D.DistrictField.bContiguous][d]
|
|
1931
|
-
&& s.districts.statistics[D.DistrictField.bNotEmbedded][d];
|
|
1932
|
-
let dPct = fractionToPercentage(s.districts.statistics[D.DistrictField.DemPct][d]);
|
|
1933
|
-
let rPct = fractionToPercentage(s.districts.statistics[D.DistrictField.RepPct][d]);
|
|
1934
|
-
let wPct = fractionToPercentage(s.districts.statistics[D.DistrictField.WhitePct][d]);
|
|
1935
|
-
let mPct = fractionToPercentage(s.districts.statistics[D.DistrictField.MinorityPct][d]);
|
|
1936
|
-
let bPct = fractionToPercentage(s.districts.statistics[D.DistrictField.BlackPct][d]);
|
|
1937
|
-
let hPct = fractionToPercentage(s.districts.statistics[D.DistrictField.HispanicPct][d]);
|
|
1938
|
-
let pPct = fractionToPercentage(s.districts.statistics[D.DistrictField.PacificPct][d]);
|
|
1939
|
-
let aPct = fractionToPercentage(s.districts.statistics[D.DistrictField.AsianPct][d]);
|
|
1940
|
-
let nPct = fractionToPercentage(s.districts.statistics[D.DistrictField.NativePct][d]);
|
|
1941
|
-
let id;
|
|
1942
|
-
if (d == 0)
|
|
1943
|
-
id = "??";
|
|
1944
|
-
else if (d == (s.districts.numberOfRows() - 1))
|
|
1945
|
-
id = " ";
|
|
1946
|
-
else
|
|
1947
|
-
id = String(d);
|
|
1948
|
-
blocks.push({
|
|
1949
|
-
variant: 'row',
|
|
1950
|
-
cells: [
|
|
1951
|
-
`${id}`,
|
|
1952
|
-
`${formatInteger(tot)}`,
|
|
1953
|
-
`${formatPercentage(dev)}%`,
|
|
1954
|
-
`${pfBoolToString(bEq)}`,
|
|
1955
|
-
`${pfBoolToString(bC)}`,
|
|
1956
|
-
`${formatPercentage(dPct)}%`,
|
|
1957
|
-
`${formatPercentage(rPct)}%`,
|
|
1958
|
-
`${formatPercentage(wPct)}%`,
|
|
1959
|
-
`${formatPercentage(mPct)}%`,
|
|
1960
|
-
`${formatPercentage(bPct)}%`,
|
|
1961
|
-
`${formatPercentage(hPct)}%`,
|
|
1962
|
-
`${formatPercentage(pPct)}%`,
|
|
1963
|
-
`${formatPercentage(aPct)}%`,
|
|
1964
|
-
`${formatPercentage(nPct)}%`
|
|
1965
|
-
]
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
blocks.push({ variant: 'endTable' });
|
|
1970
|
-
return blocks;
|
|
1971
|
-
}
|
|
1972
|
-
// TEST LOG
|
|
1973
|
-
// Prepare formatted test results for rendering
|
|
1974
|
-
function doPrepareTestLog(s) {
|
|
1975
|
-
// Initialize the output format
|
|
1976
|
-
let text = { data: [] };
|
|
1977
|
-
let blocks = text.data;
|
|
1978
|
-
// If the plan as already been analyzed, prepare a test log
|
|
1979
|
-
if (s.bPlanAnalyzed) {
|
|
1980
|
-
if (!(s.bPostProcessingDone)) {
|
|
1981
|
-
doAnalyzePostProcessing(s);
|
|
1982
|
-
}
|
|
1983
|
-
// Create a test log header
|
|
1984
|
-
blocks.push({ variant: 'h4', text: `Test Log` });
|
|
1985
|
-
let testResults = U.getNumericObjectKeys(testDefns);
|
|
1986
|
-
let suitesRequested = new Set(s.config['suites']);
|
|
1987
|
-
for (let testID of testResults) {
|
|
1988
|
-
// Filter the defined tests by the requested test suites
|
|
1989
|
-
let inSuites = testDefns[testID]['suites'];
|
|
1990
|
-
if (!(U.isArrayEmpty(inSuites.filter(x => suitesRequested.has(x))))) {
|
|
1991
|
-
// Get the test result
|
|
1992
|
-
let testResult = s.getTest(testID);
|
|
1993
|
-
// Prepare the text for it, and append it to the output
|
|
1994
|
-
let testText = prepareTestEntry(testID, testResult);
|
|
1995
|
-
blocks.push(...testText);
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
// Otherwise, return a blank test log
|
|
2000
|
-
return text;
|
|
2001
|
-
}
|
|
2002
|
-
exports.doPrepareTestLog = doPrepareTestLog;
|
|
2003
|
-
function prepareTestEntry(testID, testResult) {
|
|
2004
|
-
let text = { data: [] };
|
|
2005
|
-
let blocks = text.data;
|
|
2006
|
-
let testName = testDefns[testID]['name'];
|
|
2007
|
-
let testNameTrailer = "";
|
|
2008
|
-
if (U.keyExists('trailer', testDefns[testID])) {
|
|
2009
|
-
testNameTrailer = testDefns[testID]['trailer'];
|
|
2010
|
-
}
|
|
2011
|
-
let testType = testDefns[testID]['externalType'];
|
|
2012
|
-
let bNormalize = testDefns[testID]['normalize'];
|
|
2013
|
-
let detailsFn = testDefns[testID]['detailsFn'];
|
|
2014
|
-
let detailsText = detailsFn(testResult);
|
|
2015
|
-
let score; // NOTE - Won't be undefined here
|
|
2016
|
-
let normalizedScore;
|
|
2017
|
-
let scoreText;
|
|
2018
|
-
// Get the score ...
|
|
2019
|
-
score = testResult['score'];
|
|
2020
|
-
// ... and format it for rendering
|
|
2021
|
-
switch (testType) {
|
|
2022
|
-
case TestType.PassFail: {
|
|
2023
|
-
scoreText = pfBoolToString(score);
|
|
2024
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${scoreText} ${testNameTrailer}` });
|
|
2025
|
-
break;
|
|
2026
|
-
}
|
|
2027
|
-
case TestType.Percentage: {
|
|
2028
|
-
score = fractionToPercentage(score);
|
|
2029
|
-
if (bNormalize) {
|
|
2030
|
-
normalizedScore = testResult['normalizedScore'];
|
|
2031
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatPercentage(score)}% ${testNameTrailer}` });
|
|
2032
|
-
}
|
|
2033
|
-
else {
|
|
2034
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatPercentage(score)}% ${testNameTrailer}` });
|
|
2035
|
-
}
|
|
2036
|
-
break;
|
|
2037
|
-
}
|
|
2038
|
-
case TestType.Number: {
|
|
2039
|
-
if (bNormalize) {
|
|
2040
|
-
normalizedScore = testResult['normalizedScore'];
|
|
2041
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatNumber(score)} ${testNameTrailer}` });
|
|
2042
|
-
}
|
|
2043
|
-
else {
|
|
2044
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatNumber(score)} ${testNameTrailer}` });
|
|
2045
|
-
}
|
|
2046
|
-
break;
|
|
2047
|
-
}
|
|
2048
|
-
default: {
|
|
2049
|
-
// Unknown test type
|
|
2050
|
-
throw new RangeError();
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
// Add the details text
|
|
2054
|
-
blocks.push(...detailsText);
|
|
2055
|
-
return blocks;
|
|
2056
|
-
}
|
|
2057
|
-
// FORMATTERS FOR TEST DETAILS
|
|
2058
|
-
function doPrepareCompleteDetails(testResult) {
|
|
2059
|
-
let text = { data: [] };
|
|
2060
|
-
let blocks = text.data;
|
|
2061
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
2062
|
-
let unassignedText = "";
|
|
2063
|
-
let emptyText = "";
|
|
2064
|
-
let missingText = "";
|
|
2065
|
-
if (U.keyExists('unassignedFeatures', testResult['details'])) {
|
|
2066
|
-
let unassignedFeatures = testResult['details']['unassignedFeatures'];
|
|
2067
|
-
let unassignedList = prepareListItems(unassignedFeatures);
|
|
2068
|
-
let unassignedTextTemplates = [
|
|
2069
|
-
`GEOID ${unassignedList} is not assigned to a district.`,
|
|
2070
|
-
`GEOIDs ${unassignedList} are not assigned to districts.`,
|
|
2071
|
-
`Several GEOIDs are not assigned to districts, including ${unassignedList}.`
|
|
2072
|
-
];
|
|
2073
|
-
unassignedText = prepareListText(unassignedFeatures, unassignedTextTemplates);
|
|
2074
|
-
}
|
|
2075
|
-
if (U.keyExists('emptyDistricts', testResult['details'])) {
|
|
2076
|
-
let emptyDistricts = testResult['details']['emptyDistricts'];
|
|
2077
|
-
let emptyList = prepareListItems(emptyDistricts);
|
|
2078
|
-
let emptyTextTemplates = [
|
|
2079
|
-
`District ${emptyList} is empty.`,
|
|
2080
|
-
`Districts ${emptyList} are empty.`,
|
|
2081
|
-
`Several districts are empty, including ${emptyList}.`
|
|
2082
|
-
];
|
|
2083
|
-
emptyText = prepareListText(emptyDistricts, emptyTextTemplates);
|
|
2084
|
-
}
|
|
2085
|
-
if (U.keyExists('missingDistricts', testResult['details'])) {
|
|
2086
|
-
missingText = `Not enough districts have been defined. `;
|
|
2087
|
-
}
|
|
2088
|
-
let detailsText = " " + unassignedText + emptyText + missingText;
|
|
2089
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
2090
|
-
}
|
|
2091
|
-
return blocks;
|
|
2092
|
-
}
|
|
2093
|
-
function doPrepareContiguousDetails(testResult) {
|
|
2094
|
-
let text = { data: [] };
|
|
2095
|
-
let blocks = text.data;
|
|
2096
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
2097
|
-
let discontiguousDistricts = testResult['details']['discontiguousDistricts'];
|
|
2098
|
-
let discontiguousList = prepareListItems(discontiguousDistricts);
|
|
2099
|
-
let discontiguousTextTemplates = [
|
|
2100
|
-
`District ${discontiguousList} is not contiguous.`,
|
|
2101
|
-
`Districts ${discontiguousList} are not contiguous.`,
|
|
2102
|
-
`Several districts are not contiguous, including ${discontiguousList}.`
|
|
2103
|
-
];
|
|
2104
|
-
let detailsText = prepareListText(discontiguousDistricts, discontiguousTextTemplates);
|
|
2105
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
2106
|
-
}
|
|
2107
|
-
return blocks;
|
|
2108
|
-
}
|
|
2109
|
-
function doPrepareFreeOfHolesDetails(testResult) {
|
|
2110
|
-
let text = { data: [] };
|
|
2111
|
-
let blocks = text.data;
|
|
2112
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
2113
|
-
let embeddedDistricts = testResult['details']['embeddedDistricts'];
|
|
2114
|
-
let embeddedList = prepareListItems(embeddedDistricts);
|
|
2115
|
-
let embeddedTextTemplates = [
|
|
2116
|
-
`District ${embeddedList} is fully embedded within another district.`,
|
|
2117
|
-
`Both districts ${embeddedList} are fully embedded within other districts.`,
|
|
2118
|
-
`Several districts are fully embedded within other districts, including ${embeddedList}.`
|
|
2119
|
-
];
|
|
2120
|
-
let detailsText = prepareListText(embeddedDistricts, embeddedTextTemplates);
|
|
2121
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
2122
|
-
}
|
|
2123
|
-
return blocks;
|
|
2124
|
-
}
|
|
2125
|
-
function doPrepareEqualPopulationDetails(testResult) {
|
|
2126
|
-
let text = { data: [] };
|
|
2127
|
-
let blocks = text.data;
|
|
2128
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
2129
|
-
let popDevPct = fractionToPercentage(testResult['details']['deviation']);
|
|
2130
|
-
let thresholdPct = fractionToPercentage(1.0 - testResult['details']['thresholds'][0]);
|
|
2131
|
-
let detailsText = '';
|
|
2132
|
-
if (!(testResult['score'])) {
|
|
2133
|
-
detailsText = `The ${formatPercentage(popDevPct)}% population deviation is greater than the ${formatPercentage(thresholdPct)}% threshold tolerated by courts.`;
|
|
2134
|
-
}
|
|
2135
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
2136
|
-
}
|
|
2137
|
-
return blocks;
|
|
2138
|
-
}
|
|
2139
|
-
function doPreparePopulationDeviationDetails(testResult) {
|
|
2140
|
-
let text = { data: [] };
|
|
2141
|
-
let blocks = text.data;
|
|
2142
|
-
let n = Math.round(testResult['details']['maxDeviation']);
|
|
2143
|
-
let term = "people";
|
|
2144
|
-
if (n == 1) {
|
|
2145
|
-
term = "person";
|
|
2146
|
-
}
|
|
2147
|
-
blocks.push({ variant: 'body1', text: `The maximum population deviation between districts is ${formatInteger(n)} ${term}.` });
|
|
2148
|
-
return blocks;
|
|
2149
|
-
}
|
|
2150
|
-
function doPrepareReockDetails(testResult) {
|
|
2151
|
-
let text = { data: [] };
|
|
2152
|
-
let blocks = text.data;
|
|
2153
|
-
// TODO - No details implemented yet
|
|
2154
|
-
return blocks;
|
|
2155
|
-
}
|
|
2156
|
-
function doPreparePolsbyPopperDetails(testResult) {
|
|
2157
|
-
let text = { data: [] };
|
|
2158
|
-
let blocks = text.data;
|
|
2159
|
-
// TODO - No details implemented yet
|
|
2160
|
-
return blocks;
|
|
2161
|
-
}
|
|
2162
|
-
function doPrepareEfficiencyGapDetails(testResult) {
|
|
2163
|
-
let text = { data: [] };
|
|
2164
|
-
let blocks = text.data;
|
|
2165
|
-
// TODO - Not yet implemented
|
|
2166
|
-
return blocks;
|
|
2167
|
-
}
|
|
2168
|
-
function doPrepareCountySplitDetails(testResult) {
|
|
2169
|
-
let text = { data: [] };
|
|
2170
|
-
let blocks = text.data;
|
|
2171
|
-
let unexpectedAffected = fractionToPercentage(testResult['details']['unexpectedAffected']);
|
|
2172
|
-
let affectedText = `affecting ${formatPercentage(unexpectedAffected)}% of the total population.`;
|
|
2173
|
-
let countiesSplitUnexpectedly = testResult['details']['countiesSplitUnexpectedly'];
|
|
2174
|
-
let nCountiesSplitUnexpectedly = countiesSplitUnexpectedly.length;
|
|
2175
|
-
let nUnexpectedSplits = testResult['details']['unexpectedSplits'];
|
|
2176
|
-
let splitList = prepareListItems(countiesSplitUnexpectedly);
|
|
2177
|
-
let splitTextTemplates = [
|
|
2178
|
-
`${splitList} county is split unexpectedly, `,
|
|
2179
|
-
`${splitList} counties are split unexpectedly, `,
|
|
2180
|
-
// `These ${formatInteger(nCountiesSplitUnexpectedly)} counties are split unexpectedly--${splitList}--`
|
|
2181
|
-
`${splitList} counties are split unexpectedly ${formatInteger(nUnexpectedSplits)} times, `
|
|
2182
|
-
];
|
|
2183
|
-
let detailsText = prepareListText(countiesSplitUnexpectedly, splitTextTemplates) + affectedText;
|
|
2184
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
2185
|
-
return blocks;
|
|
2186
|
-
}
|
|
2187
|
-
// FORMATTERS FOR CATEGORIES
|
|
2188
|
-
// This function parses & formats the 'Valid' section of a scorecard.
|
|
2189
|
-
function doPrepareValidSection(s, c) {
|
|
2190
|
-
// Get the category meta data
|
|
2191
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
2192
|
-
let testReport;
|
|
2193
|
-
// Initialize the section text
|
|
2194
|
-
let text = { data: [] };
|
|
2195
|
-
let blocks = text.data;
|
|
2196
|
-
let testText;
|
|
2197
|
-
// Section header
|
|
2198
|
-
let stringScore = pfBoolToString(catScore);
|
|
2199
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName}` });
|
|
2200
|
-
// blocks.push({ variant: 'beginExpansion', text: `${catName} ${stringScore}` });
|
|
2201
|
-
// Complete
|
|
2202
|
-
testReport = catTests[0 /* Complete */];
|
|
2203
|
-
testText = prepareTestEntry(0 /* Complete */, testReport);
|
|
2204
|
-
blocks.push(...testText);
|
|
2205
|
-
// Contiguous
|
|
2206
|
-
testReport = catTests[1 /* Contiguous */];
|
|
2207
|
-
testText = prepareTestEntry(1 /* Contiguous */, testReport);
|
|
2208
|
-
blocks.push(...testText);
|
|
2209
|
-
// Free of holes (no embedded districts)
|
|
2210
|
-
testReport = catTests[2 /* FreeOfHoles */];
|
|
2211
|
-
testText = prepareTestEntry(2 /* FreeOfHoles */, testReport);
|
|
2212
|
-
blocks.push(...testText);
|
|
2213
|
-
// Equal population (w/in the appropriate legal threshold)
|
|
2214
|
-
testReport = catTests[3 /* EqualPopulation */];
|
|
2215
|
-
testText = prepareTestEntry(3 /* EqualPopulation */, testReport);
|
|
2216
|
-
blocks.push(...testText);
|
|
2217
|
-
blocks.push({ variant: 'endExpansion' });
|
|
2218
|
-
return blocks;
|
|
2219
|
-
}
|
|
2220
|
-
// TODO - NIY
|
|
2221
|
-
// This function parses & formats the 'Fair' section of a scorecard.
|
|
2222
|
-
function doPrepareFairSection(s, c) {
|
|
2223
|
-
// Get the category meta data
|
|
2224
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
2225
|
-
let testReport;
|
|
2226
|
-
// Initialize the section text
|
|
2227
|
-
let text = { data: [] };
|
|
2228
|
-
let blocks = text.data;
|
|
2229
|
-
let testText;
|
|
2230
|
-
// Section header
|
|
2231
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName} ${catScore} of 100` });
|
|
2232
|
-
// TODO - Flesh this out
|
|
2233
|
-
// There's only one test: Population Deviation.
|
|
2234
|
-
// testReport = catTests[T.Test.PopulationDeviation];
|
|
2235
|
-
// testText = prepareTestEntry(T.Test.PopulationDeviation, testReport);
|
|
2236
|
-
// blocks.push(...testText);
|
|
2237
|
-
blocks.push({ variant: 'endExpansion' });
|
|
2238
|
-
return blocks;
|
|
2239
|
-
}
|
|
2240
|
-
// This function parses & formats the 'Best' section of a scorecard.
|
|
2241
|
-
function doPrepareBestSection(s, c) {
|
|
2242
|
-
// Get the category meta data
|
|
2243
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
2244
|
-
let testReport;
|
|
2245
|
-
// Initialize the section text
|
|
2246
|
-
let text = { data: [] };
|
|
2247
|
-
let blocks = text.data;
|
|
2248
|
-
let testText;
|
|
2249
|
-
// Section header
|
|
2250
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName} (${catScore} of 100)` });
|
|
2251
|
-
// Population deviation
|
|
2252
|
-
testReport = catTests[4 /* PopulationDeviation */];
|
|
2253
|
-
testText = prepareTestEntry(4 /* PopulationDeviation */, testReport);
|
|
2254
|
-
blocks.push(...testText);
|
|
2255
|
-
// Compactness
|
|
2256
|
-
testReport = catTests[5 /* Reock */];
|
|
2257
|
-
let normalizedReock = testReport['normalizedScore'];
|
|
2258
|
-
let reockTestText = prepareTestEntry(5 /* Reock */, testReport);
|
|
2259
|
-
testReport = catTests[6 /* PolsbyPopper */];
|
|
2260
|
-
let normalizedPolsbyPopper = testReport['normalizedScore'];
|
|
2261
|
-
let polsbyPopperTestText = prepareTestEntry(6 /* PolsbyPopper */, testReport);
|
|
2262
|
-
let compactnessScore = (normalizedReock + normalizedPolsbyPopper) / 2;
|
|
2263
|
-
blocks.push({ variant: 'body1', text: `Compactness: ${compactnessScore} / 100` });
|
|
2264
|
-
blocks.push(...reockTestText);
|
|
2265
|
-
blocks.push(...polsbyPopperTestText);
|
|
2266
|
-
// County splits
|
|
2267
|
-
// TODO - Re-enable this, when the county-splitting metric is implemented.
|
|
2268
|
-
// testReport = catTests[T.Test.CountySplits];
|
|
2269
|
-
// testText = prepareTestEntry(T.Test.CountySplits, testReport);
|
|
2270
|
-
// blocks.push(...testText);
|
|
2271
|
-
blocks.push({ variant: 'endExpansion' });
|
|
2272
|
-
return blocks;
|
|
2273
|
-
}
|
|
2274
|
-
// FORMATTING HELPERS
|
|
2275
|
-
// Convert a boolean representing Pass/Fail to a string
|
|
2276
|
-
function pfBoolToString(score) {
|
|
2277
|
-
if (score) {
|
|
2278
|
-
return "Yes";
|
|
2279
|
-
}
|
|
2280
|
-
else {
|
|
2281
|
-
return "No";
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
function fractionToPercentage(f) {
|
|
2285
|
-
return f * 100;
|
|
2286
|
-
}
|
|
2287
|
-
function formatNumber(n) {
|
|
2288
|
-
let p = S.PRECISION;
|
|
2289
|
-
return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
|
|
2290
|
-
}
|
|
2291
|
-
function formatPercentage(n) {
|
|
2292
|
-
let p = (S.PRECISION / 2);
|
|
2293
|
-
return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
|
|
2294
|
-
}
|
|
2295
|
-
function formatInteger(i) {
|
|
2296
|
-
return new Intl.NumberFormat().format(i);
|
|
2297
|
-
}
|
|
2298
|
-
// Prepare the items in a list for rendering
|
|
2299
|
-
function prepareListItems(list) {
|
|
2300
|
-
let nItems = list.length;
|
|
2301
|
-
let listStr;
|
|
2302
|
-
switch (nItems) {
|
|
2303
|
-
case 1: {
|
|
2304
|
-
listStr = list[0];
|
|
2305
|
-
break;
|
|
2306
|
-
}
|
|
2307
|
-
case 2: {
|
|
2308
|
-
listStr = list[0] + " and " + list[1];
|
|
2309
|
-
break;
|
|
2310
|
-
}
|
|
2311
|
-
default: {
|
|
2312
|
-
let listWithCommas = list.join(', ');
|
|
2313
|
-
let lastCommaIndex = listWithCommas.length - ((list[list.length - 1].length) + 1);
|
|
2314
|
-
let beforeAnd = listWithCommas.substr(0, lastCommaIndex);
|
|
2315
|
-
let afterAnd = listWithCommas.substr(lastCommaIndex + 1);
|
|
2316
|
-
listStr = beforeAnd + " and " + afterAnd;
|
|
2317
|
-
break;
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
return listStr;
|
|
2321
|
-
}
|
|
2322
|
-
// Pick the rendering text for the appropriate list length
|
|
2323
|
-
function prepareListText(list, listTemplates) {
|
|
2324
|
-
let nItems = list.length;
|
|
2325
|
-
switch (nItems) {
|
|
2326
|
-
case 1: {
|
|
2327
|
-
return listTemplates[0];
|
|
2328
|
-
break;
|
|
2329
|
-
}
|
|
2330
|
-
case 2: {
|
|
2331
|
-
return listTemplates[1];
|
|
2332
|
-
break;
|
|
2333
|
-
}
|
|
2334
|
-
default: {
|
|
2335
|
-
return listTemplates[2];
|
|
2336
|
-
break;
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
/* COMMAND-LINE TESTS
|
|
2341
|
-
|
|
2342
|
-
node main.js scorecard -v -x NC -n 13 -p ~/src/district-analytics/data/SAMPLE-BG-map.csv -d ~/src/district-analytics/data/SAMPLE-BG-data2.json -s ~/src/district-analytics/data/SAMPLE-BG-shapes.geojson -g ~/src/district-analytics/data/SAMPLE-BG-graph.json -c ~/src/district-analytics/data/SAMPLE-COUNTY.geojson
|
|
2343
|
-
node --inspect --inspect-brk main.js scorecard -v -x NC -n 13 -p ~/src/district-analytics/data/SAMPLE-BG-map.csv -d ~/src/district-analytics/data/SAMPLE-BG-data2.json -s ~/src/district-analytics/data/SAMPLE-BG-shapes.geojson -g ~/src/district-analytics/data/SAMPLE-BG-graph.json -c ~/src/district-analytics/data/SAMPLE-COUNTY.geojson
|
|
2344
|
-
|
|
2345
|
-
-or-
|
|
2346
|
-
|
|
2347
|
-
./main.js scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2348
|
-
./main.js --inspect --inspect-brk scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2349
|
-
|
|
2350
|
-
These calls work at the project directory, using samples in the data/ directory:
|
|
2351
|
-
|
|
2352
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2353
|
-
|
|
2354
|
-
TODO - Fix this
|
|
2355
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map-missing.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2356
|
-
./main.js scorecard -v -x NC-n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2357
|
-
|
|
2358
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2359
|
-
./main.js scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
2360
|
-
|
|
2361
|
-
TODO - HERE
|
|
2362
|
-
|
|
2363
|
-
./main.js -v -x NC testlog -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson --empty
|
|
2364
|
-
./main.js -v -x NC scorecard -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson --empty
|
|
2365
|
-
|
|
2366
|
-
TODO
|
|
2367
|
-
|
|
2368
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2369
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2370
|
-
|
|
2371
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2372
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2376
|
-
node main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
|
|
2380
|
-
|
|
2381
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2382
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2383
|
-
|
|
2384
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2385
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
*/
|
|
2390
|
-
/* TODO - DELETE
|
|
2391
|
-
|
|
2392
|
-
These calls work at the project directory, using samples in the data/ directory:
|
|
2393
|
-
|
|
2394
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2395
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2396
|
-
|
|
2397
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2398
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2399
|
-
|
|
2400
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2401
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2402
|
-
|
|
2403
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv --empty
|
|
2404
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv --empty
|
|
2405
|
-
|
|
2406
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2407
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2408
|
-
|
|
2409
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2410
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2414
|
-
node main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
|
|
2418
|
-
|
|
2419
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2420
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2421
|
-
|
|
2422
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2423
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
2424
|
-
|
|
2425
|
-
*/
|
|
2426
2493
|
|
|
2427
2494
|
|
|
2428
2495
|
/***/ }),
|
|
@@ -2447,11 +2514,17 @@ exports.PRECISION = 4;
|
|
|
2447
2514
|
exports.NORMALIZED_RANGE = 100;
|
|
2448
2515
|
// The dummy district ID for features not assigned districts yet
|
|
2449
2516
|
exports.NOT_ASSIGNED = 0;
|
|
2450
|
-
// TODO -
|
|
2517
|
+
// TODO - TERRY/DAVE: Discuss
|
|
2451
2518
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
2452
2519
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
2453
2520
|
// The virtual geoID for "neighbors" in other states
|
|
2454
2521
|
exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
2522
|
+
// "Roughly equal" = average census block size / 2
|
|
2523
|
+
const AVERAGE_BLOCK_SIZE = 30;
|
|
2524
|
+
exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
|
|
2525
|
+
// County & district splitting weights
|
|
2526
|
+
exports.COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
2527
|
+
exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
2455
2528
|
|
|
2456
2529
|
|
|
2457
2530
|
/***/ }),
|
|
@@ -2486,8 +2559,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
2486
2559
|
//
|
|
2487
2560
|
// UTILITIES
|
|
2488
2561
|
//
|
|
2562
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
2563
|
+
if (mod && mod.__esModule) return mod;
|
|
2564
|
+
var result = {};
|
|
2565
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
2566
|
+
result["default"] = mod;
|
|
2567
|
+
return result;
|
|
2568
|
+
};
|
|
2489
2569
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2490
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
2570
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
2491
2571
|
// PLAN HELPERS
|
|
2492
2572
|
// Is a "neighbor" in state?
|
|
2493
2573
|
function isInState(geoID) {
|
|
@@ -2499,6 +2579,7 @@ function isOutOfState(geoID) {
|
|
|
2499
2579
|
return geoID == S.OUT_OF_STATE;
|
|
2500
2580
|
}
|
|
2501
2581
|
exports.isOutOfState = isOutOfState;
|
|
2582
|
+
// TODO - UNASSIGNED
|
|
2502
2583
|
// Get the districtID to which a geoID is assigned
|
|
2503
2584
|
function getDistrict(plan, geoID) {
|
|
2504
2585
|
// All geoIDs in a state *should be* assigned to a district (including the
|
|
@@ -2512,24 +2593,6 @@ function getDistrict(plan, geoID) {
|
|
|
2512
2593
|
}
|
|
2513
2594
|
}
|
|
2514
2595
|
exports.getDistrict = getDistrict;
|
|
2515
|
-
// Invert a feature assignment structure to sets of ids by district
|
|
2516
|
-
// NOTE - This is here vs. _data.ts, so it can also be used in cli.ts
|
|
2517
|
-
function invertPlan(plan) {
|
|
2518
|
-
let invertedPlan = {};
|
|
2519
|
-
// Add a dummy 'unassigned' district
|
|
2520
|
-
invertedPlan[S.NOT_ASSIGNED] = new Set();
|
|
2521
|
-
for (let geoID in plan) {
|
|
2522
|
-
let districtID = plan[geoID];
|
|
2523
|
-
// Make sure the set for the districtID exists
|
|
2524
|
-
if (!(objectContains(invertedPlan, districtID))) {
|
|
2525
|
-
invertedPlan[districtID] = new Set();
|
|
2526
|
-
}
|
|
2527
|
-
// Add the geoID to the districtID's set
|
|
2528
|
-
invertedPlan[districtID].add(geoID);
|
|
2529
|
-
}
|
|
2530
|
-
return invertedPlan;
|
|
2531
|
-
}
|
|
2532
|
-
exports.invertPlan = invertPlan;
|
|
2533
2596
|
// WORKING WITH GEOIDS
|
|
2534
2597
|
function parseGeoID(geoID) {
|
|
2535
2598
|
let parts = {};
|
|
@@ -2552,27 +2615,46 @@ function getFIPSFromCountyGeoID(geoID) {
|
|
|
2552
2615
|
return geoID.substring(2, 5);
|
|
2553
2616
|
}
|
|
2554
2617
|
exports.getFIPSFromCountyGeoID = getFIPSFromCountyGeoID;
|
|
2618
|
+
function isWaterOnly(geoID) {
|
|
2619
|
+
let waterOnlySignature = 'ZZZZZZ';
|
|
2620
|
+
if (geoID.indexOf(waterOnlySignature) >= 0)
|
|
2621
|
+
return true;
|
|
2622
|
+
else
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
exports.isWaterOnly = isWaterOnly;
|
|
2555
2626
|
// NORMALIZING RESULTS
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
if
|
|
2627
|
+
function normalize(rawScore, testScale) {
|
|
2628
|
+
let rangeMin = testScale.scale[0];
|
|
2629
|
+
let rangeMax = testScale.scale[1];
|
|
2630
|
+
// Invert the raw value if necessary to make bigger = better
|
|
2631
|
+
// TODO - This works for Population Deviation, because the max is 1.0.
|
|
2632
|
+
// Generalize this???
|
|
2633
|
+
if (testScale.bInvertRaw) {
|
|
2560
2634
|
rawScore = 1.0 - rawScore;
|
|
2561
2635
|
}
|
|
2562
2636
|
// Coerce the value to be w/in the given range
|
|
2563
|
-
let rangeMin = scale[0];
|
|
2564
|
-
let rangeMax = scale[1];
|
|
2565
2637
|
let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
|
|
2566
2638
|
// Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
|
|
2567
2639
|
let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
|
|
2640
|
+
// TODO - SPLITTING
|
|
2641
|
+
// Invert the scaled value if necessary to make bigger = better
|
|
2642
|
+
if (testScale.bInvertScaled) {
|
|
2643
|
+
scaledValue = 1.0 - scaledValue;
|
|
2644
|
+
}
|
|
2568
2645
|
// Finally, make the range [0-100]
|
|
2569
2646
|
return Math.round(scaledValue * S.NORMALIZED_RANGE);
|
|
2570
2647
|
}
|
|
2571
2648
|
exports.normalize = normalize;
|
|
2572
2649
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
2573
|
-
function trim(fullFraction) {
|
|
2574
|
-
|
|
2575
|
-
|
|
2650
|
+
function trim(fullFraction, digits = undefined) {
|
|
2651
|
+
if (digits == 0) {
|
|
2652
|
+
return Math.round(fullFraction);
|
|
2653
|
+
}
|
|
2654
|
+
else {
|
|
2655
|
+
let shiftPlaces = Math.pow(10, (digits || S.PRECISION));
|
|
2656
|
+
return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
|
|
2657
|
+
}
|
|
2576
2658
|
}
|
|
2577
2659
|
exports.trim = trim;
|
|
2578
2660
|
// ARRAY HELPERS
|
|
@@ -2601,13 +2683,14 @@ function andArray(arr) {
|
|
|
2601
2683
|
}
|
|
2602
2684
|
exports.andArray = andArray;
|
|
2603
2685
|
// WORKING WITH OBJECT KEYS/PROPERTIES
|
|
2604
|
-
// TODO -
|
|
2686
|
+
// TODO - TERRY, is this copesetic?
|
|
2687
|
+
// TODO - Handle integer keys?
|
|
2605
2688
|
// Does an object have a key/property?
|
|
2606
2689
|
function keyExists(k, o) {
|
|
2607
2690
|
return k in o;
|
|
2608
2691
|
}
|
|
2609
2692
|
exports.keyExists = keyExists;
|
|
2610
|
-
// TODO -
|
|
2693
|
+
// TODO - TERRY, can these three be combined into a generic isEmpty() check?
|
|
2611
2694
|
// Does an object (dict) have any keys/properties?
|
|
2612
2695
|
function isObjectEmpty(o) {
|
|
2613
2696
|
return Object.keys(o).length === 0;
|
|
@@ -2701,7 +2784,12 @@ function deepCopy(src) {
|
|
|
2701
2784
|
return src;
|
|
2702
2785
|
}
|
|
2703
2786
|
exports.deepCopy = deepCopy;
|
|
2704
|
-
// TODO -
|
|
2787
|
+
// TODO - TRI-STATES: Map booleans to tri-states.
|
|
2788
|
+
function mapBooleanToTriState(bool) {
|
|
2789
|
+
return (bool) ? 0 /* Green */ : 2 /* Red */;
|
|
2790
|
+
}
|
|
2791
|
+
exports.mapBooleanToTriState = mapBooleanToTriState;
|
|
2792
|
+
// TODO - TERRY: What is this the simple explanation of what this thing is doing?
|
|
2705
2793
|
var util_1 = __webpack_require__(/*! @dra2020/util */ "@dra2020/util");
|
|
2706
2794
|
exports.depthof = util_1.depthof;
|
|
2707
2795
|
|
|
@@ -2720,40 +2808,54 @@ exports.depthof = util_1.depthof;
|
|
|
2720
2808
|
//
|
|
2721
2809
|
// MAP/PLAN VALIDATIONS
|
|
2722
2810
|
//
|
|
2811
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
2812
|
+
if (mod && mod.__esModule) return mod;
|
|
2813
|
+
var result = {};
|
|
2814
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
2815
|
+
result["default"] = mod;
|
|
2816
|
+
return result;
|
|
2817
|
+
};
|
|
2723
2818
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2724
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
2725
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
2726
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
2819
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
2820
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
2821
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
2727
2822
|
//
|
|
2728
2823
|
// COMPLETE - Are all geo's assigned to a district, and do all districts have
|
|
2729
2824
|
// at least one geo assigned to them?
|
|
2730
2825
|
//
|
|
2731
|
-
function doIsComplete(s) {
|
|
2826
|
+
function doIsComplete(s, bLog = false) {
|
|
2732
2827
|
let test = s.getTest(0 /* Complete */);
|
|
2733
|
-
|
|
2734
|
-
let bNoneEmpty = true;
|
|
2735
|
-
let unassignedFeatures = [];
|
|
2736
|
-
let emptyDistricts = [];
|
|
2737
|
-
// Get the by district results, including the dummy unassigned district,
|
|
2828
|
+
// Get the by-district results, including the dummy unassigned district,
|
|
2738
2829
|
// but ignoring the N+1 summary district
|
|
2739
2830
|
let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
|
|
2740
2831
|
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
|
|
2741
2832
|
// Are all features assigned to districts?
|
|
2742
2833
|
// Check the dummy district that holds any unassigned features.
|
|
2743
|
-
|
|
2834
|
+
let unassignedFeatures = [];
|
|
2835
|
+
let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
|
|
2744
2836
|
if (!bAllAssigned) {
|
|
2745
2837
|
let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
|
|
2746
2838
|
unassignedFeatures = Array.from(unassignedDistrict);
|
|
2747
2839
|
unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
|
|
2748
2840
|
}
|
|
2749
2841
|
// Do all real districts have at least one feature assigned to them?
|
|
2750
|
-
|
|
2842
|
+
let emptyDistricts = [];
|
|
2843
|
+
let bNoneEmpty = true;
|
|
2844
|
+
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(1);
|
|
2845
|
+
let districtID = 1;
|
|
2846
|
+
bNotEmptyByDistrict.forEach(function (bNotEmpty) {
|
|
2847
|
+
if (!bNotEmpty) {
|
|
2848
|
+
bNoneEmpty = false;
|
|
2849
|
+
emptyDistricts.push(districtID);
|
|
2850
|
+
}
|
|
2851
|
+
districtID += 1;
|
|
2852
|
+
});
|
|
2751
2853
|
// Case 1 - One or more districts are missing:
|
|
2752
2854
|
// The # of enumerated districts minus the dummy NOT_ASSIGNED one should
|
|
2753
2855
|
// equal the number apportioned districts. This guards against a district
|
|
2754
2856
|
// not being included in a map that is imported.
|
|
2755
2857
|
//
|
|
2756
|
-
//
|
|
2858
|
+
// NOTE - I'm no longer checking for this, but DRA should!
|
|
2757
2859
|
// Case 2 - Or a district is explicitly named but empty:
|
|
2758
2860
|
// Note, this can happen if a district is created, and then all features
|
|
2759
2861
|
// are removed from it (in DRA).
|
|
@@ -2780,7 +2882,7 @@ exports.doIsComplete = doIsComplete;
|
|
|
2780
2882
|
//
|
|
2781
2883
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
|
|
2782
2884
|
//
|
|
2783
|
-
function doIsContiguous(s) {
|
|
2885
|
+
function doIsContiguous(s, bLog = false) {
|
|
2784
2886
|
let test = s.getTest(1 /* Contiguous */);
|
|
2785
2887
|
// Get the contiguity of each district. Ignore dummy unassigned district
|
|
2786
2888
|
// and the N+1 summary district.
|
|
@@ -2811,7 +2913,7 @@ exports.doIsContiguous = doIsContiguous;
|
|
|
2811
2913
|
// Are the features in a district fully connected?
|
|
2812
2914
|
function isConnected(districtGeos, graph) {
|
|
2813
2915
|
// export function isConnected(districtGeos: Set<string>, graph: T.ContiguityGraph): boolean {
|
|
2814
|
-
// TODO -
|
|
2916
|
+
// TODO - TERRY, why does this constructor need a <T> type specification?
|
|
2815
2917
|
let visited = new Set();
|
|
2816
2918
|
let toProcess = [];
|
|
2817
2919
|
// Start processing with the first geoID in the district
|
|
@@ -2826,7 +2928,7 @@ function isConnected(districtGeos, graph) {
|
|
|
2826
2928
|
let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
|
|
2827
2929
|
// Add neighbors to visit, if they're in the same district Y haven't already been visited
|
|
2828
2930
|
let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
|
|
2829
|
-
// TODO -
|
|
2931
|
+
// TODO - TERRY, is this the quickest/best way to do this?
|
|
2830
2932
|
toProcess.push(...neighborsToVisit);
|
|
2831
2933
|
}
|
|
2832
2934
|
// Stop when you've visited all the geoIDs in the district
|
|
@@ -2843,10 +2945,10 @@ exports.isConnected = isConnected;
|
|
|
2843
2945
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
|
|
2844
2946
|
// Buncombe County (37021), is a donut hole w/in District 3.
|
|
2845
2947
|
//
|
|
2846
|
-
// TODO -
|
|
2948
|
+
// TODO - OPTIMIZE: This to take advantage of district boundary info, if/when
|
|
2847
2949
|
// we cache one to optimize compactness.
|
|
2848
2950
|
//
|
|
2849
|
-
function doIsFreeOfHoles(s) {
|
|
2951
|
+
function doIsFreeOfHoles(s, bLog = false) {
|
|
2850
2952
|
let test = s.getTest(2 /* FreeOfHoles */);
|
|
2851
2953
|
// Initialize values
|
|
2852
2954
|
let bFreeOfHoles = true;
|
|
@@ -2877,7 +2979,7 @@ function doIsFreeOfHoles(s) {
|
|
|
2877
2979
|
exports.doIsFreeOfHoles = doIsFreeOfHoles;
|
|
2878
2980
|
// Test whether one district is embedded w/in any other.
|
|
2879
2981
|
function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
2880
|
-
//
|
|
2982
|
+
// NOTE - "features" here = "geoIDs." These aren't "features" proper, just
|
|
2881
2983
|
// identifier strings.
|
|
2882
2984
|
let features = geoIDs;
|
|
2883
2985
|
let planByGeo = plan.byGeoID();
|
|
@@ -2885,7 +2987,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
2885
2987
|
let bEmbedded = true;
|
|
2886
2988
|
// Keep track of the neighoring districts
|
|
2887
2989
|
let neighboringDistricts = new Set();
|
|
2888
|
-
// TODO - Use just the boundary features, when available
|
|
2990
|
+
// TODO - OPTIMIZE: Use just the boundary features, when available
|
|
2889
2991
|
// Get the features for the real district
|
|
2890
2992
|
let featuresToCheck = Array.from(features);
|
|
2891
2993
|
// If the district has features, check whether it is embedded
|
|
@@ -2911,7 +3013,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
2911
3013
|
break;
|
|
2912
3014
|
}
|
|
2913
3015
|
else {
|
|
2914
|
-
// TODO - Since we're checking *all* features in a district right
|
|
3016
|
+
// TODO - OPTIMIZE: Since we're checking *all* features in a district right
|
|
2915
3017
|
// now, not just boundary features and neighbors in other districts,
|
|
2916
3018
|
// prune out the current district. If/when we optimize compactness
|
|
2917
3019
|
// to cache district boundaries (as before in my Python implementation),
|
|
@@ -2937,16 +3039,19 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
2937
3039
|
return bEmbedded;
|
|
2938
3040
|
}
|
|
2939
3041
|
exports.isEmbedded = isEmbedded;
|
|
2940
|
-
// TODO - MIXED MAPS
|
|
2941
|
-
// - When we generalize to mixed maps, the determination of "neighbors in
|
|
2942
|
-
// the map" -- which is the core function in determining connectedness -- becomes
|
|
2943
|
-
// *much* more complicated and dynamic.
|
|
2944
|
-
// - I can write up (if not implement) the logic for this. It is a bit tricky
|
|
2945
|
-
// and requires special preprocessing of the summary level hierarchy (which I
|
|
2946
|
-
// also have a script for that we can repurpose) to distinguish between 'interior'
|
|
2947
|
-
// and 'edge' children.
|
|
2948
3042
|
|
|
2949
3043
|
|
|
3044
|
+
/***/ }),
|
|
3045
|
+
|
|
3046
|
+
/***/ "./static/state-reqs.json":
|
|
3047
|
+
/*!********************************!*\
|
|
3048
|
+
!*** ./static/state-reqs.json ***!
|
|
3049
|
+
\********************************/
|
|
3050
|
+
/*! exports provided: 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, default */
|
|
3051
|
+
/***/ (function(module) {
|
|
3052
|
+
|
|
3053
|
+
module.exports = JSON.parse("{\"AL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_01.pdf\",\"AK\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_02.pdf\",\"AZ\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_03.pdf\",\"AR\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_04.pdf\",\"CA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_05.pdf\",\"CO\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_06.pdf\",\"CT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_07.pdf\",\"DE\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_08.pdf\",\"FL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_09.pdf\",\"GA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_10.pdf\",\"HI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_11.pdf\",\"ID\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_12.pdf\",\"IL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_13.pdf\",\"IN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_14.pdf\",\"IA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_15.pdf\",\"KS\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_16.pdf\",\"KY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_17.pdf\",\"LA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_18.pdf\",\"ME\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_19.pdf\",\"MD\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf\",\"MA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_21.pdf\",\"MI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_22.pdf\",\"MN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_23.pdf\",\"MS\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_24.pdf\",\"MO\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_25.pdf\",\"MT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_26.pdf\",\"NE\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_27.pdf\",\"NV\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_28.pdf\",\"NH\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_29.pdf\",\"NJ\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_30.pdf\",\"NM\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_31.pdf\",\"NY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_32.pdf\",\"NC\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_33.pdf\",\"ND\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_34.pdf\",\"OH\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_35.pdf\",\"OK\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_36.pdf\",\"OR\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_37.pdf\",\"PA\":\"https://www.brennancenter.org/sites/default/files/publications/2018_05_50States_FINALsinglepages_38.pdf\",\"RI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_39.pdf\",\"SC\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_40.pdf\",\"SD\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_41.pdf\",\"TN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_42.pdf\",\"TX\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_43.pdf\",\"UT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_44.pdf\",\"VT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_45.pdf\",\"VA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_46.pdf\",\"WA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_47.pdf\",\"WV\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_48.pdf\",\"WI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_49.pdf\",\"WY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_50.pdf\"}");
|
|
3054
|
+
|
|
2950
3055
|
/***/ }),
|
|
2951
3056
|
|
|
2952
3057
|
/***/ "@dra2020/poly":
|