@dra2020/district-analytics 1.0.9 → 1.0.10
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/_api.d.ts +27 -0
- package/dist/_data.d.ts +130 -0
- package/dist/analyze.d.ts +4 -0
- package/dist/cli.js +1344 -1730
- package/dist/cli.js.map +1 -1
- package/dist/cohesive.d.ts +4 -0
- package/dist/compact.d.ts +5 -0
- package/dist/constants.d.ts +6 -0
- package/dist/district-analytics.js +1205 -1315
- package/dist/district-analytics.js.map +1 -1
- package/dist/equal.d.ts +4 -0
- package/dist/geofeature.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/minority.d.ts +3 -0
- package/dist/political.d.ts +8 -0
- package/dist/preprocess.d.ts +2 -0
- package/dist/report.d.ts +15 -0
- package/dist/settings.d.ts +5 -0
- package/dist/src/_api.d.ts +4 -4
- package/dist/src/_data.d.ts +2 -4
- package/dist/src/analyze.d.ts +1 -1
- package/dist/src/cohesive.d.ts +2 -4
- package/dist/src/equal.d.ts +2 -2
- package/dist/src/index.d.ts +0 -1
- 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 +15 -0
- package/dist/src/settings.d.ts +0 -3
- package/dist/src/types.d.ts +11 -24
- package/dist/src/utils.d.ts +3 -4
- package/dist/src/valid.d.ts +3 -3
- package/dist/test/_cli.d.ts +0 -2
- package/dist/types.d.ts +110 -0
- package/dist/utils.d.ts +28 -0
- package/dist/valid.d.ts +8 -0
- package/package.json +4 -6
- package/dist/src/results.d.ts +0 -141
|
@@ -108,25 +108,12 @@ 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
|
-
};
|
|
118
111
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
119
112
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
120
113
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// doPrepareScorecard, doPrepareTestLog, Scorecard
|
|
125
|
-
// } from './report'
|
|
126
|
-
const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
127
|
-
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
128
|
-
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
129
|
-
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
114
|
+
const report_1 = __webpack_require__(/*! ./report */ "./src/report.ts");
|
|
115
|
+
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
116
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
130
117
|
class AnalyticsSession {
|
|
131
118
|
constructor(SessionRequest) {
|
|
132
119
|
this.config = {};
|
|
@@ -135,6 +122,7 @@ class AnalyticsSession {
|
|
|
135
122
|
this.bPostProcessingDone = false;
|
|
136
123
|
this.testScales = {};
|
|
137
124
|
this.tests = {};
|
|
125
|
+
this.scorecard = {};
|
|
138
126
|
this.title = SessionRequest['title'];
|
|
139
127
|
this.legislativeDistricts = SessionRequest['legislativeDistricts'];
|
|
140
128
|
this.config = this.processConfig(SessionRequest['config']);
|
|
@@ -147,13 +135,12 @@ class AnalyticsSession {
|
|
|
147
135
|
// NOTE: I've pulled these out of the individual analytics to here. Eventually,
|
|
148
136
|
// we could want them to passed into an analytics session as data, along with
|
|
149
137
|
// everything else. For now, this keeps branching out of the main code.
|
|
150
|
-
|
|
138
|
+
report_1.doConfigureScales(this);
|
|
151
139
|
}
|
|
152
140
|
processConfig(config) {
|
|
153
141
|
// NOTE - Session settings are required:
|
|
154
142
|
// - Analytics suites can be defaulted to all with [], but
|
|
155
143
|
// - Dataset keys must be explicitly specified with 'dataset'
|
|
156
|
-
// TODO - Remove this mechanism. Always run everything.
|
|
157
144
|
let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
|
|
158
145
|
// If the config passed in has no suites = [], use the default suites
|
|
159
146
|
if (U.isArrayEmpty(config['suites'])) {
|
|
@@ -168,10 +155,10 @@ class AnalyticsSession {
|
|
|
168
155
|
// analytics & validations, saving/updating the individual test results.
|
|
169
156
|
analyzePlan(bLog = false) {
|
|
170
157
|
try {
|
|
171
|
-
preprocess_1.doPreprocessData(this
|
|
158
|
+
preprocess_1.doPreprocessData(this);
|
|
172
159
|
analyze_1.doAnalyzeDistricts(this, bLog);
|
|
173
160
|
analyze_1.doAnalyzePlan(this, bLog);
|
|
174
|
-
|
|
161
|
+
report_1.doAnalyzePostProcessing(this);
|
|
175
162
|
}
|
|
176
163
|
catch (_a) {
|
|
177
164
|
console.log("Exception caught by analyzePlan()");
|
|
@@ -179,36 +166,6 @@ class AnalyticsSession {
|
|
|
179
166
|
}
|
|
180
167
|
return true;
|
|
181
168
|
}
|
|
182
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
183
|
-
getPlanAnalytics(bLog = false) {
|
|
184
|
-
return results_2.preparePlanAnalytics(this, bLog);
|
|
185
|
-
}
|
|
186
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
187
|
-
getDistrictStatistics(bLog = false) {
|
|
188
|
-
return results_2.prepareDistrictStatistics(this, bLog);
|
|
189
|
-
}
|
|
190
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
191
|
-
getDiscontiguousDistrictFeatures(bLog = false) {
|
|
192
|
-
// Get the (possibly empty) list of discontiguous district IDs
|
|
193
|
-
let contiguousTest = this.getTest(1 /* Contiguous */);
|
|
194
|
-
let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
|
|
195
|
-
// Convert them into a (possibly empty) list of features
|
|
196
|
-
let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
|
|
197
|
-
if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
|
|
198
|
-
for (let i = 0; i < discontiguousDistrictIDs.length; i++) {
|
|
199
|
-
let poly = this.districts.getShape(i);
|
|
200
|
-
discontiguousDistrictFeatures.features.push(poly);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return discontiguousDistrictFeatures;
|
|
204
|
-
}
|
|
205
|
-
// TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
|
|
206
|
-
// Prepare a scorecard for rendering
|
|
207
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
208
|
-
// prepareScorecard(): any {
|
|
209
|
-
// return doPrepareScorecard(this);
|
|
210
|
-
// }
|
|
211
|
-
// HELPERS USED INTERNALLY
|
|
212
169
|
// Get an individual test, so you can drive UI with the results.
|
|
213
170
|
getTest(testID) {
|
|
214
171
|
// Get the existing test entries
|
|
@@ -223,15 +180,18 @@ class AnalyticsSession {
|
|
|
223
180
|
// Return a pointer to the the test entry for this test
|
|
224
181
|
return this.tests[testID];
|
|
225
182
|
}
|
|
226
|
-
//
|
|
183
|
+
// Prepare a scorecard for rendering
|
|
184
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
185
|
+
prepareScorecard() {
|
|
186
|
+
return report_1.doPrepareScorecard(this);
|
|
187
|
+
}
|
|
227
188
|
// Prepare test results for rendering
|
|
228
189
|
// NOTE - This assumes that analyzePlan() has been run!
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// NOTE - Not sure why this has to be up here.
|
|
190
|
+
prepareTestLog() {
|
|
191
|
+
return report_1.doPrepareTestLog(this);
|
|
192
|
+
}
|
|
233
193
|
populationDeviationThreshold() {
|
|
234
|
-
return 1 - this.testScales[4 /* PopulationDeviation */]['
|
|
194
|
+
return 1 - this.testScales[4 /* PopulationDeviation */]['testScale'][0];
|
|
235
195
|
}
|
|
236
196
|
}
|
|
237
197
|
exports.AnalyticsSession = AnalyticsSession;
|
|
@@ -251,16 +211,8 @@ exports.AnalyticsSession = AnalyticsSession;
|
|
|
251
211
|
//
|
|
252
212
|
// DATA ABSTRACTION LAYER
|
|
253
213
|
//
|
|
254
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
255
|
-
if (mod && mod.__esModule) return mod;
|
|
256
|
-
var result = {};
|
|
257
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
258
|
-
result["default"] = mod;
|
|
259
|
-
return result;
|
|
260
|
-
};
|
|
261
214
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
262
|
-
const U =
|
|
263
|
-
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
215
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
264
216
|
const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
|
|
265
217
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
266
218
|
const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
|
|
@@ -299,6 +251,23 @@ var DistrictField;
|
|
|
299
251
|
DistrictField[DistrictField["NativePct"] = 27] = "NativePct"; // Display
|
|
300
252
|
// 1 - MORE ...
|
|
301
253
|
})(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
|
+
];
|
|
302
271
|
class Districts {
|
|
303
272
|
constructor(s, ds) {
|
|
304
273
|
this._geoProperties = {};
|
|
@@ -307,13 +276,7 @@ class Districts {
|
|
|
307
276
|
this.statistics = this.initStatistics();
|
|
308
277
|
}
|
|
309
278
|
getShape(i) { return this._shapes.features[i]; }
|
|
310
|
-
getGeoProperties(i) {
|
|
311
|
-
// Make sure the district shape exists & has geo properties
|
|
312
|
-
if (i in this._geoProperties)
|
|
313
|
-
return this._geoProperties[i];
|
|
314
|
-
else
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
279
|
+
getGeoProperties(i) { return this._geoProperties[i]; }
|
|
317
280
|
setGeoProperties(i, p) { this._geoProperties[i] = p; }
|
|
318
281
|
numberOfColumns() { return U.countEnumValues(DistrictField); }
|
|
319
282
|
// +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
|
|
@@ -336,7 +299,7 @@ class Districts {
|
|
|
336
299
|
// TODO - Optimize for getting multiple properties from the same feature?
|
|
337
300
|
// TODO - Optimize by only re-calc'ing districts that have changed?
|
|
338
301
|
// In this case, special attention to getting county-splits right.
|
|
339
|
-
// TODO - Optimize by
|
|
302
|
+
// TODO - Optimize by asyn'ing this?
|
|
340
303
|
// TODO - Is there a way to do this programmatically off data? Does it matter?
|
|
341
304
|
recalcStatistics(bLog = false) {
|
|
342
305
|
// Compute these once per recalc cycle
|
|
@@ -345,9 +308,6 @@ class Districts {
|
|
|
345
308
|
let planByDistrict = this._session.plan.byDistrictID();
|
|
346
309
|
let plan = this._session.plan;
|
|
347
310
|
let graph = this._session.graph;
|
|
348
|
-
// TODO - SPLITTING
|
|
349
|
-
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
350
|
-
let nCountyBuckets = this._session.counties.nCounties + 1;
|
|
351
311
|
// INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
|
|
352
312
|
let stateTPVote = 0;
|
|
353
313
|
let stateDemVote = 0;
|
|
@@ -363,7 +323,7 @@ class Districts {
|
|
|
363
323
|
// NOTE - These plan-level booleans are set in their respective analytics:
|
|
364
324
|
// - Equal population (bEqualPop)
|
|
365
325
|
// - Complete (bNotEmpty)
|
|
366
|
-
// -
|
|
326
|
+
// - Contiguos (bContiguous)
|
|
367
327
|
// - Free of holes (bNotEmbedded)
|
|
368
328
|
// 2 - MORE ...
|
|
369
329
|
// Loop over the districts (including the dummy unassigned one)
|
|
@@ -371,8 +331,7 @@ class Districts {
|
|
|
371
331
|
// INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
|
|
372
332
|
let featurePop;
|
|
373
333
|
let totalPop = 0;
|
|
374
|
-
|
|
375
|
-
let countySplits = U.initArray(nCountyBuckets, 0);
|
|
334
|
+
let countySplits = U.initArray(this._session.counties.nCounties, 0);
|
|
376
335
|
let demVotes = 0;
|
|
377
336
|
let repVotes = 0;
|
|
378
337
|
let totalVAP = 0;
|
|
@@ -382,178 +341,156 @@ class Districts {
|
|
|
382
341
|
let pacificPop = 0;
|
|
383
342
|
let asianPop = 0;
|
|
384
343
|
let nativePop = 0;
|
|
385
|
-
// NOTE - Only report explicitly found validity issues
|
|
386
|
-
let bNotEmpty = false;
|
|
387
|
-
let bContiguous = true;
|
|
388
|
-
let bNotEmbedded = true;
|
|
389
|
-
let bEqualPop = true;
|
|
390
344
|
// 3 - MORE ...
|
|
391
345
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
392
346
|
let outerThis = this;
|
|
393
|
-
// Get the geoIDs assigned to
|
|
394
|
-
// Guard against empty districts
|
|
347
|
+
// Get the geoIDs assigned to it ...
|
|
395
348
|
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
349
|
+
// ... loop over them creating district-by-district statistics
|
|
350
|
+
geoIDs.forEach(function (geoID) {
|
|
351
|
+
// Map from geoID to feature index
|
|
352
|
+
let featureID = outerThis._session.features.featureID(geoID);
|
|
353
|
+
let f = outerThis._session.features.featureByIndex(featureID);
|
|
354
|
+
// ACCUMULATE VALUES
|
|
355
|
+
// Total population of each feature (used more than once)
|
|
356
|
+
featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
357
|
+
// Total district population
|
|
358
|
+
totalPop += featurePop;
|
|
359
|
+
// Total population by counties w/in a district
|
|
360
|
+
countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
|
|
361
|
+
// Democratic and Republican vote totals
|
|
362
|
+
demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
|
|
363
|
+
repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
|
|
364
|
+
// Voting-age demographic breakdowns (or citizen voting-age)
|
|
365
|
+
// Guard againt null/NaN values
|
|
366
|
+
let _totalVAP = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
|
|
367
|
+
let _whitePop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
|
|
368
|
+
let _blackPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
|
|
369
|
+
let _hispanicPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
|
|
370
|
+
let _pacificPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
|
|
371
|
+
let _asianPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
|
|
372
|
+
let _nativePop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
|
|
373
|
+
if (_totalVAP)
|
|
374
|
+
totalVAP += _totalVAP;
|
|
375
|
+
if (_whitePop)
|
|
376
|
+
whitePop += _whitePop;
|
|
377
|
+
if (_blackPop)
|
|
378
|
+
blackPop += _blackPop;
|
|
379
|
+
if (_hispanicPop)
|
|
380
|
+
hispanicPop += _hispanicPop;
|
|
381
|
+
if (_pacificPop)
|
|
382
|
+
pacificPop += _pacificPop;
|
|
383
|
+
if (_asianPop)
|
|
384
|
+
asianPop += _asianPop;
|
|
385
|
+
if (_nativePop)
|
|
386
|
+
nativePop += _nativePop;
|
|
387
|
+
// TODO - DELETE
|
|
388
|
+
// totalVAP += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.TotalPop);
|
|
389
|
+
// whitePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.WhitePop);
|
|
390
|
+
// blackPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.BlackPop);
|
|
391
|
+
// hispanicPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.HispanicPop);
|
|
392
|
+
// pacificPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.PacificPop);
|
|
393
|
+
// asianPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.AsianPop);
|
|
394
|
+
// nativePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.NativePop);
|
|
395
|
+
// 4 - MORE ...
|
|
396
|
+
});
|
|
397
|
+
// COMPUTE DERIVED VALUES
|
|
398
|
+
// Population deviation % and equal population (boolean) by district.
|
|
399
|
+
// Leave the values null for the dummy unassigned district.
|
|
400
|
+
let popDevPct = null;
|
|
401
|
+
let bEqualPop = null;
|
|
402
|
+
if (i > 0) {
|
|
403
|
+
popDevPct = (totalPop - targetSize) / targetSize;
|
|
404
|
+
bEqualPop = (popDevPct <= deviationThreshold);
|
|
405
|
+
}
|
|
406
|
+
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
407
|
+
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
408
|
+
let totVotes;
|
|
409
|
+
let demPct = 0;
|
|
410
|
+
let repPct = 0;
|
|
411
|
+
let DemSeat = 0;
|
|
412
|
+
totVotes = demVotes + repVotes;
|
|
413
|
+
if (totVotes > 0) {
|
|
414
|
+
demPct = demVotes / totVotes;
|
|
415
|
+
repPct = repVotes / totVotes;
|
|
416
|
+
DemSeat = political_1.fptpWin(demPct);
|
|
417
|
+
}
|
|
418
|
+
// Total minority VAP
|
|
419
|
+
let minorityPop = totalVAP - whitePop;
|
|
420
|
+
// Voting-age demographic proportions (or citizen voting-age)
|
|
421
|
+
let whitePct = 0;
|
|
422
|
+
let minorityPct = 0;
|
|
423
|
+
let blackPct = 0;
|
|
424
|
+
let hispanicPct = 0;
|
|
425
|
+
let pacificPct = 0;
|
|
426
|
+
let asianPct = 0;
|
|
427
|
+
let nativePct = 0;
|
|
428
|
+
if (totalVAP > 0) {
|
|
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) {
|
|
476
447
|
bContiguous = valid_1.isConnected(geoIDs, graph);
|
|
477
448
|
bNotEmbedded = (!valid_1.isEmbedded(i, planByDistrict[i], plan, graph));
|
|
478
449
|
}
|
|
479
|
-
// 6 - MORE ...
|
|
480
|
-
{ // UPDATE THE DISTRICT STATISTICS
|
|
481
|
-
// NOTE - These are set below for both non-empty & empty districts:
|
|
482
|
-
// this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
483
|
-
// this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
484
|
-
// this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
485
|
-
// this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
486
|
-
// this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
487
|
-
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
488
|
-
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
489
|
-
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
490
|
-
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
491
|
-
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
492
|
-
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
493
|
-
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
494
|
-
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
495
|
-
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
496
|
-
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
497
|
-
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
498
|
-
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
499
|
-
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
500
|
-
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
501
|
-
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
502
|
-
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
503
|
-
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
504
|
-
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
505
|
-
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
506
|
-
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
507
|
-
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
508
|
-
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
509
|
-
}
|
|
510
|
-
// 7 - MORE ...
|
|
511
|
-
{ // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
512
|
-
stateTPVote += totVotes;
|
|
513
|
-
stateDemVote += demVotes;
|
|
514
|
-
stateRepVote += repVotes;
|
|
515
|
-
stateVAPPop += totalVAP;
|
|
516
|
-
stateWhitePop += whitePop;
|
|
517
|
-
stateMinorityPop += minorityPop;
|
|
518
|
-
stateBlackPop += blackPop;
|
|
519
|
-
stateHispanicPop += hispanicPop;
|
|
520
|
-
statePacificPop += pacificPop;
|
|
521
|
-
stateAsianPop += asianPop;
|
|
522
|
-
stateNativePop += nativePop;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
else { // If a district is empty, zero these results (vs. null)
|
|
526
|
-
this.statistics[DistrictField.PopDevPct][i] = 0.0;
|
|
527
|
-
this.statistics[DistrictField.DemVotes][i] = 0;
|
|
528
|
-
this.statistics[DistrictField.RepVotes][i] = 0;
|
|
529
|
-
this.statistics[DistrictField.TwoPartyVote][i] = 0;
|
|
530
|
-
this.statistics[DistrictField.DemPct][i] = 0;
|
|
531
|
-
this.statistics[DistrictField.RepPct][i] = 0;
|
|
532
|
-
this.statistics[DistrictField.DemSeat][i] = 0;
|
|
533
|
-
this.statistics[DistrictField.WhitePop][i] = 0;
|
|
534
|
-
this.statistics[DistrictField.MinorityPop][i] = 0;
|
|
535
|
-
this.statistics[DistrictField.BlackPop][i] = 0;
|
|
536
|
-
this.statistics[DistrictField.HispanicPop][i] = 0;
|
|
537
|
-
this.statistics[DistrictField.PacificPop][i] = 0;
|
|
538
|
-
this.statistics[DistrictField.AsianPop][i] = 0;
|
|
539
|
-
this.statistics[DistrictField.NativePop][i] = 0;
|
|
540
|
-
this.statistics[DistrictField.TotalVAP][i] = 0;
|
|
541
|
-
this.statistics[DistrictField.WhitePct][i] = 0;
|
|
542
|
-
this.statistics[DistrictField.MinorityPct][i] = 0;
|
|
543
|
-
this.statistics[DistrictField.BlackPct][i] = 0;
|
|
544
|
-
this.statistics[DistrictField.HispanicPct][i] = 0;
|
|
545
|
-
this.statistics[DistrictField.PacificPct][i] = 0;
|
|
546
|
-
this.statistics[DistrictField.AsianPct][i] = 0;
|
|
547
|
-
this.statistics[DistrictField.NativePct][i] = 0;
|
|
548
|
-
}
|
|
549
|
-
{ // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
|
|
550
|
-
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
551
|
-
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
552
|
-
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
553
|
-
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
554
|
-
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
555
|
-
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
556
450
|
}
|
|
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;
|
|
557
494
|
}
|
|
558
495
|
// UPDATE STATE STATISTICS
|
|
559
496
|
let summaryRow = this.numberOfRows() - 1;
|
|
@@ -587,6 +524,16 @@ class Districts {
|
|
|
587
524
|
}
|
|
588
525
|
}
|
|
589
526
|
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
|
+
};
|
|
590
537
|
// Wrap data by feature, to abstract the specifics of the internal structure
|
|
591
538
|
class Features {
|
|
592
539
|
constructor(s, data, keys) {
|
|
@@ -597,18 +544,15 @@ class Features {
|
|
|
597
544
|
}
|
|
598
545
|
nFeatures() { return this._data.features.length; }
|
|
599
546
|
featureByIndex(i) { return this._data.features[i]; }
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
let value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
|
|
603
|
-
return value;
|
|
604
|
-
}
|
|
547
|
+
// TODO - Generalize this
|
|
548
|
+
geoIDForFeature(f) { return f.properties['GEOID10']; }
|
|
605
549
|
fieldForFeature(f, dt, fk) {
|
|
606
550
|
let dk = this._keys[dt];
|
|
607
551
|
return _getFeatures(f, dk, fk);
|
|
608
552
|
}
|
|
609
553
|
resetDataset(d, k) {
|
|
610
554
|
this._keys[d] = k;
|
|
611
|
-
// TODO -
|
|
555
|
+
// TODO - Does anything need to be recalc'd now when a dataset is changed?
|
|
612
556
|
}
|
|
613
557
|
mapGeoIDsToFeatureIDs() {
|
|
614
558
|
for (let i = 0; i < this._session.features.nFeatures(); i++) {
|
|
@@ -642,15 +586,12 @@ function _getFeatures(f, datasetKey, p) {
|
|
|
642
586
|
return o[p];
|
|
643
587
|
}
|
|
644
588
|
else {
|
|
645
|
-
if (o['datasets'] && o['datasets'][datasetKey])
|
|
646
|
-
|
|
647
|
-
if ((!(v == null)) && (!(v == undefined)))
|
|
589
|
+
if (o['datasets'] && o['datasets'][datasetKey])
|
|
590
|
+
if (o['datasets'][datasetKey][p])
|
|
648
591
|
return o['datasets'][datasetKey][p];
|
|
649
|
-
}
|
|
650
592
|
}
|
|
651
593
|
}
|
|
652
594
|
}
|
|
653
|
-
console.log(`${p} value undefined for ${f.properties['GEOID10']}!`);
|
|
654
595
|
return undefined;
|
|
655
596
|
}
|
|
656
597
|
function _fGetJoined(f) {
|
|
@@ -659,13 +600,13 @@ function _fGetJoined(f) {
|
|
|
659
600
|
// Wrap data by county, to abstract the specifics of the internal structure
|
|
660
601
|
class Counties {
|
|
661
602
|
constructor(s, data) {
|
|
662
|
-
this._countyNameLookup = {};
|
|
663
603
|
this.index = {};
|
|
664
|
-
this.totalPopulation = [];
|
|
665
604
|
this._session = s;
|
|
666
605
|
this._data = data;
|
|
667
606
|
this.nCounties = this._data.features.length;
|
|
607
|
+
this._countyNameLookup = {};
|
|
668
608
|
}
|
|
609
|
+
// nCounties(): number { return this._data.features.length; }
|
|
669
610
|
countyByIndex(i) { return this._data.features[i]; }
|
|
670
611
|
propertyForCounty(f, pk) { return f.properties[pk]; }
|
|
671
612
|
mapFIPSToName(fips, name) { this._countyNameLookup[fips] = name; }
|
|
@@ -694,24 +635,8 @@ class Plan {
|
|
|
694
635
|
this._planByDistrictID = {};
|
|
695
636
|
this.districtIDs = []; // Set when the plan in inverted
|
|
696
637
|
}
|
|
697
|
-
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
698
|
-
// for contiguity. Just skip them in aggregating district statistics.
|
|
699
|
-
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
700
|
-
// let newPlan = {} as T.PlanByGeoID;
|
|
701
|
-
// for (let geoID in plan) {
|
|
702
|
-
// // Remove water-only features
|
|
703
|
-
// if (!(U.isWaterOnly(geoID))) {
|
|
704
|
-
// newPlan[geoID] = plan[geoID];
|
|
705
|
-
// }
|
|
706
|
-
// else {
|
|
707
|
-
// console.log("Removing water-only feature", geoID);
|
|
708
|
-
// }
|
|
709
|
-
// }
|
|
710
|
-
// return newPlan;
|
|
711
|
-
// }
|
|
712
638
|
invertPlan() {
|
|
713
|
-
|
|
714
|
-
this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
|
|
639
|
+
this._planByDistrictID = U.invertPlan(this._planByGeoID);
|
|
715
640
|
this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
|
|
716
641
|
}
|
|
717
642
|
initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
|
|
@@ -721,49 +646,12 @@ class Plan {
|
|
|
721
646
|
geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
|
|
722
647
|
}
|
|
723
648
|
exports.Plan = Plan;
|
|
724
|
-
// Invert a feature assignment structure to sets of ids by district
|
|
725
|
-
function invertPlan(plan, s) {
|
|
726
|
-
let invertedPlan = {};
|
|
727
|
-
// Add a dummy 'unassigned' district
|
|
728
|
-
invertedPlan[S.NOT_ASSIGNED] = new Set();
|
|
729
|
-
// TODO - UNASSIGNED
|
|
730
|
-
// NOTE - The feature assignments coming from DRA do not include unassigned ones.
|
|
731
|
-
// - In the DRA-calling context, there's an analytics session with a reference
|
|
732
|
-
// to the features. Loop over all the features to find the unassigned ones,
|
|
733
|
-
// and add them to the dummy unassigned district explicitly.
|
|
734
|
-
// - In the CLI-calling context, there's no session (yet) but the plan is complete.
|
|
735
|
-
if (!(s == undefined)) {
|
|
736
|
-
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
737
|
-
let f = s.features.featureByIndex(i);
|
|
738
|
-
let geoID = s.features.geoIDForFeature(f);
|
|
739
|
-
// If the feature is NOT explicitly assigned to a district, add the geoID
|
|
740
|
-
// to the dummy unassigned district 0.
|
|
741
|
-
if (!(U.keyExists(geoID, plan)))
|
|
742
|
-
invertedPlan[S.NOT_ASSIGNED].add(geoID);
|
|
743
|
-
// TODO - WATER-ONLY: NOT skipping water-only features here, because we're
|
|
744
|
-
// not skipping them below when they are explicitly assigned in plans. Should
|
|
745
|
-
// we skip them in both places?
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
for (let geoID in plan) {
|
|
749
|
-
let districtID = plan[geoID];
|
|
750
|
-
// Make sure the set for the districtID exists
|
|
751
|
-
if (!(U.objectContains(invertedPlan, districtID))) {
|
|
752
|
-
invertedPlan[districtID] = new Set();
|
|
753
|
-
}
|
|
754
|
-
// Add the geoID to the districtID's set
|
|
755
|
-
invertedPlan[districtID].add(geoID);
|
|
756
|
-
if (U.isWaterOnly(geoID))
|
|
757
|
-
console.log("Water-only feature still in plan!", geoID);
|
|
758
|
-
}
|
|
759
|
-
return invertedPlan;
|
|
760
|
-
}
|
|
761
|
-
exports.invertPlan = invertPlan;
|
|
762
649
|
class Graph {
|
|
763
650
|
constructor(s, graph) {
|
|
764
651
|
this._session = s;
|
|
765
652
|
this._graph = graph;
|
|
766
653
|
}
|
|
654
|
+
// TODO - Rework this, when we support MIXED MAPS.
|
|
767
655
|
peerNeighbors(node) {
|
|
768
656
|
// Get the neighboring geoIDs connected to a geoID
|
|
769
657
|
// Ignore the lengths of the shared borders (the values), for now
|
|
@@ -800,20 +688,19 @@ function doAnalyzeDistricts(s, bLog = false) {
|
|
|
800
688
|
s.districts.extractDistrictShapeProperties(bLog);
|
|
801
689
|
}
|
|
802
690
|
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.
|
|
803
693
|
// Calculate the analytics & validations and cache the results
|
|
804
694
|
// NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
|
|
805
|
-
// NOTE - I could make this table-driven, but I'm thinking that the explicit
|
|
806
|
-
// calls might make chunking for aync easier.
|
|
807
695
|
function doAnalyzePlan(s, bLog = false) {
|
|
808
|
-
// TODO - Remove this mechanism. Always run all tests
|
|
809
696
|
// Get the requested suites, and only execute those tests
|
|
810
697
|
let requestedSuites = s.config['suites'];
|
|
811
698
|
// Tests in the "Legal" suite, i.e., pass/ fail constraints
|
|
812
699
|
if (requestedSuites.includes(0 /* Legal */)) {
|
|
813
|
-
s.tests[0 /* Complete */] = valid_1.doIsComplete(s
|
|
814
|
-
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s
|
|
815
|
-
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s
|
|
816
|
-
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s
|
|
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);
|
|
817
704
|
// NOTE - I can't check whether a population deviation is legal or not, until
|
|
818
705
|
// the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
|
|
819
706
|
// the given type of district (CD vs. LD). The EqualPopulation test is derived
|
|
@@ -823,21 +710,19 @@ function doAnalyzePlan(s, bLog = false) {
|
|
|
823
710
|
}
|
|
824
711
|
// Tests in the "Fair" suite
|
|
825
712
|
if (requestedSuites.includes(1 /* Fair */)) {
|
|
826
|
-
s.tests[
|
|
827
|
-
s.tests[
|
|
828
|
-
s.tests[
|
|
829
|
-
s.tests[
|
|
830
|
-
s.tests[
|
|
831
|
-
s.tests[
|
|
713
|
+
s.tests[9 /* SeatsBias */] = political_1.doSeatsBias(s);
|
|
714
|
+
s.tests[10 /* VotesBias */] = political_1.doVotesBias(s);
|
|
715
|
+
s.tests[11 /* Responsiveness */] = political_1.doResponsiveness(s);
|
|
716
|
+
s.tests[12 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s);
|
|
717
|
+
s.tests[13 /* EfficiencyGap */] = political_1.doEfficiencyGap(s);
|
|
718
|
+
s.tests[14 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s);
|
|
832
719
|
}
|
|
833
720
|
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
834
721
|
if (requestedSuites.includes(2 /* Best */)) {
|
|
835
722
|
s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
|
|
836
723
|
s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
|
|
837
|
-
s.tests[7 /*
|
|
838
|
-
s.tests[
|
|
839
|
-
s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
|
|
840
|
-
s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
|
|
724
|
+
s.tests[7 /* CountySplits */] = cohesive_1.doCountySplits(s);
|
|
725
|
+
s.tests[8 /* Complexity */] = cohesive_1.doPlanComplexity(s);
|
|
841
726
|
}
|
|
842
727
|
// Enable a Test Log and Scorecard to be generated
|
|
843
728
|
s.bPlanAnalyzed = true;
|
|
@@ -850,8 +735,8 @@ exports.doAnalyzePlan = doAnalyzePlan;
|
|
|
850
735
|
//
|
|
851
736
|
// NOTE - Should this be conditionalized on the test suites requested?
|
|
852
737
|
// Those are encapsulated in reports.ts right now, so not doing that.
|
|
853
|
-
function doDeriveSecondaryTests(s
|
|
854
|
-
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s
|
|
738
|
+
function doDeriveSecondaryTests(s) {
|
|
739
|
+
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s);
|
|
855
740
|
}
|
|
856
741
|
exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
857
742
|
|
|
@@ -868,237 +753,19 @@ exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
|
868
753
|
"use strict";
|
|
869
754
|
|
|
870
755
|
//
|
|
871
|
-
//
|
|
756
|
+
// "COHESIVE" - We're naming this category which is about county splitting.
|
|
872
757
|
//
|
|
873
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
874
|
-
if (mod && mod.__esModule) return mod;
|
|
875
|
-
var result = {};
|
|
876
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
877
|
-
result["default"] = mod;
|
|
878
|
-
return result;
|
|
879
|
-
};
|
|
880
758
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
881
|
-
const U =
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
function doCountySplitting(s, bLog = false) {
|
|
886
|
-
let test = s.getTest(8 /* CountySplitting */);
|
|
887
|
-
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
888
|
-
let countyTotals = s.counties.totalPopulation;
|
|
889
|
-
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
890
|
-
let f = calcCountyFractions(CxD, countyTotals);
|
|
891
|
-
let w = calcCountyWeights(countyTotals);
|
|
892
|
-
let SqEnt_DC = countySplitting(f, w, bLog);
|
|
893
|
-
let CxDreducedC = U.deepCopy(CxD);
|
|
894
|
-
reduceCSplits(CxDreducedC, districtTotals);
|
|
895
|
-
let fReduced = calcCountyFractions(CxDreducedC, countyTotals);
|
|
896
|
-
let wReduced = calcCountyWeights(countyTotals);
|
|
897
|
-
let SqEnt_DCreduced = countySplitting(fReduced, wReduced, bLog);
|
|
898
|
-
test['score'] = SqEnt_DCreduced;
|
|
899
|
-
test['details']['SqEnt_DC'] = SqEnt_DC;
|
|
900
|
-
return test;
|
|
901
|
-
}
|
|
902
|
-
exports.doCountySplitting = doCountySplitting;
|
|
903
|
-
function doDistrictSplitting(s, bLog = false) {
|
|
904
|
-
let test = s.getTest(9 /* DistrictSplitting */);
|
|
905
|
-
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
906
|
-
let countyTotals = s.counties.totalPopulation;
|
|
907
|
-
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
908
|
-
let g = calcDistrictFractions(CxD, districtTotals);
|
|
909
|
-
let x = calcDistrictWeights(districtTotals);
|
|
910
|
-
let SqEnt_CD = districtSplitting(g, x, bLog);
|
|
911
|
-
let CxDreducedD = U.deepCopy(CxD);
|
|
912
|
-
reduceDSplits(CxDreducedD, countyTotals);
|
|
913
|
-
let gReduced = calcDistrictFractions(CxDreducedD, districtTotals);
|
|
914
|
-
let xReduced = calcDistrictWeights(districtTotals);
|
|
915
|
-
let SqEnt_CDreduced = districtSplitting(gReduced, xReduced, bLog);
|
|
916
|
-
test['score'] = SqEnt_CDreduced;
|
|
917
|
-
test['details']['SqEnt_CD'] = SqEnt_CD;
|
|
918
|
-
return test;
|
|
919
|
-
}
|
|
920
|
-
exports.doDistrictSplitting = doDistrictSplitting;
|
|
921
|
-
// HELPERS
|
|
922
|
-
// Loop over all the county-district combos, skipping the virtual district 0
|
|
923
|
-
// and virtual county 0.
|
|
924
|
-
//
|
|
925
|
-
// NOTE - The county-district splits and the county & district totals may all,
|
|
926
|
-
// in general, be fractional/decimal numbers as opposed to integers, due to
|
|
927
|
-
// dissaggregation & re-aggregation. Hence, comparisons need to approximate
|
|
928
|
-
// equality.
|
|
929
|
-
// Consolidate districts (rows) consisting of just one county (column)
|
|
930
|
-
// UP into the dummy district (0).
|
|
931
|
-
function reduceCSplits(CxDreducedC, districtTotals) {
|
|
932
|
-
let nD = CxDreducedC.length;
|
|
933
|
-
let nC = CxDreducedC[0].length;
|
|
934
|
-
for (let j = 1; j < nC; j++) {
|
|
935
|
-
for (let i = 1; i < nD; i++) {
|
|
936
|
-
let split_total = CxDreducedC[i][j];
|
|
937
|
-
if (split_total > 0) {
|
|
938
|
-
if (areRoughlyEqual(split_total, districtTotals[i])) {
|
|
939
|
-
CxDreducedC[0][j] += split_total;
|
|
940
|
-
CxDreducedC[i][j] = 0;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
// Consolidate whole counties (columns) in a district (row) LEFT into the
|
|
947
|
-
// dummy county (0).
|
|
948
|
-
function reduceDSplits(CxDreducedD, countyTotals) {
|
|
949
|
-
let nD = CxDreducedD.length;
|
|
950
|
-
let nC = CxDreducedD[0].length;
|
|
951
|
-
for (let i = 1; i < nD; i++) {
|
|
952
|
-
for (let j = 1; j < nC; j++) {
|
|
953
|
-
let split_total = CxDreducedD[i][j];
|
|
954
|
-
if (split_total > 0) {
|
|
955
|
-
if (areRoughlyEqual(split_total, countyTotals[j])) {
|
|
956
|
-
CxDreducedD[i][0] += split_total;
|
|
957
|
-
CxDreducedD[i][j] = 0;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
function calcCountyWeights(countyTotals) {
|
|
964
|
-
let nC = countyTotals.length;
|
|
965
|
-
let cTotal = U.sumArray(countyTotals);
|
|
966
|
-
let w = U.initArray(nC, 0.0);
|
|
967
|
-
for (let j = 0; j < nC; j++) {
|
|
968
|
-
w[j] = countyTotals[j] / cTotal;
|
|
969
|
-
}
|
|
970
|
-
return w;
|
|
971
|
-
}
|
|
972
|
-
function calcDistrictWeights(districtTotals) {
|
|
973
|
-
let nD = districtTotals.length;
|
|
974
|
-
let dTotal = U.sumArray(districtTotals);
|
|
975
|
-
let x = U.initArray(nD, 0.0);
|
|
976
|
-
for (let i = 0; i < nD; i++) {
|
|
977
|
-
x[i] = districtTotals[i] / dTotal;
|
|
978
|
-
}
|
|
979
|
-
return x;
|
|
980
|
-
}
|
|
981
|
-
function calcCountyFractions(CxDreducedD, countyTotals) {
|
|
982
|
-
let nD = CxDreducedD.length;
|
|
983
|
-
let nC = CxDreducedD[0].length;
|
|
984
|
-
let f = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
985
|
-
for (let j = 0; j < nC; j++) {
|
|
986
|
-
for (let i = 0; i < nD; i++) {
|
|
987
|
-
if (countyTotals[j] > 0) {
|
|
988
|
-
f[i][j] = CxDreducedD[i][j] / countyTotals[j];
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
f[i][j] = 0.0;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return f;
|
|
996
|
-
}
|
|
997
|
-
function calcDistrictFractions(CxDreducedC, districtTotals) {
|
|
998
|
-
let nD = CxDreducedC.length;
|
|
999
|
-
let nC = CxDreducedC[0].length;
|
|
1000
|
-
let g = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
1001
|
-
for (let j = 0; j < nC; j++) {
|
|
1002
|
-
for (let i = 0; i < nD; i++) {
|
|
1003
|
-
if (districtTotals[i] > 0) {
|
|
1004
|
-
g[i][j] = CxDreducedC[i][j] / districtTotals[i];
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
g[i][j] = 0.0;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
return g;
|
|
1012
|
-
}
|
|
1013
|
-
// Deal with decimal census "counts" due to disagg/re-agg
|
|
1014
|
-
function areRoughlyEqual(x, y) {
|
|
1015
|
-
let delta = Math.abs(x - y);
|
|
1016
|
-
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
1017
|
-
return result;
|
|
1018
|
-
}
|
|
1019
|
-
// For all districts in a county, sum the split score.
|
|
1020
|
-
function countySplitScore(j, f, numD, bLog = false) {
|
|
1021
|
-
let e = 0.0;
|
|
1022
|
-
for (let i = 0; i < numD; i++) {
|
|
1023
|
-
e += Math.sqrt(f[i][j]);
|
|
1024
|
-
}
|
|
1025
|
-
return e;
|
|
1026
|
-
}
|
|
1027
|
-
// For all counties, sum the weighted county splits.
|
|
1028
|
-
function countySplitting(f, w, bLog = false) {
|
|
1029
|
-
let numC = f[0].length;
|
|
1030
|
-
let numD = f.length;
|
|
1031
|
-
let e = 0.0;
|
|
1032
|
-
for (let j = 0; j < numC; j++) {
|
|
1033
|
-
let splitScore = countySplitScore(j, f, numD, bLog);
|
|
1034
|
-
e += w[j] * splitScore;
|
|
1035
|
-
if (bLog)
|
|
1036
|
-
console.log("County splitting =", j, w[j], splitScore, e);
|
|
1037
|
-
}
|
|
1038
|
-
return U.trim(e, 3);
|
|
1039
|
-
}
|
|
1040
|
-
// For all counties in a district, sum the split score.
|
|
1041
|
-
function districtSplitScore(i, g, numC, bLog = false) {
|
|
1042
|
-
let e = 0.0;
|
|
1043
|
-
for (let j = 0; j < numC; j++) {
|
|
1044
|
-
e += Math.sqrt(g[i][j]);
|
|
1045
|
-
}
|
|
1046
|
-
return e;
|
|
1047
|
-
}
|
|
1048
|
-
// For all districts, sum the weighted district splits.
|
|
1049
|
-
function districtSplitting(g, x, bLog = false) {
|
|
1050
|
-
let numC = g[0].length;
|
|
1051
|
-
let numD = g.length;
|
|
1052
|
-
let e = 0.0;
|
|
1053
|
-
for (let i = 0; i < numD; i++) {
|
|
1054
|
-
let splitScore = districtSplitScore(i, g, numC, bLog);
|
|
1055
|
-
e += x[i] * splitScore;
|
|
1056
|
-
if (bLog)
|
|
1057
|
-
console.log("District split score =", i, x[i], splitScore, e);
|
|
1058
|
-
}
|
|
1059
|
-
return U.trim(e, 3);
|
|
1060
|
-
}
|
|
1061
|
-
// ANALYZE SIMPLE COUNTY & VTD SPLITTING
|
|
1062
|
-
/*
|
|
1063
|
-
|
|
1064
|
-
Sample results for NC 2016 dongressional plan
|
|
1065
|
-
________________________________________________________________________________
|
|
1066
|
-
|
|
1067
|
-
State: NC
|
|
1068
|
-
Census: 2010
|
|
1069
|
-
Total population: 9,535,483
|
|
1070
|
-
Number of districts: 13
|
|
1071
|
-
Target district size: 733,499
|
|
1072
|
-
Number of counties: 100
|
|
1073
|
-
|
|
1074
|
-
Equal Population: 11.24% deviation
|
|
1075
|
-
Compactness: None
|
|
1076
|
-
Proportionality: 27.67% gap
|
|
1077
|
-
Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
|
|
1078
|
-
|
|
1079
|
-
These counties are split unexpectedly:
|
|
1080
|
-
|
|
1081
|
-
• Bladen
|
|
1082
|
-
• Buncombe
|
|
1083
|
-
• Catawba
|
|
1084
|
-
• Cumberland
|
|
1085
|
-
• Durham
|
|
1086
|
-
• Guilford
|
|
1087
|
-
• Iredell
|
|
1088
|
-
• Johnston
|
|
1089
|
-
• Pitt
|
|
1090
|
-
• Rowan
|
|
1091
|
-
• Wilson
|
|
1092
|
-
|
|
1093
|
-
*/
|
|
1094
|
-
function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
1095
|
-
let test = s.getTest(7 /* UnexpectedCountySplits */);
|
|
759
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
760
|
+
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
761
|
+
function doCountySplits(s) {
|
|
762
|
+
let test = s.getTest(7 /* CountySplits */);
|
|
1096
763
|
// THE THREE VALUES TO DETERMINE FOR A PLAN
|
|
1097
764
|
let unexpectedSplits = 0;
|
|
1098
765
|
let unexpectedAffected = 0;
|
|
1099
766
|
let countiesSplitUnexpectedly = [];
|
|
1100
|
-
// FIRST, ANALYZE THE COUNTY
|
|
1101
|
-
//
|
|
767
|
+
// FIRST, ANALYZE THE COUNTY SPLITING FOR THE PLAN
|
|
768
|
+
// Pivot census totals into county-district "splits"
|
|
1102
769
|
let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
|
|
1103
770
|
// countiesByDistrict = countiesByDistrict.slice(1, -1);
|
|
1104
771
|
// Find the single-county districts, i.e., districts NOT split across counties.
|
|
@@ -1108,13 +775,10 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
|
1108
775
|
// See if there's only one county partition
|
|
1109
776
|
let nCountiesInDistrict = 0;
|
|
1110
777
|
for (let c = 0; c < s.counties.nCounties; c++) {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
1114
|
-
|
|
1115
|
-
if (nCountiesInDistrict > 1) {
|
|
1116
|
-
break;
|
|
1117
|
-
}
|
|
778
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
779
|
+
nCountiesInDistrict += 1;
|
|
780
|
+
if (nCountiesInDistrict > 1) {
|
|
781
|
+
break;
|
|
1118
782
|
}
|
|
1119
783
|
}
|
|
1120
784
|
}
|
|
@@ -1134,14 +798,11 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
|
1134
798
|
let nCountyParts = 0;
|
|
1135
799
|
let subtotal = 0;
|
|
1136
800
|
for (let d = 1; d <= s.state.nDistricts; d++) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (!(U.arrayContains(singleCountyDistricts, d))) {
|
|
1143
|
-
subtotal += countiesByDistrict[d][c];
|
|
1144
|
-
}
|
|
801
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
802
|
+
nPartitionsOverall += 1;
|
|
803
|
+
nCountyParts += 1;
|
|
804
|
+
if (!(U.arrayContains(singleCountyDistricts, d))) {
|
|
805
|
+
subtotal += countiesByDistrict[d][c];
|
|
1145
806
|
}
|
|
1146
807
|
}
|
|
1147
808
|
}
|
|
@@ -1179,21 +840,48 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
|
1179
840
|
countiesSplitUnexpectedly.push(s.counties.nameFromFIPS(fips));
|
|
1180
841
|
}
|
|
1181
842
|
countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
|
|
1182
|
-
|
|
1183
|
-
test['
|
|
1184
|
-
test['details']
|
|
843
|
+
// Cache the results in the test
|
|
844
|
+
test['score'] = unexpectedAffected; // TODO - Use Moon's complexity metric here
|
|
845
|
+
test['details'] = {
|
|
846
|
+
'unexpectedSplits': unexpectedSplits,
|
|
847
|
+
'unexpectedAffected': unexpectedAffected,
|
|
848
|
+
'countiesSplitUnexpectedly': countiesSplitUnexpectedly
|
|
849
|
+
};
|
|
1185
850
|
return test;
|
|
1186
851
|
}
|
|
1187
|
-
exports.
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
852
|
+
exports.doCountySplits = doCountySplits;
|
|
853
|
+
// 2 - THE COMPLEXITY ANALYTIC NEEDS THE FOLLOWING DATA:
|
|
854
|
+
//
|
|
855
|
+
// If a map is already in simplified (mixed) form, the complexity analytic needs
|
|
856
|
+
// two pieces of data:
|
|
857
|
+
// - The counts of features by summary level--i.e., the numbers of counties, tracts,
|
|
858
|
+
// block groups, and blocks in a state; and
|
|
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 ...");
|
|
1194
882
|
return test;
|
|
1195
883
|
}
|
|
1196
|
-
exports.
|
|
884
|
+
exports.doPlanComplexity = doPlanComplexity;
|
|
1197
885
|
|
|
1198
886
|
|
|
1199
887
|
/***/ }),
|
|
@@ -1210,17 +898,9 @@ exports.doFindSplitVTDs = doFindSplitVTDs;
|
|
|
1210
898
|
//
|
|
1211
899
|
// COMPACT
|
|
1212
900
|
//
|
|
1213
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1214
|
-
if (mod && mod.__esModule) return mod;
|
|
1215
|
-
var result = {};
|
|
1216
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1217
|
-
result["default"] = mod;
|
|
1218
|
-
return result;
|
|
1219
|
-
};
|
|
1220
901
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1221
|
-
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
|
|
1222
902
|
const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
|
|
1223
|
-
const U =
|
|
903
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1224
904
|
// Measures of compactness compare district shapes to various ideally compact
|
|
1225
905
|
// benchmarks, such as circles. All else equal, more compact districts are better.
|
|
1226
906
|
//
|
|
@@ -1308,19 +988,16 @@ function doReock(s, bLog = false) {
|
|
|
1308
988
|
let scores = [];
|
|
1309
989
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
1310
990
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if (bLog)
|
|
1320
|
-
console.log("Reock for district", districtID, "=", reock);
|
|
1321
|
-
}
|
|
991
|
+
let a = districtProps[0 /* Area */];
|
|
992
|
+
let d = districtProps[1 /* Diameter */];
|
|
993
|
+
let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
|
|
994
|
+
// Save each district score
|
|
995
|
+
scores.push(reock);
|
|
996
|
+
// Echo the results by district
|
|
997
|
+
if (bLog)
|
|
998
|
+
console.log("Reock for district", districtID, "=", reock);
|
|
1322
999
|
}
|
|
1323
|
-
// Populate the test entry
|
|
1000
|
+
// Populate the test entry
|
|
1324
1001
|
let averageReock = U.avgArray(scores);
|
|
1325
1002
|
test['score'] = U.trim(averageReock);
|
|
1326
1003
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -1336,19 +1013,16 @@ function doPolsbyPopper(s, bLog = false) {
|
|
|
1336
1013
|
let scores = [];
|
|
1337
1014
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
1338
1015
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (bLog)
|
|
1348
|
-
console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
|
|
1349
|
-
}
|
|
1016
|
+
let a = districtProps[0 /* Area */];
|
|
1017
|
+
let p = districtProps[2 /* Perimeter */];
|
|
1018
|
+
let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
|
|
1019
|
+
// Save each district score
|
|
1020
|
+
scores.push(polsbyPopper);
|
|
1021
|
+
// Echo the results by district
|
|
1022
|
+
if (bLog)
|
|
1023
|
+
console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
|
|
1350
1024
|
}
|
|
1351
|
-
// Populate the test entry
|
|
1025
|
+
// Populate the test entry
|
|
1352
1026
|
let averagePolsbyPopper = U.avgArray(scores);
|
|
1353
1027
|
test['score'] = U.trim(averagePolsbyPopper);
|
|
1354
1028
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -1358,24 +1032,19 @@ exports.doPolsbyPopper = doPolsbyPopper;
|
|
|
1358
1032
|
// HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
|
|
1359
1033
|
function extractDistrictProperties(s, bLog = false) {
|
|
1360
1034
|
for (let i = 1; i <= s.state.nDistricts; i++) {
|
|
1361
|
-
let j = i - 1; // TODO -
|
|
1035
|
+
let j = i - 1; // TODO - Terry: How do you get away w/o this?!?
|
|
1362
1036
|
let poly = s.districts.getShape(j);
|
|
1363
|
-
//
|
|
1364
|
-
let
|
|
1365
|
-
let
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
props[2 /* Perimeter */] = perimeter;
|
|
1375
|
-
s.districts.setGeoProperties(i, props);
|
|
1376
|
-
if (bLog)
|
|
1377
|
-
console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
|
|
1378
|
-
}
|
|
1037
|
+
// TODO - Bundle these calls?
|
|
1038
|
+
let area = geofeature_1.gfArea(poly);
|
|
1039
|
+
let perimeter = geofeature_1.gfPerimeter(poly);
|
|
1040
|
+
let diameter = geofeature_1.gfDiameter(poly);
|
|
1041
|
+
let props = [0, 0, 0]; // TODO - Terry?!?
|
|
1042
|
+
props[0 /* Area */] = area;
|
|
1043
|
+
props[1 /* Diameter */] = diameter;
|
|
1044
|
+
props[2 /* Perimeter */] = perimeter;
|
|
1045
|
+
s.districts.setGeoProperties(i, props);
|
|
1046
|
+
if (bLog)
|
|
1047
|
+
console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
|
|
1379
1048
|
}
|
|
1380
1049
|
}
|
|
1381
1050
|
exports.extractDistrictProperties = extractDistrictProperties;
|
|
@@ -1423,17 +1092,10 @@ exports.extractDistrictProperties = extractDistrictProperties;
|
|
|
1423
1092
|
//
|
|
1424
1093
|
// EQUAL POPULATION
|
|
1425
1094
|
//
|
|
1426
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1427
|
-
if (mod && mod.__esModule) return mod;
|
|
1428
|
-
var result = {};
|
|
1429
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1430
|
-
result["default"] = mod;
|
|
1431
|
-
return result;
|
|
1432
|
-
};
|
|
1433
1095
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1434
|
-
const U =
|
|
1435
|
-
const D =
|
|
1436
|
-
function doPopulationDeviation(s
|
|
1096
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1097
|
+
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1098
|
+
function doPopulationDeviation(s) {
|
|
1437
1099
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
1438
1100
|
// Compute the min, max, and average district populations,
|
|
1439
1101
|
// excluding the dummy 'unassigned' 0 and N+1 summary "districts."
|
|
@@ -1462,7 +1124,7 @@ function doPopulationDeviation(s, bLog = false) {
|
|
|
1462
1124
|
exports.doPopulationDeviation = doPopulationDeviation;
|
|
1463
1125
|
// NOTE - This validity check is *derived* and depends on population deviation %
|
|
1464
1126
|
// being computed (above) and normalized in test log & scorecard generation.
|
|
1465
|
-
function doHasEqualPopulations(s
|
|
1127
|
+
function doHasEqualPopulations(s) {
|
|
1466
1128
|
let test = s.getTest(3 /* EqualPopulation */);
|
|
1467
1129
|
// Get the normalized population deviation %
|
|
1468
1130
|
let popDevTest = s.getTest(4 /* PopulationDeviation */);
|
|
@@ -1500,19 +1162,14 @@ exports.doHasEqualPopulations = doHasEqualPopulations;
|
|
|
1500
1162
|
//
|
|
1501
1163
|
// GEO-FEATURES UTILITIES
|
|
1502
1164
|
//
|
|
1503
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1504
|
-
if (mod && mod.__esModule) return mod;
|
|
1505
|
-
var result = {};
|
|
1506
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1507
|
-
result["default"] = mod;
|
|
1508
|
-
return result;
|
|
1509
|
-
};
|
|
1510
1165
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1511
|
-
const Poly =
|
|
1166
|
+
const Poly = __webpack_require__(/*! @dra2020/poly */ "@dra2020/poly");
|
|
1512
1167
|
// CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
|
|
1513
|
-
// TODO -
|
|
1168
|
+
// TODO - Terry: Confirm Cartesian calculations
|
|
1514
1169
|
function gfArea(poly) {
|
|
1515
1170
|
let area = _polygonArea(poly);
|
|
1171
|
+
// let polyOptions = { noLatitudeCorrection: false } DELETE
|
|
1172
|
+
// let area: number = Poly.polyArea(poly, polyOptions);
|
|
1516
1173
|
return area;
|
|
1517
1174
|
}
|
|
1518
1175
|
exports.gfArea = gfArea;
|
|
@@ -1558,16 +1215,19 @@ function _polygonArea(poly) {
|
|
|
1558
1215
|
}
|
|
1559
1216
|
return a;
|
|
1560
1217
|
}
|
|
1561
|
-
// TODO -
|
|
1218
|
+
// TODO - Terry: Confirm Cartesian calculations
|
|
1562
1219
|
// The perimeter calculation already just computes cartesian distance if you
|
|
1563
1220
|
// pass in the noLatitudeCorrection flag. You would need to divide by
|
|
1564
1221
|
// Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
|
|
1565
1222
|
function gfPerimeter(poly) {
|
|
1566
1223
|
let perimeter = _polygonPerimeter(poly);
|
|
1224
|
+
// let polyOptions = { noLatitudeCorrection: true } // Cartesian distance
|
|
1225
|
+
// let perimeter: number = Poly.polyPerimeter(poly, polyOptions); DELETE
|
|
1567
1226
|
return perimeter;
|
|
1227
|
+
// return perimeter / Poly.EARTH_RADIUS; DELETE
|
|
1568
1228
|
}
|
|
1569
1229
|
exports.gfPerimeter = gfPerimeter;
|
|
1570
|
-
// TODO -
|
|
1230
|
+
// TODO - Terry: Confirm Cartesian calculations
|
|
1571
1231
|
// Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
|
|
1572
1232
|
// NOTE: No conversion of degrees to radians!
|
|
1573
1233
|
function _polygonPerimeter(poly) {
|
|
@@ -1579,8 +1239,10 @@ function _polygonPerimeter(poly) {
|
|
|
1579
1239
|
let p = poly[i][0];
|
|
1580
1240
|
for (let j = 0; j < p.length - 1; j++)
|
|
1581
1241
|
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
|
|
1582
1243
|
if (p.length > 2 && (p[0][0] != p[p.length - 1][0] || p[0][1] != p[p.length - 1][1]))
|
|
1583
1244
|
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
|
|
1584
1246
|
}
|
|
1585
1247
|
return perimeter;
|
|
1586
1248
|
}
|
|
@@ -1591,7 +1253,7 @@ function _distance(x1, y1, x2, y2) {
|
|
|
1591
1253
|
d = Math.sqrt((dLat * dLat) + (dLon * dLon));
|
|
1592
1254
|
return d;
|
|
1593
1255
|
}
|
|
1594
|
-
// TODO -
|
|
1256
|
+
// TODO - Terry: Confirm Cartesian calculations
|
|
1595
1257
|
// As I mentioned, the polyCircle code was already just treating the coordinate
|
|
1596
1258
|
// system as Cartesian. I then did polyFromCircle to convert it to a polygon that
|
|
1597
1259
|
// then could be passed to polyArea in order to take into account the projection.
|
|
@@ -1600,6 +1262,9 @@ function _distance(x1, y1, x2, y2) {
|
|
|
1600
1262
|
function gfDiameter(poly) {
|
|
1601
1263
|
let polyOptions = { noLatitudeCorrection: true }; // NO-OP
|
|
1602
1264
|
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
|
|
1603
1268
|
let diameter = circle.r * 2;
|
|
1604
1269
|
return diameter;
|
|
1605
1270
|
}
|
|
@@ -1625,7 +1290,6 @@ function __export(m) {
|
|
|
1625
1290
|
}
|
|
1626
1291
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1627
1292
|
__export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
|
|
1628
|
-
__export(__webpack_require__(/*! ./results */ "./src/results.ts"));
|
|
1629
1293
|
__export(__webpack_require__(/*! ./types */ "./src/types.ts"));
|
|
1630
1294
|
|
|
1631
1295
|
|
|
@@ -1644,17 +1308,61 @@ __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
|
|
|
1644
1308
|
// PROTECTS MINORITIES
|
|
1645
1309
|
//
|
|
1646
1310
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1311
|
+
// TODO - This definition is wrong. Need to fix it.
|
|
1312
|
+
//
|
|
1313
|
+
// MINORITY-PROTECTION ANALYTICS NEED THE FOLLOWING DATA:
|
|
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 ...");
|
|
1651
1339
|
return test;
|
|
1652
1340
|
}
|
|
1653
1341
|
exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
//
|
|
1657
|
-
// -
|
|
1342
|
+
// SAVE THESE NOTES, IN CASE WE NEED TO REWORK HOW WE DO PERFORM THESE CALCS.
|
|
1343
|
+
// THEY REFLECT HOW I/ALEC DID THESE IN PYTHON.
|
|
1344
|
+
//
|
|
1345
|
+
// MINORITY-PROTECTION ANALYTICS WILL NEED THE FOLLOWING DATA,
|
|
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?
|
|
1658
1366
|
|
|
1659
1367
|
|
|
1660
1368
|
/***/ }),
|
|
@@ -1671,17 +1379,10 @@ exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
|
1671
1379
|
//
|
|
1672
1380
|
// FAIR/PROPORTIONAL
|
|
1673
1381
|
//
|
|
1674
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1675
|
-
if (mod && mod.__esModule) return mod;
|
|
1676
|
-
var result = {};
|
|
1677
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1678
|
-
result["default"] = mod;
|
|
1679
|
-
return result;
|
|
1680
|
-
};
|
|
1681
1382
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1682
1383
|
const assert_1 = __webpack_require__(/*! assert */ "assert");
|
|
1683
|
-
const U =
|
|
1684
|
-
const D =
|
|
1384
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1385
|
+
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1685
1386
|
// Partisan analytics need the following data:
|
|
1686
1387
|
//
|
|
1687
1388
|
// An "election model" by geo_id, where each item has 4 pieces of data:
|
|
@@ -1701,41 +1402,36 @@ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
|
1701
1402
|
// I'm labelling this general concept a "Voter Preference Index (VPI)," a
|
|
1702
1403
|
// conscious +1 letter play on Cook's "PVI" acronymn.
|
|
1703
1404
|
// MEASURING BIAS & RESPONSIVENESS (NAGLE'S METHOD)
|
|
1704
|
-
function doSeatsBias(s
|
|
1705
|
-
let test = s.getTest(
|
|
1706
|
-
|
|
1707
|
-
console.log("TODO - Calculating seats bias ...");
|
|
1405
|
+
function doSeatsBias(s) {
|
|
1406
|
+
let test = s.getTest(9 /* SeatsBias */);
|
|
1407
|
+
console.log("TODO - Calculating seats bias ...");
|
|
1708
1408
|
return test;
|
|
1709
1409
|
}
|
|
1710
1410
|
exports.doSeatsBias = doSeatsBias;
|
|
1711
|
-
function doVotesBias(s
|
|
1712
|
-
let test = s.getTest(
|
|
1713
|
-
|
|
1714
|
-
console.log("TODO - Calculating votes bias ...");
|
|
1411
|
+
function doVotesBias(s) {
|
|
1412
|
+
let test = s.getTest(10 /* VotesBias */);
|
|
1413
|
+
console.log("TODO - Calculating votes bias ...");
|
|
1715
1414
|
return test;
|
|
1716
1415
|
}
|
|
1717
1416
|
exports.doVotesBias = doVotesBias;
|
|
1718
|
-
function doResponsiveness(s
|
|
1719
|
-
let test = s.getTest(
|
|
1720
|
-
|
|
1721
|
-
console.log("TODO - Calculating responsiveness ...");
|
|
1417
|
+
function doResponsiveness(s) {
|
|
1418
|
+
let test = s.getTest(11 /* Responsiveness */);
|
|
1419
|
+
console.log("TODO - Calculating responsiveness ...");
|
|
1722
1420
|
return test;
|
|
1723
1421
|
}
|
|
1724
1422
|
exports.doResponsiveness = doResponsiveness;
|
|
1725
|
-
function doResponsiveDistricts(s
|
|
1726
|
-
let test = s.getTest(
|
|
1727
|
-
|
|
1728
|
-
console.log("TODO - Calculating # of responsive districts ...");
|
|
1423
|
+
function doResponsiveDistricts(s) {
|
|
1424
|
+
let test = s.getTest(12 /* ResponsiveDistricts */);
|
|
1425
|
+
console.log("TODO - Calculating # of responsive districts ...");
|
|
1729
1426
|
return test;
|
|
1730
1427
|
}
|
|
1731
1428
|
exports.doResponsiveDistricts = doResponsiveDistricts;
|
|
1732
1429
|
// OTHER MEASURES OF PARTISAN BIAS
|
|
1733
|
-
// TODO -
|
|
1430
|
+
// TODO - This formula might need to be inverted for D vs. R +/-
|
|
1734
1431
|
// TODO - Normalize the results.
|
|
1735
|
-
function doEfficiencyGap(s
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
let test = s.getTest(15 /* EfficiencyGap */);
|
|
1432
|
+
function doEfficiencyGap(s) {
|
|
1433
|
+
console.log("TODO - Calculating the efficiency gap ...");
|
|
1434
|
+
let test = s.getTest(13 /* EfficiencyGap */);
|
|
1739
1435
|
// Get partisan statistics by districts.
|
|
1740
1436
|
// Use Democratic votes, seats, and shares by convention.
|
|
1741
1437
|
let DVotes = s.districts.statistics[D.DistrictField.DemVotes];
|
|
@@ -1782,25 +1478,17 @@ exports.fptpWin = fptpWin;
|
|
|
1782
1478
|
//
|
|
1783
1479
|
// PREPROCESS DATA
|
|
1784
1480
|
//
|
|
1785
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1786
|
-
if (mod && mod.__esModule) return mod;
|
|
1787
|
-
var result = {};
|
|
1788
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1789
|
-
result["default"] = mod;
|
|
1790
|
-
return result;
|
|
1791
|
-
};
|
|
1792
1481
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1793
|
-
const U =
|
|
1482
|
+
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
1794
1483
|
// NOTE - Do preprocessing separately, so the constructor returns quickly.
|
|
1795
|
-
function doPreprocessData(s
|
|
1484
|
+
function doPreprocessData(s) {
|
|
1796
1485
|
// If necessary, do one-time preprocessing
|
|
1797
1486
|
if (!s.bOneTimeProcessingDone) {
|
|
1798
|
-
doPreprocessCountyFeatures(s
|
|
1799
|
-
doPreprocessCensus(s
|
|
1800
|
-
doPreprocessElection(s
|
|
1487
|
+
doPreprocessCountyFeatures(s);
|
|
1488
|
+
doPreprocessCensus(s);
|
|
1489
|
+
doPreprocessElection(s);
|
|
1801
1490
|
s.bOneTimeProcessingDone = true;
|
|
1802
1491
|
}
|
|
1803
|
-
// TODO - UNASSIGNED: Made both the planByGeoID & DistrictID are right
|
|
1804
1492
|
// Invert the plan by district ID
|
|
1805
1493
|
s.plan.invertPlan();
|
|
1806
1494
|
// Create a map of geoIDs to feature IDs
|
|
@@ -1808,7 +1496,7 @@ function doPreprocessData(s, bLog = false) {
|
|
|
1808
1496
|
}
|
|
1809
1497
|
exports.doPreprocessData = doPreprocessData;
|
|
1810
1498
|
// CREATE A FIPS CODE TO COUNTY NAME LOOKUP
|
|
1811
|
-
function doPreprocessCountyFeatures(s
|
|
1499
|
+
function doPreprocessCountyFeatures(s) {
|
|
1812
1500
|
for (let i = 0; i < s.counties.nCounties; i++) {
|
|
1813
1501
|
let county = s.counties.countyByIndex(i);
|
|
1814
1502
|
let fips = s.counties.propertyForCounty(county, 'COUNTYFP');
|
|
@@ -1817,58 +1505,29 @@ function doPreprocessCountyFeatures(s, bLog = false) {
|
|
|
1817
1505
|
}
|
|
1818
1506
|
}
|
|
1819
1507
|
// ANALYZE THE CENSUS BY COUNTY
|
|
1820
|
-
function doPreprocessCensus(s
|
|
1508
|
+
function doPreprocessCensus(s) {
|
|
1821
1509
|
// The county-splitting analytic needs the following info, using NC as an example:
|
|
1822
1510
|
// '_stateTotal' = The total state population, e.g., 9,535,483 for NC's 2010 Census
|
|
1823
1511
|
// 'totalByCounty' = The total population by county FIPS code
|
|
1824
|
-
// SUM TOTAL POPULATION BY COUNTY
|
|
1825
1512
|
let totalByCounty = {};
|
|
1826
1513
|
// NOTE - This works w/o GEOIDs, because you're looping over all features.
|
|
1827
1514
|
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
1828
1515
|
let f = s.features.featureByIndex(i);
|
|
1829
1516
|
let geoID = s.features.geoIDForFeature(f);
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
if (!(U.keyExists(countyFIPS, totalByCounty))) {
|
|
1840
|
-
totalByCounty[countyFIPS] = 0;
|
|
1841
|
-
}
|
|
1842
|
-
// Sum total population by county
|
|
1843
|
-
totalByCounty[countyFIPS] += value;
|
|
1517
|
+
let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
1518
|
+
// Sum total population across the state
|
|
1519
|
+
s.state.totalPop += value;
|
|
1520
|
+
// Get the county FIPS code for the feature
|
|
1521
|
+
let county = U.parseGeoID(geoID)['county'];
|
|
1522
|
+
let countyFIPS = U.getFIPSFromCountyGeoID(county);
|
|
1523
|
+
// If a subtotal for the county doesn't exist, initialize one
|
|
1524
|
+
if (!(U.keyExists(countyFIPS, totalByCounty))) {
|
|
1525
|
+
totalByCounty[countyFIPS] = 0;
|
|
1844
1526
|
}
|
|
1845
|
-
//
|
|
1846
|
-
|
|
1847
|
-
// }
|
|
1527
|
+
// Sum total population by county
|
|
1528
|
+
totalByCounty[countyFIPS] += value;
|
|
1848
1529
|
}
|
|
1849
1530
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
1850
|
-
// CREATE A FIPS CODE-ORDINAL MAP
|
|
1851
|
-
// Get the county FIPS codes
|
|
1852
|
-
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
1853
|
-
// Sort the results
|
|
1854
|
-
fipsCodes = fipsCodes.sort();
|
|
1855
|
-
// TODO - SPLITTING
|
|
1856
|
-
// Add a dummy county, for county-district splitting analysis
|
|
1857
|
-
fipsCodes.unshift('000');
|
|
1858
|
-
// Create the ID-ordinal map
|
|
1859
|
-
for (let i in fipsCodes) {
|
|
1860
|
-
s.counties.index[fipsCodes[i]] = Number(i);
|
|
1861
|
-
}
|
|
1862
|
-
// MAKE AN ARRAY OF TOTAL POPULATIONS BY COUNTY INDEX
|
|
1863
|
-
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
1864
|
-
let nCountyBuckets = s.counties.nCounties + 1;
|
|
1865
|
-
let countyTotals = U.initArray(nCountyBuckets, 0);
|
|
1866
|
-
for (let fipsCode in totalByCounty) {
|
|
1867
|
-
let i = s.counties.indexFromFIPS(fipsCode);
|
|
1868
|
-
countyTotals[i] = totalByCounty[fipsCode];
|
|
1869
|
-
}
|
|
1870
|
-
s.counties.totalPopulation = countyTotals;
|
|
1871
|
-
// ANALYZE THE COUNTIES
|
|
1872
1531
|
// 'target_size': 733499, # calc as total / districts
|
|
1873
1532
|
let targetSize = Math.round(s.state.totalPop / s.state.nDistricts);
|
|
1874
1533
|
// Find counties that are bigger than the target district size.
|
|
@@ -1880,13 +1539,18 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
1880
1539
|
let tooBigName = [];
|
|
1881
1540
|
let expectedSplits = 0;
|
|
1882
1541
|
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
|
+
}
|
|
1883
1551
|
// Loop over the counties
|
|
1884
1552
|
for (let county in fipsCodes) {
|
|
1885
1553
|
let fipsCode = fipsCodes[county];
|
|
1886
|
-
// TODO - SPLITTING
|
|
1887
|
-
// Skip the dummy county
|
|
1888
|
-
if (fipsCode == '000')
|
|
1889
|
-
continue;
|
|
1890
1554
|
let countyAffected = 0;
|
|
1891
1555
|
// Find the number of required splits, assuming target district size.
|
|
1892
1556
|
let rawQuotient = totalByCounty[fipsCode] / (targetSize + 1);
|
|
@@ -1907,428 +1571,34 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
1907
1571
|
s.state.expectedAffected = expectedAffected;
|
|
1908
1572
|
}
|
|
1909
1573
|
// PREPROCESS ELECTION RESULTS
|
|
1910
|
-
function doPreprocessElection(s
|
|
1911
|
-
|
|
1912
|
-
console.log("TODO - Preprocessing election data ...");
|
|
1574
|
+
function doPreprocessElection(s) {
|
|
1575
|
+
console.log("TODO - Preprocessing election data ...");
|
|
1913
1576
|
}
|
|
1914
1577
|
|
|
1915
1578
|
|
|
1916
1579
|
/***/ }),
|
|
1917
1580
|
|
|
1918
|
-
/***/ "./src/
|
|
1919
|
-
|
|
1920
|
-
!*** ./src/
|
|
1921
|
-
|
|
1581
|
+
/***/ "./src/report.ts":
|
|
1582
|
+
/*!***********************!*\
|
|
1583
|
+
!*** ./src/report.ts ***!
|
|
1584
|
+
\***********************/
|
|
1922
1585
|
/*! no static exports found */
|
|
1923
1586
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1924
1587
|
|
|
1925
1588
|
"use strict";
|
|
1926
1589
|
|
|
1927
1590
|
//
|
|
1928
|
-
//
|
|
1591
|
+
// GENERATE REPORTS
|
|
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
|
|
1929
1595
|
//
|
|
1930
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
1931
|
-
if (mod && mod.__esModule) return mod;
|
|
1932
|
-
var result = {};
|
|
1933
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
1934
|
-
result["default"] = mod;
|
|
1935
|
-
return result;
|
|
1936
|
-
};
|
|
1937
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1938
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1939
|
-
};
|
|
1940
1596
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1941
|
-
const U =
|
|
1942
|
-
const S =
|
|
1943
|
-
const D =
|
|
1944
|
-
// TODO - DASHBOARD: Delete
|
|
1945
|
-
// import { doAnalyzePostProcessing } from './report'
|
|
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");
|
|
1946
1600
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
1947
|
-
|
|
1948
|
-
// Example
|
|
1949
|
-
let sampleRequirements = {
|
|
1950
|
-
score: 2 /* Red */,
|
|
1951
|
-
metrics: {
|
|
1952
|
-
complete: 0 /* Green */,
|
|
1953
|
-
contiguous: 2 /* Red */,
|
|
1954
|
-
freeOfHoles: 1 /* Yellow */,
|
|
1955
|
-
equalPopulation: 2 /* Red */
|
|
1956
|
-
},
|
|
1957
|
-
details: {
|
|
1958
|
-
unassignedFeatures: [],
|
|
1959
|
-
emptyDistricts: [],
|
|
1960
|
-
discontiguousDistricts: [2],
|
|
1961
|
-
embeddedDistricts: [],
|
|
1962
|
-
populationDeviation: 0.6748,
|
|
1963
|
-
deviationThreshold: 0.75 / 100
|
|
1964
|
-
},
|
|
1965
|
-
datasets: {
|
|
1966
|
-
census: "2010 Census Total Population"
|
|
1967
|
-
},
|
|
1968
|
-
resources: {
|
|
1969
|
-
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
1970
|
-
}
|
|
1971
|
-
};
|
|
1972
|
-
let sampleCompactness = {
|
|
1973
|
-
score: 60,
|
|
1974
|
-
metrics: {
|
|
1975
|
-
reock: 0.3773,
|
|
1976
|
-
polsby: 0.3815
|
|
1977
|
-
},
|
|
1978
|
-
details: {},
|
|
1979
|
-
datasets: {
|
|
1980
|
-
shapes: "2010 VTD shapes"
|
|
1981
|
-
},
|
|
1982
|
-
resources: {}
|
|
1983
|
-
};
|
|
1984
|
-
let sampleSplitting = {
|
|
1985
|
-
score: 73,
|
|
1986
|
-
metrics: {
|
|
1987
|
-
sqEnt_DCreduced: 1.531,
|
|
1988
|
-
sqEnt_CDreduced: 1.760
|
|
1989
|
-
},
|
|
1990
|
-
details: {
|
|
1991
|
-
countiesSplitUnexpectedly: [
|
|
1992
|
-
"Bladen", "Buncombe", "Catawba", "Cumberland", "Durham", "Guilford", "Iredell", "Johnston", "Pitt", "Rowan", "Wilson"
|
|
1993
|
-
],
|
|
1994
|
-
unexpectedAffected: 0.3096,
|
|
1995
|
-
nSplitVTDs: 12,
|
|
1996
|
-
splitVTDs: []
|
|
1997
|
-
},
|
|
1998
|
-
datasets: {},
|
|
1999
|
-
resources: {}
|
|
2000
|
-
};
|
|
2001
|
-
// TODO - This category is still being fleshed out.
|
|
2002
|
-
let samplePartisan = {
|
|
2003
|
-
score: 100,
|
|
2004
|
-
metrics: {
|
|
2005
|
-
partisanBias: 0.15,
|
|
2006
|
-
responsiveness: 2.0
|
|
2007
|
-
},
|
|
2008
|
-
details: {},
|
|
2009
|
-
datasets: {
|
|
2010
|
-
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2011
|
-
},
|
|
2012
|
-
resources: {
|
|
2013
|
-
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2014
|
-
}
|
|
2015
|
-
};
|
|
2016
|
-
// TODO - This category is still being fleshed out.
|
|
2017
|
-
let sampleMinority = {
|
|
2018
|
-
score: null,
|
|
2019
|
-
metrics: {
|
|
2020
|
-
nBlack37to50: 1,
|
|
2021
|
-
nBlackMajority: 12,
|
|
2022
|
-
nHispanic37to50: 0,
|
|
2023
|
-
nHispanicMajority: 0,
|
|
2024
|
-
nPacific37to50: 0,
|
|
2025
|
-
nPacificMajority: 0,
|
|
2026
|
-
nAsian37to50: 0,
|
|
2027
|
-
nAsianMajority: 0,
|
|
2028
|
-
nNative37to50: 0,
|
|
2029
|
-
nNativeMajority: 0,
|
|
2030
|
-
nMinority37to50: 0,
|
|
2031
|
-
nMinorityMajority: 0,
|
|
2032
|
-
averageDVoteShare: 0.90
|
|
2033
|
-
},
|
|
2034
|
-
details: {
|
|
2035
|
-
vap: true,
|
|
2036
|
-
comboCategories: true
|
|
2037
|
-
},
|
|
2038
|
-
datasets: {
|
|
2039
|
-
vap: "2010 Voting Age Population"
|
|
2040
|
-
},
|
|
2041
|
-
resources: {}
|
|
2042
|
-
};
|
|
2043
|
-
exports.samplePlanAnalytics = {
|
|
2044
|
-
requirements: sampleRequirements,
|
|
2045
|
-
compactness: sampleCompactness,
|
|
2046
|
-
// TODO - Don't show these categories yet
|
|
2047
|
-
splitting: sampleSplitting,
|
|
2048
|
-
partisan: samplePartisan,
|
|
2049
|
-
minority: sampleMinority
|
|
2050
|
-
};
|
|
2051
|
-
function preparePlanAnalytics(s, bLog = false) {
|
|
2052
|
-
if (!(s.bPostProcessingDone)) {
|
|
2053
|
-
doAnalyzePostProcessing(s);
|
|
2054
|
-
}
|
|
2055
|
-
// REQUIREMENTS CATEGORY
|
|
2056
|
-
let paRequirements;
|
|
2057
|
-
{
|
|
2058
|
-
let completeTest = s.getTest(0 /* Complete */);
|
|
2059
|
-
let contiguousTest = s.getTest(1 /* Contiguous */);
|
|
2060
|
-
let freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
|
|
2061
|
-
let equalPopulationTest = s.getTest(3 /* EqualPopulation */);
|
|
2062
|
-
// Combine individual checks into an overall score
|
|
2063
|
-
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
2064
|
-
// requirements/validations, map booleans to tri-states here.
|
|
2065
|
-
let completeMetric = U.mapBooleanToTriState(completeTest['score']);
|
|
2066
|
-
let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
|
|
2067
|
-
let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
|
|
2068
|
-
let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
|
|
2069
|
-
let reqScore = 0 /* Green */;
|
|
2070
|
-
let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
|
|
2071
|
-
if (checks.includes(1 /* Yellow */))
|
|
2072
|
-
reqScore = 1 /* Yellow */;
|
|
2073
|
-
if (checks.includes(2 /* Red */))
|
|
2074
|
-
reqScore = 2 /* Red */;
|
|
2075
|
-
// Get values to support details entries
|
|
2076
|
-
let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
|
|
2077
|
-
let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
|
|
2078
|
-
let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
|
|
2079
|
-
let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
|
|
2080
|
-
let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
|
|
2081
|
-
let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
|
|
2082
|
-
let xx = s.state.xx;
|
|
2083
|
-
// TODO - JSON: Is there a better / easier way to work with the variable?
|
|
2084
|
-
let stateReqsDict = state_reqs_json_1.default;
|
|
2085
|
-
let reqLinkToStateReqs = stateReqsDict[xx];
|
|
2086
|
-
// Populate the category
|
|
2087
|
-
paRequirements = {
|
|
2088
|
-
score: reqScore,
|
|
2089
|
-
metrics: {
|
|
2090
|
-
complete: completeMetric,
|
|
2091
|
-
contiguous: contiguousMetric,
|
|
2092
|
-
freeOfHoles: freeOfHolesMetric,
|
|
2093
|
-
equalPopulation: equalPopulationMetric
|
|
2094
|
-
},
|
|
2095
|
-
details: {
|
|
2096
|
-
unassignedFeatures: unassignedFeaturesDetail,
|
|
2097
|
-
emptyDistricts: emptyDistrictsDetail,
|
|
2098
|
-
discontiguousDistricts: discontiguousDistrictsDetail,
|
|
2099
|
-
embeddedDistricts: embeddedDistrictsDetail,
|
|
2100
|
-
populationDeviation: populationDeviationDetail,
|
|
2101
|
-
deviationThreshold: deviationThresholdDetail
|
|
2102
|
-
},
|
|
2103
|
-
datasets: {
|
|
2104
|
-
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
2105
|
-
},
|
|
2106
|
-
resources: {
|
|
2107
|
-
stateReqs: reqLinkToStateReqs
|
|
2108
|
-
}
|
|
2109
|
-
};
|
|
2110
|
-
}
|
|
2111
|
-
// COMPACTNESS CATEGORY
|
|
2112
|
-
let paCompactness;
|
|
2113
|
-
{
|
|
2114
|
-
let reockWeight = 0.5;
|
|
2115
|
-
let polsbyWeight = 1.0 - reockWeight;
|
|
2116
|
-
let reockTest = s.getTest(5 /* Reock */);
|
|
2117
|
-
let polsbyTest = s.getTest(6 /* PolsbyPopper */);
|
|
2118
|
-
let normalizedReock = reockTest['normalizedScore'];
|
|
2119
|
-
let normalizedPolsby = reockTest['normalizedScore'];
|
|
2120
|
-
let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
|
|
2121
|
-
let reockMetric = U.deepCopy(reockTest['score']);
|
|
2122
|
-
let polsbyMetric = U.deepCopy(polsbyTest['score']);
|
|
2123
|
-
// Populate the category
|
|
2124
|
-
paCompactness = {
|
|
2125
|
-
score: compactnessScore,
|
|
2126
|
-
metrics: {
|
|
2127
|
-
reock: reockMetric,
|
|
2128
|
-
polsby: polsbyMetric
|
|
2129
|
-
},
|
|
2130
|
-
details: {
|
|
2131
|
-
// None at this time
|
|
2132
|
-
},
|
|
2133
|
-
datasets: {
|
|
2134
|
-
shapes: "2010 VTD shapes"
|
|
2135
|
-
// TODO - DATASETS
|
|
2136
|
-
// shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
|
|
2137
|
-
},
|
|
2138
|
-
resources: {
|
|
2139
|
-
// None at this time
|
|
2140
|
-
}
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
// SPLITTING CATEGORY
|
|
2144
|
-
let paSplitting;
|
|
2145
|
-
{
|
|
2146
|
-
let unexpectedCountySplittingTest = s.getTest(7 /* UnexpectedCountySplits */);
|
|
2147
|
-
let VTDSplitsTest = s.getTest(10 /* VTDSplits */);
|
|
2148
|
-
let countySplittingTest = s.getTest(8 /* CountySplitting */);
|
|
2149
|
-
let districtSplittingTest = s.getTest(9 /* DistrictSplitting */);
|
|
2150
|
-
let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
|
|
2151
|
-
let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
|
|
2152
|
-
let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
|
|
2153
|
-
let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
|
|
2154
|
-
let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
|
|
2155
|
-
let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
|
|
2156
|
-
let countySplittingNormalized = countySplittingTest['normalizedScore'];
|
|
2157
|
-
let districtSplittingNormalized = districtSplittingTest['normalizedScore'];
|
|
2158
|
-
let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
|
|
2159
|
-
+(S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
|
|
2160
|
-
paSplitting = {
|
|
2161
|
-
score: splittingScore,
|
|
2162
|
-
metrics: {
|
|
2163
|
-
sqEnt_DCreduced: SqEnt_DCreducedMetric,
|
|
2164
|
-
sqEnt_CDreduced: SqEnt_CDreducedMetric
|
|
2165
|
-
// NOTE - The un-reduced raw values
|
|
2166
|
-
// sqEnt_DC : SqEnt_DCMetric,
|
|
2167
|
-
// sqEnt_CD : SqEnt_CDMetric
|
|
2168
|
-
},
|
|
2169
|
-
details: {
|
|
2170
|
-
countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
|
|
2171
|
-
unexpectedAffected: unexpectedAffectedMetric,
|
|
2172
|
-
nSplitVTDs: nVTDSplitsMetric,
|
|
2173
|
-
splitVTDs: splitVTDsDetail
|
|
2174
|
-
},
|
|
2175
|
-
datasets: {
|
|
2176
|
-
// None at this time
|
|
2177
|
-
},
|
|
2178
|
-
resources: {
|
|
2179
|
-
// None at this time
|
|
2180
|
-
}
|
|
2181
|
-
};
|
|
2182
|
-
}
|
|
2183
|
-
// PARTISAN CATEGORY
|
|
2184
|
-
//
|
|
2185
|
-
// TODO - PARTISAN: This category is still being fleshed out. Just an example below.
|
|
2186
|
-
let paPartisan;
|
|
2187
|
-
{
|
|
2188
|
-
paPartisan = {
|
|
2189
|
-
score: 100,
|
|
2190
|
-
metrics: {
|
|
2191
|
-
partisanBias: 0.15,
|
|
2192
|
-
responsiveness: 2.0
|
|
2193
|
-
},
|
|
2194
|
-
details: {},
|
|
2195
|
-
datasets: {
|
|
2196
|
-
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2197
|
-
},
|
|
2198
|
-
resources: {
|
|
2199
|
-
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2200
|
-
}
|
|
2201
|
-
};
|
|
2202
|
-
}
|
|
2203
|
-
// MINORITY CATEGORY
|
|
2204
|
-
//
|
|
2205
|
-
// TODO - MINORITY: This category is still being fleshed out. Just an example below.
|
|
2206
|
-
let paMinority;
|
|
2207
|
-
{
|
|
2208
|
-
paMinority = {
|
|
2209
|
-
score: null,
|
|
2210
|
-
metrics: {
|
|
2211
|
-
nBlack37to50: 1,
|
|
2212
|
-
nBlackMajority: 12,
|
|
2213
|
-
nHispanic37to50: 0,
|
|
2214
|
-
nHispanicMajority: 0,
|
|
2215
|
-
nPacific37to50: 0,
|
|
2216
|
-
nPacificMajority: 0,
|
|
2217
|
-
nAsian37to50: 0,
|
|
2218
|
-
nAsianMajority: 0,
|
|
2219
|
-
nNative37to50: 0,
|
|
2220
|
-
nNativeMajority: 0,
|
|
2221
|
-
nMinority37to50: 0,
|
|
2222
|
-
nMinorityMajority: 0,
|
|
2223
|
-
averageDVoteShare: 0.90
|
|
2224
|
-
},
|
|
2225
|
-
details: {
|
|
2226
|
-
vap: true,
|
|
2227
|
-
comboCategories: true
|
|
2228
|
-
},
|
|
2229
|
-
datasets: {
|
|
2230
|
-
vap: "2010 Voting Age Population"
|
|
2231
|
-
},
|
|
2232
|
-
resources: {}
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
// PLAN ANALYTICS
|
|
2236
|
-
let pa = {
|
|
2237
|
-
requirements: paRequirements,
|
|
2238
|
-
compactness: paCompactness,
|
|
2239
|
-
// TODO - Not implemented yet
|
|
2240
|
-
splitting: paSplitting,
|
|
2241
|
-
partisan: paPartisan,
|
|
2242
|
-
minority: paMinority
|
|
2243
|
-
};
|
|
2244
|
-
return pa;
|
|
2245
|
-
}
|
|
2246
|
-
exports.preparePlanAnalytics = preparePlanAnalytics;
|
|
2247
|
-
// Example
|
|
2248
|
-
exports.sampleDistrictStatistics = {
|
|
2249
|
-
table: [
|
|
2250
|
-
// District 0 is the dummy unassigned district
|
|
2251
|
-
[0, 0, 0, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2252
|
-
[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],
|
|
2253
|
-
[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],
|
|
2254
|
-
[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],
|
|
2255
|
-
[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],
|
|
2256
|
-
[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],
|
|
2257
|
-
[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],
|
|
2258
|
-
[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],
|
|
2259
|
-
[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],
|
|
2260
|
-
// District N+1 is the dummy state-summary district
|
|
2261
|
-
[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]
|
|
2262
|
-
],
|
|
2263
|
-
details: {},
|
|
2264
|
-
datasets: {
|
|
2265
|
-
shapes: "2010 VTD shapes",
|
|
2266
|
-
census: "2010 Census Total Population",
|
|
2267
|
-
vap: "2010 Voting Age Population",
|
|
2268
|
-
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2269
|
-
},
|
|
2270
|
-
resources: {}
|
|
2271
|
-
};
|
|
2272
|
-
// Create a DistrictStatistics instance, deep copying the underlying values.
|
|
2273
|
-
function prepareDistrictStatistics(s, bLog = false) {
|
|
2274
|
-
if (!(s.bPostProcessingDone)) {
|
|
2275
|
-
doAnalyzePostProcessing(s);
|
|
2276
|
-
}
|
|
2277
|
-
let dsTable = [];
|
|
2278
|
-
for (let i = 0; i < s.districts.numberOfRows(); i++) {
|
|
2279
|
-
let rawRow = [
|
|
2280
|
-
i,
|
|
2281
|
-
s.districts.statistics[D.DistrictField.TotalPop][i],
|
|
2282
|
-
s.districts.statistics[D.DistrictField.PopDevPct][i],
|
|
2283
|
-
s.districts.statistics[D.DistrictField.bEqualPop][i],
|
|
2284
|
-
s.districts.statistics[D.DistrictField.bNotEmpty][i],
|
|
2285
|
-
s.districts.statistics[D.DistrictField.bContiguous][i],
|
|
2286
|
-
s.districts.statistics[D.DistrictField.bNotEmbedded][i],
|
|
2287
|
-
s.districts.statistics[D.DistrictField.DemPct][i],
|
|
2288
|
-
s.districts.statistics[D.DistrictField.RepPct][i],
|
|
2289
|
-
s.districts.statistics[D.DistrictField.WhitePct][i],
|
|
2290
|
-
s.districts.statistics[D.DistrictField.MinorityPct][i],
|
|
2291
|
-
s.districts.statistics[D.DistrictField.BlackPct][i],
|
|
2292
|
-
s.districts.statistics[D.DistrictField.HispanicPct][i],
|
|
2293
|
-
s.districts.statistics[D.DistrictField.PacificPct][i],
|
|
2294
|
-
s.districts.statistics[D.DistrictField.AsianPct][i],
|
|
2295
|
-
s.districts.statistics[D.DistrictField.NativePct][i]
|
|
2296
|
-
];
|
|
2297
|
-
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
2298
|
-
// requirements/validations, map booleans to tri-states here.
|
|
2299
|
-
rawRow[3 /* bEqualPop */] = U.mapBooleanToTriState(rawRow[3 /* bEqualPop */]);
|
|
2300
|
-
rawRow[4 /* bNotEmpty */] = U.mapBooleanToTriState(rawRow[4 /* bNotEmpty */]);
|
|
2301
|
-
rawRow[5 /* bContiguous */] = U.mapBooleanToTriState(rawRow[5 /* bContiguous */]);
|
|
2302
|
-
rawRow[6 /* bNotEmbedded */] = U.mapBooleanToTriState(rawRow[6 /* bNotEmbedded */]);
|
|
2303
|
-
let readyRow = U.deepCopy(rawRow);
|
|
2304
|
-
dsTable.push(readyRow);
|
|
2305
|
-
}
|
|
2306
|
-
let dsDetails = {
|
|
2307
|
-
// None at this time
|
|
2308
|
-
};
|
|
2309
|
-
let dsDatasets = {
|
|
2310
|
-
shapes: "2010 VTD shapes",
|
|
2311
|
-
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
2312
|
-
vap: U.deepCopy(s.config['descriptions']['VAP']),
|
|
2313
|
-
election: U.deepCopy(s.config['descriptions']['ELECTION'])
|
|
2314
|
-
};
|
|
2315
|
-
let dsResources = {
|
|
2316
|
-
// None at this time
|
|
2317
|
-
};
|
|
2318
|
-
let ds = {
|
|
2319
|
-
table: dsTable,
|
|
2320
|
-
details: dsDetails,
|
|
2321
|
-
datasets: dsDatasets,
|
|
2322
|
-
resources: dsResources
|
|
2323
|
-
};
|
|
2324
|
-
return ds;
|
|
2325
|
-
}
|
|
2326
|
-
exports.prepareDistrictStatistics = prepareDistrictStatistics;
|
|
2327
|
-
// META-DATA FOR TESTS/ANALYTICS
|
|
2328
|
-
//
|
|
2329
|
-
// NOTE - This structure is a vestige of having created a metadata-driven
|
|
2330
|
-
// scorecard w/in district-analytics at first. It works for creating the
|
|
2331
|
-
// unstyled results structures, so it isn't a high priority to rationalize.
|
|
1601
|
+
// TEST META-DATA
|
|
2332
1602
|
var TestType;
|
|
2333
1603
|
(function (TestType) {
|
|
2334
1604
|
TestType[TestType["PassFail"] = 0] = "PassFail";
|
|
@@ -2340,6 +1610,7 @@ const completeDefn = {
|
|
|
2340
1610
|
name: "Complete",
|
|
2341
1611
|
normalize: false,
|
|
2342
1612
|
externalType: TestType.PassFail,
|
|
1613
|
+
detailsFn: doPrepareCompleteDetails,
|
|
2343
1614
|
suites: [0 /* Legal */]
|
|
2344
1615
|
};
|
|
2345
1616
|
const contiguousDefn = {
|
|
@@ -2347,6 +1618,7 @@ const contiguousDefn = {
|
|
|
2347
1618
|
name: "Contiguous",
|
|
2348
1619
|
normalize: false,
|
|
2349
1620
|
externalType: TestType.PassFail,
|
|
1621
|
+
detailsFn: doPrepareContiguousDetails,
|
|
2350
1622
|
suites: [0 /* Legal */]
|
|
2351
1623
|
};
|
|
2352
1624
|
const freeOfHolesDefn = {
|
|
@@ -2354,6 +1626,7 @@ const freeOfHolesDefn = {
|
|
|
2354
1626
|
name: "Free of Holes",
|
|
2355
1627
|
normalize: false,
|
|
2356
1628
|
externalType: TestType.PassFail,
|
|
1629
|
+
detailsFn: doPrepareFreeOfHolesDetails,
|
|
2357
1630
|
suites: [0 /* Legal */]
|
|
2358
1631
|
};
|
|
2359
1632
|
const equalPopulationDefn = {
|
|
@@ -2361,6 +1634,7 @@ const equalPopulationDefn = {
|
|
|
2361
1634
|
name: "Equal Population",
|
|
2362
1635
|
normalize: false,
|
|
2363
1636
|
externalType: TestType.PassFail,
|
|
1637
|
+
detailsFn: doPrepareEqualPopulationDetails,
|
|
2364
1638
|
suites: [0 /* Legal */]
|
|
2365
1639
|
};
|
|
2366
1640
|
const populationDeviationDefn = {
|
|
@@ -2368,6 +1642,7 @@ const populationDeviationDefn = {
|
|
|
2368
1642
|
name: "Population Deviation",
|
|
2369
1643
|
normalize: true,
|
|
2370
1644
|
externalType: TestType.Percentage,
|
|
1645
|
+
detailsFn: doPreparePopulationDeviationDetails,
|
|
2371
1646
|
suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
|
|
2372
1647
|
};
|
|
2373
1648
|
const reockDefn = {
|
|
@@ -2375,6 +1650,7 @@ const reockDefn = {
|
|
|
2375
1650
|
name: "Reock",
|
|
2376
1651
|
normalize: true,
|
|
2377
1652
|
externalType: TestType.Number,
|
|
1653
|
+
detailsFn: doPrepareReockDetails,
|
|
2378
1654
|
suites: [2 /* Best */]
|
|
2379
1655
|
};
|
|
2380
1656
|
const polsbyPopperDefn = {
|
|
@@ -2382,45 +1658,24 @@ const polsbyPopperDefn = {
|
|
|
2382
1658
|
name: "Polsby-Popper",
|
|
2383
1659
|
normalize: true,
|
|
2384
1660
|
externalType: TestType.Number,
|
|
1661
|
+
detailsFn: doPreparePolsbyPopperDetails,
|
|
2385
1662
|
suites: [2 /* Best */]
|
|
2386
1663
|
};
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
normalize: false,
|
|
2392
|
-
externalType: TestType.Percentage,
|
|
2393
|
-
suites: [2 /* Best */]
|
|
2394
|
-
};
|
|
2395
|
-
// TODO - SPLITTING
|
|
2396
|
-
const VTDSplitsDefn = {
|
|
2397
|
-
ID: 10 /* VTDSplits */,
|
|
2398
|
-
name: "VTD Splits",
|
|
2399
|
-
normalize: false,
|
|
2400
|
-
externalType: TestType.Number,
|
|
2401
|
-
suites: [2 /* Best */]
|
|
2402
|
-
};
|
|
2403
|
-
// TODO - SPLITTING
|
|
2404
|
-
const countySplittingDefn = {
|
|
2405
|
-
ID: 8 /* CountySplitting */,
|
|
2406
|
-
name: "County Splitting",
|
|
2407
|
-
normalize: true,
|
|
2408
|
-
externalType: TestType.Number,
|
|
2409
|
-
suites: [2 /* Best */]
|
|
2410
|
-
};
|
|
2411
|
-
// TODO - SPLITTING
|
|
2412
|
-
const districtSplittingDefn = {
|
|
2413
|
-
ID: 9 /* DistrictSplitting */,
|
|
2414
|
-
name: "District Splitting",
|
|
1664
|
+
const countySplitsDefn = {
|
|
1665
|
+
ID: 7 /* CountySplits */,
|
|
1666
|
+
name: "County splits",
|
|
1667
|
+
trailer: "of the population had their county split unexpectedly.",
|
|
2415
1668
|
normalize: true,
|
|
2416
|
-
externalType: TestType.
|
|
1669
|
+
externalType: TestType.Percentage,
|
|
1670
|
+
detailsFn: doPrepareCountySplitDetails,
|
|
2417
1671
|
suites: [2 /* Best */]
|
|
2418
1672
|
};
|
|
2419
1673
|
const efficiencyGapDefn = {
|
|
2420
|
-
ID:
|
|
1674
|
+
ID: 13 /* EfficiencyGap */,
|
|
2421
1675
|
name: "Efficiency Gap",
|
|
2422
1676
|
normalize: false,
|
|
2423
1677
|
externalType: TestType.Percentage,
|
|
1678
|
+
detailsFn: doPrepareEfficiencyGapDetails,
|
|
2424
1679
|
suites: [1 /* Fair */]
|
|
2425
1680
|
};
|
|
2426
1681
|
// All the tests that have been defined (can be reported on)
|
|
@@ -2432,13 +1687,72 @@ const testDefns = {
|
|
|
2432
1687
|
[4 /* PopulationDeviation */]: populationDeviationDefn,
|
|
2433
1688
|
[5 /* Reock */]: reockDefn,
|
|
2434
1689
|
[6 /* PolsbyPopper */]: polsbyPopperDefn,
|
|
2435
|
-
// TODO -
|
|
2436
|
-
[7 /*
|
|
2437
|
-
[
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
1690
|
+
// TODO - Flesh out county splits
|
|
1691
|
+
[7 /* CountySplits */]: countySplitsDefn,
|
|
1692
|
+
[13 /* EfficiencyGap */]: efficiencyGapDefn
|
|
1693
|
+
/* TODO - More tests ... */
|
|
1694
|
+
};
|
|
1695
|
+
// Scorecard category definitions
|
|
1696
|
+
const validCategory = {
|
|
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
|
|
2442
1756
|
};
|
|
2443
1757
|
// NORMALIZE RAW ANALYTICS
|
|
2444
1758
|
// Raw numeric analytics, such as population deviation, compactness, etc. are
|
|
@@ -2453,29 +1767,19 @@ function doConfigureScales(s) {
|
|
|
2453
1767
|
const LDLimit = 10.00 / 100; // Deviation threshold for LD's
|
|
2454
1768
|
const CDGoodEnough = 0.20 / 100;
|
|
2455
1769
|
const LDGoodEnough = (CDGoodEnough / CDLimit) * LDLimit;
|
|
2456
|
-
const
|
|
1770
|
+
const scale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
2457
1771
|
// const scale = [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
2458
|
-
s.testScales[4 /* PopulationDeviation */] = {
|
|
2459
|
-
s.testScales[5 /* Reock */] = {
|
|
2460
|
-
s.testScales[6 /* PolsbyPopper */] = {
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2463
|
-
// TODO - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
|
|
2464
|
-
// number of county splits seems too stringent, empirically.
|
|
2465
|
-
const allowableCountySplitsMultiplier = 1.5;
|
|
2466
|
-
const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
|
|
2467
|
-
const countySplittingThreshold = ((nAllowableSplits * 1.71) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
|
|
2468
|
-
const countySplittingScale = [1.0, countySplittingThreshold];
|
|
2469
|
-
s.testScales[8 /* CountySplitting */] = { scale: countySplittingScale, bInvertScaled: true };
|
|
2470
|
-
const districtSplittingThreshold = 1.5;
|
|
2471
|
-
const districtSplittingScale = [1.0, districtSplittingThreshold];
|
|
2472
|
-
s.testScales[9 /* DistrictSplitting */] = { scale: districtSplittingScale, bInvertScaled: true };
|
|
1772
|
+
s.testScales[4 /* PopulationDeviation */] = { testScale: scale, testInvertp: true };
|
|
1773
|
+
s.testScales[5 /* Reock */] = { testScale: [0.25, 0.50], testInvertp: false };
|
|
1774
|
+
s.testScales[6 /* PolsbyPopper */] = { testScale: [0.10, 0.50], testInvertp: false };
|
|
1775
|
+
const SPLITLimit = 0.1; // TODO - Just a placeholder default maximum (10%)
|
|
1776
|
+
s.testScales[7 /* CountySplits */] = { testScale: [1.0 - SPLITLimit, 1.0], testInvertp: true };
|
|
2473
1777
|
// TODO - More analytics ...
|
|
2474
1778
|
}
|
|
2475
1779
|
exports.doConfigureScales = doConfigureScales;
|
|
2476
1780
|
// Postprocess analytics - Normalize numeric results and derive secondary tests.
|
|
2477
1781
|
// Do this after analytics have been run and before preparing a test log or scorecard.
|
|
2478
|
-
function doAnalyzePostProcessing(s
|
|
1782
|
+
function doAnalyzePostProcessing(s) {
|
|
2479
1783
|
// Normalize the raw scores for all the numerics tests
|
|
2480
1784
|
let testResults = U.getNumericObjectKeys(testDefns);
|
|
2481
1785
|
for (let testID of testResults) {
|
|
@@ -2483,18 +1787,642 @@ function doAnalyzePostProcessing(s, bLog = false) {
|
|
|
2483
1787
|
let testResult = s.getTest(testID);
|
|
2484
1788
|
let rawScore = testResult['score'];
|
|
2485
1789
|
let normalizedScore;
|
|
2486
|
-
|
|
1790
|
+
let { testScale, testInvertp } = s.testScales[testID];
|
|
1791
|
+
normalizedScore = U.normalize(rawScore, testScale, testInvertp);
|
|
2487
1792
|
testResult['normalizedScore'] = normalizedScore;
|
|
2488
1793
|
// Add the scale used to normalize the raw score to the details
|
|
2489
|
-
testResult['details']['scale'] =
|
|
1794
|
+
testResult['details']['scale'] = testScale;
|
|
2490
1795
|
}
|
|
2491
1796
|
}
|
|
2492
1797
|
// Derive secondary tests
|
|
2493
|
-
analyze_1.doDeriveSecondaryTests(s
|
|
1798
|
+
analyze_1.doDeriveSecondaryTests(s);
|
|
2494
1799
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
2495
1800
|
s.bPostProcessingDone = true;
|
|
2496
1801
|
}
|
|
2497
1802
|
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
|
+
*/
|
|
2498
2426
|
|
|
2499
2427
|
|
|
2500
2428
|
/***/ }),
|
|
@@ -2519,17 +2447,11 @@ exports.PRECISION = 4;
|
|
|
2519
2447
|
exports.NORMALIZED_RANGE = 100;
|
|
2520
2448
|
// The dummy district ID for features not assigned districts yet
|
|
2521
2449
|
exports.NOT_ASSIGNED = 0;
|
|
2522
|
-
// TODO -
|
|
2450
|
+
// TODO - Discuss w/ Dave & Terry
|
|
2523
2451
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
2524
2452
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
2525
2453
|
// The virtual geoID for "neighbors" in other states
|
|
2526
2454
|
exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
2527
|
-
// "Roughly equal" = average census block size / 2
|
|
2528
|
-
const AVERAGE_BLOCK_SIZE = 30;
|
|
2529
|
-
exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
|
|
2530
|
-
// County & district splitting weights
|
|
2531
|
-
exports.COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
2532
|
-
exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
2533
2455
|
|
|
2534
2456
|
|
|
2535
2457
|
/***/ }),
|
|
@@ -2564,15 +2486,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
2564
2486
|
//
|
|
2565
2487
|
// UTILITIES
|
|
2566
2488
|
//
|
|
2567
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
2568
|
-
if (mod && mod.__esModule) return mod;
|
|
2569
|
-
var result = {};
|
|
2570
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
2571
|
-
result["default"] = mod;
|
|
2572
|
-
return result;
|
|
2573
|
-
};
|
|
2574
2489
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2575
|
-
const S =
|
|
2490
|
+
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
2576
2491
|
// PLAN HELPERS
|
|
2577
2492
|
// Is a "neighbor" in state?
|
|
2578
2493
|
function isInState(geoID) {
|
|
@@ -2584,7 +2499,6 @@ function isOutOfState(geoID) {
|
|
|
2584
2499
|
return geoID == S.OUT_OF_STATE;
|
|
2585
2500
|
}
|
|
2586
2501
|
exports.isOutOfState = isOutOfState;
|
|
2587
|
-
// TODO - UNASSIGNED
|
|
2588
2502
|
// Get the districtID to which a geoID is assigned
|
|
2589
2503
|
function getDistrict(plan, geoID) {
|
|
2590
2504
|
// All geoIDs in a state *should be* assigned to a district (including the
|
|
@@ -2598,6 +2512,24 @@ function getDistrict(plan, geoID) {
|
|
|
2598
2512
|
}
|
|
2599
2513
|
}
|
|
2600
2514
|
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;
|
|
2601
2533
|
// WORKING WITH GEOIDS
|
|
2602
2534
|
function parseGeoID(geoID) {
|
|
2603
2535
|
let parts = {};
|
|
@@ -2620,46 +2552,27 @@ function getFIPSFromCountyGeoID(geoID) {
|
|
|
2620
2552
|
return geoID.substring(2, 5);
|
|
2621
2553
|
}
|
|
2622
2554
|
exports.getFIPSFromCountyGeoID = getFIPSFromCountyGeoID;
|
|
2623
|
-
function isWaterOnly(geoID) {
|
|
2624
|
-
let waterOnlySignature = 'ZZZZZZ';
|
|
2625
|
-
if (geoID.indexOf(waterOnlySignature) >= 0)
|
|
2626
|
-
return true;
|
|
2627
|
-
else
|
|
2628
|
-
return false;
|
|
2629
|
-
}
|
|
2630
|
-
exports.isWaterOnly = isWaterOnly;
|
|
2631
2555
|
// NORMALIZING RESULTS
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
// TODO - This works for Population Deviation, because the max is 1.0.
|
|
2637
|
-
// Generalize this???
|
|
2638
|
-
if (testScale.bInvertRaw) {
|
|
2556
|
+
// Convert a raw score [0-1] into a normalized score [0-100]
|
|
2557
|
+
function normalize(rawScore, scale, invertp = false) {
|
|
2558
|
+
// Invert the axis if necessary to make bigger = better
|
|
2559
|
+
if (invertp) {
|
|
2639
2560
|
rawScore = 1.0 - rawScore;
|
|
2640
2561
|
}
|
|
2641
2562
|
// Coerce the value to be w/in the given range
|
|
2563
|
+
let rangeMin = scale[0];
|
|
2564
|
+
let rangeMax = scale[1];
|
|
2642
2565
|
let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
|
|
2643
2566
|
// Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
|
|
2644
2567
|
let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
|
|
2645
|
-
// TODO - SPLITTING
|
|
2646
|
-
// Invert the scaled value if necessary to make bigger = better
|
|
2647
|
-
if (testScale.bInvertScaled) {
|
|
2648
|
-
scaledValue = 1.0 - scaledValue;
|
|
2649
|
-
}
|
|
2650
2568
|
// Finally, make the range [0-100]
|
|
2651
2569
|
return Math.round(scaledValue * S.NORMALIZED_RANGE);
|
|
2652
2570
|
}
|
|
2653
2571
|
exports.normalize = normalize;
|
|
2654
2572
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
2655
|
-
function trim(fullFraction
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
}
|
|
2659
|
-
else {
|
|
2660
|
-
let shiftPlaces = Math.pow(10, (digits || S.PRECISION));
|
|
2661
|
-
return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
|
|
2662
|
-
}
|
|
2573
|
+
function trim(fullFraction) {
|
|
2574
|
+
let shiftPlaces = Math.pow(10, S.PRECISION);
|
|
2575
|
+
return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
|
|
2663
2576
|
}
|
|
2664
2577
|
exports.trim = trim;
|
|
2665
2578
|
// ARRAY HELPERS
|
|
@@ -2688,14 +2601,13 @@ function andArray(arr) {
|
|
|
2688
2601
|
}
|
|
2689
2602
|
exports.andArray = andArray;
|
|
2690
2603
|
// WORKING WITH OBJECT KEYS/PROPERTIES
|
|
2691
|
-
// TODO -
|
|
2692
|
-
// TODO - Handle integer keys?
|
|
2604
|
+
// TODO - Terry, is this copesetic?
|
|
2693
2605
|
// Does an object have a key/property?
|
|
2694
2606
|
function keyExists(k, o) {
|
|
2695
2607
|
return k in o;
|
|
2696
2608
|
}
|
|
2697
2609
|
exports.keyExists = keyExists;
|
|
2698
|
-
// TODO -
|
|
2610
|
+
// TODO - Terry, can these three be combined into a generic isEmpty() check?
|
|
2699
2611
|
// Does an object (dict) have any keys/properties?
|
|
2700
2612
|
function isObjectEmpty(o) {
|
|
2701
2613
|
return Object.keys(o).length === 0;
|
|
@@ -2789,12 +2701,7 @@ function deepCopy(src) {
|
|
|
2789
2701
|
return src;
|
|
2790
2702
|
}
|
|
2791
2703
|
exports.deepCopy = deepCopy;
|
|
2792
|
-
// TODO -
|
|
2793
|
-
function mapBooleanToTriState(bool) {
|
|
2794
|
-
return (bool) ? 0 /* Green */ : 2 /* Red */;
|
|
2795
|
-
}
|
|
2796
|
-
exports.mapBooleanToTriState = mapBooleanToTriState;
|
|
2797
|
-
// TODO - TERRY: What is this the simple explanation of what this thing is doing?
|
|
2704
|
+
// TODO - Terry: What is this the simple explanation of what this thing is doing?
|
|
2798
2705
|
var util_1 = __webpack_require__(/*! @dra2020/util */ "@dra2020/util");
|
|
2799
2706
|
exports.depthof = util_1.depthof;
|
|
2800
2707
|
|
|
@@ -2813,54 +2720,40 @@ exports.depthof = util_1.depthof;
|
|
|
2813
2720
|
//
|
|
2814
2721
|
// MAP/PLAN VALIDATIONS
|
|
2815
2722
|
//
|
|
2816
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
2817
|
-
if (mod && mod.__esModule) return mod;
|
|
2818
|
-
var result = {};
|
|
2819
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
2820
|
-
result["default"] = mod;
|
|
2821
|
-
return result;
|
|
2822
|
-
};
|
|
2823
2723
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2824
|
-
const U =
|
|
2825
|
-
const S =
|
|
2826
|
-
const D =
|
|
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");
|
|
2827
2727
|
//
|
|
2828
2728
|
// COMPLETE - Are all geo's assigned to a district, and do all districts have
|
|
2829
2729
|
// at least one geo assigned to them?
|
|
2830
2730
|
//
|
|
2831
|
-
function doIsComplete(s
|
|
2731
|
+
function doIsComplete(s) {
|
|
2832
2732
|
let test = s.getTest(0 /* Complete */);
|
|
2833
|
-
|
|
2733
|
+
let bAllAssigned = true;
|
|
2734
|
+
let bNoneEmpty = true;
|
|
2735
|
+
let unassignedFeatures = [];
|
|
2736
|
+
let emptyDistricts = [];
|
|
2737
|
+
// Get the by district results, including the dummy unassigned district,
|
|
2834
2738
|
// but ignoring the N+1 summary district
|
|
2835
2739
|
let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
|
|
2836
2740
|
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
|
|
2837
2741
|
// Are all features assigned to districts?
|
|
2838
2742
|
// Check the dummy district that holds any unassigned features.
|
|
2839
|
-
|
|
2840
|
-
let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
|
|
2743
|
+
bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
|
|
2841
2744
|
if (!bAllAssigned) {
|
|
2842
2745
|
let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
|
|
2843
2746
|
unassignedFeatures = Array.from(unassignedDistrict);
|
|
2844
2747
|
unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
|
|
2845
2748
|
}
|
|
2846
2749
|
// Do all real districts have at least one feature assigned to them?
|
|
2847
|
-
|
|
2848
|
-
let bNoneEmpty = true;
|
|
2849
|
-
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(1);
|
|
2850
|
-
let districtID = 1;
|
|
2851
|
-
bNotEmptyByDistrict.forEach(function (bNotEmpty) {
|
|
2852
|
-
if (!bNotEmpty) {
|
|
2853
|
-
bNoneEmpty = false;
|
|
2854
|
-
emptyDistricts.push(districtID);
|
|
2855
|
-
}
|
|
2856
|
-
districtID += 1;
|
|
2857
|
-
});
|
|
2750
|
+
bNoneEmpty = U.andArray(bNotEmptyByDistrict.slice(1));
|
|
2858
2751
|
// Case 1 - One or more districts are missing:
|
|
2859
2752
|
// The # of enumerated districts minus the dummy NOT_ASSIGNED one should
|
|
2860
2753
|
// equal the number apportioned districts. This guards against a district
|
|
2861
2754
|
// not being included in a map that is imported.
|
|
2862
2755
|
//
|
|
2863
|
-
//
|
|
2756
|
+
// TODO - I'm no longer checking for this, but DRA should!
|
|
2864
2757
|
// Case 2 - Or a district is explicitly named but empty:
|
|
2865
2758
|
// Note, this can happen if a district is created, and then all features
|
|
2866
2759
|
// are removed from it (in DRA).
|
|
@@ -2887,7 +2780,7 @@ exports.doIsComplete = doIsComplete;
|
|
|
2887
2780
|
//
|
|
2888
2781
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
|
|
2889
2782
|
//
|
|
2890
|
-
function doIsContiguous(s
|
|
2783
|
+
function doIsContiguous(s) {
|
|
2891
2784
|
let test = s.getTest(1 /* Contiguous */);
|
|
2892
2785
|
// Get the contiguity of each district. Ignore dummy unassigned district
|
|
2893
2786
|
// and the N+1 summary district.
|
|
@@ -2918,7 +2811,7 @@ exports.doIsContiguous = doIsContiguous;
|
|
|
2918
2811
|
// Are the features in a district fully connected?
|
|
2919
2812
|
function isConnected(districtGeos, graph) {
|
|
2920
2813
|
// export function isConnected(districtGeos: Set<string>, graph: T.ContiguityGraph): boolean {
|
|
2921
|
-
// TODO -
|
|
2814
|
+
// TODO - Terry, why does this constructor need a <T> type specification?
|
|
2922
2815
|
let visited = new Set();
|
|
2923
2816
|
let toProcess = [];
|
|
2924
2817
|
// Start processing with the first geoID in the district
|
|
@@ -2933,7 +2826,7 @@ function isConnected(districtGeos, graph) {
|
|
|
2933
2826
|
let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
|
|
2934
2827
|
// Add neighbors to visit, if they're in the same district Y haven't already been visited
|
|
2935
2828
|
let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
|
|
2936
|
-
// TODO -
|
|
2829
|
+
// TODO - Terry, is this the quickest/best way to do this?
|
|
2937
2830
|
toProcess.push(...neighborsToVisit);
|
|
2938
2831
|
}
|
|
2939
2832
|
// Stop when you've visited all the geoIDs in the district
|
|
@@ -2950,10 +2843,10 @@ exports.isConnected = isConnected;
|
|
|
2950
2843
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
|
|
2951
2844
|
// Buncombe County (37021), is a donut hole w/in District 3.
|
|
2952
2845
|
//
|
|
2953
|
-
// TODO -
|
|
2846
|
+
// TODO - Optimize this to take advantage of district boundary info, if/when
|
|
2954
2847
|
// we cache one to optimize compactness.
|
|
2955
2848
|
//
|
|
2956
|
-
function doIsFreeOfHoles(s
|
|
2849
|
+
function doIsFreeOfHoles(s) {
|
|
2957
2850
|
let test = s.getTest(2 /* FreeOfHoles */);
|
|
2958
2851
|
// Initialize values
|
|
2959
2852
|
let bFreeOfHoles = true;
|
|
@@ -2984,7 +2877,7 @@ function doIsFreeOfHoles(s, bLog = false) {
|
|
|
2984
2877
|
exports.doIsFreeOfHoles = doIsFreeOfHoles;
|
|
2985
2878
|
// Test whether one district is embedded w/in any other.
|
|
2986
2879
|
function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
2987
|
-
//
|
|
2880
|
+
// TODO - Make "features" = "geoIDs." These aren't "features" proper, just
|
|
2988
2881
|
// identifier strings.
|
|
2989
2882
|
let features = geoIDs;
|
|
2990
2883
|
let planByGeo = plan.byGeoID();
|
|
@@ -2992,7 +2885,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
2992
2885
|
let bEmbedded = true;
|
|
2993
2886
|
// Keep track of the neighoring districts
|
|
2994
2887
|
let neighboringDistricts = new Set();
|
|
2995
|
-
// TODO -
|
|
2888
|
+
// TODO - Use just the boundary features, when available
|
|
2996
2889
|
// Get the features for the real district
|
|
2997
2890
|
let featuresToCheck = Array.from(features);
|
|
2998
2891
|
// If the district has features, check whether it is embedded
|
|
@@ -3018,7 +2911,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
3018
2911
|
break;
|
|
3019
2912
|
}
|
|
3020
2913
|
else {
|
|
3021
|
-
// TODO -
|
|
2914
|
+
// TODO - Since we're checking *all* features in a district right
|
|
3022
2915
|
// now, not just boundary features and neighbors in other districts,
|
|
3023
2916
|
// prune out the current district. If/when we optimize compactness
|
|
3024
2917
|
// to cache district boundaries (as before in my Python implementation),
|
|
@@ -3044,19 +2937,16 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
3044
2937
|
return bEmbedded;
|
|
3045
2938
|
}
|
|
3046
2939
|
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.
|
|
3047
2948
|
|
|
3048
2949
|
|
|
3049
|
-
/***/ }),
|
|
3050
|
-
|
|
3051
|
-
/***/ "./static/state-reqs.json":
|
|
3052
|
-
/*!********************************!*\
|
|
3053
|
-
!*** ./static/state-reqs.json ***!
|
|
3054
|
-
\********************************/
|
|
3055
|
-
/*! 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 */
|
|
3056
|
-
/***/ (function(module) {
|
|
3057
|
-
|
|
3058
|
-
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\"}");
|
|
3059
|
-
|
|
3060
2950
|
/***/ }),
|
|
3061
2951
|
|
|
3062
2952
|
/***/ "@dra2020/poly":
|