@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.
@@ -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
- // TODO - DASHBOARD: Delete
122
- // import {
123
- // doConfigureScales, doAnalyzePostProcessing
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
- results_1.doConfigureScales(this);
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, bLog);
158
+ preprocess_1.doPreprocessData(this);
172
159
  analyze_1.doAnalyzeDistricts(this, bLog);
173
160
  analyze_1.doAnalyzePlan(this, bLog);
174
- results_1.doAnalyzePostProcessing(this, bLog);
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
- // TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
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
- // prepareTestLog(): any {
230
- // return doPrepareTestLog(this);
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 */]['scale'][0];
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
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 async'ing this?
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
- // - Contiguous (bContiguous)
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
- // TODO - SPLITTING
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 the district
394
- // Guard against empty districts
347
+ // Get the geoIDs assigned to it ...
395
348
  let geoIDs = this._session.plan.geoIDsForDistrictID(i);
396
- if (geoIDs && (geoIDs.size > 0)) {
397
- bNotEmpty = true;
398
- // ... loop over the geoIDs creating district-by-district statistics
399
- geoIDs.forEach(function (geoID) {
400
- // Skip water-only features
401
- if (!(U.isWaterOnly(geoID))) {
402
- // Map from geoID to feature index
403
- let featureID = outerThis._session.features.featureID(geoID);
404
- let f = outerThis._session.features.featureByIndex(featureID);
405
- // ACCUMULATE VALUES
406
- // Total population of each feature
407
- // NOTE - This result is used more than once
408
- featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
409
- // Total district population
410
- totalPop += featurePop;
411
- // TODO - SPLITTING
412
- // Total population by counties w/in a district,
413
- // except the dummy unassigned district 0
414
- if (i > 0)
415
- countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
416
- // Democratic and Republican vote totals
417
- demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
418
- repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
419
- // Voting-age demographic breakdowns (or citizen voting-age)
420
- totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
421
- whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
422
- blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
423
- hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
424
- pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
425
- asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
426
- nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
427
- // 4 - MORE ...
428
- }
429
- // else console.log("Skipping water-only feature in district statistics:", geoID);
430
- });
431
- // COMPUTE DERIVED VALUES
432
- // Population deviation % and equal population (boolean) by district.
433
- // Don't set the values for the dummy unassigned district.
434
- let popDevPct = null;
435
- if (i > 0) {
436
- popDevPct = (totalPop - targetSize) / targetSize;
437
- bEqualPop = (popDevPct <= deviationThreshold);
438
- }
439
- // Total two-party (not total total!) votes, Democratic and Republican vote
440
- // shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
441
- let totVotes;
442
- let demPct = 0;
443
- let repPct = 0;
444
- let DemSeat = 0;
445
- totVotes = demVotes + repVotes;
446
- if (totVotes > 0) {
447
- demPct = demVotes / totVotes;
448
- repPct = repVotes / totVotes;
449
- DemSeat = political_1.fptpWin(demPct);
450
- }
451
- // Total minority VAP
452
- let minorityPop = totalVAP - whitePop;
453
- // Voting-age demographic proportions (or citizen voting-age)
454
- let whitePct = 0;
455
- let minorityPct = 0;
456
- let blackPct = 0;
457
- let hispanicPct = 0;
458
- let pacificPct = 0;
459
- let asianPct = 0;
460
- let nativePct = 0;
461
- if (totalVAP > 0) {
462
- whitePct = whitePop / totalVAP;
463
- minorityPct = minorityPop / totalVAP;
464
- blackPct = blackPop / totalVAP;
465
- hispanicPct = hispanicPop / totalVAP;
466
- pacificPct = pacificPop / totalVAP;
467
- asianPct = asianPop / totalVAP;
468
- nativePct = nativePop / totalVAP;
469
- }
470
- // 5 - MORE ...
471
- // COMPUTE DISTRICT-LEVEL VALUES
472
- // Validations
473
- // Leave the default values for the dummy unassigned district,
474
- // and districts that are empty.
475
- if ((i > 0) && bNotEmpty) {
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
- geoIDForFeature(f) {
601
- // GEOIDs will be one of these properties
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 - RECALC: Does anything need to be recalc'd now when a dataset is changed?
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
- let v = (o['datasets'][datasetKey][p]);
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
- // TODO - UNASSIGNED
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, bLog);
814
- s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
815
- s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
816
- s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
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[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
827
- s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
828
- s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
829
- s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
830
- s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
831
- s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
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 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
838
- s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
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, bLog = false) {
854
- s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
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
- // SPLITTING of counties & districts
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
882
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
883
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
884
- // CALCULATE ENHANCED SQRT ENTROPY METRIC
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 SPLITTING FOR THE PLAN
1101
- // Get the county-district pivot ("splits")
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
- // Guard against empty district
1112
- if (countiesByDistrict[d]) {
1113
- if (countiesByDistrict[d][c] > 0) {
1114
- nCountiesInDistrict += 1;
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
- // Guard against empty district
1138
- if (countiesByDistrict[d]) {
1139
- if (countiesByDistrict[d][c] > 0) {
1140
- nPartitionsOverall += 1;
1141
- nCountyParts += 1;
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
- test['score'] = U.trim(unexpectedAffected);
1183
- test['details']['unexpectedSplits'] = unexpectedSplits;
1184
- test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
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.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
1188
- function doFindSplitVTDs(s, bLog = false) {
1189
- let test = s.getTest(10 /* VTDSplits */);
1190
- let splitVTDs = [];
1191
- // TODO - SPLITTING: Flesh this out, using Terry's virtual VTD's ...
1192
- test['score'] = splitVTDs.length;
1193
- test['details']['splitVTDs'] = splitVTDs;
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.doFindSplitVTDs = doFindSplitVTDs;
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
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
- // Guard against no shape and no properties
1312
- if (districtProps) {
1313
- let a = districtProps[0 /* Area */];
1314
- let d = districtProps[1 /* Diameter */];
1315
- let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
1316
- // Save each district score
1317
- scores.push(reock);
1318
- // Echo the results by district
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 ... for the shapes that exist!
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
- // Guard against no shape and no properties
1340
- if (districtProps) {
1341
- let a = districtProps[0 /* Area */];
1342
- let p = districtProps[2 /* Perimeter */];
1343
- let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
1344
- // Save each district score
1345
- scores.push(polsbyPopper);
1346
- // Echo the results by district
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 ... for the shapes that exist!
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 - TERRY: How do you get away w/o this?!?
1035
+ let j = i - 1; // TODO - Terry: How do you get away w/o this?!?
1362
1036
  let poly = s.districts.getShape(j);
1363
- // Guard against no shape for empty districts AND null shapes
1364
- let polyOptions = { noLatitudeCorrection: true };
1365
- let bNull = (!Poly.polyNormalize(poly, polyOptions));
1366
- if (poly && (!bNull)) {
1367
- // TODO - OPTIMIZE: Bundle these calls?
1368
- let area = geofeature_1.gfArea(poly);
1369
- let perimeter = geofeature_1.gfPerimeter(poly);
1370
- let diameter = geofeature_1.gfDiameter(poly);
1371
- let props = [0, 0, 0]; // TODO - TERRY?!?
1372
- props[0 /* Area */] = area;
1373
- props[1 /* Diameter */] = diameter;
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1435
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1436
- function doPopulationDeviation(s, bLog = false) {
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, bLog = false) {
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 = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1166
+ const Poly = __webpack_require__(/*! @dra2020/poly */ "@dra2020/poly");
1512
1167
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1513
- // TODO - TERRY: Confirm Cartesian calculations
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 - TERRY: Confirm Cartesian calculations
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 - TERRY: Confirm Cartesian calculations
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 - TERRY: Confirm Cartesian calculations
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
- function doMajorityMinorityDistricts(s, bLog = false) {
1648
- let test = s.getTest(16 /* MajorityMinorityDistricts */);
1649
- if (bLog)
1650
- console.log("TODO - Calculating # of majority-minority districts ...");
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
- // Sources for majority-minority info:
1655
- // - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
1656
- // TODO - 2020: Update/revise this, when the update comes out in September:
1657
- // - http://www.ncsl.org/Portals/1/Documents/Redistricting/Redistricting_2010.pdf
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1684
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
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, bLog = false) {
1705
- let test = s.getTest(11 /* SeatsBias */);
1706
- if (bLog)
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, bLog = false) {
1712
- let test = s.getTest(12 /* VotesBias */);
1713
- if (bLog)
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, bLog = false) {
1719
- let test = s.getTest(13 /* Responsiveness */);
1720
- if (bLog)
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, bLog = false) {
1726
- let test = s.getTest(14 /* ResponsiveDistricts */);
1727
- if (bLog)
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 - PARTISAN: This formula might need to be inverted for D vs. R +/-
1430
+ // TODO - This formula might need to be inverted for D vs. R +/-
1734
1431
  // TODO - Normalize the results.
1735
- function doEfficiencyGap(s, bLog = false) {
1736
- if (bLog)
1737
- console.log("TODO - Calculating the efficiency gap ...");
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1482
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
1794
1483
  // NOTE - Do preprocessing separately, so the constructor returns quickly.
1795
- function doPreprocessData(s, bLog = false) {
1484
+ function doPreprocessData(s) {
1796
1485
  // If necessary, do one-time preprocessing
1797
1486
  if (!s.bOneTimeProcessingDone) {
1798
- doPreprocessCountyFeatures(s, bLog);
1799
- doPreprocessCensus(s, bLog);
1800
- doPreprocessElection(s, bLog);
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, bLog = false) {
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, bLog = false) {
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
- // Skip water-only features
1831
- if (!(U.isWaterOnly(geoID))) {
1832
- let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
1833
- // Sum total population across the state
1834
- s.state.totalPop += value;
1835
- // Get the county FIPS code for the feature
1836
- let county = U.parseGeoID(geoID)['county'];
1837
- let countyFIPS = U.getFIPSFromCountyGeoID(county);
1838
- // If a subtotal for the county doesn't exist, initialize one
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
- // else {
1846
- // console.log("Skipping water-only feature in Census preprocessing:", geoID);
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, bLog = false) {
1911
- if (bLog)
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/results.ts":
1919
- /*!************************!*\
1920
- !*** ./src/results.ts ***!
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
- // TEMPLATES FOR UNFORMATTED ANALYTICS RESULTS
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1942
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
1943
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
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
- const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
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
- // TODO - SPLITTING
2388
- const unexpectedCountySplitsDefn = {
2389
- ID: 7 /* UnexpectedCountySplits */,
2390
- name: "Unexpected County Splits",
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.Number,
1669
+ externalType: TestType.Percentage,
1670
+ detailsFn: doPrepareCountySplitDetails,
2417
1671
  suites: [2 /* Best */]
2418
1672
  };
2419
1673
  const efficiencyGapDefn = {
2420
- ID: 15 /* EfficiencyGap */,
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 - SPLITTING
2436
- [7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
2437
- [10 /* VTDSplits */]: VTDSplitsDefn,
2438
- [8 /* CountySplitting */]: countySplittingDefn,
2439
- [9 /* DistrictSplitting */]: districtSplittingDefn,
2440
- // TODO - More tests ...
2441
- [15 /* EfficiencyGap */]: efficiencyGapDefn
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 popDevScale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
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 */] = { scale: popDevScale, bInvertRaw: true };
2459
- s.testScales[5 /* Reock */] = { scale: [0.25, 0.50] };
2460
- s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
2461
- const nDistricts = s.state.nDistricts;
2462
- const nCounties = s.counties.nCounties;
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, bLog = false) {
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
- normalizedScore = U.normalize(rawScore, s.testScales[testID]);
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'] = s.testScales[testID].scale;
1794
+ testResult['details']['scale'] = testScale;
2490
1795
  }
2491
1796
  }
2492
1797
  // Derive secondary tests
2493
- analyze_1.doDeriveSecondaryTests(s, bLog);
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 - TERRY/DAVE: Discuss
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 = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
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
- function normalize(rawScore, testScale) {
2633
- let rangeMin = testScale.scale[0];
2634
- let rangeMax = testScale.scale[1];
2635
- // Invert the raw value if necessary to make bigger = better
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, digits = undefined) {
2656
- if (digits == 0) {
2657
- return Math.round(fullFraction);
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 - TERRY, is this copesetic?
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 - TERRY, can these three be combined into a generic isEmpty() check?
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 - TRI-STATES: Map booleans to tri-states.
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 = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2825
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2826
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
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, bLog = false) {
2731
+ function doIsComplete(s) {
2832
2732
  let test = s.getTest(0 /* Complete */);
2833
- // Get the by-district results, including the dummy unassigned district,
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
- let unassignedFeatures = [];
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
- let emptyDistricts = [];
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
- // NOTE - I'm no longer checking for this, but DRA should!
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, bLog = false) {
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 - TERRY, why does this constructor need a <T> type specification?
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 - TERRY, is this the quickest/best way to do this?
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 - OPTIMIZE: This to take advantage of district boundary info, if/when
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, bLog = false) {
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
- // NOTE - "features" here = "geoIDs." These aren't "features" proper, just
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 - OPTIMIZE: Use just the boundary features, when available
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 - OPTIMIZE: Since we're checking *all* features in a district right
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":