@dra2020/district-analytics 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8952,25 +8952,12 @@ function sort(keys, values, left, right, compare) {
8952
8952
  //
8953
8953
  // THE NODE PACKAGE API
8954
8954
  //
8955
- var __importStar = (this && this.__importStar) || function (mod) {
8956
- if (mod && mod.__esModule) return mod;
8957
- var result = {};
8958
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
8959
- result["default"] = mod;
8960
- return result;
8961
- };
8962
8955
  Object.defineProperty(exports, "__esModule", { value: true });
8963
8956
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
8964
8957
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
8965
- // TODO - DASHBOARD: Delete
8966
- // import {
8967
- // doConfigureScales, doAnalyzePostProcessing
8968
- // doPrepareScorecard, doPrepareTestLog, Scorecard
8969
- // } from './report'
8970
- const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
8971
- const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
8972
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
8973
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
8958
+ const report_1 = __webpack_require__(/*! ./report */ "./src/report.ts");
8959
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
8960
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
8974
8961
  class AnalyticsSession {
8975
8962
  constructor(SessionRequest) {
8976
8963
  this.config = {};
@@ -8979,6 +8966,7 @@ class AnalyticsSession {
8979
8966
  this.bPostProcessingDone = false;
8980
8967
  this.testScales = {};
8981
8968
  this.tests = {};
8969
+ this.scorecard = {};
8982
8970
  this.title = SessionRequest['title'];
8983
8971
  this.legislativeDistricts = SessionRequest['legislativeDistricts'];
8984
8972
  this.config = this.processConfig(SessionRequest['config']);
@@ -8991,13 +8979,12 @@ class AnalyticsSession {
8991
8979
  // NOTE: I've pulled these out of the individual analytics to here. Eventually,
8992
8980
  // we could want them to passed into an analytics session as data, along with
8993
8981
  // everything else. For now, this keeps branching out of the main code.
8994
- results_1.doConfigureScales(this);
8982
+ report_1.doConfigureScales(this);
8995
8983
  }
8996
8984
  processConfig(config) {
8997
8985
  // NOTE - Session settings are required:
8998
8986
  // - Analytics suites can be defaulted to all with [], but
8999
8987
  // - Dataset keys must be explicitly specified with 'dataset'
9000
- // TODO - Remove this mechanism. Always run everything.
9001
8988
  let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
9002
8989
  // If the config passed in has no suites = [], use the default suites
9003
8990
  if (U.isArrayEmpty(config['suites'])) {
@@ -9012,10 +8999,10 @@ class AnalyticsSession {
9012
8999
  // analytics & validations, saving/updating the individual test results.
9013
9000
  analyzePlan(bLog = false) {
9014
9001
  try {
9015
- preprocess_1.doPreprocessData(this, bLog);
9002
+ preprocess_1.doPreprocessData(this);
9016
9003
  analyze_1.doAnalyzeDistricts(this, bLog);
9017
9004
  analyze_1.doAnalyzePlan(this, bLog);
9018
- results_1.doAnalyzePostProcessing(this, bLog);
9005
+ report_1.doAnalyzePostProcessing(this);
9019
9006
  }
9020
9007
  catch (_a) {
9021
9008
  console.log("Exception caught by analyzePlan()");
@@ -9023,36 +9010,6 @@ class AnalyticsSession {
9023
9010
  }
9024
9011
  return true;
9025
9012
  }
9026
- // NOTE - This assumes that analyzePlan() has been run!
9027
- getPlanAnalytics(bLog = false) {
9028
- return results_2.preparePlanAnalytics(this, bLog);
9029
- }
9030
- // NOTE - This assumes that analyzePlan() has been run!
9031
- getDistrictStatistics(bLog = false) {
9032
- return results_2.prepareDistrictStatistics(this, bLog);
9033
- }
9034
- // NOTE - This assumes that analyzePlan() has been run!
9035
- getDiscontiguousDistrictFeatures(bLog = false) {
9036
- // Get the (possibly empty) list of discontiguous district IDs
9037
- let contiguousTest = this.getTest(1 /* Contiguous */);
9038
- let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
9039
- // Convert them into a (possibly empty) list of features
9040
- let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
9041
- if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
9042
- for (let i = 0; i < discontiguousDistrictIDs.length; i++) {
9043
- let poly = this.districts.getShape(i);
9044
- discontiguousDistrictFeatures.features.push(poly);
9045
- }
9046
- }
9047
- return discontiguousDistrictFeatures;
9048
- }
9049
- // TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
9050
- // Prepare a scorecard for rendering
9051
- // NOTE - This assumes that analyzePlan() has been run!
9052
- // prepareScorecard(): any {
9053
- // return doPrepareScorecard(this);
9054
- // }
9055
- // HELPERS USED INTERNALLY
9056
9013
  // Get an individual test, so you can drive UI with the results.
9057
9014
  getTest(testID) {
9058
9015
  // Get the existing test entries
@@ -9067,15 +9024,18 @@ class AnalyticsSession {
9067
9024
  // Return a pointer to the the test entry for this test
9068
9025
  return this.tests[testID];
9069
9026
  }
9070
- // TODO - DASHBOARD: Delete, when cut over to the new analytics UI.
9027
+ // Prepare a scorecard for rendering
9028
+ // NOTE - This assumes that analyzePlan() has been run!
9029
+ prepareScorecard() {
9030
+ return report_1.doPrepareScorecard(this);
9031
+ }
9071
9032
  // Prepare test results for rendering
9072
9033
  // NOTE - This assumes that analyzePlan() has been run!
9073
- // prepareTestLog(): any {
9074
- // return doPrepareTestLog(this);
9075
- // }
9076
- // NOTE - Not sure why this has to be up here.
9034
+ prepareTestLog() {
9035
+ return report_1.doPrepareTestLog(this);
9036
+ }
9077
9037
  populationDeviationThreshold() {
9078
- return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
9038
+ return 1 - this.testScales[4 /* PopulationDeviation */]['testScale'][0];
9079
9039
  }
9080
9040
  }
9081
9041
  exports.AnalyticsSession = AnalyticsSession;
@@ -9095,16 +9055,8 @@ exports.AnalyticsSession = AnalyticsSession;
9095
9055
  //
9096
9056
  // DATA ABSTRACTION LAYER
9097
9057
  //
9098
- var __importStar = (this && this.__importStar) || function (mod) {
9099
- if (mod && mod.__esModule) return mod;
9100
- var result = {};
9101
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
9102
- result["default"] = mod;
9103
- return result;
9104
- };
9105
9058
  Object.defineProperty(exports, "__esModule", { value: true });
9106
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
9107
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
9059
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
9108
9060
  const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
9109
9061
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
9110
9062
  const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
@@ -9143,6 +9095,23 @@ var DistrictField;
9143
9095
  DistrictField[DistrictField["NativePct"] = 27] = "NativePct"; // Display
9144
9096
  // 1 - MORE ...
9145
9097
  })(DistrictField = exports.DistrictField || (exports.DistrictField = {}));
9098
+ // The fields to display in a District Statistics pane
9099
+ exports.DisplayFields = [
9100
+ DistrictField.TotalPop,
9101
+ DistrictField.PopDevPct,
9102
+ DistrictField.bEqualPop,
9103
+ DistrictField.bNotEmpty,
9104
+ DistrictField.bContiguous,
9105
+ DistrictField.bNotEmbedded,
9106
+ DistrictField.DemPct,
9107
+ DistrictField.WhitePct,
9108
+ DistrictField.MinorityPct,
9109
+ DistrictField.BlackPct,
9110
+ DistrictField.HispanicPct,
9111
+ DistrictField.PacificPct,
9112
+ DistrictField.AsianPct,
9113
+ DistrictField.NativePct
9114
+ ];
9146
9115
  class Districts {
9147
9116
  constructor(s, ds) {
9148
9117
  this._geoProperties = {};
@@ -9151,13 +9120,7 @@ class Districts {
9151
9120
  this.statistics = this.initStatistics();
9152
9121
  }
9153
9122
  getShape(i) { return this._shapes.features[i]; }
9154
- getGeoProperties(i) {
9155
- // Make sure the district shape exists & has geo properties
9156
- if (i in this._geoProperties)
9157
- return this._geoProperties[i];
9158
- else
9159
- return null;
9160
- }
9123
+ getGeoProperties(i) { return this._geoProperties[i]; }
9161
9124
  setGeoProperties(i, p) { this._geoProperties[i] = p; }
9162
9125
  numberOfColumns() { return U.countEnumValues(DistrictField); }
9163
9126
  // +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
@@ -9180,7 +9143,7 @@ class Districts {
9180
9143
  // TODO - Optimize for getting multiple properties from the same feature?
9181
9144
  // TODO - Optimize by only re-calc'ing districts that have changed?
9182
9145
  // In this case, special attention to getting county-splits right.
9183
- // TODO - Optimize by async'ing this?
9146
+ // TODO - Optimize by asyn'ing this?
9184
9147
  // TODO - Is there a way to do this programmatically off data? Does it matter?
9185
9148
  recalcStatistics(bLog = false) {
9186
9149
  // Compute these once per recalc cycle
@@ -9189,9 +9152,6 @@ class Districts {
9189
9152
  let planByDistrict = this._session.plan.byDistrictID();
9190
9153
  let plan = this._session.plan;
9191
9154
  let graph = this._session.graph;
9192
- // TODO - SPLITTING
9193
- // Add an extra 0th virtual county bucket for county-district splitting analysis
9194
- let nCountyBuckets = this._session.counties.nCounties + 1;
9195
9155
  // INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
9196
9156
  let stateTPVote = 0;
9197
9157
  let stateDemVote = 0;
@@ -9207,7 +9167,7 @@ class Districts {
9207
9167
  // NOTE - These plan-level booleans are set in their respective analytics:
9208
9168
  // - Equal population (bEqualPop)
9209
9169
  // - Complete (bNotEmpty)
9210
- // - Contiguous (bContiguous)
9170
+ // - Contiguos (bContiguous)
9211
9171
  // - Free of holes (bNotEmbedded)
9212
9172
  // 2 - MORE ...
9213
9173
  // Loop over the districts (including the dummy unassigned one)
@@ -9215,8 +9175,7 @@ class Districts {
9215
9175
  // INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
9216
9176
  let featurePop;
9217
9177
  let totalPop = 0;
9218
- // TODO - SPLITTING
9219
- let countySplits = U.initArray(nCountyBuckets, 0);
9178
+ let countySplits = U.initArray(this._session.counties.nCounties, 0);
9220
9179
  let demVotes = 0;
9221
9180
  let repVotes = 0;
9222
9181
  let totalVAP = 0;
@@ -9226,178 +9185,156 @@ class Districts {
9226
9185
  let pacificPop = 0;
9227
9186
  let asianPop = 0;
9228
9187
  let nativePop = 0;
9229
- // NOTE - Only report explicitly found validity issues
9230
- let bNotEmpty = false;
9231
- let bContiguous = true;
9232
- let bNotEmbedded = true;
9233
- let bEqualPop = true;
9234
9188
  // 3 - MORE ...
9235
9189
  // HACK - Because "this" gets ghosted inside the forEach loop below
9236
9190
  let outerThis = this;
9237
- // Get the geoIDs assigned to the district
9238
- // Guard against empty districts
9191
+ // Get the geoIDs assigned to it ...
9239
9192
  let geoIDs = this._session.plan.geoIDsForDistrictID(i);
9240
- if (geoIDs && (geoIDs.size > 0)) {
9241
- bNotEmpty = true;
9242
- // ... loop over the geoIDs creating district-by-district statistics
9243
- geoIDs.forEach(function (geoID) {
9244
- // Skip water-only features
9245
- if (!(U.isWaterOnly(geoID))) {
9246
- // Map from geoID to feature index
9247
- let featureID = outerThis._session.features.featureID(geoID);
9248
- let f = outerThis._session.features.featureByIndex(featureID);
9249
- // ACCUMULATE VALUES
9250
- // Total population of each feature
9251
- // NOTE - This result is used more than once
9252
- featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
9253
- // Total district population
9254
- totalPop += featurePop;
9255
- // TODO - SPLITTING
9256
- // Total population by counties w/in a district,
9257
- // except the dummy unassigned district 0
9258
- if (i > 0)
9259
- countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
9260
- // Democratic and Republican vote totals
9261
- demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
9262
- repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
9263
- // Voting-age demographic breakdowns (or citizen voting-age)
9264
- totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
9265
- whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
9266
- blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
9267
- hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
9268
- pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
9269
- asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
9270
- nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
9271
- // 4 - MORE ...
9272
- }
9273
- // else console.log("Skipping water-only feature in district statistics:", geoID);
9274
- });
9275
- // COMPUTE DERIVED VALUES
9276
- // Population deviation % and equal population (boolean) by district.
9277
- // Don't set the values for the dummy unassigned district.
9278
- let popDevPct = null;
9279
- if (i > 0) {
9280
- popDevPct = (totalPop - targetSize) / targetSize;
9281
- bEqualPop = (popDevPct <= deviationThreshold);
9282
- }
9283
- // Total two-party (not total total!) votes, Democratic and Republican vote
9284
- // shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
9285
- let totVotes;
9286
- let demPct = 0;
9287
- let repPct = 0;
9288
- let DemSeat = 0;
9289
- totVotes = demVotes + repVotes;
9290
- if (totVotes > 0) {
9291
- demPct = demVotes / totVotes;
9292
- repPct = repVotes / totVotes;
9293
- DemSeat = political_1.fptpWin(demPct);
9294
- }
9295
- // Total minority VAP
9296
- let minorityPop = totalVAP - whitePop;
9297
- // Voting-age demographic proportions (or citizen voting-age)
9298
- let whitePct = 0;
9299
- let minorityPct = 0;
9300
- let blackPct = 0;
9301
- let hispanicPct = 0;
9302
- let pacificPct = 0;
9303
- let asianPct = 0;
9304
- let nativePct = 0;
9305
- if (totalVAP > 0) {
9306
- whitePct = whitePop / totalVAP;
9307
- minorityPct = minorityPop / totalVAP;
9308
- blackPct = blackPop / totalVAP;
9309
- hispanicPct = hispanicPop / totalVAP;
9310
- pacificPct = pacificPop / totalVAP;
9311
- asianPct = asianPop / totalVAP;
9312
- nativePct = nativePop / totalVAP;
9313
- }
9314
- // 5 - MORE ...
9315
- // COMPUTE DISTRICT-LEVEL VALUES
9316
- // Validations
9317
- // Leave the default values for the dummy unassigned district,
9318
- // and districts that are empty.
9319
- if ((i > 0) && bNotEmpty) {
9193
+ // ... loop over them creating district-by-district statistics
9194
+ geoIDs.forEach(function (geoID) {
9195
+ // Map from geoID to feature index
9196
+ let featureID = outerThis._session.features.featureID(geoID);
9197
+ let f = outerThis._session.features.featureByIndex(featureID);
9198
+ // ACCUMULATE VALUES
9199
+ // Total population of each feature (used more than once)
9200
+ featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
9201
+ // Total district population
9202
+ totalPop += featurePop;
9203
+ // Total population by counties w/in a district
9204
+ countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
9205
+ // Democratic and Republican vote totals
9206
+ demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
9207
+ repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
9208
+ // Voting-age demographic breakdowns (or citizen voting-age)
9209
+ // Guard againt null/NaN values
9210
+ let _totalVAP = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
9211
+ let _whitePop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
9212
+ let _blackPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
9213
+ let _hispanicPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
9214
+ let _pacificPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
9215
+ let _asianPop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
9216
+ let _nativePop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
9217
+ if (_totalVAP)
9218
+ totalVAP += _totalVAP;
9219
+ if (_whitePop)
9220
+ whitePop += _whitePop;
9221
+ if (_blackPop)
9222
+ blackPop += _blackPop;
9223
+ if (_hispanicPop)
9224
+ hispanicPop += _hispanicPop;
9225
+ if (_pacificPop)
9226
+ pacificPop += _pacificPop;
9227
+ if (_asianPop)
9228
+ asianPop += _asianPop;
9229
+ if (_nativePop)
9230
+ nativePop += _nativePop;
9231
+ // TODO - DELETE
9232
+ // totalVAP += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.TotalPop);
9233
+ // whitePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.WhitePop);
9234
+ // blackPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.BlackPop);
9235
+ // hispanicPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.HispanicPop);
9236
+ // pacificPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.PacificPop);
9237
+ // asianPop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.AsianPop);
9238
+ // nativePop += outerThis._session.features.fieldForFeature(f, Dataset.VAP, FeatureField.NativePop);
9239
+ // 4 - MORE ...
9240
+ });
9241
+ // COMPUTE DERIVED VALUES
9242
+ // Population deviation % and equal population (boolean) by district.
9243
+ // Leave the values null for the dummy unassigned district.
9244
+ let popDevPct = null;
9245
+ let bEqualPop = null;
9246
+ if (i > 0) {
9247
+ popDevPct = (totalPop - targetSize) / targetSize;
9248
+ bEqualPop = (popDevPct <= deviationThreshold);
9249
+ }
9250
+ // Total two-party (not total total!) votes, Democratic and Republican vote
9251
+ // shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
9252
+ let totVotes;
9253
+ let demPct = 0;
9254
+ let repPct = 0;
9255
+ let DemSeat = 0;
9256
+ totVotes = demVotes + repVotes;
9257
+ if (totVotes > 0) {
9258
+ demPct = demVotes / totVotes;
9259
+ repPct = repVotes / totVotes;
9260
+ DemSeat = political_1.fptpWin(demPct);
9261
+ }
9262
+ // Total minority VAP
9263
+ let minorityPop = totalVAP - whitePop;
9264
+ // Voting-age demographic proportions (or citizen voting-age)
9265
+ let whitePct = 0;
9266
+ let minorityPct = 0;
9267
+ let blackPct = 0;
9268
+ let hispanicPct = 0;
9269
+ let pacificPct = 0;
9270
+ let asianPct = 0;
9271
+ let nativePct = 0;
9272
+ if (totalVAP > 0) {
9273
+ whitePct = whitePop / totalVAP;
9274
+ minorityPct = minorityPop / totalVAP;
9275
+ blackPct = blackPop / totalVAP;
9276
+ hispanicPct = hispanicPop / totalVAP;
9277
+ pacificPct = pacificPop / totalVAP;
9278
+ asianPct = asianPop / totalVAP;
9279
+ nativePct = nativePop / totalVAP;
9280
+ }
9281
+ // 5 - MORE ...
9282
+ // COMPUTE DISTRICT-LEVEL VALUES
9283
+ // Validations
9284
+ let bNotEmpty = (!U.isSetEmpty(geoIDs));
9285
+ let bContiguous = null;
9286
+ let bNotEmbedded = null;
9287
+ // Leave the values null for the dummy unassigned district,
9288
+ // and districts that are empty.
9289
+ if (i > 0) {
9290
+ if (bNotEmpty) {
9320
9291
  bContiguous = valid_1.isConnected(geoIDs, graph);
9321
9292
  bNotEmbedded = (!valid_1.isEmbedded(i, planByDistrict[i], plan, graph));
9322
9293
  }
9323
- // 6 - MORE ...
9324
- { // UPDATE THE DISTRICT STATISTICS
9325
- // NOTE - These are set below for both non-empty & empty districts:
9326
- // this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
9327
- // this.statistics[DistrictField.bContiguous][i] = bContiguous;
9328
- // this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
9329
- // this.statistics[DistrictField.TotalPop][i] = totalPop;
9330
- // this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
9331
- this.statistics[DistrictField.PopDevPct][i] = popDevPct;
9332
- this.statistics[DistrictField.DemVotes][i] = demVotes;
9333
- this.statistics[DistrictField.RepVotes][i] = repVotes;
9334
- this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
9335
- this.statistics[DistrictField.DemPct][i] = demPct;
9336
- this.statistics[DistrictField.RepPct][i] = repPct;
9337
- this.statistics[DistrictField.DemSeat][i] = DemSeat;
9338
- this.statistics[DistrictField.WhitePop][i] = whitePop;
9339
- this.statistics[DistrictField.MinorityPop][i] = minorityPop;
9340
- this.statistics[DistrictField.BlackPop][i] = blackPop;
9341
- this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
9342
- this.statistics[DistrictField.PacificPop][i] = pacificPop;
9343
- this.statistics[DistrictField.AsianPop][i] = asianPop;
9344
- this.statistics[DistrictField.NativePop][i] = nativePop;
9345
- this.statistics[DistrictField.TotalVAP][i] = totalVAP;
9346
- this.statistics[DistrictField.WhitePct][i] = whitePct;
9347
- this.statistics[DistrictField.MinorityPct][i] = minorityPct;
9348
- this.statistics[DistrictField.BlackPct][i] = blackPct;
9349
- this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
9350
- this.statistics[DistrictField.PacificPct][i] = pacificPct;
9351
- this.statistics[DistrictField.AsianPct][i] = asianPct;
9352
- this.statistics[DistrictField.NativePct][i] = nativePct;
9353
- }
9354
- // 7 - MORE ...
9355
- { // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
9356
- stateTPVote += totVotes;
9357
- stateDemVote += demVotes;
9358
- stateRepVote += repVotes;
9359
- stateVAPPop += totalVAP;
9360
- stateWhitePop += whitePop;
9361
- stateMinorityPop += minorityPop;
9362
- stateBlackPop += blackPop;
9363
- stateHispanicPop += hispanicPop;
9364
- statePacificPop += pacificPop;
9365
- stateAsianPop += asianPop;
9366
- stateNativePop += nativePop;
9367
- }
9368
- }
9369
- else { // If a district is empty, zero these results (vs. null)
9370
- this.statistics[DistrictField.PopDevPct][i] = 0.0;
9371
- this.statistics[DistrictField.DemVotes][i] = 0;
9372
- this.statistics[DistrictField.RepVotes][i] = 0;
9373
- this.statistics[DistrictField.TwoPartyVote][i] = 0;
9374
- this.statistics[DistrictField.DemPct][i] = 0;
9375
- this.statistics[DistrictField.RepPct][i] = 0;
9376
- this.statistics[DistrictField.DemSeat][i] = 0;
9377
- this.statistics[DistrictField.WhitePop][i] = 0;
9378
- this.statistics[DistrictField.MinorityPop][i] = 0;
9379
- this.statistics[DistrictField.BlackPop][i] = 0;
9380
- this.statistics[DistrictField.HispanicPop][i] = 0;
9381
- this.statistics[DistrictField.PacificPop][i] = 0;
9382
- this.statistics[DistrictField.AsianPop][i] = 0;
9383
- this.statistics[DistrictField.NativePop][i] = 0;
9384
- this.statistics[DistrictField.TotalVAP][i] = 0;
9385
- this.statistics[DistrictField.WhitePct][i] = 0;
9386
- this.statistics[DistrictField.MinorityPct][i] = 0;
9387
- this.statistics[DistrictField.BlackPct][i] = 0;
9388
- this.statistics[DistrictField.HispanicPct][i] = 0;
9389
- this.statistics[DistrictField.PacificPct][i] = 0;
9390
- this.statistics[DistrictField.AsianPct][i] = 0;
9391
- this.statistics[DistrictField.NativePct][i] = 0;
9392
- }
9393
- { // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
9394
- this.statistics[DistrictField.TotalPop][i] = totalPop;
9395
- this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
9396
- this.statistics[DistrictField.bContiguous][i] = bContiguous;
9397
- this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
9398
- this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
9399
- this.statistics[DistrictField.CountySplits][i] = countySplits;
9400
9294
  }
9295
+ // 6 - MORE ...
9296
+ // UPDATE THE DISTRICT STATISTICS
9297
+ this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
9298
+ this.statistics[DistrictField.bContiguous][i] = bContiguous;
9299
+ this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
9300
+ this.statistics[DistrictField.TotalPop][i] = totalPop;
9301
+ this.statistics[DistrictField.PopDevPct][i] = popDevPct;
9302
+ this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
9303
+ this.statistics[DistrictField.CountySplits][i] = countySplits;
9304
+ this.statistics[DistrictField.DemVotes][i] = demVotes;
9305
+ this.statistics[DistrictField.RepVotes][i] = repVotes;
9306
+ this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
9307
+ this.statistics[DistrictField.DemPct][i] = demPct;
9308
+ this.statistics[DistrictField.RepPct][i] = repPct;
9309
+ this.statistics[DistrictField.DemSeat][i] = DemSeat;
9310
+ this.statistics[DistrictField.WhitePop][i] = whitePop;
9311
+ this.statistics[DistrictField.MinorityPop][i] = minorityPop;
9312
+ this.statistics[DistrictField.BlackPop][i] = blackPop;
9313
+ this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
9314
+ this.statistics[DistrictField.PacificPop][i] = pacificPop;
9315
+ this.statistics[DistrictField.AsianPop][i] = asianPop;
9316
+ this.statistics[DistrictField.NativePop][i] = nativePop;
9317
+ this.statistics[DistrictField.TotalVAP][i] = totalVAP;
9318
+ this.statistics[DistrictField.WhitePct][i] = whitePct;
9319
+ this.statistics[DistrictField.MinorityPct][i] = minorityPct;
9320
+ this.statistics[DistrictField.BlackPct][i] = blackPct;
9321
+ this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
9322
+ this.statistics[DistrictField.PacificPct][i] = pacificPct;
9323
+ this.statistics[DistrictField.AsianPct][i] = asianPct;
9324
+ this.statistics[DistrictField.NativePct][i] = nativePct;
9325
+ // 7 - MORE ...
9326
+ // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
9327
+ stateTPVote += totVotes;
9328
+ stateDemVote += demVotes;
9329
+ stateRepVote += repVotes;
9330
+ stateVAPPop += totalVAP;
9331
+ stateWhitePop += whitePop;
9332
+ stateMinorityPop += minorityPop;
9333
+ stateBlackPop += blackPop;
9334
+ stateHispanicPop += hispanicPop;
9335
+ statePacificPop += pacificPop;
9336
+ stateAsianPop += asianPop;
9337
+ stateNativePop += nativePop;
9401
9338
  }
9402
9339
  // UPDATE STATE STATISTICS
9403
9340
  let summaryRow = this.numberOfRows() - 1;
@@ -9431,6 +9368,16 @@ class Districts {
9431
9368
  }
9432
9369
  }
9433
9370
  exports.Districts = Districts;
9371
+ exports.DatasetDescriptions = {
9372
+ D16F: "2016 ACS Total Population",
9373
+ D16T: "2016 ACS Voting Age Population",
9374
+ E16GPR: "2016 Presidential Election",
9375
+ D10F: "2010 Census Total Population",
9376
+ D10T: "2010 Voting Age Population",
9377
+ C16GCO: "2016 Presidential, US Senate, Governor, and AG election results"
9378
+ // TODO - What other potential datasets?
9379
+ // MORE ...
9380
+ };
9434
9381
  // Wrap data by feature, to abstract the specifics of the internal structure
9435
9382
  class Features {
9436
9383
  constructor(s, data, keys) {
@@ -9441,18 +9388,15 @@ class Features {
9441
9388
  }
9442
9389
  nFeatures() { return this._data.features.length; }
9443
9390
  featureByIndex(i) { return this._data.features[i]; }
9444
- geoIDForFeature(f) {
9445
- // GEOIDs will be one of these properties
9446
- let value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
9447
- return value;
9448
- }
9391
+ // TODO - Generalize this
9392
+ geoIDForFeature(f) { return f.properties['GEOID10']; }
9449
9393
  fieldForFeature(f, dt, fk) {
9450
9394
  let dk = this._keys[dt];
9451
9395
  return _getFeatures(f, dk, fk);
9452
9396
  }
9453
9397
  resetDataset(d, k) {
9454
9398
  this._keys[d] = k;
9455
- // TODO - RECALC: Does anything need to be recalc'd now when a dataset is changed?
9399
+ // TODO - Does anything need to be recalc'd now when a dataset is changed?
9456
9400
  }
9457
9401
  mapGeoIDsToFeatureIDs() {
9458
9402
  for (let i = 0; i < this._session.features.nFeatures(); i++) {
@@ -9486,15 +9430,12 @@ function _getFeatures(f, datasetKey, p) {
9486
9430
  return o[p];
9487
9431
  }
9488
9432
  else {
9489
- if (o['datasets'] && o['datasets'][datasetKey]) {
9490
- let v = (o['datasets'][datasetKey][p]);
9491
- if ((!(v == null)) && (!(v == undefined)))
9433
+ if (o['datasets'] && o['datasets'][datasetKey])
9434
+ if (o['datasets'][datasetKey][p])
9492
9435
  return o['datasets'][datasetKey][p];
9493
- }
9494
9436
  }
9495
9437
  }
9496
9438
  }
9497
- console.log(`${p} value undefined for ${f.properties['GEOID10']}!`);
9498
9439
  return undefined;
9499
9440
  }
9500
9441
  function _fGetJoined(f) {
@@ -9503,13 +9444,13 @@ function _fGetJoined(f) {
9503
9444
  // Wrap data by county, to abstract the specifics of the internal structure
9504
9445
  class Counties {
9505
9446
  constructor(s, data) {
9506
- this._countyNameLookup = {};
9507
9447
  this.index = {};
9508
- this.totalPopulation = [];
9509
9448
  this._session = s;
9510
9449
  this._data = data;
9511
9450
  this.nCounties = this._data.features.length;
9451
+ this._countyNameLookup = {};
9512
9452
  }
9453
+ // nCounties(): number { return this._data.features.length; }
9513
9454
  countyByIndex(i) { return this._data.features[i]; }
9514
9455
  propertyForCounty(f, pk) { return f.properties[pk]; }
9515
9456
  mapFIPSToName(fips, name) { this._countyNameLookup[fips] = name; }
@@ -9538,24 +9479,8 @@ class Plan {
9538
9479
  this._planByDistrictID = {};
9539
9480
  this.districtIDs = []; // Set when the plan in inverted
9540
9481
  }
9541
- // NOTE - DON'T remove water-only features from the plan, as they may be required
9542
- // for contiguity. Just skip them in aggregating district statistics.
9543
- // removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
9544
- // let newPlan = {} as T.PlanByGeoID;
9545
- // for (let geoID in plan) {
9546
- // // Remove water-only features
9547
- // if (!(U.isWaterOnly(geoID))) {
9548
- // newPlan[geoID] = plan[geoID];
9549
- // }
9550
- // else {
9551
- // console.log("Removing water-only feature", geoID);
9552
- // }
9553
- // }
9554
- // return newPlan;
9555
- // }
9556
9482
  invertPlan() {
9557
- // TODO - UNASSIGNED
9558
- this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
9483
+ this._planByDistrictID = U.invertPlan(this._planByGeoID);
9559
9484
  this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
9560
9485
  }
9561
9486
  initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
@@ -9565,49 +9490,12 @@ class Plan {
9565
9490
  geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
9566
9491
  }
9567
9492
  exports.Plan = Plan;
9568
- // Invert a feature assignment structure to sets of ids by district
9569
- function invertPlan(plan, s) {
9570
- let invertedPlan = {};
9571
- // Add a dummy 'unassigned' district
9572
- invertedPlan[S.NOT_ASSIGNED] = new Set();
9573
- // TODO - UNASSIGNED
9574
- // NOTE - The feature assignments coming from DRA do not include unassigned ones.
9575
- // - In the DRA-calling context, there's an analytics session with a reference
9576
- // to the features. Loop over all the features to find the unassigned ones,
9577
- // and add them to the dummy unassigned district explicitly.
9578
- // - In the CLI-calling context, there's no session (yet) but the plan is complete.
9579
- if (!(s == undefined)) {
9580
- for (let i = 0; i < s.features.nFeatures(); i++) {
9581
- let f = s.features.featureByIndex(i);
9582
- let geoID = s.features.geoIDForFeature(f);
9583
- // If the feature is NOT explicitly assigned to a district, add the geoID
9584
- // to the dummy unassigned district 0.
9585
- if (!(U.keyExists(geoID, plan)))
9586
- invertedPlan[S.NOT_ASSIGNED].add(geoID);
9587
- // TODO - WATER-ONLY: NOT skipping water-only features here, because we're
9588
- // not skipping them below when they are explicitly assigned in plans. Should
9589
- // we skip them in both places?
9590
- }
9591
- }
9592
- for (let geoID in plan) {
9593
- let districtID = plan[geoID];
9594
- // Make sure the set for the districtID exists
9595
- if (!(U.objectContains(invertedPlan, districtID))) {
9596
- invertedPlan[districtID] = new Set();
9597
- }
9598
- // Add the geoID to the districtID's set
9599
- invertedPlan[districtID].add(geoID);
9600
- if (U.isWaterOnly(geoID))
9601
- console.log("Water-only feature still in plan!", geoID);
9602
- }
9603
- return invertedPlan;
9604
- }
9605
- exports.invertPlan = invertPlan;
9606
9493
  class Graph {
9607
9494
  constructor(s, graph) {
9608
9495
  this._session = s;
9609
9496
  this._graph = graph;
9610
9497
  }
9498
+ // TODO - Rework this, when we support MIXED MAPS.
9611
9499
  peerNeighbors(node) {
9612
9500
  // Get the neighboring geoIDs connected to a geoID
9613
9501
  // Ignore the lengths of the shared borders (the values), for now
@@ -9644,20 +9532,19 @@ function doAnalyzeDistricts(s, bLog = false) {
9644
9532
  s.districts.extractDistrictShapeProperties(bLog);
9645
9533
  }
9646
9534
  exports.doAnalyzeDistricts = doAnalyzeDistricts;
9535
+ // TODO - I could make this table-driven, but I'm thinking that the explicit
9536
+ // calls might make chunking for aync easier.
9647
9537
  // Calculate the analytics & validations and cache the results
9648
9538
  // NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
9649
- // NOTE - I could make this table-driven, but I'm thinking that the explicit
9650
- // calls might make chunking for aync easier.
9651
9539
  function doAnalyzePlan(s, bLog = false) {
9652
- // TODO - Remove this mechanism. Always run all tests
9653
9540
  // Get the requested suites, and only execute those tests
9654
9541
  let requestedSuites = s.config['suites'];
9655
9542
  // Tests in the "Legal" suite, i.e., pass/ fail constraints
9656
9543
  if (requestedSuites.includes(0 /* Legal */)) {
9657
- s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
9658
- s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
9659
- s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
9660
- s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
9544
+ s.tests[0 /* Complete */] = valid_1.doIsComplete(s);
9545
+ s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s);
9546
+ s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s);
9547
+ s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s);
9661
9548
  // NOTE - I can't check whether a population deviation is legal or not, until
9662
9549
  // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
9663
9550
  // the given type of district (CD vs. LD). The EqualPopulation test is derived
@@ -9667,21 +9554,19 @@ function doAnalyzePlan(s, bLog = false) {
9667
9554
  }
9668
9555
  // Tests in the "Fair" suite
9669
9556
  if (requestedSuites.includes(1 /* Fair */)) {
9670
- s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
9671
- s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
9672
- s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
9673
- s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
9674
- s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
9675
- s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
9557
+ s.tests[9 /* SeatsBias */] = political_1.doSeatsBias(s);
9558
+ s.tests[10 /* VotesBias */] = political_1.doVotesBias(s);
9559
+ s.tests[11 /* Responsiveness */] = political_1.doResponsiveness(s);
9560
+ s.tests[12 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s);
9561
+ s.tests[13 /* EfficiencyGap */] = political_1.doEfficiencyGap(s);
9562
+ s.tests[14 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s);
9676
9563
  }
9677
9564
  // Tests in the "Best" suite, i.e., criteria for better/worse
9678
9565
  if (requestedSuites.includes(2 /* Best */)) {
9679
9566
  s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
9680
9567
  s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
9681
- s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
9682
- s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
9683
- s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
9684
- s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
9568
+ s.tests[7 /* CountySplits */] = cohesive_1.doCountySplits(s);
9569
+ s.tests[8 /* Complexity */] = cohesive_1.doPlanComplexity(s);
9685
9570
  }
9686
9571
  // Enable a Test Log and Scorecard to be generated
9687
9572
  s.bPlanAnalyzed = true;
@@ -9694,8 +9579,8 @@ exports.doAnalyzePlan = doAnalyzePlan;
9694
9579
  //
9695
9580
  // NOTE - Should this be conditionalized on the test suites requested?
9696
9581
  // Those are encapsulated in reports.ts right now, so not doing that.
9697
- function doDeriveSecondaryTests(s, bLog = false) {
9698
- s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
9582
+ function doDeriveSecondaryTests(s) {
9583
+ s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s);
9699
9584
  }
9700
9585
  exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
9701
9586
 
@@ -9712,237 +9597,19 @@ exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
9712
9597
  "use strict";
9713
9598
 
9714
9599
  //
9715
- // SPLITTING of counties & districts
9600
+ // "COHESIVE" - We're naming this category which is about county splitting.
9716
9601
  //
9717
- var __importStar = (this && this.__importStar) || function (mod) {
9718
- if (mod && mod.__esModule) return mod;
9719
- var result = {};
9720
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
9721
- result["default"] = mod;
9722
- return result;
9723
- };
9724
9602
  Object.defineProperty(exports, "__esModule", { value: true });
9725
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
9726
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
9727
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
9728
- // CALCULATE ENHANCED SQRT ENTROPY METRIC
9729
- function doCountySplitting(s, bLog = false) {
9730
- let test = s.getTest(8 /* CountySplitting */);
9731
- let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
9732
- let countyTotals = s.counties.totalPopulation;
9733
- let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
9734
- let f = calcCountyFractions(CxD, countyTotals);
9735
- let w = calcCountyWeights(countyTotals);
9736
- let SqEnt_DC = countySplitting(f, w, bLog);
9737
- let CxDreducedC = U.deepCopy(CxD);
9738
- reduceCSplits(CxDreducedC, districtTotals);
9739
- let fReduced = calcCountyFractions(CxDreducedC, countyTotals);
9740
- let wReduced = calcCountyWeights(countyTotals);
9741
- let SqEnt_DCreduced = countySplitting(fReduced, wReduced, bLog);
9742
- test['score'] = SqEnt_DCreduced;
9743
- test['details']['SqEnt_DC'] = SqEnt_DC;
9744
- return test;
9745
- }
9746
- exports.doCountySplitting = doCountySplitting;
9747
- function doDistrictSplitting(s, bLog = false) {
9748
- let test = s.getTest(9 /* DistrictSplitting */);
9749
- let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
9750
- let countyTotals = s.counties.totalPopulation;
9751
- let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
9752
- let g = calcDistrictFractions(CxD, districtTotals);
9753
- let x = calcDistrictWeights(districtTotals);
9754
- let SqEnt_CD = districtSplitting(g, x, bLog);
9755
- let CxDreducedD = U.deepCopy(CxD);
9756
- reduceDSplits(CxDreducedD, countyTotals);
9757
- let gReduced = calcDistrictFractions(CxDreducedD, districtTotals);
9758
- let xReduced = calcDistrictWeights(districtTotals);
9759
- let SqEnt_CDreduced = districtSplitting(gReduced, xReduced, bLog);
9760
- test['score'] = SqEnt_CDreduced;
9761
- test['details']['SqEnt_CD'] = SqEnt_CD;
9762
- return test;
9763
- }
9764
- exports.doDistrictSplitting = doDistrictSplitting;
9765
- // HELPERS
9766
- // Loop over all the county-district combos, skipping the virtual district 0
9767
- // and virtual county 0.
9768
- //
9769
- // NOTE - The county-district splits and the county & district totals may all,
9770
- // in general, be fractional/decimal numbers as opposed to integers, due to
9771
- // dissaggregation & re-aggregation. Hence, comparisons need to approximate
9772
- // equality.
9773
- // Consolidate districts (rows) consisting of just one county (column)
9774
- // UP into the dummy district (0).
9775
- function reduceCSplits(CxDreducedC, districtTotals) {
9776
- let nD = CxDreducedC.length;
9777
- let nC = CxDreducedC[0].length;
9778
- for (let j = 1; j < nC; j++) {
9779
- for (let i = 1; i < nD; i++) {
9780
- let split_total = CxDreducedC[i][j];
9781
- if (split_total > 0) {
9782
- if (areRoughlyEqual(split_total, districtTotals[i])) {
9783
- CxDreducedC[0][j] += split_total;
9784
- CxDreducedC[i][j] = 0;
9785
- }
9786
- }
9787
- }
9788
- }
9789
- }
9790
- // Consolidate whole counties (columns) in a district (row) LEFT into the
9791
- // dummy county (0).
9792
- function reduceDSplits(CxDreducedD, countyTotals) {
9793
- let nD = CxDreducedD.length;
9794
- let nC = CxDreducedD[0].length;
9795
- for (let i = 1; i < nD; i++) {
9796
- for (let j = 1; j < nC; j++) {
9797
- let split_total = CxDreducedD[i][j];
9798
- if (split_total > 0) {
9799
- if (areRoughlyEqual(split_total, countyTotals[j])) {
9800
- CxDreducedD[i][0] += split_total;
9801
- CxDreducedD[i][j] = 0;
9802
- }
9803
- }
9804
- }
9805
- }
9806
- }
9807
- function calcCountyWeights(countyTotals) {
9808
- let nC = countyTotals.length;
9809
- let cTotal = U.sumArray(countyTotals);
9810
- let w = U.initArray(nC, 0.0);
9811
- for (let j = 0; j < nC; j++) {
9812
- w[j] = countyTotals[j] / cTotal;
9813
- }
9814
- return w;
9815
- }
9816
- function calcDistrictWeights(districtTotals) {
9817
- let nD = districtTotals.length;
9818
- let dTotal = U.sumArray(districtTotals);
9819
- let x = U.initArray(nD, 0.0);
9820
- for (let i = 0; i < nD; i++) {
9821
- x[i] = districtTotals[i] / dTotal;
9822
- }
9823
- return x;
9824
- }
9825
- function calcCountyFractions(CxDreducedD, countyTotals) {
9826
- let nD = CxDreducedD.length;
9827
- let nC = CxDreducedD[0].length;
9828
- let f = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
9829
- for (let j = 0; j < nC; j++) {
9830
- for (let i = 0; i < nD; i++) {
9831
- if (countyTotals[j] > 0) {
9832
- f[i][j] = CxDreducedD[i][j] / countyTotals[j];
9833
- }
9834
- else {
9835
- f[i][j] = 0.0;
9836
- }
9837
- }
9838
- }
9839
- return f;
9840
- }
9841
- function calcDistrictFractions(CxDreducedC, districtTotals) {
9842
- let nD = CxDreducedC.length;
9843
- let nC = CxDreducedC[0].length;
9844
- let g = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
9845
- for (let j = 0; j < nC; j++) {
9846
- for (let i = 0; i < nD; i++) {
9847
- if (districtTotals[i] > 0) {
9848
- g[i][j] = CxDreducedC[i][j] / districtTotals[i];
9849
- }
9850
- else {
9851
- g[i][j] = 0.0;
9852
- }
9853
- }
9854
- }
9855
- return g;
9856
- }
9857
- // Deal with decimal census "counts" due to disagg/re-agg
9858
- function areRoughlyEqual(x, y) {
9859
- let delta = Math.abs(x - y);
9860
- let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
9861
- return result;
9862
- }
9863
- // For all districts in a county, sum the split score.
9864
- function countySplitScore(j, f, numD, bLog = false) {
9865
- let e = 0.0;
9866
- for (let i = 0; i < numD; i++) {
9867
- e += Math.sqrt(f[i][j]);
9868
- }
9869
- return e;
9870
- }
9871
- // For all counties, sum the weighted county splits.
9872
- function countySplitting(f, w, bLog = false) {
9873
- let numC = f[0].length;
9874
- let numD = f.length;
9875
- let e = 0.0;
9876
- for (let j = 0; j < numC; j++) {
9877
- let splitScore = countySplitScore(j, f, numD, bLog);
9878
- e += w[j] * splitScore;
9879
- if (bLog)
9880
- console.log("County splitting =", j, w[j], splitScore, e);
9881
- }
9882
- return U.trim(e, 3);
9883
- }
9884
- // For all counties in a district, sum the split score.
9885
- function districtSplitScore(i, g, numC, bLog = false) {
9886
- let e = 0.0;
9887
- for (let j = 0; j < numC; j++) {
9888
- e += Math.sqrt(g[i][j]);
9889
- }
9890
- return e;
9891
- }
9892
- // For all districts, sum the weighted district splits.
9893
- function districtSplitting(g, x, bLog = false) {
9894
- let numC = g[0].length;
9895
- let numD = g.length;
9896
- let e = 0.0;
9897
- for (let i = 0; i < numD; i++) {
9898
- let splitScore = districtSplitScore(i, g, numC, bLog);
9899
- e += x[i] * splitScore;
9900
- if (bLog)
9901
- console.log("District split score =", i, x[i], splitScore, e);
9902
- }
9903
- return U.trim(e, 3);
9904
- }
9905
- // ANALYZE SIMPLE COUNTY & VTD SPLITTING
9906
- /*
9907
-
9908
- Sample results for NC 2016 dongressional plan
9909
- ________________________________________________________________________________
9910
-
9911
- State: NC
9912
- Census: 2010
9913
- Total population: 9,535,483
9914
- Number of districts: 13
9915
- Target district size: 733,499
9916
- Number of counties: 100
9917
-
9918
- Equal Population: 11.24% deviation
9919
- Compactness: None
9920
- Proportionality: 27.67% gap
9921
- Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
9922
-
9923
- These counties are split unexpectedly:
9924
-
9925
- • Bladen
9926
- • Buncombe
9927
- • Catawba
9928
- • Cumberland
9929
- • Durham
9930
- • Guilford
9931
- • Iredell
9932
- • Johnston
9933
- • Pitt
9934
- • Rowan
9935
- • Wilson
9936
-
9937
- */
9938
- function doFindCountiesSplitUnexpectedly(s, bLog = false) {
9939
- let test = s.getTest(7 /* UnexpectedCountySplits */);
9603
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
9604
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
9605
+ function doCountySplits(s) {
9606
+ let test = s.getTest(7 /* CountySplits */);
9940
9607
  // THE THREE VALUES TO DETERMINE FOR A PLAN
9941
9608
  let unexpectedSplits = 0;
9942
9609
  let unexpectedAffected = 0;
9943
9610
  let countiesSplitUnexpectedly = [];
9944
- // FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
9945
- // Get the county-district pivot ("splits")
9611
+ // FIRST, ANALYZE THE COUNTY SPLITING FOR THE PLAN
9612
+ // Pivot census totals into county-district "splits"
9946
9613
  let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
9947
9614
  // countiesByDistrict = countiesByDistrict.slice(1, -1);
9948
9615
  // Find the single-county districts, i.e., districts NOT split across counties.
@@ -9952,13 +9619,10 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
9952
9619
  // See if there's only one county partition
9953
9620
  let nCountiesInDistrict = 0;
9954
9621
  for (let c = 0; c < s.counties.nCounties; c++) {
9955
- // Guard against empty district
9956
- if (countiesByDistrict[d]) {
9957
- if (countiesByDistrict[d][c] > 0) {
9958
- nCountiesInDistrict += 1;
9959
- if (nCountiesInDistrict > 1) {
9960
- break;
9961
- }
9622
+ if (countiesByDistrict[d][c] > 0) {
9623
+ nCountiesInDistrict += 1;
9624
+ if (nCountiesInDistrict > 1) {
9625
+ break;
9962
9626
  }
9963
9627
  }
9964
9628
  }
@@ -9978,14 +9642,11 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
9978
9642
  let nCountyParts = 0;
9979
9643
  let subtotal = 0;
9980
9644
  for (let d = 1; d <= s.state.nDistricts; d++) {
9981
- // Guard against empty district
9982
- if (countiesByDistrict[d]) {
9983
- if (countiesByDistrict[d][c] > 0) {
9984
- nPartitionsOverall += 1;
9985
- nCountyParts += 1;
9986
- if (!(U.arrayContains(singleCountyDistricts, d))) {
9987
- subtotal += countiesByDistrict[d][c];
9988
- }
9645
+ if (countiesByDistrict[d][c] > 0) {
9646
+ nPartitionsOverall += 1;
9647
+ nCountyParts += 1;
9648
+ if (!(U.arrayContains(singleCountyDistricts, d))) {
9649
+ subtotal += countiesByDistrict[d][c];
9989
9650
  }
9990
9651
  }
9991
9652
  }
@@ -10023,21 +9684,48 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
10023
9684
  countiesSplitUnexpectedly.push(s.counties.nameFromFIPS(fips));
10024
9685
  }
10025
9686
  countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
10026
- test['score'] = U.trim(unexpectedAffected);
10027
- test['details']['unexpectedSplits'] = unexpectedSplits;
10028
- test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
9687
+ // Cache the results in the test
9688
+ test['score'] = unexpectedAffected; // TODO - Use Moon's complexity metric here
9689
+ test['details'] = {
9690
+ 'unexpectedSplits': unexpectedSplits,
9691
+ 'unexpectedAffected': unexpectedAffected,
9692
+ 'countiesSplitUnexpectedly': countiesSplitUnexpectedly
9693
+ };
10029
9694
  return test;
10030
9695
  }
10031
- exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
10032
- function doFindSplitVTDs(s, bLog = false) {
10033
- let test = s.getTest(10 /* VTDSplits */);
10034
- let splitVTDs = [];
10035
- // TODO - SPLITTING: Flesh this out, using Terry's virtual VTD's ...
10036
- test['score'] = splitVTDs.length;
10037
- test['details']['splitVTDs'] = splitVTDs;
9696
+ exports.doCountySplits = doCountySplits;
9697
+ // 2 - THE COMPLEXITY ANALYTIC NEEDS THE FOLLOWING DATA:
9698
+ //
9699
+ // If a map is already in simplified (mixed) form, the complexity analytic needs
9700
+ // two pieces of data:
9701
+ // - The counts of features by summary level--i.e., the numbers of counties, tracts,
9702
+ // block groups, and blocks in a state; and
9703
+ // - The map -- So it can count the features by summary level in the map,
9704
+ // as well as the number of BG’s that are split.
9705
+ //
9706
+ // TODO - Where would the state counts come from? Preprocessed and passed in, or
9707
+ // done in a one-time initialization call (which would require a full set of
9708
+ // block geo_id's for the state).
9709
+ //
9710
+ // However, if a map is not yet (fully) simplified, then determining the
9711
+ // complexity of a map also requires a preprocessed summary level hierarchy, so
9712
+ // you can get the child features (e.g., tracts) of a parent feature (e.g.,
9713
+ // a county).
9714
+ //
9715
+ // NOTE - I have script for producing this hierarchy which we could repurpose.
9716
+ //
9717
+ // TODO - For mixed map processing--specfically to find the neighbors of a feature
9718
+ // that are actually in the map (as opposed to just neighbors at the same
9719
+ // summary level in the static graph)--you need a special hierarchy that
9720
+ // distinguishes between the 'interior' and 'edge children of a feature.
9721
+ //
9722
+ // NOTE - The script noted above does this.
9723
+ function doPlanComplexity(s) {
9724
+ let test = s.getTest(8 /* Complexity */);
9725
+ console.log("TODO - Calculating plan complexity ...");
10038
9726
  return test;
10039
9727
  }
10040
- exports.doFindSplitVTDs = doFindSplitVTDs;
9728
+ exports.doPlanComplexity = doPlanComplexity;
10041
9729
 
10042
9730
 
10043
9731
  /***/ }),
@@ -10054,17 +9742,9 @@ exports.doFindSplitVTDs = doFindSplitVTDs;
10054
9742
  //
10055
9743
  // COMPACT
10056
9744
  //
10057
- var __importStar = (this && this.__importStar) || function (mod) {
10058
- if (mod && mod.__esModule) return mod;
10059
- var result = {};
10060
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10061
- result["default"] = mod;
10062
- return result;
10063
- };
10064
9745
  Object.defineProperty(exports, "__esModule", { value: true });
10065
- const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
10066
9746
  const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
10067
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
9747
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
10068
9748
  // Measures of compactness compare district shapes to various ideally compact
10069
9749
  // benchmarks, such as circles. All else equal, more compact districts are better.
10070
9750
  //
@@ -10152,19 +9832,16 @@ function doReock(s, bLog = false) {
10152
9832
  let scores = [];
10153
9833
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
10154
9834
  let districtProps = s.districts.getGeoProperties(districtID);
10155
- // Guard against no shape and no properties
10156
- if (districtProps) {
10157
- let a = districtProps[0 /* Area */];
10158
- let d = districtProps[1 /* Diameter */];
10159
- let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
10160
- // Save each district score
10161
- scores.push(reock);
10162
- // Echo the results by district
10163
- if (bLog)
10164
- console.log("Reock for district", districtID, "=", reock);
10165
- }
10166
- }
10167
- // Populate the test entry ... for the shapes that exist!
9835
+ let a = districtProps[0 /* Area */];
9836
+ let d = districtProps[1 /* Diameter */];
9837
+ let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
9838
+ // Save each district score
9839
+ scores.push(reock);
9840
+ // Echo the results by district
9841
+ if (bLog)
9842
+ console.log("Reock for district", districtID, "=", reock);
9843
+ }
9844
+ // Populate the test entry
10168
9845
  let averageReock = U.avgArray(scores);
10169
9846
  test['score'] = U.trim(averageReock);
10170
9847
  test['details'] = {}; // TODO - Any details?
@@ -10180,19 +9857,16 @@ function doPolsbyPopper(s, bLog = false) {
10180
9857
  let scores = [];
10181
9858
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
10182
9859
  let districtProps = s.districts.getGeoProperties(districtID);
10183
- // Guard against no shape and no properties
10184
- if (districtProps) {
10185
- let a = districtProps[0 /* Area */];
10186
- let p = districtProps[2 /* Perimeter */];
10187
- let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
10188
- // Save each district score
10189
- scores.push(polsbyPopper);
10190
- // Echo the results by district
10191
- if (bLog)
10192
- console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
10193
- }
10194
- }
10195
- // Populate the test entry ... for the shapes that exist!
9860
+ let a = districtProps[0 /* Area */];
9861
+ let p = districtProps[2 /* Perimeter */];
9862
+ let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
9863
+ // Save each district score
9864
+ scores.push(polsbyPopper);
9865
+ // Echo the results by district
9866
+ if (bLog)
9867
+ console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
9868
+ }
9869
+ // Populate the test entry
10196
9870
  let averagePolsbyPopper = U.avgArray(scores);
10197
9871
  test['score'] = U.trim(averagePolsbyPopper);
10198
9872
  test['details'] = {}; // TODO - Any details?
@@ -10202,24 +9876,19 @@ exports.doPolsbyPopper = doPolsbyPopper;
10202
9876
  // HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
10203
9877
  function extractDistrictProperties(s, bLog = false) {
10204
9878
  for (let i = 1; i <= s.state.nDistricts; i++) {
10205
- let j = i - 1; // TODO - TERRY: How do you get away w/o this?!?
9879
+ let j = i - 1; // TODO - Terry: How do you get away w/o this?!?
10206
9880
  let poly = s.districts.getShape(j);
10207
- // Guard against no shape for empty districts AND null shapes
10208
- let polyOptions = { noLatitudeCorrection: true };
10209
- let bNull = (!Poly.polyNormalize(poly, polyOptions));
10210
- if (poly && (!bNull)) {
10211
- // TODO - OPTIMIZE: Bundle these calls?
10212
- let area = geofeature_1.gfArea(poly);
10213
- let perimeter = geofeature_1.gfPerimeter(poly);
10214
- let diameter = geofeature_1.gfDiameter(poly);
10215
- let props = [0, 0, 0]; // TODO - TERRY?!?
10216
- props[0 /* Area */] = area;
10217
- props[1 /* Diameter */] = diameter;
10218
- props[2 /* Perimeter */] = perimeter;
10219
- s.districts.setGeoProperties(i, props);
10220
- if (bLog)
10221
- console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
10222
- }
9881
+ // TODO - Bundle these calls?
9882
+ let area = geofeature_1.gfArea(poly);
9883
+ let perimeter = geofeature_1.gfPerimeter(poly);
9884
+ let diameter = geofeature_1.gfDiameter(poly);
9885
+ let props = [0, 0, 0]; // TODO - Terry?!?
9886
+ props[0 /* Area */] = area;
9887
+ props[1 /* Diameter */] = diameter;
9888
+ props[2 /* Perimeter */] = perimeter;
9889
+ s.districts.setGeoProperties(i, props);
9890
+ if (bLog)
9891
+ console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
10223
9892
  }
10224
9893
  }
10225
9894
  exports.extractDistrictProperties = extractDistrictProperties;
@@ -10267,17 +9936,10 @@ exports.extractDistrictProperties = extractDistrictProperties;
10267
9936
  //
10268
9937
  // EQUAL POPULATION
10269
9938
  //
10270
- var __importStar = (this && this.__importStar) || function (mod) {
10271
- if (mod && mod.__esModule) return mod;
10272
- var result = {};
10273
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10274
- result["default"] = mod;
10275
- return result;
10276
- };
10277
9939
  Object.defineProperty(exports, "__esModule", { value: true });
10278
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
10279
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
10280
- function doPopulationDeviation(s, bLog = false) {
9940
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
9941
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
9942
+ function doPopulationDeviation(s) {
10281
9943
  let test = s.getTest(4 /* PopulationDeviation */);
10282
9944
  // Compute the min, max, and average district populations,
10283
9945
  // excluding the dummy 'unassigned' 0 and N+1 summary "districts."
@@ -10306,7 +9968,7 @@ function doPopulationDeviation(s, bLog = false) {
10306
9968
  exports.doPopulationDeviation = doPopulationDeviation;
10307
9969
  // NOTE - This validity check is *derived* and depends on population deviation %
10308
9970
  // being computed (above) and normalized in test log & scorecard generation.
10309
- function doHasEqualPopulations(s, bLog = false) {
9971
+ function doHasEqualPopulations(s) {
10310
9972
  let test = s.getTest(3 /* EqualPopulation */);
10311
9973
  // Get the normalized population deviation %
10312
9974
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
@@ -10344,19 +10006,14 @@ exports.doHasEqualPopulations = doHasEqualPopulations;
10344
10006
  //
10345
10007
  // GEO-FEATURES UTILITIES
10346
10008
  //
10347
- var __importStar = (this && this.__importStar) || function (mod) {
10348
- if (mod && mod.__esModule) return mod;
10349
- var result = {};
10350
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10351
- result["default"] = mod;
10352
- return result;
10353
- };
10354
10009
  Object.defineProperty(exports, "__esModule", { value: true });
10355
- const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
10010
+ const Poly = __webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js");
10356
10011
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
10357
- // TODO - TERRY: Confirm Cartesian calculations
10012
+ // TODO - Terry: Confirm Cartesian calculations
10358
10013
  function gfArea(poly) {
10359
10014
  let area = _polygonArea(poly);
10015
+ // let polyOptions = { noLatitudeCorrection: false } DELETE
10016
+ // let area: number = Poly.polyArea(poly, polyOptions);
10360
10017
  return area;
10361
10018
  }
10362
10019
  exports.gfArea = gfArea;
@@ -10402,16 +10059,19 @@ function _polygonArea(poly) {
10402
10059
  }
10403
10060
  return a;
10404
10061
  }
10405
- // TODO - TERRY: Confirm Cartesian calculations
10062
+ // TODO - Terry: Confirm Cartesian calculations
10406
10063
  // The perimeter calculation already just computes cartesian distance if you
10407
10064
  // pass in the noLatitudeCorrection flag. You would need to divide by
10408
10065
  // Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
10409
10066
  function gfPerimeter(poly) {
10410
10067
  let perimeter = _polygonPerimeter(poly);
10068
+ // let polyOptions = { noLatitudeCorrection: true } // Cartesian distance
10069
+ // let perimeter: number = Poly.polyPerimeter(poly, polyOptions); DELETE
10411
10070
  return perimeter;
10071
+ // return perimeter / Poly.EARTH_RADIUS; DELETE
10412
10072
  }
10413
10073
  exports.gfPerimeter = gfPerimeter;
10414
- // TODO - TERRY: Confirm Cartesian calculations
10074
+ // TODO - Terry: Confirm Cartesian calculations
10415
10075
  // Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
10416
10076
  // NOTE: No conversion of degrees to radians!
10417
10077
  function _polygonPerimeter(poly) {
@@ -10423,8 +10083,10 @@ function _polygonPerimeter(poly) {
10423
10083
  let p = poly[i][0];
10424
10084
  for (let j = 0; j < p.length - 1; j++)
10425
10085
  perimeter += _distance(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1]);
10086
+ // perimeter += haversine(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1], options); DELETE
10426
10087
  if (p.length > 2 && (p[0][0] != p[p.length - 1][0] || p[0][1] != p[p.length - 1][1]))
10427
10088
  perimeter += _distance(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1]);
10089
+ // perimeter += haversine(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1], options); DELETE
10428
10090
  }
10429
10091
  return perimeter;
10430
10092
  }
@@ -10435,7 +10097,7 @@ function _distance(x1, y1, x2, y2) {
10435
10097
  d = Math.sqrt((dLat * dLat) + (dLon * dLon));
10436
10098
  return d;
10437
10099
  }
10438
- // TODO - TERRY: Confirm Cartesian calculations
10100
+ // TODO - Terry: Confirm Cartesian calculations
10439
10101
  // As I mentioned, the polyCircle code was already just treating the coordinate
10440
10102
  // system as Cartesian. I then did polyFromCircle to convert it to a polygon that
10441
10103
  // then could be passed to polyArea in order to take into account the projection.
@@ -10444,6 +10106,9 @@ function _distance(x1, y1, x2, y2) {
10444
10106
  function gfDiameter(poly) {
10445
10107
  let polyOptions = { noLatitudeCorrection: true }; // NO-OP
10446
10108
  let circle = Poly.polyToCircle(poly, polyOptions);
10109
+ // let circleArea: number = Poly.polyArea(Poly.polyFromCircle(circle, undefined, polyOptions), polyOptions);
10110
+ // let circleRadius: number = Math.sqrt(circleArea / Math.PI);
10111
+ // let diameter: number = circleRadius * 2; DELETE
10447
10112
  let diameter = circle.r * 2;
10448
10113
  return diameter;
10449
10114
  }
@@ -10465,17 +10130,61 @@ exports.gfDiameter = gfDiameter;
10465
10130
  // PROTECTS MINORITIES
10466
10131
  //
10467
10132
  Object.defineProperty(exports, "__esModule", { value: true });
10468
- function doMajorityMinorityDistricts(s, bLog = false) {
10469
- let test = s.getTest(16 /* MajorityMinorityDistricts */);
10470
- if (bLog)
10471
- console.log("TODO - Calculating # of majority-minority districts ...");
10133
+ // TODO - This definition is wrong. Need to fix it.
10134
+ //
10135
+ // MINORITY-PROTECTION ANALYTICS NEED THE FOLLOWING DATA:
10136
+ //
10137
+ // The TOTAL, WHITE, BLACK, and HISPANIC counts from the Census, aggregated by
10138
+ // district. We *might* also ultimately need TOTAL18, WHITE18, BLACK18, and
10139
+ // HISPANIC18 counts by feature for these analytics.
10140
+ //
10141
+ // The minority population of a feature will probably be calculated as everyone
10142
+ // exceot non-Hispanic Whites:
10143
+ //
10144
+ // MINORITY = TOTAL - (WHITE - HISPANIC)
10145
+ //
10146
+ // That could be calculated as part of preprocessing the Census data, or it
10147
+ // could be computed on the fly. Since it's derived data and the formula might
10148
+ // change, it's probably best to compute it on the fly.
10149
+ //
10150
+ // In addition to the Census extract, these analytics need:
10151
+ // - The # of districts in the map, for determining minority proportionality
10152
+ // - Minorities as a % of the total population; possibly the voting age share
10153
+ //
10154
+ // TODO - Is the # of districts passed as a parameter or inferred from the # in
10155
+ // the map?
10156
+ // TODO - Is minority share preprocessed once and passed as a parameter or
10157
+ // computed in a initialization routine?
10158
+ function doMajorityMinorityDistricts(s) {
10159
+ let test = s.getTest(14 /* MajorityMinorityDistricts */);
10160
+ console.log("TODO - Calculating # of majority-minority districts ...");
10472
10161
  return test;
10473
10162
  }
10474
10163
  exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
10475
- // Sources for majority-minority info:
10476
- // - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
10477
- // TODO - 2020: Update/revise this, when the update comes out in September:
10478
- // - http://www.ncsl.org/Portals/1/Documents/Redistricting/Redistricting_2010.pdf
10164
+ // SAVE THESE NOTES, IN CASE WE NEED TO REWORK HOW WE DO PERFORM THESE CALCS.
10165
+ // THEY REFLECT HOW I/ALEC DID THESE IN PYTHON.
10166
+ //
10167
+ // MINORITY-PROTECTION ANALYTICS WILL NEED THE FOLLOWING DATA,
10168
+ // IN ADDITION TO THE MAP (IDEALLY, GEO_IDS INDEXED BY DISTRICT_ID)
10169
+ //
10170
+ // Census data by geo_id - { total population | white | black | hispanic }
10171
+ //
10172
+ // The minority population of a feature will probably be calculated as the # of
10173
+ // non-White Hispanics:
10174
+ //
10175
+ // MINORITY = TOTAL - (WHITE - HISPANIC)
10176
+ //
10177
+ // That could be calculated as part of preprocessing the Census data, or it
10178
+ // could be computed on the fly.
10179
+ //
10180
+ // And probably:
10181
+ // 'districts' - The # of districts for determining minority proportionality.
10182
+ // 'minority_share' - Minorities as a % of the total population
10183
+ //
10184
+ // TODO - Is the # of districts passed as a parameter or inferred from the # in
10185
+ // the map?
10186
+ // TODO - Is minority share preprocessed once and passed as a parameter or
10187
+ // computed in a initialization routine?
10479
10188
 
10480
10189
 
10481
10190
  /***/ }),
@@ -10492,17 +10201,10 @@ exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
10492
10201
  //
10493
10202
  // FAIR/PROPORTIONAL
10494
10203
  //
10495
- var __importStar = (this && this.__importStar) || function (mod) {
10496
- if (mod && mod.__esModule) return mod;
10497
- var result = {};
10498
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10499
- result["default"] = mod;
10500
- return result;
10501
- };
10502
10204
  Object.defineProperty(exports, "__esModule", { value: true });
10503
10205
  const assert_1 = __webpack_require__(/*! assert */ "assert");
10504
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
10505
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
10206
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
10207
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
10506
10208
  // Partisan analytics need the following data:
10507
10209
  //
10508
10210
  // An "election model" by geo_id, where each item has 4 pieces of data:
@@ -10522,41 +10224,36 @@ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
10522
10224
  // I'm labelling this general concept a "Voter Preference Index (VPI)," a
10523
10225
  // conscious +1 letter play on Cook's "PVI" acronymn.
10524
10226
  // MEASURING BIAS & RESPONSIVENESS (NAGLE'S METHOD)
10525
- function doSeatsBias(s, bLog = false) {
10526
- let test = s.getTest(11 /* SeatsBias */);
10527
- if (bLog)
10528
- console.log("TODO - Calculating seats bias ...");
10227
+ function doSeatsBias(s) {
10228
+ let test = s.getTest(9 /* SeatsBias */);
10229
+ console.log("TODO - Calculating seats bias ...");
10529
10230
  return test;
10530
10231
  }
10531
10232
  exports.doSeatsBias = doSeatsBias;
10532
- function doVotesBias(s, bLog = false) {
10533
- let test = s.getTest(12 /* VotesBias */);
10534
- if (bLog)
10535
- console.log("TODO - Calculating votes bias ...");
10233
+ function doVotesBias(s) {
10234
+ let test = s.getTest(10 /* VotesBias */);
10235
+ console.log("TODO - Calculating votes bias ...");
10536
10236
  return test;
10537
10237
  }
10538
10238
  exports.doVotesBias = doVotesBias;
10539
- function doResponsiveness(s, bLog = false) {
10540
- let test = s.getTest(13 /* Responsiveness */);
10541
- if (bLog)
10542
- console.log("TODO - Calculating responsiveness ...");
10239
+ function doResponsiveness(s) {
10240
+ let test = s.getTest(11 /* Responsiveness */);
10241
+ console.log("TODO - Calculating responsiveness ...");
10543
10242
  return test;
10544
10243
  }
10545
10244
  exports.doResponsiveness = doResponsiveness;
10546
- function doResponsiveDistricts(s, bLog = false) {
10547
- let test = s.getTest(14 /* ResponsiveDistricts */);
10548
- if (bLog)
10549
- console.log("TODO - Calculating # of responsive districts ...");
10245
+ function doResponsiveDistricts(s) {
10246
+ let test = s.getTest(12 /* ResponsiveDistricts */);
10247
+ console.log("TODO - Calculating # of responsive districts ...");
10550
10248
  return test;
10551
10249
  }
10552
10250
  exports.doResponsiveDistricts = doResponsiveDistricts;
10553
10251
  // OTHER MEASURES OF PARTISAN BIAS
10554
- // TODO - PARTISAN: This formula might need to be inverted for D vs. R +/-
10252
+ // TODO - This formula might need to be inverted for D vs. R +/-
10555
10253
  // TODO - Normalize the results.
10556
- function doEfficiencyGap(s, bLog = false) {
10557
- if (bLog)
10558
- console.log("TODO - Calculating the efficiency gap ...");
10559
- let test = s.getTest(15 /* EfficiencyGap */);
10254
+ function doEfficiencyGap(s) {
10255
+ console.log("TODO - Calculating the efficiency gap ...");
10256
+ let test = s.getTest(13 /* EfficiencyGap */);
10560
10257
  // Get partisan statistics by districts.
10561
10258
  // Use Democratic votes, seats, and shares by convention.
10562
10259
  let DVotes = s.districts.statistics[D.DistrictField.DemVotes];
@@ -10603,25 +10300,17 @@ exports.fptpWin = fptpWin;
10603
10300
  //
10604
10301
  // PREPROCESS DATA
10605
10302
  //
10606
- var __importStar = (this && this.__importStar) || function (mod) {
10607
- if (mod && mod.__esModule) return mod;
10608
- var result = {};
10609
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10610
- result["default"] = mod;
10611
- return result;
10612
- };
10613
10303
  Object.defineProperty(exports, "__esModule", { value: true });
10614
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
10304
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
10615
10305
  // NOTE - Do preprocessing separately, so the constructor returns quickly.
10616
- function doPreprocessData(s, bLog = false) {
10306
+ function doPreprocessData(s) {
10617
10307
  // If necessary, do one-time preprocessing
10618
10308
  if (!s.bOneTimeProcessingDone) {
10619
- doPreprocessCountyFeatures(s, bLog);
10620
- doPreprocessCensus(s, bLog);
10621
- doPreprocessElection(s, bLog);
10309
+ doPreprocessCountyFeatures(s);
10310
+ doPreprocessCensus(s);
10311
+ doPreprocessElection(s);
10622
10312
  s.bOneTimeProcessingDone = true;
10623
10313
  }
10624
- // TODO - UNASSIGNED: Made both the planByGeoID & DistrictID are right
10625
10314
  // Invert the plan by district ID
10626
10315
  s.plan.invertPlan();
10627
10316
  // Create a map of geoIDs to feature IDs
@@ -10629,7 +10318,7 @@ function doPreprocessData(s, bLog = false) {
10629
10318
  }
10630
10319
  exports.doPreprocessData = doPreprocessData;
10631
10320
  // CREATE A FIPS CODE TO COUNTY NAME LOOKUP
10632
- function doPreprocessCountyFeatures(s, bLog = false) {
10321
+ function doPreprocessCountyFeatures(s) {
10633
10322
  for (let i = 0; i < s.counties.nCounties; i++) {
10634
10323
  let county = s.counties.countyByIndex(i);
10635
10324
  let fips = s.counties.propertyForCounty(county, 'COUNTYFP');
@@ -10638,58 +10327,29 @@ function doPreprocessCountyFeatures(s, bLog = false) {
10638
10327
  }
10639
10328
  }
10640
10329
  // ANALYZE THE CENSUS BY COUNTY
10641
- function doPreprocessCensus(s, bLog = false) {
10330
+ function doPreprocessCensus(s) {
10642
10331
  // The county-splitting analytic needs the following info, using NC as an example:
10643
10332
  // '_stateTotal' = The total state population, e.g., 9,535,483 for NC's 2010 Census
10644
10333
  // 'totalByCounty' = The total population by county FIPS code
10645
- // SUM TOTAL POPULATION BY COUNTY
10646
10334
  let totalByCounty = {};
10647
10335
  // NOTE - This works w/o GEOIDs, because you're looping over all features.
10648
10336
  for (let i = 0; i < s.features.nFeatures(); i++) {
10649
10337
  let f = s.features.featureByIndex(i);
10650
10338
  let geoID = s.features.geoIDForFeature(f);
10651
- // Skip water-only features
10652
- if (!(U.isWaterOnly(geoID))) {
10653
- let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
10654
- // Sum total population across the state
10655
- s.state.totalPop += value;
10656
- // Get the county FIPS code for the feature
10657
- let county = U.parseGeoID(geoID)['county'];
10658
- let countyFIPS = U.getFIPSFromCountyGeoID(county);
10659
- // If a subtotal for the county doesn't exist, initialize one
10660
- if (!(U.keyExists(countyFIPS, totalByCounty))) {
10661
- totalByCounty[countyFIPS] = 0;
10662
- }
10663
- // Sum total population by county
10664
- totalByCounty[countyFIPS] += value;
10665
- }
10666
- // else {
10667
- // console.log("Skipping water-only feature in Census preprocessing:", geoID);
10668
- // }
10339
+ let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
10340
+ // Sum total population across the state
10341
+ s.state.totalPop += value;
10342
+ // Get the county FIPS code for the feature
10343
+ let county = U.parseGeoID(geoID)['county'];
10344
+ let countyFIPS = U.getFIPSFromCountyGeoID(county);
10345
+ // If a subtotal for the county doesn't exist, initialize one
10346
+ if (!(U.keyExists(countyFIPS, totalByCounty))) {
10347
+ totalByCounty[countyFIPS] = 0;
10348
+ }
10349
+ // Sum total population by county
10350
+ totalByCounty[countyFIPS] += value;
10669
10351
  }
10670
10352
  // NOTE - The above could be replaced, if I got totals on county.geojson.
10671
- // CREATE A FIPS CODE-ORDINAL MAP
10672
- // Get the county FIPS codes
10673
- let fipsCodes = U.getObjectKeys(totalByCounty);
10674
- // Sort the results
10675
- fipsCodes = fipsCodes.sort();
10676
- // TODO - SPLITTING
10677
- // Add a dummy county, for county-district splitting analysis
10678
- fipsCodes.unshift('000');
10679
- // Create the ID-ordinal map
10680
- for (let i in fipsCodes) {
10681
- s.counties.index[fipsCodes[i]] = Number(i);
10682
- }
10683
- // MAKE AN ARRAY OF TOTAL POPULATIONS BY COUNTY INDEX
10684
- // Add an extra 0th virtual county bucket for county-district splitting analysis
10685
- let nCountyBuckets = s.counties.nCounties + 1;
10686
- let countyTotals = U.initArray(nCountyBuckets, 0);
10687
- for (let fipsCode in totalByCounty) {
10688
- let i = s.counties.indexFromFIPS(fipsCode);
10689
- countyTotals[i] = totalByCounty[fipsCode];
10690
- }
10691
- s.counties.totalPopulation = countyTotals;
10692
- // ANALYZE THE COUNTIES
10693
10353
  // 'target_size': 733499, # calc as total / districts
10694
10354
  let targetSize = Math.round(s.state.totalPop / s.state.nDistricts);
10695
10355
  // Find counties that are bigger than the target district size.
@@ -10701,13 +10361,18 @@ function doPreprocessCensus(s, bLog = false) {
10701
10361
  let tooBigName = [];
10702
10362
  let expectedSplits = 0;
10703
10363
  let expectedAffected = 0;
10364
+ // Create a FIPS code-ordinal map
10365
+ // Get the county FIPS codes
10366
+ let fipsCodes = U.getObjectKeys(totalByCounty);
10367
+ // Sort the results
10368
+ fipsCodes = fipsCodes.sort();
10369
+ // Create the ID-ordinal map
10370
+ for (let i in fipsCodes) {
10371
+ s.counties.index[fipsCodes[i]] = Number(i);
10372
+ }
10704
10373
  // Loop over the counties
10705
10374
  for (let county in fipsCodes) {
10706
10375
  let fipsCode = fipsCodes[county];
10707
- // TODO - SPLITTING
10708
- // Skip the dummy county
10709
- if (fipsCode == '000')
10710
- continue;
10711
10376
  let countyAffected = 0;
10712
10377
  // Find the number of required splits, assuming target district size.
10713
10378
  let rawQuotient = totalByCounty[fipsCode] / (targetSize + 1);
@@ -10728,428 +10393,34 @@ function doPreprocessCensus(s, bLog = false) {
10728
10393
  s.state.expectedAffected = expectedAffected;
10729
10394
  }
10730
10395
  // PREPROCESS ELECTION RESULTS
10731
- function doPreprocessElection(s, bLog = false) {
10732
- if (bLog)
10733
- console.log("TODO - Preprocessing election data ...");
10396
+ function doPreprocessElection(s) {
10397
+ console.log("TODO - Preprocessing election data ...");
10734
10398
  }
10735
10399
 
10736
10400
 
10737
10401
  /***/ }),
10738
10402
 
10739
- /***/ "./src/results.ts":
10740
- /*!************************!*\
10741
- !*** ./src/results.ts ***!
10742
- \************************/
10403
+ /***/ "./src/report.ts":
10404
+ /*!***********************!*\
10405
+ !*** ./src/report.ts ***!
10406
+ \***********************/
10743
10407
  /*! no static exports found */
10744
10408
  /***/ (function(module, exports, __webpack_require__) {
10745
10409
 
10746
10410
  "use strict";
10747
10411
 
10748
10412
  //
10749
- // TEMPLATES FOR UNFORMATTED ANALYTICS RESULTS
10413
+ // GENERATE REPORTS
10414
+ // - A test log: a simple enumeration of all analytics & validations w/ raw results
10415
+ // - A scorecard: a structured subset of analytics & validations w/ normalized
10416
+ // results, cateories, and an overall score
10750
10417
  //
10751
- var __importStar = (this && this.__importStar) || function (mod) {
10752
- if (mod && mod.__esModule) return mod;
10753
- var result = {};
10754
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
10755
- result["default"] = mod;
10756
- return result;
10757
- };
10758
- var __importDefault = (this && this.__importDefault) || function (mod) {
10759
- return (mod && mod.__esModule) ? mod : { "default": mod };
10760
- };
10761
10418
  Object.defineProperty(exports, "__esModule", { value: true });
10762
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
10763
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
10764
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
10765
- // TODO - DASHBOARD: Delete
10766
- // import { doAnalyzePostProcessing } from './report'
10419
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
10420
+ const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
10421
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
10767
10422
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
10768
- const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
10769
- // Example
10770
- let sampleRequirements = {
10771
- score: 2 /* Red */,
10772
- metrics: {
10773
- complete: 0 /* Green */,
10774
- contiguous: 2 /* Red */,
10775
- freeOfHoles: 1 /* Yellow */,
10776
- equalPopulation: 2 /* Red */
10777
- },
10778
- details: {
10779
- unassignedFeatures: [],
10780
- emptyDistricts: [],
10781
- discontiguousDistricts: [2],
10782
- embeddedDistricts: [],
10783
- populationDeviation: 0.6748,
10784
- deviationThreshold: 0.75 / 100
10785
- },
10786
- datasets: {
10787
- census: "2010 Census Total Population"
10788
- },
10789
- resources: {
10790
- stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
10791
- }
10792
- };
10793
- let sampleCompactness = {
10794
- score: 60,
10795
- metrics: {
10796
- reock: 0.3773,
10797
- polsby: 0.3815
10798
- },
10799
- details: {},
10800
- datasets: {
10801
- shapes: "2010 VTD shapes"
10802
- },
10803
- resources: {}
10804
- };
10805
- let sampleSplitting = {
10806
- score: 73,
10807
- metrics: {
10808
- sqEnt_DCreduced: 1.531,
10809
- sqEnt_CDreduced: 1.760
10810
- },
10811
- details: {
10812
- countiesSplitUnexpectedly: [
10813
- "Bladen", "Buncombe", "Catawba", "Cumberland", "Durham", "Guilford", "Iredell", "Johnston", "Pitt", "Rowan", "Wilson"
10814
- ],
10815
- unexpectedAffected: 0.3096,
10816
- nSplitVTDs: 12,
10817
- splitVTDs: []
10818
- },
10819
- datasets: {},
10820
- resources: {}
10821
- };
10822
- // TODO - This category is still being fleshed out.
10823
- let samplePartisan = {
10824
- score: 100,
10825
- metrics: {
10826
- partisanBias: 0.15,
10827
- responsiveness: 2.0
10828
- },
10829
- details: {},
10830
- datasets: {
10831
- election: "2016 Presidential, US Senate, Governor, and AG election results"
10832
- },
10833
- resources: {
10834
- planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
10835
- }
10836
- };
10837
- // TODO - This category is still being fleshed out.
10838
- let sampleMinority = {
10839
- score: null,
10840
- metrics: {
10841
- nBlack37to50: 1,
10842
- nBlackMajority: 12,
10843
- nHispanic37to50: 0,
10844
- nHispanicMajority: 0,
10845
- nPacific37to50: 0,
10846
- nPacificMajority: 0,
10847
- nAsian37to50: 0,
10848
- nAsianMajority: 0,
10849
- nNative37to50: 0,
10850
- nNativeMajority: 0,
10851
- nMinority37to50: 0,
10852
- nMinorityMajority: 0,
10853
- averageDVoteShare: 0.90
10854
- },
10855
- details: {
10856
- vap: true,
10857
- comboCategories: true
10858
- },
10859
- datasets: {
10860
- vap: "2010 Voting Age Population"
10861
- },
10862
- resources: {}
10863
- };
10864
- exports.samplePlanAnalytics = {
10865
- requirements: sampleRequirements,
10866
- compactness: sampleCompactness,
10867
- // TODO - Don't show these categories yet
10868
- splitting: sampleSplitting,
10869
- partisan: samplePartisan,
10870
- minority: sampleMinority
10871
- };
10872
- function preparePlanAnalytics(s, bLog = false) {
10873
- if (!(s.bPostProcessingDone)) {
10874
- doAnalyzePostProcessing(s);
10875
- }
10876
- // REQUIREMENTS CATEGORY
10877
- let paRequirements;
10878
- {
10879
- let completeTest = s.getTest(0 /* Complete */);
10880
- let contiguousTest = s.getTest(1 /* Contiguous */);
10881
- let freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
10882
- let equalPopulationTest = s.getTest(3 /* EqualPopulation */);
10883
- // Combine individual checks into an overall score
10884
- // TODO - DASHBOARD: Until we add three-state support top to bottom in
10885
- // requirements/validations, map booleans to tri-states here.
10886
- let completeMetric = U.mapBooleanToTriState(completeTest['score']);
10887
- let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
10888
- let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
10889
- let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
10890
- let reqScore = 0 /* Green */;
10891
- let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
10892
- if (checks.includes(1 /* Yellow */))
10893
- reqScore = 1 /* Yellow */;
10894
- if (checks.includes(2 /* Red */))
10895
- reqScore = 2 /* Red */;
10896
- // Get values to support details entries
10897
- let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
10898
- let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
10899
- let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
10900
- let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
10901
- let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
10902
- let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
10903
- let xx = s.state.xx;
10904
- // TODO - JSON: Is there a better / easier way to work with the variable?
10905
- let stateReqsDict = state_reqs_json_1.default;
10906
- let reqLinkToStateReqs = stateReqsDict[xx];
10907
- // Populate the category
10908
- paRequirements = {
10909
- score: reqScore,
10910
- metrics: {
10911
- complete: completeMetric,
10912
- contiguous: contiguousMetric,
10913
- freeOfHoles: freeOfHolesMetric,
10914
- equalPopulation: equalPopulationMetric
10915
- },
10916
- details: {
10917
- unassignedFeatures: unassignedFeaturesDetail,
10918
- emptyDistricts: emptyDistrictsDetail,
10919
- discontiguousDistricts: discontiguousDistrictsDetail,
10920
- embeddedDistricts: embeddedDistrictsDetail,
10921
- populationDeviation: populationDeviationDetail,
10922
- deviationThreshold: deviationThresholdDetail
10923
- },
10924
- datasets: {
10925
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
10926
- },
10927
- resources: {
10928
- stateReqs: reqLinkToStateReqs
10929
- }
10930
- };
10931
- }
10932
- // COMPACTNESS CATEGORY
10933
- let paCompactness;
10934
- {
10935
- let reockWeight = 0.5;
10936
- let polsbyWeight = 1.0 - reockWeight;
10937
- let reockTest = s.getTest(5 /* Reock */);
10938
- let polsbyTest = s.getTest(6 /* PolsbyPopper */);
10939
- let normalizedReock = reockTest['normalizedScore'];
10940
- let normalizedPolsby = reockTest['normalizedScore'];
10941
- let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
10942
- let reockMetric = U.deepCopy(reockTest['score']);
10943
- let polsbyMetric = U.deepCopy(polsbyTest['score']);
10944
- // Populate the category
10945
- paCompactness = {
10946
- score: compactnessScore,
10947
- metrics: {
10948
- reock: reockMetric,
10949
- polsby: polsbyMetric
10950
- },
10951
- details: {
10952
- // None at this time
10953
- },
10954
- datasets: {
10955
- shapes: "2010 VTD shapes"
10956
- // TODO - DATASETS
10957
- // shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
10958
- },
10959
- resources: {
10960
- // None at this time
10961
- }
10962
- };
10963
- }
10964
- // SPLITTING CATEGORY
10965
- let paSplitting;
10966
- {
10967
- let unexpectedCountySplittingTest = s.getTest(7 /* UnexpectedCountySplits */);
10968
- let VTDSplitsTest = s.getTest(10 /* VTDSplits */);
10969
- let countySplittingTest = s.getTest(8 /* CountySplitting */);
10970
- let districtSplittingTest = s.getTest(9 /* DistrictSplitting */);
10971
- let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
10972
- let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
10973
- let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
10974
- let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
10975
- let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
10976
- let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
10977
- let countySplittingNormalized = countySplittingTest['normalizedScore'];
10978
- let districtSplittingNormalized = districtSplittingTest['normalizedScore'];
10979
- let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
10980
- +(S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
10981
- paSplitting = {
10982
- score: splittingScore,
10983
- metrics: {
10984
- sqEnt_DCreduced: SqEnt_DCreducedMetric,
10985
- sqEnt_CDreduced: SqEnt_CDreducedMetric
10986
- // NOTE - The un-reduced raw values
10987
- // sqEnt_DC : SqEnt_DCMetric,
10988
- // sqEnt_CD : SqEnt_CDMetric
10989
- },
10990
- details: {
10991
- countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
10992
- unexpectedAffected: unexpectedAffectedMetric,
10993
- nSplitVTDs: nVTDSplitsMetric,
10994
- splitVTDs: splitVTDsDetail
10995
- },
10996
- datasets: {
10997
- // None at this time
10998
- },
10999
- resources: {
11000
- // None at this time
11001
- }
11002
- };
11003
- }
11004
- // PARTISAN CATEGORY
11005
- //
11006
- // TODO - PARTISAN: This category is still being fleshed out. Just an example below.
11007
- let paPartisan;
11008
- {
11009
- paPartisan = {
11010
- score: 100,
11011
- metrics: {
11012
- partisanBias: 0.15,
11013
- responsiveness: 2.0
11014
- },
11015
- details: {},
11016
- datasets: {
11017
- election: "2016 Presidential, US Senate, Governor, and AG election results"
11018
- },
11019
- resources: {
11020
- planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
11021
- }
11022
- };
11023
- }
11024
- // MINORITY CATEGORY
11025
- //
11026
- // TODO - MINORITY: This category is still being fleshed out. Just an example below.
11027
- let paMinority;
11028
- {
11029
- paMinority = {
11030
- score: null,
11031
- metrics: {
11032
- nBlack37to50: 1,
11033
- nBlackMajority: 12,
11034
- nHispanic37to50: 0,
11035
- nHispanicMajority: 0,
11036
- nPacific37to50: 0,
11037
- nPacificMajority: 0,
11038
- nAsian37to50: 0,
11039
- nAsianMajority: 0,
11040
- nNative37to50: 0,
11041
- nNativeMajority: 0,
11042
- nMinority37to50: 0,
11043
- nMinorityMajority: 0,
11044
- averageDVoteShare: 0.90
11045
- },
11046
- details: {
11047
- vap: true,
11048
- comboCategories: true
11049
- },
11050
- datasets: {
11051
- vap: "2010 Voting Age Population"
11052
- },
11053
- resources: {}
11054
- };
11055
- }
11056
- // PLAN ANALYTICS
11057
- let pa = {
11058
- requirements: paRequirements,
11059
- compactness: paCompactness,
11060
- // TODO - Not implemented yet
11061
- splitting: paSplitting,
11062
- partisan: paPartisan,
11063
- minority: paMinority
11064
- };
11065
- return pa;
11066
- }
11067
- exports.preparePlanAnalytics = preparePlanAnalytics;
11068
- // Example
11069
- exports.sampleDistrictStatistics = {
11070
- table: [
11071
- // District 0 is the dummy unassigned district
11072
- [0, 0, 0, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
11073
- [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],
11074
- [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],
11075
- [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],
11076
- [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],
11077
- [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],
11078
- [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],
11079
- [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],
11080
- [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],
11081
- // District N+1 is the dummy state-summary district
11082
- [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]
11083
- ],
11084
- details: {},
11085
- datasets: {
11086
- shapes: "2010 VTD shapes",
11087
- census: "2010 Census Total Population",
11088
- vap: "2010 Voting Age Population",
11089
- election: "2016 Presidential, US Senate, Governor, and AG election results"
11090
- },
11091
- resources: {}
11092
- };
11093
- // Create a DistrictStatistics instance, deep copying the underlying values.
11094
- function prepareDistrictStatistics(s, bLog = false) {
11095
- if (!(s.bPostProcessingDone)) {
11096
- doAnalyzePostProcessing(s);
11097
- }
11098
- let dsTable = [];
11099
- for (let i = 0; i < s.districts.numberOfRows(); i++) {
11100
- let rawRow = [
11101
- i,
11102
- s.districts.statistics[D.DistrictField.TotalPop][i],
11103
- s.districts.statistics[D.DistrictField.PopDevPct][i],
11104
- s.districts.statistics[D.DistrictField.bEqualPop][i],
11105
- s.districts.statistics[D.DistrictField.bNotEmpty][i],
11106
- s.districts.statistics[D.DistrictField.bContiguous][i],
11107
- s.districts.statistics[D.DistrictField.bNotEmbedded][i],
11108
- s.districts.statistics[D.DistrictField.DemPct][i],
11109
- s.districts.statistics[D.DistrictField.RepPct][i],
11110
- s.districts.statistics[D.DistrictField.WhitePct][i],
11111
- s.districts.statistics[D.DistrictField.MinorityPct][i],
11112
- s.districts.statistics[D.DistrictField.BlackPct][i],
11113
- s.districts.statistics[D.DistrictField.HispanicPct][i],
11114
- s.districts.statistics[D.DistrictField.PacificPct][i],
11115
- s.districts.statistics[D.DistrictField.AsianPct][i],
11116
- s.districts.statistics[D.DistrictField.NativePct][i]
11117
- ];
11118
- // TODO - DASHBOARD: Until we add three-state support top to bottom in
11119
- // requirements/validations, map booleans to tri-states here.
11120
- rawRow[3 /* bEqualPop */] = U.mapBooleanToTriState(rawRow[3 /* bEqualPop */]);
11121
- rawRow[4 /* bNotEmpty */] = U.mapBooleanToTriState(rawRow[4 /* bNotEmpty */]);
11122
- rawRow[5 /* bContiguous */] = U.mapBooleanToTriState(rawRow[5 /* bContiguous */]);
11123
- rawRow[6 /* bNotEmbedded */] = U.mapBooleanToTriState(rawRow[6 /* bNotEmbedded */]);
11124
- let readyRow = U.deepCopy(rawRow);
11125
- dsTable.push(readyRow);
11126
- }
11127
- let dsDetails = {
11128
- // None at this time
11129
- };
11130
- let dsDatasets = {
11131
- shapes: "2010 VTD shapes",
11132
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
11133
- vap: U.deepCopy(s.config['descriptions']['VAP']),
11134
- election: U.deepCopy(s.config['descriptions']['ELECTION'])
11135
- };
11136
- let dsResources = {
11137
- // None at this time
11138
- };
11139
- let ds = {
11140
- table: dsTable,
11141
- details: dsDetails,
11142
- datasets: dsDatasets,
11143
- resources: dsResources
11144
- };
11145
- return ds;
11146
- }
11147
- exports.prepareDistrictStatistics = prepareDistrictStatistics;
11148
- // META-DATA FOR TESTS/ANALYTICS
11149
- //
11150
- // NOTE - This structure is a vestige of having created a metadata-driven
11151
- // scorecard w/in district-analytics at first. It works for creating the
11152
- // unstyled results structures, so it isn't a high priority to rationalize.
10423
+ // TEST META-DATA
11153
10424
  var TestType;
11154
10425
  (function (TestType) {
11155
10426
  TestType[TestType["PassFail"] = 0] = "PassFail";
@@ -11161,6 +10432,7 @@ const completeDefn = {
11161
10432
  name: "Complete",
11162
10433
  normalize: false,
11163
10434
  externalType: TestType.PassFail,
10435
+ detailsFn: doPrepareCompleteDetails,
11164
10436
  suites: [0 /* Legal */]
11165
10437
  };
11166
10438
  const contiguousDefn = {
@@ -11168,6 +10440,7 @@ const contiguousDefn = {
11168
10440
  name: "Contiguous",
11169
10441
  normalize: false,
11170
10442
  externalType: TestType.PassFail,
10443
+ detailsFn: doPrepareContiguousDetails,
11171
10444
  suites: [0 /* Legal */]
11172
10445
  };
11173
10446
  const freeOfHolesDefn = {
@@ -11175,6 +10448,7 @@ const freeOfHolesDefn = {
11175
10448
  name: "Free of Holes",
11176
10449
  normalize: false,
11177
10450
  externalType: TestType.PassFail,
10451
+ detailsFn: doPrepareFreeOfHolesDetails,
11178
10452
  suites: [0 /* Legal */]
11179
10453
  };
11180
10454
  const equalPopulationDefn = {
@@ -11182,6 +10456,7 @@ const equalPopulationDefn = {
11182
10456
  name: "Equal Population",
11183
10457
  normalize: false,
11184
10458
  externalType: TestType.PassFail,
10459
+ detailsFn: doPrepareEqualPopulationDetails,
11185
10460
  suites: [0 /* Legal */]
11186
10461
  };
11187
10462
  const populationDeviationDefn = {
@@ -11189,6 +10464,7 @@ const populationDeviationDefn = {
11189
10464
  name: "Population Deviation",
11190
10465
  normalize: true,
11191
10466
  externalType: TestType.Percentage,
10467
+ detailsFn: doPreparePopulationDeviationDetails,
11192
10468
  suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
11193
10469
  };
11194
10470
  const reockDefn = {
@@ -11196,6 +10472,7 @@ const reockDefn = {
11196
10472
  name: "Reock",
11197
10473
  normalize: true,
11198
10474
  externalType: TestType.Number,
10475
+ detailsFn: doPrepareReockDetails,
11199
10476
  suites: [2 /* Best */]
11200
10477
  };
11201
10478
  const polsbyPopperDefn = {
@@ -11203,45 +10480,24 @@ const polsbyPopperDefn = {
11203
10480
  name: "Polsby-Popper",
11204
10481
  normalize: true,
11205
10482
  externalType: TestType.Number,
10483
+ detailsFn: doPreparePolsbyPopperDetails,
11206
10484
  suites: [2 /* Best */]
11207
10485
  };
11208
- // TODO - SPLITTING
11209
- const unexpectedCountySplitsDefn = {
11210
- ID: 7 /* UnexpectedCountySplits */,
11211
- name: "Unexpected County Splits",
11212
- normalize: false,
11213
- externalType: TestType.Percentage,
11214
- suites: [2 /* Best */]
11215
- };
11216
- // TODO - SPLITTING
11217
- const VTDSplitsDefn = {
11218
- ID: 10 /* VTDSplits */,
11219
- name: "VTD Splits",
11220
- normalize: false,
11221
- externalType: TestType.Number,
11222
- suites: [2 /* Best */]
11223
- };
11224
- // TODO - SPLITTING
11225
- const countySplittingDefn = {
11226
- ID: 8 /* CountySplitting */,
11227
- name: "County Splitting",
11228
- normalize: true,
11229
- externalType: TestType.Number,
11230
- suites: [2 /* Best */]
11231
- };
11232
- // TODO - SPLITTING
11233
- const districtSplittingDefn = {
11234
- ID: 9 /* DistrictSplitting */,
11235
- name: "District Splitting",
10486
+ const countySplitsDefn = {
10487
+ ID: 7 /* CountySplits */,
10488
+ name: "County splits",
10489
+ trailer: "of the population had their county split unexpectedly.",
11236
10490
  normalize: true,
11237
- externalType: TestType.Number,
10491
+ externalType: TestType.Percentage,
10492
+ detailsFn: doPrepareCountySplitDetails,
11238
10493
  suites: [2 /* Best */]
11239
10494
  };
11240
10495
  const efficiencyGapDefn = {
11241
- ID: 15 /* EfficiencyGap */,
10496
+ ID: 13 /* EfficiencyGap */,
11242
10497
  name: "Efficiency Gap",
11243
10498
  normalize: false,
11244
10499
  externalType: TestType.Percentage,
10500
+ detailsFn: doPrepareEfficiencyGapDetails,
11245
10501
  suites: [1 /* Fair */]
11246
10502
  };
11247
10503
  // All the tests that have been defined (can be reported on)
@@ -11253,13 +10509,72 @@ const testDefns = {
11253
10509
  [4 /* PopulationDeviation */]: populationDeviationDefn,
11254
10510
  [5 /* Reock */]: reockDefn,
11255
10511
  [6 /* PolsbyPopper */]: polsbyPopperDefn,
11256
- // TODO - SPLITTING
11257
- [7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
11258
- [10 /* VTDSplits */]: VTDSplitsDefn,
11259
- [8 /* CountySplitting */]: countySplittingDefn,
11260
- [9 /* DistrictSplitting */]: districtSplittingDefn,
11261
- // TODO - More tests ...
11262
- [15 /* EfficiencyGap */]: efficiencyGapDefn
10512
+ // TODO - Flesh out county splits
10513
+ [7 /* CountySplits */]: countySplitsDefn,
10514
+ [13 /* EfficiencyGap */]: efficiencyGapDefn
10515
+ /* TODO - More tests ... */
10516
+ };
10517
+ // Scorecard category definitions
10518
+ const validCategory = {
10519
+ catName: "Map Validations",
10520
+ catTests: [
10521
+ { testID: 0 /* Complete */ },
10522
+ { testID: 1 /* Contiguous */ },
10523
+ { testID: 2 /* FreeOfHoles */ },
10524
+ { testID: 3 /* EqualPopulation */ }
10525
+ ],
10526
+ catNumeric: false,
10527
+ catWeight: undefined,
10528
+ catPrepareFn: doPrepareValidSection
10529
+ };
10530
+ const fairCategory = {
10531
+ // TODO - Change this label
10532
+ catName: "Is the map fair?",
10533
+ catTests: [
10534
+ {
10535
+ testID: 13 /* EfficiencyGap */,
10536
+ testWeight: 100
10537
+ }
10538
+ ],
10539
+ catNumeric: true,
10540
+ catWeight: undefined,
10541
+ catPrepareFn: doPrepareFairSection
10542
+ };
10543
+ // TODO - Decide on the relative weights of these tests!
10544
+ // NOTE: 'testWeights' are simply relative, i.e., each normalized score is
10545
+ // multiplied by the associated 'testWeight', and the sum of those is divided
10546
+ // by the total weight. Weights don't have to add to 100.
10547
+ const bestCategory = {
10548
+ catName: "Traditional Districting Principles",
10549
+ catTests: [
10550
+ {
10551
+ testID: 4 /* PopulationDeviation */,
10552
+ testWeight: 10
10553
+ },
10554
+ {
10555
+ testID: 5 /* Reock */,
10556
+ testWeight: 25
10557
+ },
10558
+ {
10559
+ testID: 6 /* PolsbyPopper */,
10560
+ testWeight: 25
10561
+ }
10562
+ // TODO - Re-enable, when the metric is implemented
10563
+ // {
10564
+ // testID: T.Test.CountySplits,
10565
+ // testWeight: 50
10566
+ // }
10567
+ ],
10568
+ catNumeric: true,
10569
+ catWeight: undefined,
10570
+ catPrepareFn: doPrepareBestSection
10571
+ };
10572
+ // The overall scorecard definition
10573
+ const scorecardDefn = {
10574
+ [0 /* Legal */]: validCategory,
10575
+ // TODO - NIY
10576
+ // [T.Suite.Fair]: fairCategory,
10577
+ [2 /* Best */]: bestCategory
11263
10578
  };
11264
10579
  // NORMALIZE RAW ANALYTICS
11265
10580
  // Raw numeric analytics, such as population deviation, compactness, etc. are
@@ -11274,29 +10589,19 @@ function doConfigureScales(s) {
11274
10589
  const LDLimit = 10.00 / 100; // Deviation threshold for LD's
11275
10590
  const CDGoodEnough = 0.20 / 100;
11276
10591
  const LDGoodEnough = (CDGoodEnough / CDLimit) * LDLimit;
11277
- const popDevScale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
10592
+ const scale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
11278
10593
  // const scale = [1.0 - CDLimit, 1.0 - CDGoodEnough];
11279
- s.testScales[4 /* PopulationDeviation */] = { scale: popDevScale, bInvertRaw: true };
11280
- s.testScales[5 /* Reock */] = { scale: [0.25, 0.50] };
11281
- s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
11282
- const nDistricts = s.state.nDistricts;
11283
- const nCounties = s.counties.nCounties;
11284
- // TODO - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
11285
- // number of county splits seems too stringent, empirically.
11286
- const allowableCountySplitsMultiplier = 1.5;
11287
- const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
11288
- const countySplittingThreshold = ((nAllowableSplits * 1.71) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
11289
- const countySplittingScale = [1.0, countySplittingThreshold];
11290
- s.testScales[8 /* CountySplitting */] = { scale: countySplittingScale, bInvertScaled: true };
11291
- const districtSplittingThreshold = 1.5;
11292
- const districtSplittingScale = [1.0, districtSplittingThreshold];
11293
- s.testScales[9 /* DistrictSplitting */] = { scale: districtSplittingScale, bInvertScaled: true };
10594
+ s.testScales[4 /* PopulationDeviation */] = { testScale: scale, testInvertp: true };
10595
+ s.testScales[5 /* Reock */] = { testScale: [0.25, 0.50], testInvertp: false };
10596
+ s.testScales[6 /* PolsbyPopper */] = { testScale: [0.10, 0.50], testInvertp: false };
10597
+ const SPLITLimit = 0.1; // TODO - Just a placeholder default maximum (10%)
10598
+ s.testScales[7 /* CountySplits */] = { testScale: [1.0 - SPLITLimit, 1.0], testInvertp: true };
11294
10599
  // TODO - More analytics ...
11295
10600
  }
11296
10601
  exports.doConfigureScales = doConfigureScales;
11297
10602
  // Postprocess analytics - Normalize numeric results and derive secondary tests.
11298
10603
  // Do this after analytics have been run and before preparing a test log or scorecard.
11299
- function doAnalyzePostProcessing(s, bLog = false) {
10604
+ function doAnalyzePostProcessing(s) {
11300
10605
  // Normalize the raw scores for all the numerics tests
11301
10606
  let testResults = U.getNumericObjectKeys(testDefns);
11302
10607
  for (let testID of testResults) {
@@ -11304,18 +10609,642 @@ function doAnalyzePostProcessing(s, bLog = false) {
11304
10609
  let testResult = s.getTest(testID);
11305
10610
  let rawScore = testResult['score'];
11306
10611
  let normalizedScore;
11307
- normalizedScore = U.normalize(rawScore, s.testScales[testID]);
10612
+ let { testScale, testInvertp } = s.testScales[testID];
10613
+ normalizedScore = U.normalize(rawScore, testScale, testInvertp);
11308
10614
  testResult['normalizedScore'] = normalizedScore;
11309
10615
  // Add the scale used to normalize the raw score to the details
11310
- testResult['details']['scale'] = s.testScales[testID].scale;
10616
+ testResult['details']['scale'] = testScale;
11311
10617
  }
11312
10618
  }
11313
10619
  // Derive secondary tests
11314
- analyze_1.doDeriveSecondaryTests(s, bLog);
10620
+ analyze_1.doDeriveSecondaryTests(s);
11315
10621
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
11316
10622
  s.bPostProcessingDone = true;
11317
10623
  }
11318
10624
  exports.doAnalyzePostProcessing = doAnalyzePostProcessing;
10625
+ // Prepare a structured but unformatted scorecard, from the test results
10626
+ function doGenerateScorecard(s) {
10627
+ if (!(s.bPostProcessingDone)) {
10628
+ doAnalyzePostProcessing(s);
10629
+ }
10630
+ // Create a new scorecard
10631
+ let scorecard = {};
10632
+ // Filter the defined scorecard categories by the requested test suites
10633
+ let categories = U.getNumericObjectKeys(scorecardDefn);
10634
+ let suitesRequested = s.config['suites'];
10635
+ categories = categories.filter(x => suitesRequested.includes(x));
10636
+ // ... and initialize each one in the new scorecard
10637
+ for (let c of categories) {
10638
+ scorecard[c] = {};
10639
+ scorecard[c]['catName'] = scorecardDefn[c]['catName'];
10640
+ scorecard[c]['catTests'] = {};
10641
+ // scorecard[c]['catScore'] = undefined;
10642
+ }
10643
+ // For each scorecard category
10644
+ for (let c of categories) {
10645
+ // Grab the scorecard category definition
10646
+ let { catName, catTests, catNumeric } = scorecardDefn[c];
10647
+ let numericCategoryScore = 0;
10648
+ let totalWeight = 0;
10649
+ let booleanCategoryScore = true;
10650
+ // Process the results for each test result in the category
10651
+ for (let testDefn of catTests) {
10652
+ // Get the config info for the test
10653
+ let testID = testDefn['testID'];
10654
+ // ... and the actual test result
10655
+ let testResult = s.getTest(testID);
10656
+ // Create a new test entry for the scorecard
10657
+ let testReport = U.deepCopy(testResult);
10658
+ // Add the name
10659
+ testReport['name'] = testDefns[testID]['name'];
10660
+ if (catNumeric) {
10661
+ // Normalize raw numeric scores ... moved to FIRST PASS above
10662
+ // Accumulate a category score
10663
+ let normalizedScore = testReport['normalizedScore'];
10664
+ numericCategoryScore += normalizedScore * testDefn['testWeight'];
10665
+ totalWeight += testDefn['testWeight'];
10666
+ }
10667
+ else {
10668
+ // AND together pass/fail tests into a category score
10669
+ if (!testReport['score']) {
10670
+ booleanCategoryScore = false;
10671
+ }
10672
+ }
10673
+ scorecard[c]['catTests'][testID] = testReport;
10674
+ }
10675
+ // Set the category score
10676
+ if (catNumeric) {
10677
+ scorecard[c]['catScore'] = Math.round(numericCategoryScore / totalWeight);
10678
+ }
10679
+ else {
10680
+ scorecard[c]['catScore'] = booleanCategoryScore;
10681
+ }
10682
+ }
10683
+ // TODO - Compute an overall score from the category weights
10684
+ return scorecard;
10685
+ }
10686
+ // Prepare a formatted scorecard suitable for rendering
10687
+ function doPrepareScorecard(s) {
10688
+ // Initialize the output format
10689
+ let text = { data: [] };
10690
+ let blocks = text.data;
10691
+ // If the plan as already been analyzed, prepare a scorecard
10692
+ if (s.bPlanAnalyzed) {
10693
+ // Create and cache a new, unformatted scorecard
10694
+ s.scorecard = doGenerateScorecard(s);
10695
+ // Create a scorecard header
10696
+ blocks.push({ variant: 'h4', text: `Analysis - NC 2019 Special Edition` });
10697
+ 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!` });
10698
+ blocks.push({ variant: 'body1', text: `For more details, see our blog post on Medium.` });
10699
+ // Prepare each scorecard category
10700
+ blocks.push({ variant: 'beginExpansion', text: `Overall Plan` });
10701
+ let categories = U.getNumericObjectKeys(s.scorecard);
10702
+ for (let c of categories) {
10703
+ let sectionPrepareFn = scorecardDefn[c]['catPrepareFn'];
10704
+ let sectionText = sectionPrepareFn(s, c);
10705
+ blocks.push(...sectionText);
10706
+ }
10707
+ // blocks.push({ variant: 'body1', text: `` });
10708
+ // 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.` });
10709
+ blocks.push({ variant: 'endExpansion' });
10710
+ // Report district statistics
10711
+ blocks.push({ variant: 'beginExpansion', text: `Individual Districts` });
10712
+ let districtStatisticsText = doPrepareDistrictStatistics(s);
10713
+ blocks.push(...districtStatisticsText);
10714
+ blocks.push({ variant: 'endExpansion' });
10715
+ // Report what datasets were used
10716
+ let c = s.config['datasets']["CENSUS" /* CENSUS */];
10717
+ let v = s.config['datasets']["VAP" /* VAP */];
10718
+ let e = s.config['datasets']["ELECTION" /* ELECTION */];
10719
+ blocks.push({ variant: 'beginExpansion', text: `About the Data` });
10720
+ blocks.push({ variant: 'body1', text: `These are the 4 datasets used in this analysis:` });
10721
+ // TODO - Get the shape "dataset" from dra-client
10722
+ blocks.push({ variant: 'body1', text: `* Shapes: 2010 VTD shapes` });
10723
+ blocks.push({ variant: 'body1', text: `* Census: ${D.DatasetDescriptions[c]}` });
10724
+ blocks.push({ variant: 'body1', text: `* VAP: ${D.DatasetDescriptions[v]}` });
10725
+ blocks.push({ variant: 'body1', text: `* Elections: ${D.DatasetDescriptions[e]}` });
10726
+ blocks.push({ variant: 'endExpansion' });
10727
+ blocks.push({ variant: 'body1', text: `` });
10728
+ blocks.push({
10729
+ "variant": "link",
10730
+ "text": "Questions or comments?",
10731
+ "label": "analytics@davesredistricting.org",
10732
+ "link": "mailto:analytics@davesredistricting.org"
10733
+ });
10734
+ }
10735
+ // Otherwise, return a blank scorecard
10736
+ return text;
10737
+ }
10738
+ exports.doPrepareScorecard = doPrepareScorecard;
10739
+ function doPrepareDistrictStatistics(s) {
10740
+ let text = { data: [] };
10741
+ let blocks = text.data;
10742
+ blocks.push({ variant: 'beginTable' });
10743
+ blocks.push({ variant: 'row', cells: ['ID', 'Total', 'Δ%', 'OK?', '*', 'Dem', 'Rep', 'White', 'Minority', 'Black', 'Hispanic', 'Pacific', 'Asian', 'Native'] });
10744
+ for (let d = 0; d < s.districts.numberOfRows(); d++) {
10745
+ let tot = s.districts.statistics[D.DistrictField.TotalPop][d];
10746
+ if (tot == 0)
10747
+ blocks.push({ variant: 'row', cells: [String(d), '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-'] });
10748
+ else {
10749
+ tot = Math.round(tot);
10750
+ let dev = fractionToPercentage(s.districts.statistics[D.DistrictField.PopDevPct][d]);
10751
+ let bEq = s.districts.statistics[D.DistrictField.bEqualPop][d];
10752
+ let bC = s.districts.statistics[D.DistrictField.bContiguous][d]
10753
+ && s.districts.statistics[D.DistrictField.bNotEmbedded][d];
10754
+ let dPct = fractionToPercentage(s.districts.statistics[D.DistrictField.DemPct][d]);
10755
+ let rPct = fractionToPercentage(s.districts.statistics[D.DistrictField.RepPct][d]);
10756
+ let wPct = fractionToPercentage(s.districts.statistics[D.DistrictField.WhitePct][d]);
10757
+ let mPct = fractionToPercentage(s.districts.statistics[D.DistrictField.MinorityPct][d]);
10758
+ let bPct = fractionToPercentage(s.districts.statistics[D.DistrictField.BlackPct][d]);
10759
+ let hPct = fractionToPercentage(s.districts.statistics[D.DistrictField.HispanicPct][d]);
10760
+ let pPct = fractionToPercentage(s.districts.statistics[D.DistrictField.PacificPct][d]);
10761
+ let aPct = fractionToPercentage(s.districts.statistics[D.DistrictField.AsianPct][d]);
10762
+ let nPct = fractionToPercentage(s.districts.statistics[D.DistrictField.NativePct][d]);
10763
+ let id;
10764
+ if (d == 0)
10765
+ id = "??";
10766
+ else if (d == (s.districts.numberOfRows() - 1))
10767
+ id = " ";
10768
+ else
10769
+ id = String(d);
10770
+ blocks.push({
10771
+ variant: 'row',
10772
+ cells: [
10773
+ `${id}`,
10774
+ `${formatInteger(tot)}`,
10775
+ `${formatPercentage(dev)}%`,
10776
+ `${pfBoolToString(bEq)}`,
10777
+ `${pfBoolToString(bC)}`,
10778
+ `${formatPercentage(dPct)}%`,
10779
+ `${formatPercentage(rPct)}%`,
10780
+ `${formatPercentage(wPct)}%`,
10781
+ `${formatPercentage(mPct)}%`,
10782
+ `${formatPercentage(bPct)}%`,
10783
+ `${formatPercentage(hPct)}%`,
10784
+ `${formatPercentage(pPct)}%`,
10785
+ `${formatPercentage(aPct)}%`,
10786
+ `${formatPercentage(nPct)}%`
10787
+ ]
10788
+ });
10789
+ }
10790
+ }
10791
+ blocks.push({ variant: 'endTable' });
10792
+ return blocks;
10793
+ }
10794
+ // TEST LOG
10795
+ // Prepare formatted test results for rendering
10796
+ function doPrepareTestLog(s) {
10797
+ // Initialize the output format
10798
+ let text = { data: [] };
10799
+ let blocks = text.data;
10800
+ // If the plan as already been analyzed, prepare a test log
10801
+ if (s.bPlanAnalyzed) {
10802
+ if (!(s.bPostProcessingDone)) {
10803
+ doAnalyzePostProcessing(s);
10804
+ }
10805
+ // Create a test log header
10806
+ blocks.push({ variant: 'h4', text: `Test Log` });
10807
+ let testResults = U.getNumericObjectKeys(testDefns);
10808
+ let suitesRequested = new Set(s.config['suites']);
10809
+ for (let testID of testResults) {
10810
+ // Filter the defined tests by the requested test suites
10811
+ let inSuites = testDefns[testID]['suites'];
10812
+ if (!(U.isArrayEmpty(inSuites.filter(x => suitesRequested.has(x))))) {
10813
+ // Get the test result
10814
+ let testResult = s.getTest(testID);
10815
+ // Prepare the text for it, and append it to the output
10816
+ let testText = prepareTestEntry(testID, testResult);
10817
+ blocks.push(...testText);
10818
+ }
10819
+ }
10820
+ }
10821
+ // Otherwise, return a blank test log
10822
+ return text;
10823
+ }
10824
+ exports.doPrepareTestLog = doPrepareTestLog;
10825
+ function prepareTestEntry(testID, testResult) {
10826
+ let text = { data: [] };
10827
+ let blocks = text.data;
10828
+ let testName = testDefns[testID]['name'];
10829
+ let testNameTrailer = "";
10830
+ if (U.keyExists('trailer', testDefns[testID])) {
10831
+ testNameTrailer = testDefns[testID]['trailer'];
10832
+ }
10833
+ let testType = testDefns[testID]['externalType'];
10834
+ let bNormalize = testDefns[testID]['normalize'];
10835
+ let detailsFn = testDefns[testID]['detailsFn'];
10836
+ let detailsText = detailsFn(testResult);
10837
+ let score; // NOTE - Won't be undefined here
10838
+ let normalizedScore;
10839
+ let scoreText;
10840
+ // Get the score ...
10841
+ score = testResult['score'];
10842
+ // ... and format it for rendering
10843
+ switch (testType) {
10844
+ case TestType.PassFail: {
10845
+ scoreText = pfBoolToString(score);
10846
+ blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${scoreText} ${testNameTrailer}` });
10847
+ break;
10848
+ }
10849
+ case TestType.Percentage: {
10850
+ score = fractionToPercentage(score);
10851
+ if (bNormalize) {
10852
+ normalizedScore = testResult['normalizedScore'];
10853
+ blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatPercentage(score)}% ${testNameTrailer}` });
10854
+ }
10855
+ else {
10856
+ blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatPercentage(score)}% ${testNameTrailer}` });
10857
+ }
10858
+ break;
10859
+ }
10860
+ case TestType.Number: {
10861
+ if (bNormalize) {
10862
+ normalizedScore = testResult['normalizedScore'];
10863
+ blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatNumber(score)} ${testNameTrailer}` });
10864
+ }
10865
+ else {
10866
+ blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatNumber(score)} ${testNameTrailer}` });
10867
+ }
10868
+ break;
10869
+ }
10870
+ default: {
10871
+ // Unknown test type
10872
+ throw new RangeError();
10873
+ }
10874
+ }
10875
+ // Add the details text
10876
+ blocks.push(...detailsText);
10877
+ return blocks;
10878
+ }
10879
+ // FORMATTERS FOR TEST DETAILS
10880
+ function doPrepareCompleteDetails(testResult) {
10881
+ let text = { data: [] };
10882
+ let blocks = text.data;
10883
+ if (!U.isObjectEmpty(testResult['details'])) {
10884
+ let unassignedText = "";
10885
+ let emptyText = "";
10886
+ let missingText = "";
10887
+ if (U.keyExists('unassignedFeatures', testResult['details'])) {
10888
+ let unassignedFeatures = testResult['details']['unassignedFeatures'];
10889
+ let unassignedList = prepareListItems(unassignedFeatures);
10890
+ let unassignedTextTemplates = [
10891
+ `GEOID ${unassignedList} is not assigned to a district.`,
10892
+ `GEOIDs ${unassignedList} are not assigned to districts.`,
10893
+ `Several GEOIDs are not assigned to districts, including ${unassignedList}.`
10894
+ ];
10895
+ unassignedText = prepareListText(unassignedFeatures, unassignedTextTemplates);
10896
+ }
10897
+ if (U.keyExists('emptyDistricts', testResult['details'])) {
10898
+ let emptyDistricts = testResult['details']['emptyDistricts'];
10899
+ let emptyList = prepareListItems(emptyDistricts);
10900
+ let emptyTextTemplates = [
10901
+ `District ${emptyList} is empty.`,
10902
+ `Districts ${emptyList} are empty.`,
10903
+ `Several districts are empty, including ${emptyList}.`
10904
+ ];
10905
+ emptyText = prepareListText(emptyDistricts, emptyTextTemplates);
10906
+ }
10907
+ if (U.keyExists('missingDistricts', testResult['details'])) {
10908
+ missingText = `Not enough districts have been defined. `;
10909
+ }
10910
+ let detailsText = " " + unassignedText + emptyText + missingText;
10911
+ blocks.push({ variant: 'body1', text: detailsText });
10912
+ }
10913
+ return blocks;
10914
+ }
10915
+ function doPrepareContiguousDetails(testResult) {
10916
+ let text = { data: [] };
10917
+ let blocks = text.data;
10918
+ if (!U.isObjectEmpty(testResult['details'])) {
10919
+ let discontiguousDistricts = testResult['details']['discontiguousDistricts'];
10920
+ let discontiguousList = prepareListItems(discontiguousDistricts);
10921
+ let discontiguousTextTemplates = [
10922
+ `District ${discontiguousList} is not contiguous.`,
10923
+ `Districts ${discontiguousList} are not contiguous.`,
10924
+ `Several districts are not contiguous, including ${discontiguousList}.`
10925
+ ];
10926
+ let detailsText = prepareListText(discontiguousDistricts, discontiguousTextTemplates);
10927
+ blocks.push({ variant: 'body1', text: detailsText });
10928
+ }
10929
+ return blocks;
10930
+ }
10931
+ function doPrepareFreeOfHolesDetails(testResult) {
10932
+ let text = { data: [] };
10933
+ let blocks = text.data;
10934
+ if (!U.isObjectEmpty(testResult['details'])) {
10935
+ let embeddedDistricts = testResult['details']['embeddedDistricts'];
10936
+ let embeddedList = prepareListItems(embeddedDistricts);
10937
+ let embeddedTextTemplates = [
10938
+ `District ${embeddedList} is fully embedded within another district.`,
10939
+ `Both districts ${embeddedList} are fully embedded within other districts.`,
10940
+ `Several districts are fully embedded within other districts, including ${embeddedList}.`
10941
+ ];
10942
+ let detailsText = prepareListText(embeddedDistricts, embeddedTextTemplates);
10943
+ blocks.push({ variant: 'body1', text: detailsText });
10944
+ }
10945
+ return blocks;
10946
+ }
10947
+ function doPrepareEqualPopulationDetails(testResult) {
10948
+ let text = { data: [] };
10949
+ let blocks = text.data;
10950
+ if (!U.isObjectEmpty(testResult['details'])) {
10951
+ let popDevPct = fractionToPercentage(testResult['details']['deviation']);
10952
+ let thresholdPct = fractionToPercentage(1.0 - testResult['details']['thresholds'][0]);
10953
+ let detailsText = '';
10954
+ if (!(testResult['score'])) {
10955
+ detailsText = `The ${formatPercentage(popDevPct)}% population deviation is greater than the ${formatPercentage(thresholdPct)}% threshold tolerated by courts.`;
10956
+ }
10957
+ blocks.push({ variant: 'body1', text: detailsText });
10958
+ }
10959
+ return blocks;
10960
+ }
10961
+ function doPreparePopulationDeviationDetails(testResult) {
10962
+ let text = { data: [] };
10963
+ let blocks = text.data;
10964
+ let n = Math.round(testResult['details']['maxDeviation']);
10965
+ let term = "people";
10966
+ if (n == 1) {
10967
+ term = "person";
10968
+ }
10969
+ blocks.push({ variant: 'body1', text: `The maximum population deviation between districts is ${formatInteger(n)} ${term}.` });
10970
+ return blocks;
10971
+ }
10972
+ function doPrepareReockDetails(testResult) {
10973
+ let text = { data: [] };
10974
+ let blocks = text.data;
10975
+ // TODO - No details implemented yet
10976
+ return blocks;
10977
+ }
10978
+ function doPreparePolsbyPopperDetails(testResult) {
10979
+ let text = { data: [] };
10980
+ let blocks = text.data;
10981
+ // TODO - No details implemented yet
10982
+ return blocks;
10983
+ }
10984
+ function doPrepareEfficiencyGapDetails(testResult) {
10985
+ let text = { data: [] };
10986
+ let blocks = text.data;
10987
+ // TODO - Not yet implemented
10988
+ return blocks;
10989
+ }
10990
+ function doPrepareCountySplitDetails(testResult) {
10991
+ let text = { data: [] };
10992
+ let blocks = text.data;
10993
+ let unexpectedAffected = fractionToPercentage(testResult['details']['unexpectedAffected']);
10994
+ let affectedText = `affecting ${formatPercentage(unexpectedAffected)}% of the total population.`;
10995
+ let countiesSplitUnexpectedly = testResult['details']['countiesSplitUnexpectedly'];
10996
+ let nCountiesSplitUnexpectedly = countiesSplitUnexpectedly.length;
10997
+ let nUnexpectedSplits = testResult['details']['unexpectedSplits'];
10998
+ let splitList = prepareListItems(countiesSplitUnexpectedly);
10999
+ let splitTextTemplates = [
11000
+ `${splitList} county is split unexpectedly, `,
11001
+ `${splitList} counties are split unexpectedly, `,
11002
+ // `These ${formatInteger(nCountiesSplitUnexpectedly)} counties are split unexpectedly--${splitList}--`
11003
+ `${splitList} counties are split unexpectedly ${formatInteger(nUnexpectedSplits)} times, `
11004
+ ];
11005
+ let detailsText = prepareListText(countiesSplitUnexpectedly, splitTextTemplates) + affectedText;
11006
+ blocks.push({ variant: 'body1', text: detailsText });
11007
+ return blocks;
11008
+ }
11009
+ // FORMATTERS FOR CATEGORIES
11010
+ // This function parses & formats the 'Valid' section of a scorecard.
11011
+ function doPrepareValidSection(s, c) {
11012
+ // Get the category meta data
11013
+ let { catName, catScore, catTests } = s.scorecard[c];
11014
+ let testReport;
11015
+ // Initialize the section text
11016
+ let text = { data: [] };
11017
+ let blocks = text.data;
11018
+ let testText;
11019
+ // Section header
11020
+ let stringScore = pfBoolToString(catScore);
11021
+ blocks.push({ variant: 'beginExpansion', text: `${catName}` });
11022
+ // blocks.push({ variant: 'beginExpansion', text: `${catName} ${stringScore}` });
11023
+ // Complete
11024
+ testReport = catTests[0 /* Complete */];
11025
+ testText = prepareTestEntry(0 /* Complete */, testReport);
11026
+ blocks.push(...testText);
11027
+ // Contiguous
11028
+ testReport = catTests[1 /* Contiguous */];
11029
+ testText = prepareTestEntry(1 /* Contiguous */, testReport);
11030
+ blocks.push(...testText);
11031
+ // Free of holes (no embedded districts)
11032
+ testReport = catTests[2 /* FreeOfHoles */];
11033
+ testText = prepareTestEntry(2 /* FreeOfHoles */, testReport);
11034
+ blocks.push(...testText);
11035
+ // Equal population (w/in the appropriate legal threshold)
11036
+ testReport = catTests[3 /* EqualPopulation */];
11037
+ testText = prepareTestEntry(3 /* EqualPopulation */, testReport);
11038
+ blocks.push(...testText);
11039
+ blocks.push({ variant: 'endExpansion' });
11040
+ return blocks;
11041
+ }
11042
+ // TODO - NIY
11043
+ // This function parses & formats the 'Fair' section of a scorecard.
11044
+ function doPrepareFairSection(s, c) {
11045
+ // Get the category meta data
11046
+ let { catName, catScore, catTests } = s.scorecard[c];
11047
+ let testReport;
11048
+ // Initialize the section text
11049
+ let text = { data: [] };
11050
+ let blocks = text.data;
11051
+ let testText;
11052
+ // Section header
11053
+ blocks.push({ variant: 'beginExpansion', text: `${catName} ${catScore} of 100` });
11054
+ // TODO - Flesh this out
11055
+ // There's only one test: Population Deviation.
11056
+ // testReport = catTests[T.Test.PopulationDeviation];
11057
+ // testText = prepareTestEntry(T.Test.PopulationDeviation, testReport);
11058
+ // blocks.push(...testText);
11059
+ blocks.push({ variant: 'endExpansion' });
11060
+ return blocks;
11061
+ }
11062
+ // This function parses & formats the 'Best' section of a scorecard.
11063
+ function doPrepareBestSection(s, c) {
11064
+ // Get the category meta data
11065
+ let { catName, catScore, catTests } = s.scorecard[c];
11066
+ let testReport;
11067
+ // Initialize the section text
11068
+ let text = { data: [] };
11069
+ let blocks = text.data;
11070
+ let testText;
11071
+ // Section header
11072
+ blocks.push({ variant: 'beginExpansion', text: `${catName} (${catScore} of 100)` });
11073
+ // Population deviation
11074
+ testReport = catTests[4 /* PopulationDeviation */];
11075
+ testText = prepareTestEntry(4 /* PopulationDeviation */, testReport);
11076
+ blocks.push(...testText);
11077
+ // Compactness
11078
+ testReport = catTests[5 /* Reock */];
11079
+ let normalizedReock = testReport['normalizedScore'];
11080
+ let reockTestText = prepareTestEntry(5 /* Reock */, testReport);
11081
+ testReport = catTests[6 /* PolsbyPopper */];
11082
+ let normalizedPolsbyPopper = testReport['normalizedScore'];
11083
+ let polsbyPopperTestText = prepareTestEntry(6 /* PolsbyPopper */, testReport);
11084
+ let compactnessScore = (normalizedReock + normalizedPolsbyPopper) / 2;
11085
+ blocks.push({ variant: 'body1', text: `Compactness: ${compactnessScore} / 100` });
11086
+ blocks.push(...reockTestText);
11087
+ blocks.push(...polsbyPopperTestText);
11088
+ // County splits
11089
+ // TODO - Re-enable this, when the county-splitting metric is implemented.
11090
+ // testReport = catTests[T.Test.CountySplits];
11091
+ // testText = prepareTestEntry(T.Test.CountySplits, testReport);
11092
+ // blocks.push(...testText);
11093
+ blocks.push({ variant: 'endExpansion' });
11094
+ return blocks;
11095
+ }
11096
+ // FORMATTING HELPERS
11097
+ // Convert a boolean representing Pass/Fail to a string
11098
+ function pfBoolToString(score) {
11099
+ if (score) {
11100
+ return "Yes";
11101
+ }
11102
+ else {
11103
+ return "No";
11104
+ }
11105
+ }
11106
+ function fractionToPercentage(f) {
11107
+ return f * 100;
11108
+ }
11109
+ function formatNumber(n) {
11110
+ let p = S.PRECISION;
11111
+ return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
11112
+ }
11113
+ function formatPercentage(n) {
11114
+ let p = (S.PRECISION / 2);
11115
+ return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
11116
+ }
11117
+ function formatInteger(i) {
11118
+ return new Intl.NumberFormat().format(i);
11119
+ }
11120
+ // Prepare the items in a list for rendering
11121
+ function prepareListItems(list) {
11122
+ let nItems = list.length;
11123
+ let listStr;
11124
+ switch (nItems) {
11125
+ case 1: {
11126
+ listStr = list[0];
11127
+ break;
11128
+ }
11129
+ case 2: {
11130
+ listStr = list[0] + " and " + list[1];
11131
+ break;
11132
+ }
11133
+ default: {
11134
+ let listWithCommas = list.join(', ');
11135
+ let lastCommaIndex = listWithCommas.length - ((list[list.length - 1].length) + 1);
11136
+ let beforeAnd = listWithCommas.substr(0, lastCommaIndex);
11137
+ let afterAnd = listWithCommas.substr(lastCommaIndex + 1);
11138
+ listStr = beforeAnd + " and " + afterAnd;
11139
+ break;
11140
+ }
11141
+ }
11142
+ return listStr;
11143
+ }
11144
+ // Pick the rendering text for the appropriate list length
11145
+ function prepareListText(list, listTemplates) {
11146
+ let nItems = list.length;
11147
+ switch (nItems) {
11148
+ case 1: {
11149
+ return listTemplates[0];
11150
+ break;
11151
+ }
11152
+ case 2: {
11153
+ return listTemplates[1];
11154
+ break;
11155
+ }
11156
+ default: {
11157
+ return listTemplates[2];
11158
+ break;
11159
+ }
11160
+ }
11161
+ }
11162
+ /* COMMAND-LINE TESTS
11163
+
11164
+ 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
11165
+ 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
11166
+
11167
+ -or-
11168
+
11169
+ ./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
11170
+ ./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
11171
+
11172
+ These calls work at the project directory, using samples in the data/ directory:
11173
+
11174
+ ./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
11175
+
11176
+ TODO - Fix this
11177
+ ./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
11178
+ ./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
11179
+
11180
+ ./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
11181
+ ./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
11182
+
11183
+ TODO - HERE
11184
+
11185
+ ./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
11186
+ ./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
11187
+
11188
+ TODO
11189
+
11190
+ ./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
11191
+ ./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
11192
+
11193
+ ./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
11194
+ ./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
11195
+
11196
+
11197
+ 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
11198
+ 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
11199
+
11200
+
11201
+ These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
11202
+
11203
+ 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
11204
+ 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
11205
+
11206
+ 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
11207
+ 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
11208
+
11209
+
11210
+
11211
+ */
11212
+ /* TODO - DELETE
11213
+
11214
+ These calls work at the project directory, using samples in the data/ directory:
11215
+
11216
+ ./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
11217
+ ./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
11218
+
11219
+ ./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
11220
+ ./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
11221
+
11222
+ ./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
11223
+ ./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
11224
+
11225
+ ./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
11226
+ ./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
11227
+
11228
+ ./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
11229
+ ./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
11230
+
11231
+ ./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
11232
+ ./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
11233
+
11234
+
11235
+ 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
11236
+ 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
11237
+
11238
+
11239
+ These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
11240
+
11241
+ 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
11242
+ 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
11243
+
11244
+ 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
11245
+ 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
11246
+
11247
+ */
11319
11248
 
11320
11249
 
11321
11250
  /***/ }),
@@ -11340,17 +11269,11 @@ exports.PRECISION = 4;
11340
11269
  exports.NORMALIZED_RANGE = 100;
11341
11270
  // The dummy district ID for features not assigned districts yet
11342
11271
  exports.NOT_ASSIGNED = 0;
11343
- // TODO - TERRY/DAVE: Discuss
11272
+ // TODO - Discuss w/ Dave & Terry
11344
11273
  // # of items to report as problematic (e.g., features, districts, etc.)
11345
11274
  exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
11346
11275
  // The virtual geoID for "neighbors" in other states
11347
11276
  exports.OUT_OF_STATE = "OUT_OF_STATE";
11348
- // "Roughly equal" = average census block size / 2
11349
- const AVERAGE_BLOCK_SIZE = 30;
11350
- exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
11351
- // County & district splitting weights
11352
- exports.COUNTY_SPLITTING_WEIGHT = 0.8;
11353
- exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
11354
11277
 
11355
11278
 
11356
11279
  /***/ }),
@@ -11367,15 +11290,8 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
11367
11290
  //
11368
11291
  // UTILITIES
11369
11292
  //
11370
- var __importStar = (this && this.__importStar) || function (mod) {
11371
- if (mod && mod.__esModule) return mod;
11372
- var result = {};
11373
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
11374
- result["default"] = mod;
11375
- return result;
11376
- };
11377
11293
  Object.defineProperty(exports, "__esModule", { value: true });
11378
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
11294
+ const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
11379
11295
  // PLAN HELPERS
11380
11296
  // Is a "neighbor" in state?
11381
11297
  function isInState(geoID) {
@@ -11387,7 +11303,6 @@ function isOutOfState(geoID) {
11387
11303
  return geoID == S.OUT_OF_STATE;
11388
11304
  }
11389
11305
  exports.isOutOfState = isOutOfState;
11390
- // TODO - UNASSIGNED
11391
11306
  // Get the districtID to which a geoID is assigned
11392
11307
  function getDistrict(plan, geoID) {
11393
11308
  // All geoIDs in a state *should be* assigned to a district (including the
@@ -11401,6 +11316,24 @@ function getDistrict(plan, geoID) {
11401
11316
  }
11402
11317
  }
11403
11318
  exports.getDistrict = getDistrict;
11319
+ // Invert a feature assignment structure to sets of ids by district
11320
+ // NOTE - This is here vs. _data.ts, so it can also be used in cli.ts
11321
+ function invertPlan(plan) {
11322
+ let invertedPlan = {};
11323
+ // Add a dummy 'unassigned' district
11324
+ invertedPlan[S.NOT_ASSIGNED] = new Set();
11325
+ for (let geoID in plan) {
11326
+ let districtID = plan[geoID];
11327
+ // Make sure the set for the districtID exists
11328
+ if (!(objectContains(invertedPlan, districtID))) {
11329
+ invertedPlan[districtID] = new Set();
11330
+ }
11331
+ // Add the geoID to the districtID's set
11332
+ invertedPlan[districtID].add(geoID);
11333
+ }
11334
+ return invertedPlan;
11335
+ }
11336
+ exports.invertPlan = invertPlan;
11404
11337
  // WORKING WITH GEOIDS
11405
11338
  function parseGeoID(geoID) {
11406
11339
  let parts = {};
@@ -11423,46 +11356,27 @@ function getFIPSFromCountyGeoID(geoID) {
11423
11356
  return geoID.substring(2, 5);
11424
11357
  }
11425
11358
  exports.getFIPSFromCountyGeoID = getFIPSFromCountyGeoID;
11426
- function isWaterOnly(geoID) {
11427
- let waterOnlySignature = 'ZZZZZZ';
11428
- if (geoID.indexOf(waterOnlySignature) >= 0)
11429
- return true;
11430
- else
11431
- return false;
11432
- }
11433
- exports.isWaterOnly = isWaterOnly;
11434
11359
  // NORMALIZING RESULTS
11435
- function normalize(rawScore, testScale) {
11436
- let rangeMin = testScale.scale[0];
11437
- let rangeMax = testScale.scale[1];
11438
- // Invert the raw value if necessary to make bigger = better
11439
- // TODO - This works for Population Deviation, because the max is 1.0.
11440
- // Generalize this???
11441
- if (testScale.bInvertRaw) {
11360
+ // Convert a raw score [0-1] into a normalized score [0-100]
11361
+ function normalize(rawScore, scale, invertp = false) {
11362
+ // Invert the axis if necessary to make bigger = better
11363
+ if (invertp) {
11442
11364
  rawScore = 1.0 - rawScore;
11443
11365
  }
11444
11366
  // Coerce the value to be w/in the given range
11367
+ let rangeMin = scale[0];
11368
+ let rangeMax = scale[1];
11445
11369
  let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
11446
11370
  // Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
11447
11371
  let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
11448
- // TODO - SPLITTING
11449
- // Invert the scaled value if necessary to make bigger = better
11450
- if (testScale.bInvertScaled) {
11451
- scaledValue = 1.0 - scaledValue;
11452
- }
11453
11372
  // Finally, make the range [0-100]
11454
11373
  return Math.round(scaledValue * S.NORMALIZED_RANGE);
11455
11374
  }
11456
11375
  exports.normalize = normalize;
11457
11376
  // Round a fractional number [0-1] to the desired level of PRECISION.
11458
- function trim(fullFraction, digits = undefined) {
11459
- if (digits == 0) {
11460
- return Math.round(fullFraction);
11461
- }
11462
- else {
11463
- let shiftPlaces = Math.pow(10, (digits || S.PRECISION));
11464
- return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
11465
- }
11377
+ function trim(fullFraction) {
11378
+ let shiftPlaces = Math.pow(10, S.PRECISION);
11379
+ return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
11466
11380
  }
11467
11381
  exports.trim = trim;
11468
11382
  // ARRAY HELPERS
@@ -11491,14 +11405,13 @@ function andArray(arr) {
11491
11405
  }
11492
11406
  exports.andArray = andArray;
11493
11407
  // WORKING WITH OBJECT KEYS/PROPERTIES
11494
- // TODO - TERRY, is this copesetic?
11495
- // TODO - Handle integer keys?
11408
+ // TODO - Terry, is this copesetic?
11496
11409
  // Does an object have a key/property?
11497
11410
  function keyExists(k, o) {
11498
11411
  return k in o;
11499
11412
  }
11500
11413
  exports.keyExists = keyExists;
11501
- // TODO - TERRY, can these three be combined into a generic isEmpty() check?
11414
+ // TODO - Terry, can these three be combined into a generic isEmpty() check?
11502
11415
  // Does an object (dict) have any keys/properties?
11503
11416
  function isObjectEmpty(o) {
11504
11417
  return Object.keys(o).length === 0;
@@ -11592,12 +11505,7 @@ function deepCopy(src) {
11592
11505
  return src;
11593
11506
  }
11594
11507
  exports.deepCopy = deepCopy;
11595
- // TODO - TRI-STATES: Map booleans to tri-states.
11596
- function mapBooleanToTriState(bool) {
11597
- return (bool) ? 0 /* Green */ : 2 /* Red */;
11598
- }
11599
- exports.mapBooleanToTriState = mapBooleanToTriState;
11600
- // TODO - TERRY: What is this the simple explanation of what this thing is doing?
11508
+ // TODO - Terry: What is this the simple explanation of what this thing is doing?
11601
11509
  var util_1 = __webpack_require__(/*! @dra2020/util */ "./node_modules/@dra2020/util/dist/util.js");
11602
11510
  exports.depthof = util_1.depthof;
11603
11511
 
@@ -11616,54 +11524,40 @@ exports.depthof = util_1.depthof;
11616
11524
  //
11617
11525
  // MAP/PLAN VALIDATIONS
11618
11526
  //
11619
- var __importStar = (this && this.__importStar) || function (mod) {
11620
- if (mod && mod.__esModule) return mod;
11621
- var result = {};
11622
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
11623
- result["default"] = mod;
11624
- return result;
11625
- };
11626
11527
  Object.defineProperty(exports, "__esModule", { value: true });
11627
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
11628
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
11629
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
11528
+ const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
11529
+ const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
11530
+ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
11630
11531
  //
11631
11532
  // COMPLETE - Are all geo's assigned to a district, and do all districts have
11632
11533
  // at least one geo assigned to them?
11633
11534
  //
11634
- function doIsComplete(s, bLog = false) {
11535
+ function doIsComplete(s) {
11635
11536
  let test = s.getTest(0 /* Complete */);
11636
- // Get the by-district results, including the dummy unassigned district,
11537
+ let bAllAssigned = true;
11538
+ let bNoneEmpty = true;
11539
+ let unassignedFeatures = [];
11540
+ let emptyDistricts = [];
11541
+ // Get the by district results, including the dummy unassigned district,
11637
11542
  // but ignoring the N+1 summary district
11638
11543
  let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
11639
11544
  bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
11640
11545
  // Are all features assigned to districts?
11641
11546
  // Check the dummy district that holds any unassigned features.
11642
- let unassignedFeatures = [];
11643
- let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
11547
+ bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
11644
11548
  if (!bAllAssigned) {
11645
11549
  let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
11646
11550
  unassignedFeatures = Array.from(unassignedDistrict);
11647
11551
  unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
11648
11552
  }
11649
11553
  // Do all real districts have at least one feature assigned to them?
11650
- let emptyDistricts = [];
11651
- let bNoneEmpty = true;
11652
- bNotEmptyByDistrict = bNotEmptyByDistrict.slice(1);
11653
- let districtID = 1;
11654
- bNotEmptyByDistrict.forEach(function (bNotEmpty) {
11655
- if (!bNotEmpty) {
11656
- bNoneEmpty = false;
11657
- emptyDistricts.push(districtID);
11658
- }
11659
- districtID += 1;
11660
- });
11554
+ bNoneEmpty = U.andArray(bNotEmptyByDistrict.slice(1));
11661
11555
  // Case 1 - One or more districts are missing:
11662
11556
  // The # of enumerated districts minus the dummy NOT_ASSIGNED one should
11663
11557
  // equal the number apportioned districts. This guards against a district
11664
11558
  // not being included in a map that is imported.
11665
11559
  //
11666
- // NOTE - I'm no longer checking for this, but DRA should!
11560
+ // TODO - I'm no longer checking for this, but DRA should!
11667
11561
  // Case 2 - Or a district is explicitly named but empty:
11668
11562
  // Note, this can happen if a district is created, and then all features
11669
11563
  // are removed from it (in DRA).
@@ -11690,7 +11584,7 @@ exports.doIsComplete = doIsComplete;
11690
11584
  //
11691
11585
  // To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
11692
11586
  //
11693
- function doIsContiguous(s, bLog = false) {
11587
+ function doIsContiguous(s) {
11694
11588
  let test = s.getTest(1 /* Contiguous */);
11695
11589
  // Get the contiguity of each district. Ignore dummy unassigned district
11696
11590
  // and the N+1 summary district.
@@ -11721,7 +11615,7 @@ exports.doIsContiguous = doIsContiguous;
11721
11615
  // Are the features in a district fully connected?
11722
11616
  function isConnected(districtGeos, graph) {
11723
11617
  // export function isConnected(districtGeos: Set<string>, graph: T.ContiguityGraph): boolean {
11724
- // TODO - TERRY, why does this constructor need a <T> type specification?
11618
+ // TODO - Terry, why does this constructor need a <T> type specification?
11725
11619
  let visited = new Set();
11726
11620
  let toProcess = [];
11727
11621
  // Start processing with the first geoID in the district
@@ -11736,7 +11630,7 @@ function isConnected(districtGeos, graph) {
11736
11630
  let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
11737
11631
  // Add neighbors to visit, if they're in the same district Y haven't already been visited
11738
11632
  let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
11739
- // TODO - TERRY, is this the quickest/best way to do this?
11633
+ // TODO - Terry, is this the quickest/best way to do this?
11740
11634
  toProcess.push(...neighborsToVisit);
11741
11635
  }
11742
11636
  // Stop when you've visited all the geoIDs in the district
@@ -11753,10 +11647,10 @@ exports.isConnected = isConnected;
11753
11647
  // To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
11754
11648
  // Buncombe County (37021), is a donut hole w/in District 3.
11755
11649
  //
11756
- // TODO - OPTIMIZE: This to take advantage of district boundary info, if/when
11650
+ // TODO - Optimize this to take advantage of district boundary info, if/when
11757
11651
  // we cache one to optimize compactness.
11758
11652
  //
11759
- function doIsFreeOfHoles(s, bLog = false) {
11653
+ function doIsFreeOfHoles(s) {
11760
11654
  let test = s.getTest(2 /* FreeOfHoles */);
11761
11655
  // Initialize values
11762
11656
  let bFreeOfHoles = true;
@@ -11787,7 +11681,7 @@ function doIsFreeOfHoles(s, bLog = false) {
11787
11681
  exports.doIsFreeOfHoles = doIsFreeOfHoles;
11788
11682
  // Test whether one district is embedded w/in any other.
11789
11683
  function isEmbedded(districtID, geoIDs, plan, graph) {
11790
- // NOTE - "features" here = "geoIDs." These aren't "features" proper, just
11684
+ // TODO - Make "features" = "geoIDs." These aren't "features" proper, just
11791
11685
  // identifier strings.
11792
11686
  let features = geoIDs;
11793
11687
  let planByGeo = plan.byGeoID();
@@ -11795,7 +11689,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
11795
11689
  let bEmbedded = true;
11796
11690
  // Keep track of the neighoring districts
11797
11691
  let neighboringDistricts = new Set();
11798
- // TODO - OPTIMIZE: Use just the boundary features, when available
11692
+ // TODO - Use just the boundary features, when available
11799
11693
  // Get the features for the real district
11800
11694
  let featuresToCheck = Array.from(features);
11801
11695
  // If the district has features, check whether it is embedded
@@ -11821,7 +11715,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
11821
11715
  break;
11822
11716
  }
11823
11717
  else {
11824
- // TODO - OPTIMIZE: Since we're checking *all* features in a district right
11718
+ // TODO - Since we're checking *all* features in a district right
11825
11719
  // now, not just boundary features and neighbors in other districts,
11826
11720
  // prune out the current district. If/when we optimize compactness
11827
11721
  // to cache district boundaries (as before in my Python implementation),
@@ -11847,19 +11741,16 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
11847
11741
  return bEmbedded;
11848
11742
  }
11849
11743
  exports.isEmbedded = isEmbedded;
11744
+ // TODO - MIXED MAPS
11745
+ // - When we generalize to mixed maps, the determination of "neighbors in
11746
+ // the map" -- which is the core function in determining connectedness -- becomes
11747
+ // *much* more complicated and dynamic.
11748
+ // - I can write up (if not implement) the logic for this. It is a bit tricky
11749
+ // and requires special preprocessing of the summary level hierarchy (which I
11750
+ // also have a script for that we can repurpose) to distinguish between 'interior'
11751
+ // and 'edge' children.
11850
11752
 
11851
11753
 
11852
- /***/ }),
11853
-
11854
- /***/ "./static/state-reqs.json":
11855
- /*!********************************!*\
11856
- !*** ./static/state-reqs.json ***!
11857
- \********************************/
11858
- /*! 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 */
11859
- /***/ (function(module) {
11860
-
11861
- 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\"}");
11862
-
11863
11754
  /***/ }),
11864
11755
 
11865
11756
  /***/ "./test/_cli.ts":
@@ -11870,107 +11761,35 @@ module.exports = JSON.parse("{\"AL\":\"https://www.brennancenter.org/sites/defau
11870
11761
  /***/ (function(module, exports, __webpack_require__) {
11871
11762
 
11872
11763
  "use strict";
11873
-
11764
+ /* WEBPACK VAR INJECTION */(function(__dirname) {
11874
11765
  //
11875
11766
  // A SIMPLE COMMAND-LINE INTERFACE FOR EXERCISING THE ANALYTICS & VALIDATIONS
11876
11767
  //
11877
- var __importDefault = (this && this.__importDefault) || function (mod) {
11878
- return (mod && mod.__esModule) ? mod : { "default": mod };
11879
- };
11880
- var __importStar = (this && this.__importStar) || function (mod) {
11881
- if (mod && mod.__esModule) return mod;
11882
- var result = {};
11883
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
11884
- result["default"] = mod;
11885
- return result;
11886
- };
11887
11768
  Object.defineProperty(exports, "__esModule", { value: true });
11888
- /*
11889
-
11890
- CONFIGURATIONS
11891
-
11892
- These configurations implement various CLI tests (using the commands in parens)
11893
- w/in VS Code:
11894
-
11895
- * api-analyze (analyze) - Analyzes a plan and logs the results.
11896
-
11897
- * unit-valid (valid) - Exercises the validations.
11898
- * unit-equal (equal) - Exercises the population deviation analytic.
11899
- * unit-compact (compact) - Exercises the compactness analytics.
11900
- * unit-cohesive (cohesive) - Exercises the splitting analytics.
11901
- * unit-political (political) - Exercises the partisan analytics.
11902
- * unit-minority (minority) - Exercises the minority analytics.
11903
-
11904
- * invalid-unassigned (valid) - Tests a plan w/ unassigned features.
11905
- * invalid-missing (valid) - Tests a plan w/ a missing district.
11906
- * invalid-empty (valid) - Tests a plan w/ an empty district.
11907
- * invalid-discontiguous (valid) - Tests a plan w/ a discontiguous district.
11908
- * invalid-hole (valid) - Tests a plan w/ an embedded district.
11909
-
11910
-
11911
- COMMANDS
11912
-
11913
- Tests can also be exercised from the commmand line from the project directory.
11914
- This is a sample. Use "node --inspect --inspect-brk main.js ..." to attach a debugger.
11915
-
11916
- 1 - Plain vanilla run:
11917
- ./main.js analyze -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
11918
-
11919
- 2 - Missing district:
11920
- ./main.js analyze -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
11921
-
11922
- 3 - Empty district:
11923
- ./main.js valid -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 --empty
11924
-
11925
- 4 - Unassigned features:
11926
- ./main.js analyze -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
11927
-
11928
- 5 - Discontiguous districts:
11929
- ./main.js analyze -n 13 -p data/SAMPLE-BG-map-discontiguous.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
11930
-
11931
- 6 - Embedded district:
11932
- ./main.js analyze -n 13 -p data/SAMPLE-BG-map-hole.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
11933
-
11934
- 7 - Test using NC block-level data <<< TODO - Need BLOCK-data2.json & COUNTY.geojson to run this again.
11935
-
11936
- cd ~/Documents/Professional/Projects/Redistricting/DRA2020/Analytics/compact/classic/ts_sample
11937
- ./main.js analyze -v -n 13 -p SAMPLE-BLOCK-map.csv -d SAMPLE-BLOCK-data2.json -s SAMPLE-BLOCK-shapes.geojson -g SAMPLE-BLOCK-graph.json -c SAMPLE-COUNTY.geojson
11938
-
11939
- */
11940
- const yargs_1 = __importDefault(__webpack_require__(/*! yargs */ "yargs"));
11941
- // TODO - Fix this import, so I don't have to do the 'union' workaround below.
11942
- const PC = __importStar(__webpack_require__(/*! polygon-clipping */ "./node_modules/polygon-clipping/dist/polygon-clipping.esm.js"));
11943
- const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
11944
- const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
11945
- const path = __importStar(__webpack_require__(/*! path */ "path"));
11946
- const sync_1 = __importDefault(__webpack_require__(/*! csv-parse/lib/sync */ "./node_modules/csv-parse/lib/sync.js"));
11769
+ const yargs = __webpack_require__(/*! yargs */ "yargs");
11770
+ const PC = __webpack_require__(/*! polygon-clipping */ "./node_modules/polygon-clipping/dist/polygon-clipping.esm.js");
11771
+ const Poly = __webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js");
11772
+ const fs = __webpack_require__(/*! fs */ "fs");
11773
+ const path = __webpack_require__(/*! path */ "path");
11774
+ const parse = __webpack_require__(/*! csv-parse/lib/sync */ "./node_modules/csv-parse/lib/sync.js");
11947
11775
  const _api_1 = __webpack_require__(/*! ../src/_api */ "./src/_api.ts");
11948
11776
  const preprocess_1 = __webpack_require__(/*! ../src/preprocess */ "./src/preprocess.ts");
11949
11777
  const analyze_1 = __webpack_require__(/*! ../src/analyze */ "./src/analyze.ts");
11950
- const results_1 = __webpack_require__(/*! ../src/results */ "./src/results.ts");
11951
- // TODO - DASHBOARD: Delete
11952
- // import { doAnalyzePostProcessing } from '../src/report'
11953
11778
  const valid_1 = __webpack_require__(/*! ../src/valid */ "./src/valid.ts");
11954
11779
  const equal_1 = __webpack_require__(/*! ../src/equal */ "./src/equal.ts");
11955
11780
  const compact_1 = __webpack_require__(/*! ../src/compact */ "./src/compact.ts");
11956
11781
  const cohesive_1 = __webpack_require__(/*! ../src/cohesive */ "./src/cohesive.ts");
11957
11782
  const political_1 = __webpack_require__(/*! ../src/political */ "./src/political.ts");
11958
11783
  const minority_1 = __webpack_require__(/*! ../src/minority */ "./src/minority.ts");
11959
- const U = __importStar(__webpack_require__(/*! ../src/utils */ "./src/utils.ts"));
11960
- const S = __importStar(__webpack_require__(/*! ../src/settings */ "./src/settings.ts"));
11961
- const D = __importStar(__webpack_require__(/*! ../src/_data */ "./src/_data.ts"));
11784
+ const U = __webpack_require__(/*! ../src/utils */ "./src/utils.ts");
11785
+ const S = __webpack_require__(/*! ../src/settings */ "./src/settings.ts");
11962
11786
  // Simulate DRA unioning district shapes in the background
11963
11787
  function addToPoly(poly, polys) {
11964
- // TODO - Fix 'poly' import, so I don't have to do this workaround.
11965
- // return PC.union(poly, ...polys);
11966
- let union = PC.union;
11967
- if (union === undefined)
11968
- union = PC.default.union;
11969
- return union(poly, ...polys);
11788
+ return PC.union(poly, ...polys);
11970
11789
  }
11971
11790
  console.log("Starting command @ ", new Date());
11972
11791
  // COMMAND LINE
11973
- let argv = yargs_1.default
11792
+ let argv = yargs
11974
11793
  .usage('Usage: $0 command [options]')
11975
11794
  .example('$0 equal -f foo.geojson', 'Calculate population deviation')
11976
11795
  .demandCommand(1, 'You must specify a command to execute.')
@@ -12050,9 +11869,15 @@ let xx = argv.state;
12050
11869
  let bLegislativeDistricts = argv.legislative;
12051
11870
  let nDistricts = argv.n;
12052
11871
  let planByGeoID = readPlanCSV(argv.plan);
12053
- let graph = readJSON(argv.graph);
12054
- let data = readJSON(argv.data);
12055
- let counties = readJSON(argv.counties);
11872
+ let graph = {};
11873
+ let data = {};
11874
+ let counties = {};
11875
+ // Don't load other datasets, if just calculating compactness
11876
+ if (command != 'compact') {
11877
+ graph = readJSON(argv.graph);
11878
+ data = readJSON(argv.data);
11879
+ counties = readJSON(argv.counties);
11880
+ }
12056
11881
  let shapes = readJSON(argv.shapes);
12057
11882
  let bEmpty = argv.empty;
12058
11883
  let suites = argv.suites;
@@ -12062,23 +11887,16 @@ let datasetKeys = {
12062
11887
  VAP: "D16T",
12063
11888
  ELECTION: "E16GPR"
12064
11889
  };
12065
- let datasetDescriptions = {
12066
- CENSUS: "2016 ACS Total Population",
12067
- VAP: "2016 ACS Voting Age Population",
12068
- ELECTION: "2016 Presidential Election",
12069
- };
12070
11890
  // Session settings are required:
12071
11891
  // - Analytics suites can be defaulted to all with [], but
12072
11892
  // - Dataset must be explicitly specified
12073
11893
  let sessionSettings = {
12074
11894
  'suites': suites,
12075
- 'datasets': datasetKeys,
12076
- 'descriptions': datasetDescriptions
11895
+ 'datasets': datasetKeys
12077
11896
  };
12078
11897
  let bLog = argv.verbose;
12079
11898
  // Invert the plan, so you can create the district shapes for the SessionRequest.
12080
- // NOTE - The plan here is complete (all features assigned).
12081
- let planByDistrictID = D.invertPlan(planByGeoID);
11899
+ let planByDistrictID = U.invertPlan(planByGeoID);
12082
11900
  // SIMULATE THE HOST
12083
11901
  // 1 - Create district shapes & extract properties (area, diameter, perimeter)
12084
11902
  const polyOptions = {
@@ -12139,33 +11957,30 @@ districtShapes['type'] = "FeatureCollection";
12139
11957
  districtShapes['features'] = [];
12140
11958
  for (let districtID = 1; districtID <= nDistricts; districtID++) {
12141
11959
  let districtFeatures = [];
12142
- // If the district is not empty, collect its Features
12143
- // TODO - Should we generate degenerate shape, if a district is empty?
12144
- if (planByDistrictID[districtID]) {
12145
- planByDistrictID[districtID].forEach(function (geoID) {
12146
- districtFeatures.push(shapesByGeoID[geoID]);
12147
- });
12148
- // Union them together
12149
- let poly1Coordinates = Poly.polyNormalize(districtFeatures[0], polyOptions);
12150
- let polyNCoordinates = districtFeatures.slice(1).map((i) => Poly.polyNormalize(i, polyOptions));
12151
- let poly = addToPoly(poly1Coordinates, polyNCoordinates);
12152
- // Convert the result to a Feature
12153
- let bPolygon = U.depthof(poly) == 4;
12154
- if (!bPolygon && poly.length == 1) {
12155
- bPolygon = true;
12156
- poly = poly[0];
12157
- }
12158
- eliminateAnomalousHoles(poly);
12159
- let f = {
12160
- type: 'Feature',
12161
- properties: { districtID: `${districtID}` },
12162
- geometry: {
12163
- type: bPolygon ? 'Polygon' : 'MultiPolygon',
12164
- coordinates: poly
12165
- }
12166
- };
12167
- districtShapes.features.push(f);
12168
- } // End non-empty districts
11960
+ // Collect Features by district
11961
+ planByDistrictID[districtID].forEach(function (geoID) {
11962
+ districtFeatures.push(shapesByGeoID[geoID]);
11963
+ });
11964
+ // Union them together
11965
+ let poly1Coordinates = Poly.polyNormalize(districtFeatures[0], polyOptions);
11966
+ let polyNCoordinates = districtFeatures.slice(1).map((i) => Poly.polyNormalize(i, polyOptions));
11967
+ let poly = addToPoly(poly1Coordinates, polyNCoordinates);
11968
+ // Convert the result to a Feature
11969
+ let bPolygon = U.depthof(poly) == 4;
11970
+ if (!bPolygon && poly.length == 1) {
11971
+ bPolygon = true;
11972
+ poly = poly[0];
11973
+ }
11974
+ eliminateAnomalousHoles(poly);
11975
+ let f = {
11976
+ type: 'Feature',
11977
+ properties: { districtID: `${districtID}` },
11978
+ geometry: {
11979
+ type: bPolygon ? 'Polygon' : 'MultiPolygon',
11980
+ coordinates: poly
11981
+ }
11982
+ };
11983
+ districtShapes.features.push(f);
12169
11984
  }
12170
11985
  // CONSTRUCT A SESSION REQUEST
12171
11986
  let SessionRequest = {};
@@ -12183,7 +11998,21 @@ SessionRequest['config'] = sessionSettings;
12183
11998
  let s = new _api_1.AnalyticsSession(SessionRequest);
12184
11999
  // DISPATCH TO REQUESTED COMMAND W/ NEEDED SCAFFOLDING
12185
12000
  let t;
12186
- // let text: any;
12001
+ let text;
12002
+ function echoTestResult(test, t) {
12003
+ console.log("");
12004
+ console.log("Test:", test);
12005
+ console.log("Score:", t['score']);
12006
+ let keys = U.getObjectKeys(t['details']);
12007
+ if (keys.length > 0) {
12008
+ console.log("Details:");
12009
+ for (let i in keys) {
12010
+ let key = keys[i];
12011
+ console.log("-", key, "=", t['details'][key]);
12012
+ }
12013
+ }
12014
+ console.log("___");
12015
+ }
12187
12016
  switch (command) {
12188
12017
  case 'valid': {
12189
12018
  preprocess_1.doPreprocessData(s);
@@ -12195,17 +12024,16 @@ switch (command) {
12195
12024
  s.plan.initializeDistrict(randomDistrict);
12196
12025
  }
12197
12026
  analyze_1.doAnalyzeDistricts(s);
12198
- let t1 = valid_1.doIsComplete(s);
12199
- let t2 = valid_1.doIsContiguous(s);
12200
- let t3 = valid_1.doIsFreeOfHoles(s);
12201
- let t4 = equal_1.doPopulationDeviation(s);
12202
- let t5 = equal_1.doHasEqualPopulations(s);
12203
- results_1.doAnalyzePostProcessing(s);
12204
- echoTestResult("Complete:", t1);
12205
- echoTestResult("Contiguous:", t2);
12206
- echoTestResult("Free of holes:", t3);
12207
- echoTestResult("Population deviation (%):", t4);
12208
- echoTestResult("Equal populations:", t5);
12027
+ t = valid_1.doIsComplete(s);
12028
+ echoTestResult("Complete:", t);
12029
+ t = valid_1.doIsContiguous(s);
12030
+ echoTestResult("Contiguous:", t);
12031
+ t = valid_1.doIsFreeOfHoles(s);
12032
+ echoTestResult("Free of holes:", t);
12033
+ t = equal_1.doPopulationDeviation(s);
12034
+ echoTestResult("Population deviation (%):", t);
12035
+ t = equal_1.doHasEqualPopulations(s);
12036
+ echoTestResult("Equal populations:", t);
12209
12037
  break;
12210
12038
  }
12211
12039
  case 'equal': {
@@ -12213,313 +12041,98 @@ switch (command) {
12213
12041
  analyze_1.doAnalyzeDistricts(s);
12214
12042
  t = equal_1.doPopulationDeviation(s);
12215
12043
  echoTestResult("Population deviation (%):", t);
12216
- results_1.doAnalyzePostProcessing(s);
12217
12044
  break;
12218
12045
  }
12219
12046
  case 'compact': {
12220
12047
  preprocess_1.doPreprocessData(s);
12221
12048
  analyze_1.doAnalyzeDistricts(s);
12222
- let t1 = compact_1.doReock(s, bLog);
12223
- let t2 = compact_1.doPolsbyPopper(s, bLog);
12224
- results_1.doAnalyzePostProcessing(s);
12225
- echoTestResult("Reock:", t1);
12226
- echoTestResult("Polsby-Popper:", t2);
12049
+ t = compact_1.doReock(s, bLog);
12050
+ echoTestResult("Reock:", t);
12051
+ t = compact_1.doPolsbyPopper(s, bLog);
12052
+ echoTestResult("Polsby-Popper:", t);
12227
12053
  break;
12228
12054
  }
12229
12055
  case 'cohesive': {
12230
12056
  preprocess_1.doPreprocessData(s);
12231
12057
  analyze_1.doAnalyzeDistricts(s);
12232
- // TODO - SPLITTING
12233
- let t1 = cohesive_1.doFindCountiesSplitUnexpectedly(s);
12234
- let t2 = cohesive_1.doFindSplitVTDs(s);
12235
- let t3 = cohesive_1.doCountySplitting(s);
12236
- let t4 = cohesive_1.doDistrictSplitting(s);
12237
- results_1.doAnalyzePostProcessing(s);
12238
- echoTestResult("Counties split unexpectedly:", t1);
12239
- echoTestResult("Split VTDs:", t2);
12240
- echoTestResult("County splitting:", t3);
12241
- echoTestResult("District splitting:", t4);
12058
+ t = cohesive_1.doCountySplits(s);
12059
+ echoTestResult("County splits:", t);
12060
+ cohesive_1.doPlanComplexity(s);
12242
12061
  break;
12243
12062
  }
12244
12063
  case 'political': {
12245
12064
  preprocess_1.doPreprocessData(s);
12246
12065
  analyze_1.doAnalyzeDistricts(s);
12247
- let t1 = political_1.doSeatsBias(s);
12248
- let t2 = political_1.doVotesBias(s);
12249
- let t3 = political_1.doResponsiveness(s);
12250
- let t4 = political_1.doResponsiveDistricts(s);
12251
- let t5 = political_1.doEfficiencyGap(s);
12252
- results_1.doAnalyzePostProcessing(s);
12253
- // echoTestResult("Seats Bias:", t1); TODO
12254
- // echoTestResult("Votes Bias:", t2); TODO
12255
- // echoTestResult("Responsiveness:", t3); TODO
12256
- // echoTestResult("Responsive Districts:", t4); TODO
12257
- echoTestResult("Efficiency gap (%):", t5);
12066
+ political_1.doSeatsBias(s);
12067
+ political_1.doVotesBias(s);
12068
+ political_1.doResponsiveness(s);
12069
+ political_1.doResponsiveDistricts(s);
12070
+ t = political_1.doEfficiencyGap(s);
12071
+ echoTestResult("Efficiency gap (%):", t);
12258
12072
  break;
12259
12073
  }
12260
12074
  case 'minority': {
12261
12075
  preprocess_1.doPreprocessData(s);
12262
12076
  analyze_1.doAnalyzeDistricts(s);
12263
- let t1 = minority_1.doMajorityMinorityDistricts(s);
12264
- results_1.doAnalyzePostProcessing(s);
12265
- // echoTestResult("Majority-Minority Districts:", t1); TODO
12077
+ minority_1.doMajorityMinorityDistricts(s);
12266
12078
  break;
12267
12079
  }
12268
- // TODO - DASHBOARD: DELETE
12269
- // case 'analyze': {
12270
- // console.log("");
12271
- // console.log("Analyzing a plan ...");
12272
- // console.log("");
12273
- // s.analyzePlan(bLog);
12274
- // break;
12275
- // }
12276
- // TODO - DASHBOARD: DELETE
12277
- // case 'scorecard': {
12278
- // s.analyzePlan(bLog);
12279
- // text = s.prepareScorecard();
12280
- // console.log("");
12281
- // for (let line of text.data) {
12282
- // switch (line['variant']) {
12283
- // case 'beginTable':
12284
- // case 'endTable':
12285
- // console.log(line['variant']);
12286
- // break;
12287
- // case 'row':
12288
- // console.log(line['variant'], line['cells']);
12289
- // break;
12290
- // default:
12291
- // console.log(line['variant'], line['text']);
12292
- // break;
12293
- // }
12294
- // }
12295
- // console.log("");
12296
- // break;
12297
- // }
12298
- // TODO - DASHBOARD: Print the structures out
12299
12080
  case 'analyze': {
12081
+ console.log("");
12082
+ console.log("Analyzing a plan ...");
12083
+ console.log("");
12300
12084
  s.analyzePlan(bLog);
12301
- let planAnalytics = s.getPlanAnalytics(bLog);
12302
- let districtStatistics = s.getDistrictStatistics(bLog);
12303
- echoPlanAnalytics(planAnalytics);
12304
- echoDistrictStatistics(districtStatistics);
12305
12085
  break;
12306
12086
  }
12307
- // // TODO - DASHBOARD: DELETE
12308
- // case 'testlog': {
12309
- // s.analyzePlan(bLog);
12310
- // text = s.prepareTestLog();
12311
- // console.log("");
12312
- // for (let line of text.data) {
12313
- // console.log(line['variant'], line['text']);
12314
- // }
12315
- // console.log("");
12316
- // break;
12317
- // }
12318
- // TODO - DASHBOARD: DELETE
12319
- // case 'limit': {
12320
- // s.analyzePlan(bLog);
12321
- // text = s.prepareScorecard();
12322
- // console.log("");
12323
- // for (let line of text.data) {
12324
- // switch (line['variant']) {
12325
- // case 'beginTable':
12326
- // case 'endTable':
12327
- // console.log(line['variant']);
12328
- // break;
12329
- // case 'row':
12330
- // console.log(line['variant'], line['cells']);
12331
- // break;
12332
- // default:
12333
- // console.log(line['variant'], line['text']);
12334
- // break;
12335
- // }
12336
- // }
12337
- // console.log("");
12338
- // break;
12339
- // }
12340
- }
12341
- console.log("Ending command @ ", new Date());
12342
- // HELPER FUNCTIONS FOR ECHOING RESULTS
12343
- function echoTestResult(test, t) {
12344
- console.log("");
12345
- console.log("Test:", test);
12346
- console.log("Score:", t['score']);
12347
- let keys = U.getObjectKeys(t['details']);
12348
- if (keys.length > 0) {
12349
- console.log("Details:");
12350
- for (let i in keys) {
12351
- let key = keys[i];
12352
- console.log("-", key, "=", t['details'][key]);
12087
+ case 'scorecard': {
12088
+ s.analyzePlan(bLog);
12089
+ text = s.prepareScorecard();
12090
+ console.log("");
12091
+ for (let line of text.data) {
12092
+ switch (line['variant']) {
12093
+ case 'beginTable':
12094
+ case 'endTable':
12095
+ console.log(line['variant']);
12096
+ break;
12097
+ case 'row':
12098
+ console.log(line['variant'], line['cells']);
12099
+ break;
12100
+ default:
12101
+ console.log(line['variant'], line['text']);
12102
+ break;
12103
+ }
12353
12104
  }
12105
+ console.log("");
12106
+ break;
12354
12107
  }
12355
- console.log("___");
12356
- }
12357
- // Prepare the items in a list for rendering
12358
- function prepareListItems(list) {
12359
- if (U.isArrayEmpty(list)) {
12360
- return "";
12361
- }
12362
- else {
12363
- let nItems = list.length;
12364
- let listStr;
12365
- switch (nItems) {
12366
- case 1: {
12367
- listStr = list[0];
12368
- break;
12369
- }
12370
- case 2: {
12371
- listStr = list[0] + " and " + list[1];
12372
- break;
12373
- }
12374
- default: {
12375
- let listWithCommas = list.join(', ');
12376
- let lastCommaIndex = listWithCommas.length - ((list[list.length - 1].length) + 1);
12377
- let beforeAnd = listWithCommas.substr(0, lastCommaIndex);
12378
- let afterAnd = listWithCommas.substr(lastCommaIndex + 1);
12379
- listStr = beforeAnd + " and " + afterAnd;
12380
- break;
12381
- }
12108
+ case 'testlog': {
12109
+ s.analyzePlan(bLog);
12110
+ text = s.prepareTestLog();
12111
+ console.log("");
12112
+ for (let line of text.data) {
12113
+ console.log(line['variant'], line['text']);
12382
12114
  }
12383
- return listStr;
12115
+ console.log("");
12116
+ break;
12384
12117
  }
12385
- }
12386
- function formatNumber(n) {
12387
- let p = S.PRECISION;
12388
- return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
12389
- }
12390
- function echoPlanAnalytics(pa) {
12391
- console.log("Plan Analytics:");
12392
- console.log("");
12393
- console.log("Requirements:");
12394
- console.log("* Score:", pa.requirements.score);
12395
- console.log("* Metrics:");
12396
- console.log(" - Complete:", pa.requirements.metrics.complete);
12397
- console.log(" - Contiguous:", pa.requirements.metrics.contiguous);
12398
- console.log(" - Free of holes:", pa.requirements.metrics.freeOfHoles);
12399
- console.log(" - Equal population:", pa.requirements.metrics.equalPopulation);
12400
- console.log("* Details:");
12401
- console.log(" - Empty districts:", prepareListItems(pa.requirements.details.emptyDistricts));
12402
- console.log(" - Unassigned features:", prepareListItems(pa.requirements.details.unassignedFeatures));
12403
- console.log(" - Population deviation:", pa.requirements.details.populationDeviation);
12404
- console.log(" - Deviation threshold:", pa.requirements.details.deviationThreshold);
12405
- console.log(" - Discontiguous districts:", prepareListItems(pa.requirements.details.discontiguousDistricts));
12406
- console.log(" - Embedded districts:", prepareListItems(pa.requirements.details.embeddedDistricts));
12407
- console.log("* Datasets:");
12408
- console.log("- Shapes:", pa.requirements.datasets.shapes);
12409
- console.log("- Census:", pa.requirements.datasets.census);
12410
- console.log("- VAP:", pa.requirements.datasets.vap);
12411
- console.log("- Election:", pa.requirements.datasets.election);
12412
- console.log("* Resources:");
12413
- console.log("- State requirements:", pa.requirements.resources.stateReqs);
12414
- console.log("");
12415
- console.log("Compactness:");
12416
- console.log("* Score:", pa.compactness.score);
12417
- console.log("* Metrics:");
12418
- console.log(" - Reock:", pa.compactness.metrics.reock);
12419
- console.log(" - Polsby-Popper:", pa.compactness.metrics.polsby);
12420
- console.log("* Details:");
12421
- console.log(" - N/A");
12422
- console.log("* Datasets:");
12423
- console.log("- Shapes:", pa.compactness.datasets.shapes);
12424
- console.log("- Census:", pa.compactness.datasets.census);
12425
- console.log("- VAP:", pa.compactness.datasets.vap);
12426
- console.log("- Election:", pa.compactness.datasets.election);
12427
- console.log("* Resources:");
12428
- console.log("- N/A");
12429
- console.log("");
12430
- console.log("Splitting:");
12431
- console.log("* Score:", pa.splitting.score);
12432
- console.log("* Metrics:");
12433
- console.log(" - sqEnt_DCreduced:", pa.splitting.metrics.sqEnt_DCreduced);
12434
- console.log(" - sqEnt_CDreduced:", pa.splitting.metrics.sqEnt_CDreduced);
12435
- console.log("* Details:");
12436
- console.log(" - countiesSplitUnexpectedly:", prepareListItems(pa.splitting.details.countiesSplitUnexpectedly));
12437
- console.log(" - unexpectedAffected:", pa.splitting.details.unexpectedAffected);
12438
- console.log(" - splitVTDs:", pa.splitting.details.splitVTDs);
12439
- console.log("* Datasets:");
12440
- console.log("- Shapes:", pa.splitting.datasets.shapes);
12441
- console.log("- Census:", pa.splitting.datasets.census);
12442
- console.log("- VAP:", pa.splitting.datasets.vap);
12443
- console.log("- Election:", pa.splitting.datasets.election);
12444
- console.log("* Resources:");
12445
- console.log("- N/A");
12446
- console.log("");
12447
- console.log("Partisan:");
12448
- console.log("* Score:", pa.partisan.score);
12449
- console.log("* Metrics:");
12450
- console.log(" - sqEnt_DCreduced:", pa.partisan.metrics.partisanBias);
12451
- console.log(" - sqEnt_CDreduced:", pa.partisan.metrics.responsiveness);
12452
- console.log("* Details:");
12453
- console.log(" - N/A");
12454
- console.log("* Datasets:");
12455
- console.log("- Shapes:", pa.partisan.datasets.shapes);
12456
- console.log("- Census:", pa.partisan.datasets.census);
12457
- console.log("- VAP:", pa.partisan.datasets.vap);
12458
- console.log("- Election:", pa.partisan.datasets.election);
12459
- console.log("* Resources:");
12460
- console.log(" - Plan Score:", pa.partisan.resources.planScore);
12461
- console.log("");
12462
- console.log("Minority:");
12463
- console.log("* Score:", pa.minority.score);
12464
- console.log("* Metrics:");
12465
- console.log(" - nBlack37to50:", pa.minority.metrics.nBlack37to50);
12466
- console.log(" - nBlackMajority:", pa.minority.metrics.nBlackMajority);
12467
- console.log(" - nHispanic37to50:", pa.minority.metrics.nHispanic37to50);
12468
- console.log(" - nHispanicMajority:", pa.minority.metrics.nHispanicMajority);
12469
- console.log(" - nPacific37to50:", pa.minority.metrics.nPacific37to50);
12470
- console.log(" - nPacificMajority:", pa.minority.metrics.nPacificMajority);
12471
- console.log(" - nAsian37to50:", pa.minority.metrics.nAsian37to50);
12472
- console.log(" - nAsianMajority:", pa.minority.metrics.nAsianMajority);
12473
- console.log(" - nNative37to50:", pa.minority.metrics.nNative37to50);
12474
- console.log(" - nNativeMajority:", pa.minority.metrics.nNativeMajority);
12475
- console.log(" - nMinority37to50:", pa.minority.metrics.nMinority37to50);
12476
- console.log(" - nMinorityMajority:", pa.minority.metrics.nMinorityMajority);
12477
- console.log("* Details:");
12478
- console.log(" - VAP:", pa.minority.details.vap);
12479
- console.log(" - comboCategoroes:", pa.minority.details.comboCategories);
12480
- console.log("* Datasets:");
12481
- console.log("- Shapes:", pa.minority.datasets.shapes);
12482
- console.log("- Census:", pa.minority.datasets.census);
12483
- console.log("- VAP:", pa.minority.datasets.vap);
12484
- console.log("- Election:", pa.minority.datasets.election);
12485
- console.log("* Resources:");
12486
- console.log("- N/A");
12487
- console.log("");
12488
- }
12489
- function echoDistrictStatistics(ds) {
12490
- console.log("District Statistics:");
12491
- for (let row of ds.table) {
12492
- let DistrictID = row[0 /* DistrictID */];
12493
- let TotalPop = row[1 /* TotalPop */];
12494
- let PopDevPct = row[2 /* PopDevPct */];
12495
- let bEqualPop = row[3 /* bEqualPop */];
12496
- let bNotEmpty = row[4 /* bNotEmpty */];
12497
- let bContiguous = row[5 /* bContiguous */];
12498
- let bNotEmbedded = row[6 /* bNotEmbedded */];
12499
- let DemPct = row[7 /* DemPct */];
12500
- let RepPct = row[8 /* RepPct */];
12501
- let WhitePct = row[9 /* WhitePct */];
12502
- let MinorityPct = row[10 /* MinorityPct */];
12503
- let BlackPct = row[11 /* BlackPct */];
12504
- let HispanicPct = row[12 /* HispanicPct */];
12505
- let PacificPct = row[13 /* PacificPct */];
12506
- let AsianPct = row[14 /* AsianPct */];
12507
- let NativePct = row[15 /* NativePct */];
12508
- console.log(DistrictID, formatNumber(TotalPop), formatNumber(PopDevPct), bEqualPop, bNotEmpty, bContiguous, bNotEmbedded, formatNumber(DemPct), formatNumber(RepPct), formatNumber(WhitePct), formatNumber(MinorityPct), formatNumber(BlackPct), formatNumber(HispanicPct), formatNumber(PacificPct), formatNumber(AsianPct), formatNumber(NativePct));
12118
+ case 'limit': {
12119
+ s.analyzePlan(bLog);
12120
+ text = s.prepareScorecard();
12121
+ console.log("");
12122
+ for (let line of text.data) {
12123
+ console.log(line['variant'], line['text']);
12124
+ }
12125
+ console.log("");
12126
+ text = s.prepareTestLog();
12127
+ console.log("");
12128
+ for (let line of text.data) {
12129
+ console.log(line['variant'], line['text']);
12130
+ }
12131
+ console.log("");
12132
+ break;
12509
12133
  }
12510
- console.log("");
12511
- console.log("* Details:");
12512
- console.log(" - N/A");
12513
- console.log("* Datasets:");
12514
- console.log("- Shapes:", ds.datasets.shapes);
12515
- console.log("- Census:", ds.datasets.census);
12516
- console.log("- VAP:", ds.datasets.vap);
12517
- console.log("- Election:", ds.datasets.election);
12518
- console.log("* Resources:");
12519
- console.log("- N/A");
12520
- console.log("");
12521
12134
  }
12522
- exports.echoDistrictStatistics = echoDistrictStatistics;
12135
+ console.log("Ending command @ ", new Date());
12523
12136
  // HELPERS TO LOAD SAMPLE DATA FROM DISK
12524
12137
  // A clone of 'carefulRead' in DRA-CLI
12525
12138
  function readJSONcareful(file) {
@@ -12540,7 +12153,7 @@ function readJSON(file) {
12540
12153
  fullPath = file;
12541
12154
  }
12542
12155
  else {
12543
- fullPath = path.resolve(file);
12156
+ fullPath = path.join(__dirname, file);
12544
12157
  }
12545
12158
  return readJSONcareful(fullPath);
12546
12159
  }
@@ -12549,7 +12162,7 @@ exports.readJSON = readJSON;
12549
12162
  function readCSV(file) {
12550
12163
  try {
12551
12164
  let input = fs.readFileSync(file, 'utf8');
12552
- let dictRows = sync_1.default(input, {
12165
+ let dictRows = parse(input, {
12553
12166
  columns: true,
12554
12167
  skip_empty_lines: true
12555
12168
  });
@@ -12569,7 +12182,7 @@ function readPlanCSV(file) {
12569
12182
  fullPath = file;
12570
12183
  }
12571
12184
  else {
12572
- fullPath = path.resolve(file);
12185
+ fullPath = path.join(__dirname, file);
12573
12186
  }
12574
12187
  var csvArray = readCSV(fullPath);
12575
12188
  for (let dictRow of csvArray) {
@@ -12580,8 +12193,9 @@ function readPlanCSV(file) {
12580
12193
  return plan;
12581
12194
  }
12582
12195
  exports.readPlanCSV = readPlanCSV;
12583
- // END
12196
+ // FIN
12584
12197
 
12198
+ /* WEBPACK VAR INJECTION */}.call(this, "/"))
12585
12199
 
12586
12200
  /***/ }),
12587
12201