@dra2020/district-analytics 1.0.10 → 1.0.11

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