@dra2020/district-analytics 1.0.10 → 2.0.1

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