@dra2020/district-analytics 17.0.1 → 17.1.0

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.
@@ -20,7 +20,7 @@ return /******/ (() => { // webpackBootstrap
20
20
 
21
21
 
22
22
  //
23
- // THE NODE PACKAGE API
23
+ // ANALYTICS INTERFACE
24
24
  //
25
25
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
26
  if (k2 === undefined) k2 = k;
@@ -57,120 +57,23 @@ var __importStar = (this && this.__importStar) || (function () {
57
57
  })();
58
58
  Object.defineProperty(exports, "__esModule", ({ value: true }));
59
59
  exports.AnalyticsSession = void 0;
60
- const baseclient_1 = __webpack_require__(/*! @dra2020/baseclient */ "@dra2020/baseclient");
61
- const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
62
- const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
63
60
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
64
- const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
65
- const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
66
- const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
67
- const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
68
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
69
- const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
61
+ const rpv_1 = __webpack_require__(/*! ./rpv */ "./src/rpv.ts");
62
+ const D = __importStar(__webpack_require__(/*! ./data */ "./src/data.ts"));
70
63
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
71
- // MMD - Extended for optional # reps per district
72
64
  class AnalyticsSession {
73
65
  constructor(SessionRequest) {
74
- this.legislativeDistricts = false; // 2020
75
- this.config = {};
76
- this.bOneTimeProcessingDone = false;
77
- this.bPlanAnalyzed = false;
78
- this.bPostProcessingDone = false;
79
- this.tests = {};
80
- this.title = SessionRequest['title'];
81
- this.config = this.processConfig(SessionRequest['config']);
82
- // 2020 - Set the CD / LD toggle:
83
- // * If 2020, use the PlanType, if given.
84
- // * If 2010, use the legacy LD toggle, if given.
85
- // * Otherwise assume CD's.
86
- // * Use the LD population deviation threshold for districts that are not CD's.
87
- if ((this.config.cycle == 2020) && (SessionRequest['planType'] !== undefined))
88
- this.legislativeDistricts = (SessionRequest['planType'] != 'congress');
89
- else if ((this.config.cycle == 2010) && (SessionRequest['legislativeDistricts'] !== undefined))
90
- this.legislativeDistricts = SessionRequest['legislativeDistricts'];
91
- // MMD - Validate & handle optional # reps per district
92
- const repsByDistrict = SessionRequest['repsByDistrict'];
93
- const nDistricts = SessionRequest['nDistricts'];
94
- if (repsByDistrict !== undefined) {
95
- if (repsByDistrict.length != nDistricts)
96
- throw new Error("Mismatched #'s of districts passed to AnalyticsSession constructor!");
97
- if (repsByDistrict.includes(0))
98
- throw new Error("Zero reps for a district passed to AnalyticsSession constructor!");
99
- // Assume a positive integer # of reps per district
100
- this.repsByDistrict = repsByDistrict;
101
- }
102
- this.state = new D.State(this, SessionRequest['stateXX'], nDistricts);
103
- this.counties = new D.Counties(this, SessionRequest['counties']);
104
- this.graph = new D.GraphClass(this, SessionRequest['graph']);
105
- this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
106
- this.plan = new D.Plan(this, SessionRequest['plan']);
107
- this.districts = new D.Districts(this, SessionRequest['districtShapes']);
108
- this.aggregates = SessionRequest['aggregates'];
109
- }
110
- processConfig(config) {
111
- // Default the Census & redistricting cycle to 2010
112
- if (!(U.keyExists('cycle', config))) {
113
- config['cycle'] = 2010;
114
- // DEBUG
115
- console.log("Cycle was not set explicitly on the session request.");
116
- }
117
- // DEBUG - Turned off 03/05/21
118
- // console.log("Cycle = ", config['cycle']);
119
- // console.log("Shapes = ", config['datasets'][T.Dataset.SHAPES], ": ", config['descriptions']['SHAPES']);
120
- return config;
66
+ this.bLog = false;
67
+ this.data = new D.SessionData(this, SessionRequest);
121
68
  }
122
- // Using the the data in the analytics session, calculate all the
123
- // analytics & validations, saving/updating the individual test results.
124
69
  analyzePlan(bLog = false, overridesJSON) {
125
70
  // Return values:
126
71
  // * true = everything good
127
72
  // * false = should not have been called with empty plan
128
73
  // * exception = exception caught and logged on the console
129
74
  try {
130
- // Guard against being handed a map w/o any precincts assigned
131
- if (U.isObjectEmpty(this.plan._planByGeoID))
132
- return false;
133
- (0, preprocess_1.doPreprocessData)(this, bLog);
134
- (0, analyze_1.doAnalyzeDistricts)(this, bLog);
135
- // This does a little stuff that didn't get factored out into dra-score and then dra-analytics
136
- (0, analyze_1.doAnalyzePlan)(this, bLog);
137
- // THE MAIN ANALYTICS
138
- // Even though we don't save the profile (as I thought we would),
139
- // we allow it to be exported, so keep generating it, and use it
140
- // to gather the inputs to new analytics (as well as the legacy).
141
- this._profile = (0, score_1.profilePlan)(this, bLog);
142
- let legacyScorecardAlt = {};
143
- let newScorecard = {};
144
- // Construct a new/alternate scorecard
145
- newScorecard = (0, score_1.computeMetrics)(this._profile, this.getGoodShapes(), bLog); // w/o ratings
146
- newScorecard = (0, score_1.rateKeyDimensions)(newScorecard, this._profile, bLog); // w/ ratings
147
- legacyScorecardAlt = (0, score_1.thunkScorecard)(newScorecard, bLog);
148
- // Add minority notes
149
- legacyScorecardAlt.minority.details['majorityMinority'] = M.getMajorityMinority(this);
150
- legacyScorecardAlt.minority.details['vraPreclearance'] = M.getVRASection5(this);
151
- this._scorecard = legacyScorecardAlt;
152
- // Before returning, create a dummy population deviation test, for
153
- // doHasEqualPopulations() to use later.This is preserving the old calling sequence.
154
- let test = this.getTest(4 /* T.Test.PopulationDeviation */);
155
- // Get the raw population deviation
156
- const popDev = this._scorecard.populationDeviation.raw;
157
- // Populate the test entry
158
- test['score'] = popDev;
159
- test['details'] = { 'maxDeviation': this._scorecard.populationDeviation.notes['maxDeviation'] };
160
- // Populate the N+1 summary "district" in district.statistics
161
- let totalPop = this.districts.table.totalPop;
162
- let popDevPct = this.districts.table.popDevPct;
163
- let totalVAP = this.districts.table.totalVAP;
164
- const summaryRow = this.districts.numberOfRows() - 1;
165
- totalPop[summaryRow] = this._profile.population.targetSize; // MMD - This is generalized when the profile is created.
166
- popDevPct[summaryRow] = popDev;
167
- totalVAP[summaryRow] = Math.round(totalVAP[summaryRow] / this._profile.nDistricts);
168
- // Added w/ new scorecard
169
- // Use 'roughly' equal population from dra-analytics
170
- let test2 = this.getTest(3 /* T.Test.EqualPopulation */);
171
- test2['score'] = newScorecard.populationDeviation.roughlyEqual;
172
- // END main analytics
173
- (0, results_1.doAnalyzePostProcessing)(this, bLog);
75
+ this.bLog = bLog;
76
+ (0, analyze_1.newAnalyzePlan)(this);
174
77
  }
175
78
  catch (e) {
176
79
  console.log("Exception caught by analyzePlan().");
@@ -179,108 +82,223 @@ class AnalyticsSession {
179
82
  }
180
83
  return true;
181
84
  }
182
- getGoodShapes() {
183
- const rawShapes = this.districts.getDistrictShapes();
184
- // Filter the real shapes & throw everything else away
185
- let goodShapes = {};
186
- goodShapes['type'] = "FeatureCollection";
187
- goodShapes['features'] = [];
188
- for (let i = 0; i < rawShapes.features.length; i++) {
189
- const shape = rawShapes.features[i];
190
- if (isAShape(shape)) {
191
- const d = baseclient_1.Poly.polyDescribe(shape);
192
- let f = {
193
- type: 'Feature',
194
- properties: { districtID: `${i + 1}` },
195
- geometry: {
196
- type: (d.npoly > 1) ? 'MultiPolygon' : 'Polygon',
197
- coordinates: shape.geometry.coordinates
198
- }
199
- };
200
- goodShapes.features.push(f);
201
- }
202
- }
203
- return goodShapes;
204
- }
205
- // 11-03-2020 - Added for racially polarized voting analysis
206
- // NOTE - This assumes that analyzePlan() has been run!
207
85
  analyzeRacialPolarization(districtID, groups, bLog = false) {
208
- return (0, minority_1.doAnalyzeRacialPolarization)(this, districtID, groups, bLog);
209
- }
210
- // NOTE - This assumes that analyzePlan() has been run!
211
- getDistrictStatistics(bLog = false) {
212
- return (0, results_2.prepareDistrictStatistics)(this, bLog);
86
+ return (0, rpv_1.doRPV)(this.data, districtID, groups, this.bLog);
213
87
  }
88
+ // NOTE - These methods assume that analyzePlan() has been run.
214
89
  getPlanProfile(bLog = false) {
215
- return this._profile;
90
+ return U.deepCopy(this.data.profile);
216
91
  }
217
92
  getPlanScorecard(bLog = false) {
218
- return this._scorecard;
93
+ return U.deepCopy(this.data.legacyScorecardAlt);
219
94
  }
220
95
  getRatings(bLog = false) {
221
- const scorecard = this._scorecard;
222
- const r = {
223
- proportionality: scorecard.partisan.bias.score,
224
- competitiveness: scorecard.partisan.responsiveness.score,
225
- minorityRights: scorecard.minority.score,
226
- compactness: scorecard.compactness.score,
227
- splitting: scorecard.splitting.score
228
- };
229
- return r;
96
+ return U.deepCopy(this.data.ratings);
230
97
  }
231
- // NOTE - This assumes that analyzePlan() has been run!
232
98
  getRequirementsChecklist(bLog = false) {
233
- return (0, results_2.prepareRequirementsChecklist)(this, bLog);
234
- }
235
- // NOTE - This assumes that analyzePlan() has been run!
236
- getDiscontiguousDistrictFeatures(bLog = false) {
237
- // Get the (possibly empty) list of discontiguous district IDs
238
- const contiguousTest = this.getTest(1 /* T.Test.Contiguous */);
239
- const discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
240
- // Convert them into a (possibly empty) list of features
241
- let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
242
- if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
243
- for (let id of discontiguousDistrictIDs) {
244
- let poly = this.districts.getDistrictShapeByID(id);
245
- if (poly) {
246
- // If a district has a shape & it is not contiguous, by definition,
247
- // it will be a Multipolygon, i.e., it will have multiple pieces, some
248
- // possibly embedded w/in other districts. Get & add all the pieces.
249
- const districtParts = baseclient_1.Poly.polyParts(poly);
250
- discontiguousDistrictFeatures.features.push(...districtParts.features);
251
- // discontiguousDistrictFeatures.features.push(poly);
252
- }
253
- }
254
- }
255
- return discontiguousDistrictFeatures;
256
- }
257
- // Comments clipped from dra-client geodistrict.ts.
258
- // Discontiguous polygons are:
259
- // 1. All polygons in a multi-polygon; and
260
- // 2. All holes in a otherwise cohesive polygon.
261
- // Note that all non-cohesive features are always simple polygons.
262
- // HELPERS USED INTERNALLY
263
- // Get an individual test, so you can drive UI with the results.
264
- getTest(testID) {
265
- // Get the existing test entries
266
- // T.Test is a numeric enum, so convert the string keys to numbers
267
- let testValues = U.getNumericObjectKeys(this.tests);
268
- // If there's no entry for this test, create & initialize one
269
- if (!(testValues.includes(testID))) {
270
- this.tests[testID] = {};
271
- this.tests[testID]['score'] = undefined;
272
- this.tests[testID]['details'] = {};
273
- }
274
- // Return a pointer to the the test entry for this test
275
- return this.tests[testID];
99
+ return U.deepCopy(this.data.checklist);
276
100
  }
277
- // NOTE - Not sure why this has to be up here ...
278
- populationDeviationThreshold() {
279
- const threshold = dra_analytics_1.Rate.popdevThreshold(this.legislativeDistricts);
280
- return threshold;
101
+ getDistrictStatistics(bLog = false) {
102
+ return U.deepCopy(this.data.statistics);
281
103
  }
282
104
  }
283
105
  exports.AnalyticsSession = AnalyticsSession;
106
+ // END
107
+
108
+
109
+ /***/ }),
110
+
111
+ /***/ "./src/analyze.ts":
112
+ /*!************************!*\
113
+ !*** ./src/analyze.ts ***!
114
+ \************************/
115
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
116
+
117
+
118
+ //
119
+ // TOP-LEVEL ANALYTICS FUNCTIONS
120
+ // re-factored here to keep _api.ts small
121
+ //
122
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
123
+ if (k2 === undefined) k2 = k;
124
+ var desc = Object.getOwnPropertyDescriptor(m, k);
125
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
126
+ desc = { enumerable: true, get: function() { return m[k]; } };
127
+ }
128
+ Object.defineProperty(o, k2, desc);
129
+ }) : (function(o, m, k, k2) {
130
+ if (k2 === undefined) k2 = k;
131
+ o[k2] = m[k];
132
+ }));
133
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
134
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
135
+ }) : function(o, v) {
136
+ o["default"] = v;
137
+ });
138
+ var __importStar = (this && this.__importStar) || (function () {
139
+ var ownKeys = function(o) {
140
+ ownKeys = Object.getOwnPropertyNames || function (o) {
141
+ var ar = [];
142
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
143
+ return ar;
144
+ };
145
+ return ownKeys(o);
146
+ };
147
+ return function (mod) {
148
+ if (mod && mod.__esModule) return mod;
149
+ var result = {};
150
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
151
+ __setModuleDefault(result, mod);
152
+ return result;
153
+ };
154
+ })();
155
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
156
+ exports.newAnalyzePlan = newAnalyzePlan;
157
+ const baseclient_1 = __webpack_require__(/*! @dra2020/baseclient */ "@dra2020/baseclient");
158
+ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
159
+ const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
160
+ const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
161
+ const requirements_1 = __webpack_require__(/*! ./requirements */ "./src/requirements.ts");
162
+ const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
163
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
164
+ function newAnalyzePlan(s) {
165
+ if (U.isObjectEmpty(s.data.plan._planByGeoID))
166
+ return false;
167
+ (0, preprocess_1.initialize)(s);
168
+ s.data.aggregates.process();
169
+ s.data.profile = (0, score_1.makeProfile)(s.data);
170
+ s.data.newScorecard = (0, score_1.computeMetrics)(s.data.profile, getGoodShapes(s.data.districts.getShapes()), false /* s.bLog */);
171
+ s.data.newScorecard = (0, score_1.rateKeyDimensions)(s.data.newScorecard, s.data.profile, false /* s.bLog */);
172
+ s.data.legacyScorecardAlt = (0, score_1.thunkScorecard)(s.data.newScorecard, false /* s.bLog */);
173
+ s.data.legacyScorecardAlt.minority.details['majorityMinority'] = {};
174
+ s.data.legacyScorecardAlt.minority.details['vraPreclearance'] = "";
175
+ _add_details(s);
176
+ s.data.ratings = _extractRatings(s);
177
+ s.data.checklist = _makeRequirementsChecklist(s);
178
+ s.data.statistics = _makeDistrictStatistics(s);
179
+ return true;
180
+ }
181
+ function _add_details(s) {
182
+ const scorecard = s.data.legacyScorecardAlt;
183
+ const datasets = {
184
+ shapes: U.deepCopy(s.data.config['descriptions']['SHAPES']),
185
+ census: U.deepCopy(s.data.config['descriptions']['CENSUS']),
186
+ vap: U.deepCopy(s.data.config['descriptions']['VAP']),
187
+ election: U.deepCopy(s.data.config['descriptions']['ELECTION'])
188
+ };
189
+ scorecard.partisan.details['election'] = datasets.election;
190
+ scorecard.minority.details['vap'] = datasets.vap;
191
+ scorecard.details['shapes'] = datasets.shapes;
192
+ scorecard.details['census'] = datasets.census;
193
+ const simpleSplits = (0, cohesive_1.findCountiesSplitUnexpectedly)(s.data, false /* s.bLog */);
194
+ scorecard.splitting.details['unexpectedAffected'] = simpleSplits['score'];
195
+ scorecard.splitting.details['nSplits'] = simpleSplits['details']['nSplits'];
196
+ scorecard.splitting.details['countiesSplitUnexpectedly'] = U.deepCopy(simpleSplits['details']['countiesSplitUnexpectedly']);
197
+ scorecard.splitting.details['nTooBigCounties'] = simpleSplits['details']['expectedSplits'];
198
+ scorecard.splitting.details['tooBigName'] = simpleSplits['details']['tooBigName'];
199
+ scorecard.splitting.details['nCountiesSplit'] = simpleSplits['details']['nCountiesSplit'];
200
+ scorecard.splitting.details['nSingleCountyDistricts'] = simpleSplits['details']['nSingleCountyDistricts'];
201
+ scorecard.splitting.details['singleCountyDistrictMax'] = simpleSplits['details']['singleCountyDistrictMax'];
202
+ scorecard.splitting.details['countiesWithSplits'] = simpleSplits['details']['countiesWithSplits'];
203
+ }
204
+ function _makeRequirementsChecklist(s) {
205
+ var _a;
206
+ const popByDistricts = s.data.aggregates.columns.totalPop;
207
+ const completeness = (0, requirements_1.isComplete)(popByDistricts);
208
+ const bContiguousByDistrict = s.data.aggregates.columns.bContiguous;
209
+ const discontiguousDistrictsList = U.falseIndexes(bContiguousByDistrict);
210
+ const contiguity = bContiguousByDistrict.at(-1);
211
+ const bNotEmbeddedByDistrict = s.data.aggregates.columns.bNotEmbedded;
212
+ const embeddedDistrictsList = U.falseIndexes(bNotEmbeddedByDistrict);
213
+ const freeOfHoles = bNotEmbeddedByDistrict.at(-1);
214
+ const roughlyEqual = (_a = s.data.newScorecard) === null || _a === void 0 ? void 0 : _a.populationDeviation.roughlyEqual;
215
+ const reqScore = completeness && contiguity && freeOfHoles && roughlyEqual;
216
+ // Get values to support details/notes entries in dra-client
217
+ const unassignedFeaturesDetail = (0, requirements_1.getUnassignedFeatures)(s.data);
218
+ const emptyDistrictsDetail = (0, requirements_1.emptyDistricts)(popByDistricts);
219
+ const discontiguousDistrictsDetail = discontiguousDistrictsList;
220
+ const embeddedDistrictsDetail = embeddedDistrictsList;
221
+ const newScorecard = s.data.newScorecard;
222
+ const populationDeviationDetail = newScorecard.populationDeviation.deviation;
223
+ const deviationThresholdDetail = U.trim(dra_analytics_1.Rate.popdevThreshold(s.data.legislativeDistricts));
224
+ const census = U.deepCopy(s.data.config['descriptions']['CENSUS']);
225
+ // Populate the checklist
226
+ let paRequirements = {
227
+ score: reqScore,
228
+ metrics: {
229
+ complete: completeness,
230
+ contiguous: contiguity,
231
+ freeOfHoles: freeOfHoles,
232
+ equalPopulation: roughlyEqual
233
+ },
234
+ details: {
235
+ unassignedFeatures: unassignedFeaturesDetail,
236
+ emptyDistricts: emptyDistrictsDetail,
237
+ discontiguousDistricts: discontiguousDistrictsDetail,
238
+ embeddedDistricts: embeddedDistrictsDetail,
239
+ populationDeviation: populationDeviationDetail,
240
+ deviationThreshold: deviationThresholdDetail
241
+ },
242
+ datasets: {
243
+ census: census,
244
+ },
245
+ resources: {
246
+ // stateReqs: reqLinkToStateReqs
247
+ }
248
+ };
249
+ return paRequirements;
250
+ }
251
+ function _extractRatings(s) {
252
+ const scorecard = s.data.legacyScorecardAlt;
253
+ const r = {
254
+ proportionality: scorecard.partisan.bias.score,
255
+ competitiveness: scorecard.partisan.responsiveness.score,
256
+ minorityRights: scorecard.minority.score,
257
+ compactness: scorecard.compactness.score,
258
+ splitting: scorecard.splitting.score
259
+ };
260
+ s.data.ratings = r;
261
+ return r;
262
+ }
263
+ function _makeDistrictStatistics(s) {
264
+ const table = s.data.aggregates.transpose();
265
+ const datasets = {
266
+ shapes: U.deepCopy(s.data.config['descriptions']['SHAPES']),
267
+ census: U.deepCopy(s.data.config['descriptions']['CENSUS']),
268
+ vap: U.deepCopy(s.data.config['descriptions']['VAP']),
269
+ election: U.deepCopy(s.data.config['descriptions']['ELECTION'])
270
+ };
271
+ const statistics = {
272
+ table: table,
273
+ details: {}, // None at this time
274
+ datasets: datasets,
275
+ resources: {} // None at this time
276
+ };
277
+ return statistics;
278
+ }
279
+ // HELPERS
280
+ function getGoodShapes(rawShapes) {
281
+ // Filter the real shapes & throw everything else away
282
+ let goodShapes = {};
283
+ goodShapes['type'] = "FeatureCollection";
284
+ goodShapes['features'] = [];
285
+ for (let i = 0; i < rawShapes.features.length; i++) {
286
+ const shape = rawShapes.features[i];
287
+ if (isAShape(shape)) {
288
+ const d = baseclient_1.Poly.polyDescribe(shape);
289
+ let f = {
290
+ type: 'Feature',
291
+ properties: { districtID: `${i + 1}` },
292
+ geometry: {
293
+ type: (d.npoly > 1) ? 'MultiPolygon' : 'Polygon',
294
+ coordinates: shape.geometry.coordinates
295
+ }
296
+ };
297
+ goodShapes.features.push(f);
298
+ }
299
+ }
300
+ return goodShapes;
301
+ }
284
302
  function isAShape(poly) {
285
303
  if (poly == null)
286
304
  return false;
@@ -288,19 +306,21 @@ function isAShape(poly) {
288
306
  return false;
289
307
  return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
290
308
  }
309
+ // END
291
310
 
292
311
 
293
312
  /***/ }),
294
313
 
295
- /***/ "./src/_data.ts":
296
- /*!**********************!*\
297
- !*** ./src/_data.ts ***!
298
- \**********************/
314
+ /***/ "./src/cohesive.ts":
315
+ /*!*************************!*\
316
+ !*** ./src/cohesive.ts ***!
317
+ \*************************/
299
318
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
300
319
 
301
320
 
302
321
  //
303
- // DATA ABSTRACTION LAYER
322
+ // ANALYZE SIMPLE COUNTY SPLITTING
323
+ // The main county-district splitting code is in the dra-analytics package.
304
324
  //
305
325
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
306
326
  if (k2 === undefined) k2 = k;
@@ -336,652 +356,487 @@ var __importStar = (this && this.__importStar) || (function () {
336
356
  };
337
357
  })();
338
358
  Object.defineProperty(exports, "__esModule", ({ value: true }));
339
- exports.GraphClass = exports.Plan = exports.State = exports.Counties = exports.Features = exports.Districts = void 0;
340
- exports.inferSelectedMinority = inferSelectedMinority;
341
- exports.geoIDForFeature = geoIDForFeature;
342
- exports.fieldForFeature = fieldForFeature;
343
- exports.invertPlan = invertPlan;
344
- const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
345
- const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
359
+ exports.findCountiesSplitUnexpectedly = findCountiesSplitUnexpectedly;
346
360
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
347
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
348
- const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
349
- const dra_analytics_2 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
350
- const dra_types_1 = __webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types");
351
- // DEBUG COUNTERS
352
- let nMissingDataset = 0;
353
- let nMissingProperty = 0;
354
- class Districts {
355
- constructor(s, ds) {
356
- this._geoProperties = {};
357
- this._session = s;
358
- this._shapes = ds;
359
- this.table = this.initTable();
360
- }
361
- getDistrictShapes() {
362
- return this._shapes;
363
- }
364
- getDistrictShapeByID(id) {
365
- // NOTE - Find the district shape by ID (not index)
366
- // return this._shapes.features[id];
367
- for (let f of this._shapes.features) {
368
- if (f.properties && (f.properties.id == id)) {
369
- return f;
370
- }
371
- }
372
- return undefined;
373
- }
374
- getGeoProperties(i) {
375
- // Make sure the district shape exists & has geo properties
376
- if (i in this._geoProperties)
377
- return this._geoProperties[i];
378
- else
379
- return null;
380
- }
381
- setGeoProperties(i, p) { this._geoProperties[i] = p; }
382
- // +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for state-level values.
383
- // Real districts are 1–N.
384
- numberOfRows() { return this._session.state.nDistricts + 2; }
385
- numberOfWorkingDistricts() { return this._session.state.nDistricts + 1; }
386
- initTable() {
387
- let nRows = this.numberOfRows(); // # of districts plus unassigned & summary
388
- const t = {
389
- totalPop: U.initArray(nRows, null),
390
- popDevPct: U.initArray(nRows, null),
391
- bEqualPop: U.initArray(nRows, null),
392
- bNotEmpty: U.initArray(nRows, null),
393
- bContiguous: U.initArray(nRows, null),
394
- bNotEmbedded: U.initArray(nRows, null),
395
- countySplits: U.initArray(nRows, null),
396
- demVotes: U.initArray(nRows, null),
397
- repVotes: U.initArray(nRows, null),
398
- otherVotes: U.initArray(nRows, null),
399
- demPct: U.initArray(nRows, null),
400
- repPct: U.initArray(nRows, null),
401
- otherPct: U.initArray(nRows, null),
402
- demSeat: U.initArray(nRows, null),
403
- totalVAP: U.initArray(nRows, null),
404
- minorityPop: U.initArray(nRows, null),
405
- whitePop: U.initArray(nRows, null),
406
- blackPop: U.initArray(nRows, null),
407
- hispanicPop: U.initArray(nRows, null),
408
- pacificPop: U.initArray(nRows, null),
409
- asianPop: U.initArray(nRows, null),
410
- nativePop: U.initArray(nRows, null),
411
- whitePct: U.initArray(nRows, null),
412
- minorityPct: U.initArray(nRows, null),
413
- blackPct: U.initArray(nRows, null),
414
- hispanicPct: U.initArray(nRows, null),
415
- pacificPct: U.initArray(nRows, null),
416
- asianPct: U.initArray(nRows, null),
417
- nativePct: U.initArray(nRows, null)
418
- };
419
- return t;
420
- }
421
- // MMD - Generalized district statistics for MMD's
422
- // This is the workhorse computational routine!
423
- recalcStatistics(bLog = false) {
424
- // Initialize debug counters
425
- nMissingDataset = 0;
426
- nMissingProperty = 0;
427
- // MMD - Generalized targetSize
428
- // Compute these once per recalc cycle
429
- const nDistricts = this._session.state.nDistricts;
430
- const repsByDistrict = this._session.repsByDistrict;
431
- const nReps = this._session.state.nReps;
432
- const stateTotal = this._session.state.totalPop;
433
- const targetSize = this._session.state.targetSize;
434
- // const targetSize = Math.round(this._session.state.totalPop / this._session.state.nDistricts);
435
- const deviationThreshold = this._session.populationDeviationThreshold();
436
- // let planByDistrict = this._session.plan.byDistrictID();
437
- const plan = this._session.plan;
438
- const graph = this._session.graph;
439
- // Add an extra 0th virtual county bucket for county-district splitting analysis
440
- let nCountyBuckets = this._session.counties.nCounties + 1;
441
- // INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
442
- // 10-22-2020 - Added Other votes
443
- let stateDemVote = 0;
444
- let stateRepVote = 0;
445
- let stateOthVote = 0;
446
- let stateTotVote = 0;
447
- // let stateTPVote = 0;
448
- let stateVAPPop = 0;
449
- let stateWhitePop = 0;
450
- let stateMinorityPop = 0;
451
- let stateBlackPop = 0;
452
- let stateHispanicPop = 0;
453
- let statePacificPop = 0;
454
- let stateAsianPop = 0;
455
- let stateNativePop = 0;
456
- // NOTE - These plan-level booleans are set in their respective analytics:
457
- // - Equal population (bEqualPop)
458
- // - Complete (bNotEmpty)
459
- // - Contiguous (bContiguous)
460
- // - Free of holes (bNotEmbedded)
461
- // Loop over the districts (including the dummy unassigned one)
462
- for (let i = 0; i < this.numberOfWorkingDistricts(); i++) {
463
- // INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
464
- let featurePop;
465
- let totalPop = 0;
466
- let countySplits = U.initArray(nCountyBuckets, 0);
467
- // 10-22-2020 - Added Other votes
468
- let demVotes = 0;
469
- let repVotes = 0;
470
- let othVotes = 0;
471
- let totVotes = 0;
472
- let totalVAP = 0;
473
- let whitePop = 0;
474
- let blackPop = 0;
475
- let hispanicPop = 0;
476
- let pacificPop = 0;
477
- let asianPop = 0;
478
- let nativePop = 0;
479
- // NOTE - Only report explicitly found validity issues
480
- let bNotEmpty = false;
481
- let bContiguous = true;
482
- let bNotEmbedded = true;
483
- let bEqualPop = true;
484
- // HACK - Because "this" gets ghosted inside the forEach loop below
485
- let outerThis = this;
486
- // Default the pop dev % for the dummy Unassigned district to 0%.
487
- // Default the pop dev % for real (1–N) but empty districts to 100%.
488
- let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
489
- // Get the geoIDs assigned to the district
490
- // Guard against empty districts
491
- let geoIDs = this._session.plan.geoIDsForDistrictID(i);
492
- if (geoIDs && (geoIDs.size > 0)) {
493
- bNotEmpty = true;
494
- // ... loop over the geoIDs creating district-by-district statistics
495
- geoIDs.forEach(function (geoID) {
496
- // 01-04-22 -- Removed water-only guard
497
- // // Skip water-only features
498
- // if (!(U.isWaterOnly(geoID)))
499
- // {
500
- // Map from geoID to feature index
501
- let featureID = outerThis._session.features.featureID(geoID);
502
- let f = outerThis._session.features.featureByIndex(featureID);
503
- if (f == undefined) {
504
- if (bLog)
505
- console.log("Statistics: Skipping undefined feature in district statistics: GEOID =", geoID, "Feature ID =", featureID);
506
- }
507
- else {
508
- // ACCUMULATE VALUES
509
- // Total population of each feature
510
- // NOTE - This result is used more than once
511
- // 03-27-21
512
- const dkCENSUS = outerThis._session.features._keys["CENSUS" /* T.Dataset.CENSUS */];
513
- featurePop = fieldForFeature(f, dkCENSUS, 0 /* T.FeatureField.TotalPop */);
514
- // featurePop = outerThis._session.features.fieldForFeature(f, T.Dataset.CENSUS, T.FeatureField.TotalPop);
515
- // Total district population
516
- totalPop += featurePop;
517
- // Ignore features when the county is unrecognized
518
- const countyFIPS = U.parseGeoID(geoID)['county'];
519
- if (U.keyExists(countyFIPS, outerThis._session.counties.index)) {
520
- // Total population by counties w/in a district,
521
- // except the dummy unassigned district 0
522
- if (i > 0)
523
- countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
524
- }
525
- else {
526
- if (bLog)
527
- console.log("Statistics: County not recognized:", geoID);
528
- }
529
- // Democratic and Republican vote totals
530
- // 10-22-2020 - Added Other votes
531
- // 10-24-2020 - Added guard against inconsistent election data
532
- // 03-27-2021
533
- const dkELECTION = outerThis._session.features._keys["ELECTION" /* T.Dataset.ELECTION */];
534
- const featureDem = fieldForFeature(f, dkELECTION, 7 /* T.FeatureField.DemVotes */);
535
- // const featureDem = outerThis._session.features.fieldForFeature(f, T.Dataset.ELECTION, T.FeatureField.DemVotes);
536
- const featureRep = fieldForFeature(f, dkELECTION, 8 /* T.FeatureField.RepVotes */);
537
- const featureTot = fieldForFeature(f, dkELECTION, 9 /* T.FeatureField.TotalVotes */);
538
- demVotes += featureDem;
539
- repVotes += featureRep;
540
- totVotes += featureTot;
541
- // NOTE: Unless you grab the values above before accumulating them,
542
- // you can't accumulate othVotes for districts. You must calculate
543
- // them by implication later.
544
- if (bLog) {
545
- const bBadElection = (featureDem + featureRep > featureTot) ? true : false;
546
- if (bBadElection)
547
- console.log("Statistics: Inconsistent election data for precinct:", geoID, featureDem, featureRep, featureTot);
548
- }
549
- // Voting-age demographic breakdowns (or citizen voting-age)
550
- // 03-27-21
551
- const dkVAP = outerThis._session.features._keys["VAP" /* T.Dataset.VAP */];
552
- totalVAP += fieldForFeature(f, dkVAP, 0 /* T.FeatureField.TotalPop */);
553
- // totalVAP += outerThis._session.features.fieldForFeature(f, T.Dataset.VAP, T.FeatureField.TotalPop);
554
- whitePop += fieldForFeature(f, dkVAP, 1 /* T.FeatureField.WhitePop */);
555
- blackPop += fieldForFeature(f, dkVAP, 2 /* T.FeatureField.BlackPop */);
556
- hispanicPop += fieldForFeature(f, dkVAP, 3 /* T.FeatureField.HispanicPop */);
557
- pacificPop += fieldForFeature(f, dkVAP, 5 /* T.FeatureField.PacificPop */);
558
- asianPop += fieldForFeature(f, dkVAP, 4 /* T.FeatureField.AsianPop */);
559
- nativePop += fieldForFeature(f, dkVAP, 6 /* T.FeatureField.NativePop */);
560
- }
561
- // }
562
- // else
563
- // {
564
- // if (bLog) console.log("Statistics: Skipping water-only feature in district statistics:", geoID);
565
- // }
566
- });
567
- //console.log(`totalVAP: ${totalVAP}, blackPop: ${blackPop}`);
568
- // COMPUTE DERIVED VALUES
569
- // MMD - Generalized the per-district population deviations's for MMD's with variable #'s of reps per district.
570
- // - The real districts are indexed 1–N.
571
- // - But the # reps per district are indexed from zero.
572
- // Population deviation % and equal population (boolean) by district.
573
- if (i > 0) {
574
- if (totalPop > 0) {
575
- const n = (repsByDistrict) ? repsByDistrict[i - 1] : 1; // MMD - # of reps for the district
576
- popDevPct = (totalPop - (n * targetSize)) / (n * targetSize); // MMD
577
- // popDevPct = (totalPop - targetSize) / targetSize;
578
- bEqualPop = (Math.abs(popDevPct) <= deviationThreshold);
361
+ /*
362
+
363
+ Sample results for NC 2016 congressional plan
364
+ ________________________________________________________________________________
365
+
366
+ State: NC
367
+ Census: 2010
368
+ Total population: 9,535,483
369
+ Number of districts: 13
370
+ Target district size: 733,499
371
+ Number of counties: 100
372
+
373
+ Equal Population: 11.24% deviation
374
+ Compactness: None
375
+ Proportionality: 27.67% gap
376
+ Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
377
+
378
+ These counties are split unexpectedly (meaning that they're smaller than a district):
379
+
380
+ Bladen
381
+ Buncombe
382
+ Catawba
383
+ Cumberland
384
+ • Durham
385
+ • Guilford
386
+ Iredell
387
+ • Johnston
388
+ Pitt
389
+ Rowan
390
+ Wilson
391
+
392
+ */
393
+ // This analysis *assumes* that a map is complete (or nearly so).
394
+ // - We do the analysis regardless, but
395
+ // - Protect against weird results in the client
396
+ function findCountiesSplitUnexpectedly(container, bLog = false) {
397
+ // Simulate an old school test entry, to preserve this and downstream code
398
+ let test = {};
399
+ test['score'] = undefined;
400
+ test['details'] = {};
401
+ // THE THREE VALUES TO DETERMINE FOR A PLAN
402
+ let unexpectedSplits = 0;
403
+ let unexpectedAffected = 0;
404
+ let countiesSplitUnexpectedly = [];
405
+ // FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
406
+ // Get the county-district pivot ("splits")
407
+ let CxD = container.CxD;
408
+ // NOTE - Unlike the legacy code that uses the district statistics table with extra virtual districts & counties,
409
+ // this CxD matrix just has the right number that are zero-indexed.
410
+ // Find the single-county districts, i.e., districts NOT split across counties.
411
+ // Ignore the dummy unassigned 0 and N+1 summary "districts."
412
+ let singleCountyDistricts = [];
413
+ for (let d = 0; d < container.state.nDistricts; d++) {
414
+ // See if there's only one county partition
415
+ // Ignore the dummy unassigned 0 "county."
416
+ let nCountiesInDistrict = 0;
417
+ for (let c = 0; c < container.counties.nCounties; c++) {
418
+ // Guard against empty district
419
+ if (CxD[d]) {
420
+ if (CxD[d][c] > 0) {
421
+ nCountiesInDistrict += 1;
422
+ if (nCountiesInDistrict > 1) {
423
+ break;
579
424
  }
580
425
  }
581
- // 10-22-2020 - Added Other votes. Revised from two-party to include Other.
582
- // let totVotes: number; <<< Now being accumulated by district
583
- let demPct = 0;
584
- let repPct = 0;
585
- let othPct = 0;
586
- let DemSeat = 0;
587
- // totVotes now being accumulated by district
588
- if (totVotes > 0) {
589
- // 10-24-2020 - Added guard against inconsistent election data.
590
- const bBadElection = (demVotes + repVotes > totVotes) ? true : false;
591
- if (bBadElection && bLog)
592
- console.log("Statistics: Inconsistent election data for district:", i, demVotes, repVotes, totVotes);
593
- if (bBadElection)
594
- totVotes = demVotes + repVotes; // HACK to guard against bad election data!
595
- othVotes = totVotes - demVotes - repVotes;
596
- demPct = demVotes / totVotes;
597
- repPct = repVotes / totVotes;
598
- othPct = othVotes / totVotes;
599
- DemSeat = dra_analytics_2.Partisan.fptpWin(demPct);
600
- }
601
- // Total minority VAP
602
- let minorityPop = totalVAP - whitePop;
603
- // Voting-age demographic proportions (or citizen voting-age)
604
- let whitePct = 0;
605
- let minorityPct = 0;
606
- let blackPct = 0;
607
- let hispanicPct = 0;
608
- let pacificPct = 0;
609
- let asianPct = 0;
610
- let nativePct = 0;
611
- if (totalVAP > 0) {
612
- whitePct = whitePop / totalVAP;
613
- minorityPct = minorityPop / totalVAP;
614
- blackPct = blackPop / totalVAP;
615
- hispanicPct = hispanicPop / totalVAP;
616
- pacificPct = pacificPop / totalVAP;
617
- asianPct = asianPop / totalVAP;
618
- nativePct = nativePop / totalVAP;
619
- }
620
- // COMPUTE DISTRICT-LEVEL VALUES
621
- // Validations
622
- // Leave the default values for the dummy unassigned district,
623
- // and districts that are empty.
624
- if ((i > 0) && bNotEmpty) {
625
- // GRAPH - 10/05/2020 - Using dra-graph instead.
626
- // bContiguous = isConnected(geoIDs, graph);
627
- // bNotEmbedded = (!isEmbedded(i, planByDistrict[i], plan, graph));
628
- // GRAPH - 10/05/2020 - Using dra-graph.
629
- const features = geoIDs;
630
- const nakedGraph = graph._graph;
631
- const nakedPlan = plan._planByGeoID;
632
- bContiguous = dra_analytics_1.Graph.isConnected(features, nakedGraph, bLog);
633
- bNotEmbedded = (!dra_analytics_1.Graph.isEmbedded(i, features, nakedPlan, nakedGraph, bLog));
634
- }
635
- { // UPDATE THE DISTRICT STATISTICS
636
- // NOTE - These are set below for both non-empty & empty districts:
637
- // * bNotEmpty;
638
- // * bContiguous;
639
- // * bNotEmbedded;
640
- // * totalPop;
641
- // * bEqualPop;
642
- this.table.popDevPct[i] = popDevPct;
643
- this.table.demVotes[i] = demVotes;
644
- this.table.repVotes[i] = repVotes;
645
- this.table.otherVotes[i] = othVotes;
646
- this.table.demPct[i] = demPct;
647
- this.table.repPct[i] = repPct;
648
- this.table.otherPct[i] = othPct;
649
- this.table.demSeat[i] = DemSeat;
650
- this.table.whitePop[i] = whitePop;
651
- this.table.minorityPop[i] = minorityPop;
652
- this.table.blackPop[i] = blackPop;
653
- this.table.hispanicPop[i] = hispanicPop;
654
- this.table.pacificPop[i] = pacificPop;
655
- this.table.asianPop[i] = asianPop;
656
- this.table.nativePop[i] = nativePop;
657
- this.table.totalVAP[i] = totalVAP;
658
- this.table.whitePct[i] = whitePct;
659
- this.table.minorityPct[i] = minorityPct;
660
- this.table.blackPct[i] = blackPct;
661
- this.table.hispanicPct[i] = hispanicPct;
662
- this.table.pacificPct[i] = pacificPct;
663
- this.table.asianPct[i] = asianPct;
664
- this.table.nativePct[i] = nativePct;
665
- }
666
- { // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
667
- // 10-22-2020 - Added Other votes
668
- stateTotVote += totVotes;
669
- // stateTPVote += totVotes;
670
- stateDemVote += demVotes;
671
- stateRepVote += repVotes;
672
- stateOthVote += othVotes;
673
- stateVAPPop += totalVAP;
674
- stateWhitePop += whitePop;
675
- stateMinorityPop += minorityPop;
676
- stateBlackPop += blackPop;
677
- stateHispanicPop += hispanicPop;
678
- statePacificPop += pacificPop;
679
- stateAsianPop += asianPop;
680
- stateNativePop += nativePop;
681
- }
682
- }
683
- else { // If a district is empty, zero these results (vs. null)
684
- this.table.popDevPct[i] = popDevPct;
685
- this.table.demVotes[i] = 0;
686
- this.table.repVotes[i] = 0;
687
- this.table.otherVotes[i] = 0;
688
- this.table.demPct[i] = 0;
689
- this.table.repPct[i] = 0;
690
- this.table.otherPct[i] = 0;
691
- this.table.demSeat[i] = 0;
692
- this.table.whitePop[i] = 0;
693
- this.table.minorityPop[i] = 0;
694
- this.table.blackPop[i] = 0;
695
- this.table.hispanicPop[i] = 0;
696
- this.table.pacificPop[i] = 0;
697
- this.table.asianPop[i] = 0;
698
- this.table.nativePop[i] = 0;
699
- this.table.totalVAP[i] = 0;
700
- this.table.whitePct[i] = 0;
701
- this.table.minorityPct[i] = 0;
702
- this.table.blackPct[i] = 0;
703
- this.table.hispanicPct[i] = 0;
704
- this.table.pacificPct[i] = 0;
705
- this.table.asianPct[i] = 0;
706
- this.table.nativePct[i] = 0;
707
- }
708
- { // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
709
- this.table.totalPop[i] = totalPop;
710
- this.table.bNotEmpty[i] = bNotEmpty;
711
- this.table.bContiguous[i] = bContiguous;
712
- this.table.bNotEmbedded[i] = bNotEmbedded;
713
- this.table.bEqualPop[i] = bEqualPop;
714
- this.table.countySplits[i] = countySplits;
715
426
  }
716
427
  }
717
- // UPDATE STATE STATISTICS
718
- let summaryRow = this.numberOfRows() - 1;
719
- // 10-22-2020 - Added Other votes
720
- if (stateTotVote > 0) {
721
- this.table.demVotes[summaryRow] = stateDemVote;
722
- this.table.repVotes[summaryRow] = stateRepVote;
723
- this.table.demPct[summaryRow] = stateDemVote / stateTotVote;
724
- this.table.repPct[summaryRow] = stateRepVote / stateTotVote;
725
- this.table.otherPct[summaryRow] = stateOthVote / stateTotVote;
726
- }
727
- if (stateVAPPop > 0) {
728
- this.table.totalVAP[summaryRow] = stateVAPPop;
729
- this.table.whitePct[summaryRow] = stateWhitePop / stateVAPPop;
730
- this.table.minorityPct[summaryRow] = stateMinorityPop / stateVAPPop;
731
- this.table.blackPct[summaryRow] = stateBlackPop / stateVAPPop;
732
- this.table.hispanicPct[summaryRow] = stateHispanicPop / stateVAPPop;
733
- this.table.pacificPct[summaryRow] = statePacificPop / stateVAPPop;
734
- this.table.asianPct[summaryRow] = stateAsianPop / stateVAPPop;
735
- this.table.nativePct[summaryRow] = stateNativePop / stateVAPPop;
736
- }
737
- if (bLog) {
738
- console.log(`Statistics: ${nMissingDataset} features with missing datasets.`);
739
- console.log(`Statistics: ${nMissingProperty} features with missing properties.`);
428
+ // If so, save the district
429
+ if (nCountiesInDistrict == 1) {
430
+ singleCountyDistricts.push(d + 1); // Districts are 1-indexed
740
431
  }
741
432
  }
742
- // NOTE - I did not roll these into district statistics, because creating the
743
- // district shapes themselves is the big district-by-district activity, these
744
- // calc's already work, and I'm not going to expose these values. Wrapping
745
- // the underlying function and exposing it here to illustrate the parallelism
746
- // with recalcStatistics(). These are called in tandem by doAnalyzeDistricts().
747
- extractDistrictShapeProperties(bLog = false) {
748
- (0, compact_1.extractDistrictProperties)(this._session, bLog);
749
- }
750
- getCountyIndex(geoID) {
751
- let countyFIPS = U.parseGeoID(geoID)['county'];
752
- let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
753
- return countyIndex;
754
- }
755
- // 11-03-2020 - Added for racially polarized voting analysis
756
- extractVotesByDemographic(districtID, groups, bLog = false) {
757
- let ids = [];
758
- let comparisonPts = []; // Either White -or- (1 – <selected minority>) i.e., White++
759
- let hispanicPts = [];
760
- let blackPts = [];
761
- let pacificPts = [];
762
- let asianPts = [];
763
- let nativePts = [];
764
- let minorityPts = [];
765
- // let dataDump: any[] = []; // For testing
766
- // Gather [demographic %, D %] points for the selected district ID
767
- let i = districtID;
768
- // HACK - For comparing a minority group to everyone else (vs. just White),
769
- // infer the *single* minority group that has been selected.
770
- const selectedMinority = inferSelectedMinority(groups);
771
- // HACK - Because "this" gets ghosted inside the forEach loop below
772
- let outerThis = this;
773
- // Get the geoIDs assigned to the district
774
- // Guard against empty districts
775
- let geoIDs = this._session.plan.geoIDsForDistrictID(i);
776
- if (geoIDs && (geoIDs.size > 0)) {
777
- // ... loop over the geoIDs collecting the points for ecological regression
778
- geoIDs.forEach(function (geoID) {
779
- // Skip water-only features
780
- if (!(U.isWaterOnly(geoID))) {
781
- // Map from geoID to feature index
782
- let featureID = outerThis._session.features.featureID(geoID);
783
- let f = outerThis._session.features.featureByIndex(featureID);
784
- if (!(f == undefined)) {
785
- // Calculate the Dem two-party vote
786
- // 03-27-21
787
- const dkELECTION = outerThis._session.features._keys["ELECTION" /* T.Dataset.ELECTION */];
788
- const featureDem = fieldForFeature(f, dkELECTION, 7 /* T.FeatureField.DemVotes */);
789
- const featureRep = fieldForFeature(f, dkELECTION, 8 /* T.FeatureField.RepVotes */);
790
- // const featureRep = outerThis._session.features.fieldForFeature(f, T.Dataset.ELECTION, T.FeatureField.RepVotes);
791
- if ((featureDem + featureRep) > 0) {
792
- const pctDem = featureDem / (featureDem + featureRep);
793
- // Calculate the VAP/CVAP percentages by demographic
794
- // 03-27-21
795
- const dkVAP = outerThis._session.features._keys["VAP" /* T.Dataset.VAP */];
796
- const totalVAP = fieldForFeature(f, dkVAP, 0 /* T.FeatureField.TotalPop */);
797
- // const totalVAP = outerThis._session.features.fieldForFeature(f, T.Dataset.VAP, T.FeatureField.TotalPop);
798
- if (totalVAP > 0) {
799
- // Gather all points, for debugging purposes ...
800
- const hispanicVAP = fieldForFeature(f, dkVAP, 3 /* T.FeatureField.HispanicPop */);
801
- const blackVAP = fieldForFeature(f, dkVAP, 2 /* T.FeatureField.BlackPop */);
802
- const pacificVAP = fieldForFeature(f, dkVAP, 5 /* T.FeatureField.PacificPop */);
803
- const asianVAP = fieldForFeature(f, dkVAP, 4 /* T.FeatureField.AsianPop */);
804
- const nativeVAP = fieldForFeature(f, dkVAP, 6 /* T.FeatureField.NativePop */);
805
- const pctHispanic = hispanicVAP / totalVAP;
806
- const pctBlack = blackVAP / totalVAP;
807
- const pctPacific = pacificVAP / totalVAP;
808
- const pctAsian = asianVAP / totalVAP;
809
- const pctNative = nativeVAP / totalVAP;
810
- const whiteVAP = fieldForFeature(f, dkVAP, 1 /* T.FeatureField.WhitePop */);
811
- const minorityVAP = totalVAP - whiteVAP;
812
- const pctMinority = minorityVAP / totalVAP;
813
- let pctComparison = whiteVAP / totalVAP;
814
- if (groups.invertSelection) {
815
- switch (selectedMinority) {
816
- case 'Hispanic': {
817
- pctComparison = (totalVAP - hispanicVAP) / totalVAP;
818
- break;
819
- }
820
- case 'Black': {
821
- pctComparison = (totalVAP - blackVAP) / totalVAP;
822
- break;
823
- }
824
- case 'Pacific': {
825
- pctComparison = (totalVAP - pacificVAP) / totalVAP;
826
- break;
827
- }
828
- case 'Asian': {
829
- pctComparison = (totalVAP - asianVAP) / totalVAP;
830
- break;
831
- }
832
- case 'Native': {
833
- pctComparison = (totalVAP - nativeVAP) / totalVAP;
834
- break;
835
- }
836
- case 'Minority': {
837
- pctComparison = (totalVAP - minorityVAP) / totalVAP;
838
- break;
839
- }
840
- default: {
841
- console.log("Selected minority not recognized!");
842
- break;
843
- }
844
- }
845
- }
846
- ids.push(geoID);
847
- comparisonPts.push({ x: pctComparison, y: pctDem }); // White -or- (1 – <selected minority>)
848
- hispanicPts.push({ x: pctHispanic, y: pctDem });
849
- blackPts.push({ x: pctBlack, y: pctDem });
850
- pacificPts.push({ x: pctPacific, y: pctDem });
851
- asianPts.push({ x: pctAsian, y: pctDem });
852
- nativePts.push({ x: pctNative, y: pctDem });
853
- minorityPts.push({ x: pctMinority, y: pctDem });
854
- /* Dump test data based on config switch
855
- const s: AnalyticsSession = outerThis._session;
856
- const bDump = (('dump' in s.config) && s.config.dump) ? true : false;
857
- if (bDump)
858
- {
859
- const row: number[] = [pctWhite, pctMinority, pctBlack, pctHispanic, pctPacific, pctAsian, pctNative, pctDem];
860
- dataDump.push(row);
861
- }
862
- */
863
- }
864
- else {
865
- if (bLog)
866
- console.log("RPV: Total VAP not > 0.");
867
- }
868
- }
869
- else {
870
- if (bLog)
871
- console.log("RPV: D + R not > 0.");
872
- }
433
+ // Process the splits/partitions in the plan:
434
+ // - Count the total # of partitions,
435
+ // - Find the counties split across districts, and
436
+ // - Accumulate the number people affected (except when single-county districts)
437
+ let nPartitionsOverall = 0;
438
+ let splitCounties = new Set(); // The counties that are split across districts
439
+ let totalAffected = 0; // The total population affected by those splits
440
+ // For enumerating # of times each split county is split
441
+ let countyIndexesWithSplits = [];
442
+ for (let c = 0; c < container.counties.nCounties; c++) {
443
+ let nCountyParts = 0;
444
+ let subtotal = 0;
445
+ for (let d = 0; d < container.state.nDistricts; d++) {
446
+ // Guard against empty district
447
+ if (CxD[d]) {
448
+ if (CxD[d][c] > 0) {
449
+ nPartitionsOverall += 1;
450
+ nCountyParts += 1;
451
+ if (!(U.arrayContains(singleCountyDistricts, d + 1))) // Districts are 1-indexed
452
+ {
453
+ subtotal += CxD[d][c];
873
454
  }
874
455
  }
875
- });
876
- // ... but only keep points for the demographics that are going to be analyzed
877
- // dataDump = [];
878
- return {
879
- ids: ids,
880
- comparison: comparisonPts,
881
- minority: groups.minority ? minorityPts : [],
882
- black: groups.black ? blackPts : [],
883
- hispanic: groups.hispanic ? hispanicPts : [],
884
- pacific: groups.pacific ? pacificPts : [],
885
- asian: groups.asian ? asianPts : [],
886
- native: groups.native ? nativePts : []
887
- };
456
+ }
457
+ }
458
+ if (nCountyParts > 1) {
459
+ // Counties are 1-indexed (because of the virtual county bucket)
460
+ splitCounties.add(c + 1);
461
+ totalAffected += subtotal;
462
+ // Enumerate # of times each split county is split
463
+ countyIndexesWithSplits.push([c + 1, nCountyParts - 1]);
888
464
  }
889
- // If a district is empty
890
- return undefined;
891
465
  }
466
+ // Enumerate # of times each split county is split
467
+ let splitCountiesWithSplits = [];
468
+ countyIndexesWithSplits.forEach(pair => {
469
+ const index = pair[0];
470
+ const splits = pair[1];
471
+ // Convert 1–N indices to FIPS codes
472
+ const fips = container.counties.fips[index];
473
+ if (fips) {
474
+ // Convert FIPS codes to names
475
+ const name = container.counties.nameFromFIPS(fips);
476
+ // Combine # of splits with names
477
+ if (name) {
478
+ const text = `${name} (${splits})`;
479
+ splitCountiesWithSplits.push(text);
480
+ }
481
+ }
482
+ });
483
+ // Sort by name
484
+ splitCountiesWithSplits.sort();
485
+ // Convert county ordinals to FIPS codes
486
+ let splitCountiesFIPS = U.getSelectObjectKeys(container.counties.index, [...splitCounties]);
487
+ // THEN TAKE ACCOUNT OF THE COUNTY SPLITTING THAT IS EXPECTED (REQUIRED)
488
+ // Compute the total number of splits this way, in case any counties are split
489
+ // more than once. I.e., it's not just len(all_counties_split).
490
+ let nSplits = nPartitionsOverall - container.counties.nCounties;
491
+ // Determine the number of *unexpected* splits. NOTE: Prevent negative numbers,
492
+ // in case you have a plan the *doesn't* split counties that would have to be
493
+ // split due to their size.
494
+ unexpectedSplits = Math.max(0, nSplits - container.state.expectedSplits);
495
+ // Subtract off the total population that *has* to be affected by splits,
496
+ // because their counties are too big. NOTE: Again, prevent negative numbers,
497
+ // in case you have a plan the *doesn't* split counties that would have to be
498
+ // split due to their size.
499
+ unexpectedAffected = U.trim(Math.max(0, totalAffected - container.state.expectedAffected) / container.state.totalPop);
500
+ // Find the counties that are split *unexpectedly*. From all the counties that
501
+ // are split, remove those that *have* to be split, because they are bigger than
502
+ // a district.
503
+ let countiesSplitUnexpectedlyFIPS = [];
504
+ for (let fips of splitCountiesFIPS) {
505
+ if (!(U.arrayContains(container.state.tooBigFIPS, fips))) {
506
+ countiesSplitUnexpectedlyFIPS.push(fips);
507
+ }
508
+ }
509
+ // ... and convert the FIPS codes to county names.
510
+ for (let fips of countiesSplitUnexpectedlyFIPS) {
511
+ const name = container.counties.nameFromFIPS(fips);
512
+ // 07-06-20 - Guard in case the FIPS code isn't in the county name lookup
513
+ if (name)
514
+ countiesSplitUnexpectedly.push(name);
515
+ else {
516
+ if (bLog)
517
+ console.log("County is not in the FIPS-to-name lookup table: ", fips);
518
+ }
519
+ }
520
+ countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
521
+ test['score'] = U.trim(unexpectedAffected);
522
+ test['details']['nSplits'] = nSplits;
523
+ test['details']['unexpectedSplits'] = unexpectedSplits;
524
+ test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
525
+ test['details']['expectedSplits'] = container.state.expectedSplits;
526
+ test['details']['tooBigName'] = container.state.tooBigName;
527
+ test['details']['singleCountyDistrictMax'] = container.state.singleCountyDistrictMax;
528
+ test['details']['nCountiesSplit'] = splitCountiesFIPS.length;
529
+ test['details']['nSingleCountyDistricts'] = singleCountyDistricts.length;
530
+ test['details']['countiesWithSplits'] = splitCountiesWithSplits;
531
+ return test;
892
532
  }
893
- exports.Districts = Districts;
894
- function inferSelectedMinority(groups) {
895
- if (groups.hispanic)
896
- return 'Hispanic';
897
- if (groups.black)
898
- return 'Black';
899
- if (groups.pacific)
900
- return 'Pacific';
901
- if (groups.asian)
902
- return 'Asian';
903
- if (groups.native)
904
- return 'Native';
905
- if (groups.minority)
906
- return 'Minority';
907
- return 'No minority selected!';
533
+ // END
534
+
535
+
536
+ /***/ }),
537
+
538
+ /***/ "./src/data.ts":
539
+ /*!*********************!*\
540
+ !*** ./src/data.ts ***!
541
+ \*********************/
542
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
543
+
544
+
545
+ //
546
+ // DATA ABSTRACTION LAYER
547
+ //
548
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
549
+ if (k2 === undefined) k2 = k;
550
+ var desc = Object.getOwnPropertyDescriptor(m, k);
551
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
552
+ desc = { enumerable: true, get: function() { return m[k]; } };
553
+ }
554
+ Object.defineProperty(o, k2, desc);
555
+ }) : (function(o, m, k, k2) {
556
+ if (k2 === undefined) k2 = k;
557
+ o[k2] = m[k];
558
+ }));
559
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
560
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
561
+ }) : function(o, v) {
562
+ o["default"] = v;
563
+ });
564
+ var __importStar = (this && this.__importStar) || (function () {
565
+ var ownKeys = function(o) {
566
+ ownKeys = Object.getOwnPropertyNames || function (o) {
567
+ var ar = [];
568
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
569
+ return ar;
570
+ };
571
+ return ownKeys(o);
572
+ };
573
+ return function (mod) {
574
+ if (mod && mod.__esModule) return mod;
575
+ var result = {};
576
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
577
+ __setModuleDefault(result, mod);
578
+ return result;
579
+ };
580
+ })();
581
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
582
+ exports.Plan = exports.State = exports.Counties = exports.Features = exports.Districts = exports.Aggregates = exports.SessionData = void 0;
583
+ exports.geoIDForFeature = geoIDForFeature;
584
+ exports.fieldForFeature = fieldForFeature;
585
+ exports.invertPlan = invertPlan;
586
+ exports.processConfig = processConfig;
587
+ const dra_types_1 = __webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types");
588
+ const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
589
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
590
+ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
591
+ const requirements_1 = __webpack_require__(/*! ./requirements */ "./src/requirements.ts");
592
+ class SessionData {
593
+ constructor(s, SessionRequest) {
594
+ this.bOneTimeProcessingDone = false;
595
+ this.bPlanAnalyzed = false;
596
+ this.legislativeDistricts = false;
597
+ this.config = {};
598
+ this.CxD = []; // Derived later
599
+ this._session = s;
600
+ this.title = SessionRequest['title'];
601
+ this.config = processConfig(SessionRequest['config']);
602
+ // 2020 - Set the CD / LD toggle:
603
+ // * If 2020+, use the PlanType, if given.
604
+ // * If 2010, use the legacy LD toggle, if given.
605
+ // * Otherwise assume CD's.
606
+ // * Use the LD population deviation threshold for districts that are not CD's.
607
+ if ((this.config.cycle > 2010) && (SessionRequest['planType'] !== undefined))
608
+ this.legislativeDistricts = (SessionRequest['planType'] != 'congress');
609
+ else if ((this.config.cycle == 2010) && (SessionRequest['legislativeDistricts'] !== undefined))
610
+ this.legislativeDistricts = SessionRequest['legislativeDistricts'];
611
+ const repsByDistrict = SessionRequest['repsByDistrict'];
612
+ const nDistricts = SessionRequest['nDistricts'];
613
+ if (repsByDistrict !== undefined) {
614
+ if (repsByDistrict.length != nDistricts)
615
+ throw new Error("Mismatched #'s of districts passed to AnalyticsSession constructor!");
616
+ if (repsByDistrict.includes(0))
617
+ throw new Error("Zero reps for a district passed to AnalyticsSession constructor!");
618
+ // Assume a positive integer # of reps per district
619
+ this.repsByDistrict = repsByDistrict;
620
+ }
621
+ this.state = new State(this, SessionRequest['stateXX'], SessionRequest['nDistricts']);
622
+ this.counties = new Counties(this, SessionRequest['counties']);
623
+ this.graph = SessionRequest['graph'];
624
+ this.features = new Features(this, SessionRequest['data'], this.config['datasets']);
625
+ this.plan = new Plan(this, SessionRequest['plan']);
626
+ this.districts = new Districts(this, SessionRequest['districtShapes']);
627
+ this.aggregates = new Aggregates(this, SessionRequest['aggregates']);
628
+ }
629
+ }
630
+ exports.SessionData = SessionData;
631
+ // CLASSES FOR DISTRICTS & DISTRICT AGGREGATES
632
+ class Aggregates {
633
+ constructor(container, aggregates) {
634
+ this._container = container;
635
+ this._aggregates = aggregates;
636
+ this.unpacked = {};
637
+ this.columns = {};
638
+ }
639
+ process() {
640
+ this._unpack();
641
+ this._columnize();
642
+ this._checkReqs();
643
+ this._convert();
644
+ }
645
+ transpose() {
646
+ const nRows = this._container.state.nDistricts + 2; // +1 for unassigned, +1 for statewide
647
+ let table = [];
648
+ for (let i = 0; i < nRows; i++) {
649
+ const row = {
650
+ districtID: this.columns['districtID'][i],
651
+ totalPop: this.columns['totalPop'][i],
652
+ popDevPct: this.columns['popDevPct'][i],
653
+ bEqualPop: this.columns['bEqualPop'][i],
654
+ bNotEmpty: this.columns['bNotEmpty'][i],
655
+ bContiguous: this.columns['bContiguous'][i],
656
+ bNotEmbedded: this.columns['bNotEmbedded'][i],
657
+ demPct: this.columns['demPct'][i],
658
+ repPct: this.columns['repPct'][i],
659
+ othPct: this.columns['othPct'][i],
660
+ whitePct: this.columns['whitePct'][i],
661
+ totalVAP: this.columns['totalVAP'][i],
662
+ minorityPct: this.columns['minorityPct'][i],
663
+ blackPct: this.columns['blackPct'][i],
664
+ hispanicPct: this.columns['hispanicPct'][i],
665
+ pacificPct: this.columns['pacificPct'][i],
666
+ asianPct: this.columns['asianPct'][i],
667
+ nativePct: this.columns['nativePct'][i]
668
+ };
669
+ table.push(row);
670
+ }
671
+ return table;
672
+ }
673
+ // PRIVATE HELPER METHODS
674
+ _unpack() {
675
+ // NOTE - This maintains the dataset hierarchy, but merges multi-fields into single buckets
676
+ var _a, _b, _c;
677
+ for (const dataset of Object.values(T.Dataset)) {
678
+ if (dataset === T.Dataset.SHAPES)
679
+ continue;
680
+ if (!this.unpacked[dataset]) {
681
+ this.unpacked[dataset] = {};
682
+ }
683
+ const did = this._container.config['datasets'][dataset];
684
+ const fields = packedFields(this._aggregates, did);
685
+ for (const field of fields) {
686
+ const mappedField = (_a = T.fieldMap[dataset]) === null || _a === void 0 ? void 0 : _a[field];
687
+ if (mappedField) {
688
+ const bucket = T.multiFields.includes(field) ? field : mappedField;
689
+ this.unpacked[dataset][bucket] = unpackedFields(this._aggregates, did, field);
690
+ }
691
+ }
692
+ // Post-process potential multiField pairs
693
+ for (const [field1, field2] of T.multiFieldPairs) {
694
+ const has1 = field1 in this.unpacked[dataset];
695
+ const has2 = field2 in this.unpacked[dataset];
696
+ if (has1 && has2) {
697
+ // Both exist - pick the one with max sum
698
+ const sum1 = this.unpacked[dataset][field1].reduce((a, b) => a + b, 0);
699
+ const sum2 = this.unpacked[dataset][field2].reduce((a, b) => a + b, 0);
700
+ const keepField = sum1 >= sum2 ? field1 : field2;
701
+ const removeField = sum1 >= sum2 ? field2 : field1;
702
+ const mappedField = (_b = T.fieldMap[dataset]) === null || _b === void 0 ? void 0 : _b[keepField];
703
+ if (mappedField) {
704
+ this.unpacked[dataset][mappedField] = this.unpacked[dataset][keepField];
705
+ delete this.unpacked[dataset][keepField];
706
+ }
707
+ delete this.unpacked[dataset][removeField];
708
+ }
709
+ else if (has1 || has2) {
710
+ // Only one exists - rename it
711
+ const existingField = has1 ? field1 : field2;
712
+ const mappedField = (_c = T.fieldMap[dataset]) === null || _c === void 0 ? void 0 : _c[existingField];
713
+ if (mappedField) {
714
+ this.unpacked[dataset][mappedField] = this.unpacked[dataset][existingField];
715
+ delete this.unpacked[dataset][existingField];
716
+ }
717
+ }
718
+ }
719
+ }
720
+ }
721
+ _columnize() {
722
+ // Flatten the unpacked aggregates
723
+ for (const dataset in this.unpacked) {
724
+ Object.assign(this.columns, this.unpacked[dataset]);
725
+ }
726
+ // Add statewide totals
727
+ for (const field in this.columns) {
728
+ const statewide = U.sumArray(this.columns[field]);
729
+ this.columns[field].push(statewide);
730
+ }
731
+ // Add a district id column
732
+ const nRows = this._container.state.nDistricts + 2; // +1 for unassigned, +1 for statewide
733
+ this.columns['districtID'] = [...Array(nRows).keys()];
734
+ }
735
+ // Check district requirements and other other booleans for the district statistics table
736
+ _checkReqs() {
737
+ let contiguous = [true]; // Dummy value for the unassigned district
738
+ let notEmbedded = [true];
739
+ const simpleGraph = this._container.graph; // HACK
740
+ for (let d = 1; d <= this._container.state.nDistricts; d++) // Skip unassigned district
741
+ {
742
+ let geoIDs = this._container.plan.geoIDsForDistrictID(d);
743
+ if (geoIDs && (geoIDs.size > 0)) {
744
+ contiguous.push((0, requirements_1.isContiguous)(geoIDs, simpleGraph));
745
+ notEmbedded.push(!(0, requirements_1.isEmbedded)(d, geoIDs, this._container.plan.byGeoID(), simpleGraph));
746
+ }
747
+ else {
748
+ contiguous.push(true); // Empty districts are trivially contiguous
749
+ notEmbedded.push(true); // Empty districts are trivially non-embedded
750
+ }
751
+ }
752
+ const bPlanContiguous = contiguous.every(c => c);
753
+ contiguous.push(bPlanContiguous);
754
+ const bPlanFreeOfHoles = notEmbedded.every(ne => ne);
755
+ notEmbedded.push(bPlanFreeOfHoles);
756
+ const nRows = this._container.state.nDistricts + 2; // +1 for unassigned, +1 for statewide
757
+ let equalPop = U.initArray(nRows, true);
758
+ this.columns['bContiguous'] = contiguous;
759
+ this.columns['bNotEmbedded'] = notEmbedded;
760
+ this.columns['bEqualPop'] = equalPop;
761
+ this.columns['bNotEmpty'] = this.columns.TotalPop.map((value, index) => index === 0 || index === this.columns.TotalPop.length - 1 || value !== 0);
762
+ }
763
+ // Convert counts to %'s for the plan profile and the district statistics table
764
+ _convert() {
765
+ // Compute population deviations by district
766
+ {
767
+ let popDevPct = [];
768
+ const targetSize = this._container.state.targetSize;
769
+ const repsByDistrict = this._container.repsByDistrict;
770
+ for (let i = 0; i < this.columns.TotalPop.length; i++) {
771
+ if (i === 0) {
772
+ popDevPct.push(0.0); // Dummy value for the unassigned district
773
+ continue;
774
+ }
775
+ const totalPop = this.columns.TotalPop[i];
776
+ if ((totalPop === 0) || (i === this.columns.TotalPop.length - 1)) {
777
+ popDevPct.push(1.0);
778
+ continue;
779
+ }
780
+ // NOTE - Generalized for MMD
781
+ const n = (repsByDistrict) ? repsByDistrict[i - 1] : 1;
782
+ const dev = (totalPop - (n * targetSize)) / (n * targetSize);
783
+ popDevPct.push(dev);
784
+ }
785
+ this.columns['popDevPct'] = popDevPct;
786
+ }
787
+ // Convert election data to %'s
788
+ {
789
+ this.columns['demPct'] = U.divideArrays(this.columns['DemVotes'], this.columns['TotalVotes']);
790
+ this.columns['repPct'] = U.divideArrays(this.columns['RepVotes'], this.columns['TotalVotes']);
791
+ const twoPartyTotals = U.addArrays(this.columns['DemVotes'], this.columns['RepVotes']);
792
+ this.columns['othPct'] = U.divideArrays(U.subtractArrays(this.columns['TotalVotes'], twoPartyTotals), this.columns['TotalVotes']);
793
+ this.columns['Vf'] = U.divideArrays(this.columns['DemVotes'], twoPartyTotals);
794
+ }
795
+ // Convert VAP/CVAP data to %'s
796
+ {
797
+ // Derive minority VAP from total and white VAP
798
+ this.columns['MinorityVAP'] = U.subtractArrays(this.columns['TotalVAP'], this.columns['WhiteVAP']);
799
+ // Rename total pop and total VAP fields for district statistics table
800
+ this.columns['totalPop'] = this.columns['TotalPop'];
801
+ delete this.columns['TotalPop'];
802
+ this.columns['totalVAP'] = this.columns['TotalVAP'];
803
+ delete this.columns['TotalVAP'];
804
+ // Compute %'s
805
+ this.columns['whitePct'] = U.divideArrays(this.columns['WhiteVAP'], this.columns['totalVAP']);
806
+ this.columns['minorityPct'] = U.divideArrays(this.columns['MinorityVAP'], this.columns['totalVAP']);
807
+ this.columns['blackPct'] = U.divideArrays(this.columns['BlackVAP'], this.columns['totalVAP']);
808
+ this.columns['hispanicPct'] = U.divideArrays(this.columns['HispanicVAP'], this.columns['totalVAP']);
809
+ this.columns['pacificPct'] = U.divideArrays(this.columns['PacificVAP'], this.columns['totalVAP']);
810
+ this.columns['asianPct'] = U.divideArrays(this.columns['AsianVAP'], this.columns['totalVAP']);
811
+ this.columns['nativePct'] = U.divideArrays(this.columns['NativeVAP'], this.columns['totalVAP']);
812
+ }
813
+ }
908
814
  }
909
- // CLASSES, ETC. FOR FEATURE & COUNTY DATA
910
- // Wrap data by feature, to abstract the specifics of the internal structure
815
+ exports.Aggregates = Aggregates;
816
+ class Districts {
817
+ constructor(container, ds) {
818
+ this._container = container;
819
+ this._shapes = ds;
820
+ }
821
+ getShapes() {
822
+ return this._shapes;
823
+ }
824
+ }
825
+ exports.Districts = Districts;
826
+ // CLASSES & HELPERS FOR FEATURE & COUNTY DATA
911
827
  class Features {
912
- constructor(s, data, keys) {
828
+ constructor(container, data, keys) {
913
829
  this._featureIDs = {};
914
- this._session = s;
830
+ this._container = container;
915
831
  this._data = data;
916
832
  this._keys = keys;
917
833
  }
918
834
  nFeatures() { return this._data.features.length; }
919
835
  featureByIndex(i) { return this._data.features[i]; }
920
- /* 03/27/21 - Moved to top-level & exported:
921
- geoIDForFeature(f: any): string
922
- {
923
- // 12-02-2020 - Switched to using ids vs. geoids per Terry
924
- if (f.properties && f.properties['id'])
925
- {
926
- return f.properties['id'];
927
- // GEOIDs will be one of these properties
928
- // const value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
929
-
930
- // return value;
931
- }
932
- else
933
- {
934
- console.log("This feature does not have an id property:", f);
935
- throw "Feature with no id property.";
936
- }
937
- }
938
- fieldForFeature(f: any, dt: T.Dataset, fk: string): any
939
- {
940
- const dk: string = this._keys[dt];
941
-
942
- let result = undefined;
943
-
944
- // 07-31-2020 - Fix to post-process PVI into the expected election composite format.
945
- if (dk === 'P16GPR')
946
- {
947
- // 10-27-2020 - Enabling a 'Tot' result for PVI, now that we're reporting 'Other'
948
- switch (fk)
949
- {
950
- case 'D': {
951
- result = (_getFeatures(f, dk, 'D12') + _getFeatures(f, dk, 'D16')) / 2;
952
- break;
953
- }
954
- case 'R': {
955
- result = (_getFeatures(f, dk, 'R12') + _getFeatures(f, dk, 'R16')) / 2;
956
- break;
957
- }
958
- case 'Tot': {
959
- result = (_getFeatures(f, dk, 'D12') + _getFeatures(f, dk, 'D16') + _getFeatures(f, dk, 'R12') + _getFeatures(f, dk, 'R16')) / 2;
960
- break;
961
- }
962
- default: {
963
- console.log("Field not recognized.");
964
- break;
965
- }
966
- }
967
- // result = (_getFeatures(f, dk, (fk === 'D' ? 'D12' : 'R12')) + _getFeatures(f, dk, (fk === 'D' ? 'D16' : 'R16'))) / 2;
968
- }
969
- else
970
- result = _getFeatures(f, dk, fk);
971
-
972
- return result;
973
- }
974
- */
975
- resetDataset(d, k) {
976
- this._keys[d] = k;
977
- // NOTE - RECALC: Does anything need to be recalc'd now when a dataset is changed?
978
- }
979
836
  mapGeoIDsToFeatureIDs() {
980
- for (let i = 0; i < this._session.features.nFeatures(); i++) {
981
- let f = this._session.features.featureByIndex(i);
982
- // 03-27-21
837
+ for (let i = 0; i < this.nFeatures(); i++) {
838
+ let f = this.featureByIndex(i);
983
839
  let geoID = geoIDForFeature(f);
984
- // let geoID: string = this._session.features.geoIDForFeature(f);
985
840
  this._featureIDs[geoID] = i;
986
841
  }
987
842
  }
@@ -1006,10 +861,10 @@ function fieldForFeature(f, dk /* dt: T.Dataset */, ff) {
1006
861
  // Take the max (typically one field will be zero, but some datasets contain Bl subset of BlC)
1007
862
  return Math.max(...T.fieldsFromFeatureField(dk, ff).map(key => _getFeatures(f, dk, key)));
1008
863
  }
1009
- // NOTE - This accessor is cloned from fGetW() in dra-client/restrict.ts
1010
- // f is a direct GeoJSON feature
1011
- // p is a geoID
1012
864
  function _getFeatures(f, datasetKey, p) {
865
+ // This accessor is cloned from fGetW() in dra-client/restrict.ts
866
+ // f is a direct GeoJSON feature
867
+ // p is a geoID
1013
868
  if (!f.properties)
1014
869
  return 0;
1015
870
  // This is actual path
@@ -1022,19 +877,17 @@ function _getFeatures(f, datasetKey, p) {
1022
877
  return !n || isNaN(n) ? 0 : n;
1023
878
  */
1024
879
  }
1025
- // Wrap data by county, to abstract the specifics of the internal structure
1026
880
  class Counties {
1027
- constructor(s, data) {
881
+ constructor(container, data) {
1028
882
  this._countyNameLookup = {};
1029
883
  this.index = {};
1030
884
  this.fips = {};
1031
885
  this.totalPopulation = [];
1032
- this._session = s;
886
+ this._container = container;
1033
887
  this._data = data;
1034
888
  this.nCounties = this._data.features.length;
1035
889
  }
1036
890
  countyByIndex(i) { return this._data.features[i]; }
1037
- // 10-16-21 - Analyze view crashing because county names were undefined in the lookup table.
1038
891
  // Property had changed from 'NAME' to 'name' for *some* states.
1039
892
  propertyForCounty(f, pk) {
1040
893
  return f.properties[pk] ? f.properties[pk] : f.properties[pk.toLowerCase()];
@@ -1044,542 +897,94 @@ class Counties {
1044
897
  indexFromFIPS(fips) { return this.index[fips]; }
1045
898
  }
1046
899
  exports.Counties = Counties;
1047
- // CLASSES TO ORGANIZE AND/OR ABSTRACT OTHER DATA
1048
- // MMD - Extended this for # of reps
900
+ // CLASSES & HELPERS FOR STATE & PLAN DATA
1049
901
  class State {
1050
- constructor(s, xx, n) {
902
+ constructor(container, xx, n) {
1051
903
  this.totalPop = 0;
1052
- this.targetSize = 0; // HACK - Will get set by doPreprocessCensus()
904
+ this.targetSize = 0; // HACK - This gets set later
1053
905
  this.tooBigFIPS = [];
1054
906
  this.tooBigName = [];
1055
907
  this.singleCountyDistrictMax = 0;
1056
908
  this.expectedSplits = 0;
1057
909
  this.expectedAffected = 0;
1058
- this._session = s;
910
+ this._container = container;
1059
911
  this.xx = xx;
1060
912
  this.nDistricts = n;
1061
- this.nReps = (s.repsByDistrict) ? s.repsByDistrict.reduce((a, b) => a + b, 0) : this.nDistricts; // MMD
913
+ this.nReps = (container.repsByDistrict) ? container.repsByDistrict.reduce((a, b) => a + b, 0) : this.nDistricts;
1062
914
  }
1063
915
  }
1064
916
  exports.State = State;
1065
- class Plan {
1066
- constructor(s, p) {
1067
- this._session = s;
1068
- this._planByGeoID = p;
1069
- this._planByDistrictID = {};
1070
- this.districtIDs = []; // Set when the plan in inverted
1071
- }
1072
- // NOTE - DON'T remove water-only features from the plan, as they may be required
1073
- // for contiguity. Just skip them in aggregating district statistics.
1074
- //
1075
- // removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
1076
- // let newPlan = {} as T.PlanByGeoID;
1077
- // for (let geoID in plan) {
1078
- // // Remove water-only features
1079
- // if (!(U.isWaterOnly(geoID))) {
1080
- // newPlan[geoID] = plan[geoID];
1081
- // }
1082
- // else {
1083
- // console.log("Removing water-only feature", geoID);
1084
- // }
1085
- // }
1086
- // return newPlan;
1087
- // }
1088
- invertPlan(bLog = false) {
1089
- this._planByDistrictID = invertPlan(this._planByGeoID, this._session, bLog);
1090
- this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
1091
- }
1092
- initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
1093
- byGeoID() { return this._planByGeoID; }
1094
- byDistrictID() { return this._planByDistrictID; }
1095
- districtForGeoID(i) { return this._planByGeoID[i]; }
1096
- geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
1097
- }
1098
- exports.Plan = Plan;
1099
- // Invert a feature assignment structure to sets of ids by district
1100
- function invertPlan(plan, s, bLog = false) {
1101
- let invertedPlan = {};
1102
- // Add a dummy 'unassigned' district
1103
- invertedPlan[S.NOT_ASSIGNED] = new Set();
1104
- // The feature assignments coming from DRA do not include unassigned ones.
1105
- // - In the DRA-calling context, there's an analytics session with a reference
1106
- // to the features. Loop over all the features to find the unassigned ones,
1107
- // and add them to the dummy unassigned district explicitly.
1108
- // - In the CLI-calling context, there's no session (yet) but the plan is complete.
1109
- if (!(s == undefined)) {
1110
- for (let i = 0; i < s.features.nFeatures(); i++) {
1111
- let f = s.features.featureByIndex(i);
1112
- // 03-27-21
1113
- let geoID = geoIDForFeature(f);
1114
- // let geoID: string = s.features.geoIDForFeature(f);
1115
- // If the feature is NOT explicitly assigned to a district, add the geoID
1116
- // to the dummy unassigned district 0.
1117
- if (!(U.keyExists(geoID, plan)))
1118
- invertedPlan[S.NOT_ASSIGNED].add(geoID);
1119
- // NOTE - NOT skipping WATER-ONLY features here, because we're
1120
- // not skipping them below when they are explicitly assigned in plans. Should
1121
- // we skip them in both places?
1122
- }
1123
- }
1124
- for (let geoID in plan) {
1125
- let districtID = plan[geoID];
1126
- // Make sure the set for the districtID exists
1127
- if (!(U.objectContains(invertedPlan, districtID))) {
1128
- invertedPlan[districtID] = new Set();
1129
- }
1130
- // Add the geoID to the districtID's set
1131
- invertedPlan[districtID].add(geoID);
1132
- if (U.isWaterOnly(geoID) && bLog)
1133
- console.log("Invert plan: Water-only feature still in plan!", geoID);
1134
- }
1135
- return invertedPlan;
1136
- }
1137
- class GraphClass {
1138
- constructor(s, graph) {
1139
- this._session = s;
1140
- this._graph = graph;
1141
- }
1142
- peerNeighbors(node) {
1143
- // Get the neighboring geoIDs connected to a geoID
1144
- // Ignore the lengths of the shared borders (the values), for now
1145
- // Protect against getting a GEOID that's not in the graph
1146
- if (U.keyExists(node, this._graph)) {
1147
- // Handle both unweighted & weighted neighbors
1148
- let n = this._graph[node];
1149
- let l = (n instanceof Array) ? n : U.getObjectKeys(n);
1150
- return l;
1151
- // return U.getObjectKeys(this._graph[node]);
1152
- }
1153
- else
1154
- return [];
1155
- // return U.getObjectKeys(this._graph[node]);
1156
- }
1157
- }
1158
- exports.GraphClass = GraphClass;
1159
-
1160
-
1161
- /***/ }),
1162
-
1163
- /***/ "./src/analyze.ts":
1164
- /*!************************!*\
1165
- !*** ./src/analyze.ts ***!
1166
- \************************/
1167
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1168
-
1169
-
1170
- //
1171
- // ANALYZE A PLAN
1172
- //
1173
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1174
- exports.doAnalyzeDistricts = doAnalyzeDistricts;
1175
- exports.doAnalyzePlan = doAnalyzePlan;
1176
- exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
1177
- const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
1178
- const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
1179
- const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
1180
- // Compile district-level info for plan/map-level analytics
1181
- function doAnalyzeDistricts(s, bLog = false) {
1182
- s.districts.recalcStatistics(bLog);
1183
- s.districts.extractDistrictShapeProperties(bLog);
1184
- }
1185
- // Calculate the analytics & validations and cache the results
1186
- // NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
1187
- // NOTE - I could make this table-driven, but I'm thinking that the explicit
1188
- // calls might make chunking for aync easier.
1189
- function doAnalyzePlan(s, bLog = false) {
1190
- s.tests[0 /* T.Test.Complete */] = (0, valid_1.doIsComplete)(s, bLog);
1191
- s.tests[1 /* T.Test.Contiguous */] = (0, valid_1.doIsContiguous)(s, bLog);
1192
- s.tests[2 /* T.Test.FreeOfHoles */] = (0, valid_1.doIsFreeOfHoles)(s, bLog);
1193
- // NOTE - I can't check whether a population deviation is legal or not, until
1194
- // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
1195
- // the given type of district (CD vs. LD). The EqualPopulation test is derived
1196
- // from PopulationDeviation, as part of scorecard or test log preparation.
1197
- // Create an empty test entry here though ...
1198
- s.tests[3 /* T.Test.EqualPopulation */] = s.getTest(3 /* T.Test.EqualPopulation */);
1199
- s.tests[5 /* T.Test.UnexpectedCountySplits */] = (0, cohesive_1.doFindCountiesSplitUnexpectedly)(s, bLog);
1200
- s.tests[6 /* T.Test.VTDSplits */] = (0, cohesive_1.doFindSplitVTDs)(s, bLog);
1201
- // Enable a Test Log and Scorecard to be generated
1202
- s.bPlanAnalyzed = true;
1203
- s.bPostProcessingDone = false;
1204
- }
1205
- //
1206
- // Derive secondary analytics that are based on primary tests.
1207
- // This concept allows Population Deviation to be a primary numeric test and
1208
- // Equal Population to be secondary pass/fail validation.
1209
- //
1210
- function doDeriveSecondaryTests(s, bLog = false) {
1211
- s.tests[3 /* T.Test.EqualPopulation */] = (0, equal_1.doHasEqualPopulations)(s, bLog);
1212
- }
1213
-
1214
-
1215
- /***/ }),
1216
-
1217
- /***/ "./src/cohesive.ts":
1218
- /*!*************************!*\
1219
- !*** ./src/cohesive.ts ***!
1220
- \*************************/
1221
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1222
-
1223
-
1224
- //
1225
- // SPLITTING
1226
- //
1227
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1228
- if (k2 === undefined) k2 = k;
1229
- var desc = Object.getOwnPropertyDescriptor(m, k);
1230
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1231
- desc = { enumerable: true, get: function() { return m[k]; } };
1232
- }
1233
- Object.defineProperty(o, k2, desc);
1234
- }) : (function(o, m, k, k2) {
1235
- if (k2 === undefined) k2 = k;
1236
- o[k2] = m[k];
1237
- }));
1238
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
1239
- Object.defineProperty(o, "default", { enumerable: true, value: v });
1240
- }) : function(o, v) {
1241
- o["default"] = v;
1242
- });
1243
- var __importStar = (this && this.__importStar) || (function () {
1244
- var ownKeys = function(o) {
1245
- ownKeys = Object.getOwnPropertyNames || function (o) {
1246
- var ar = [];
1247
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
1248
- return ar;
1249
- };
1250
- return ownKeys(o);
1251
- };
1252
- return function (mod) {
1253
- if (mod && mod.__esModule) return mod;
1254
- var result = {};
1255
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1256
- __setModuleDefault(result, mod);
1257
- return result;
1258
- };
1259
- })();
1260
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1261
- exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
1262
- exports.doFindSplitVTDs = doFindSplitVTDs;
1263
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1264
- // The main county-district splitting code is in the dra-analytics package.
1265
- // ANALYZE SIMPLE COUNTY & VTD SPLITTING
1266
- /*
1267
-
1268
- Sample results for NC 2016 dongressional plan
1269
- ________________________________________________________________________________
1270
-
1271
- State: NC
1272
- Census: 2010
1273
- Total population: 9,535,483
1274
- Number of districts: 13
1275
- Target district size: 733,499
1276
- Number of counties: 100
1277
-
1278
- Equal Population: 11.24% deviation
1279
- Compactness: None
1280
- Proportionality: 27.67% gap
1281
- Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
1282
-
1283
- These counties are split unexpectedly (meaning that they're smaller than a district):
1284
-
1285
- • Bladen
1286
- • Buncombe
1287
- • Catawba
1288
- • Cumberland
1289
- • Durham
1290
- • Guilford
1291
- • Iredell
1292
- • Johnston
1293
- • Pitt
1294
- • Rowan
1295
- • Wilson
1296
-
1297
- */
1298
- // 10-23-21 - This analysis *assumes* that a map is complete (or nearly so).
1299
- // - We do the analysis regardless, but
1300
- // - Protect against weird results in the client
1301
- function doFindCountiesSplitUnexpectedly(s, bLog = false) {
1302
- let test = s.getTest(5 /* T.Test.UnexpectedCountySplits */);
1303
- // THE THREE VALUES TO DETERMINE FOR A PLAN
1304
- let unexpectedSplits = 0;
1305
- let unexpectedAffected = 0;
1306
- let countiesSplitUnexpectedly = [];
1307
- // FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
1308
- // Get the county-district pivot ("splits")
1309
- let countiesByDistrict = s.districts.table.countySplits;
1310
- // Find the single-county districts, i.e., districts NOT split across counties.
1311
- // Ignore the dummy unassigned 0 and N+1 summary "districts."
1312
- let singleCountyDistricts = [];
1313
- for (let d = 1; d <= s.state.nDistricts; d++) {
1314
- // See if there's only one county partition
1315
- // Ignore the dummy unassigned 0 "county."
1316
- let nCountiesInDistrict = 0;
1317
- for (let c = 1; c <= s.counties.nCounties; c++) {
1318
- // Guard against empty district
1319
- if (countiesByDistrict[d]) {
1320
- if (countiesByDistrict[d][c] > 0) {
1321
- nCountiesInDistrict += 1;
1322
- if (nCountiesInDistrict > 1) {
1323
- break;
1324
- }
1325
- }
1326
- }
1327
- }
1328
- // If so, save the district
1329
- if (nCountiesInDistrict == 1) {
1330
- singleCountyDistricts.push(d);
1331
- }
917
+ class Plan {
918
+ constructor(container, p) {
919
+ this._container = container;
920
+ this._planByGeoID = p;
921
+ this._planByDistrictID = {};
922
+ this.districtIDs = []; // Set when the plan in inverted
1332
923
  }
1333
- // Process the splits/partitions in the plan:
1334
- // - Count the total # of partitions,
1335
- // - Find the counties split across districts, and
1336
- // - Accumulate the number people affected (except when single-county districts)
1337
- let nPartitionsOverall = 0;
1338
- let splitCounties = new Set(); // The counties that are split across districts
1339
- let totalAffected = 0; // The total population affected by those splits
1340
- // 07-17-21 - For enumerating # of times each split county is split
1341
- let countyIndexesWithSplits = [];
1342
- // Ignore the dummy unassigned 0 "county."
1343
- for (let c = 1; c <= s.counties.nCounties; c++) {
1344
- let nCountyParts = 0;
1345
- let subtotal = 0;
1346
- // Ignore the dummy unassigned 0 and N+1 summary "districts."
1347
- for (let d = 1; d <= s.state.nDistricts; d++) {
1348
- // Guard against empty district
1349
- if (countiesByDistrict[d]) {
1350
- if (countiesByDistrict[d][c] > 0) {
1351
- nPartitionsOverall += 1;
1352
- nCountyParts += 1;
1353
- if (!(U.arrayContains(singleCountyDistricts, d))) {
1354
- subtotal += countiesByDistrict[d][c];
1355
- }
1356
- }
1357
- }
1358
- }
1359
- if (nCountyParts > 1) {
1360
- splitCounties.add(c);
1361
- totalAffected += subtotal;
1362
- // 07-17-21 - Enumerate # of times each split county is split
1363
- countyIndexesWithSplits.push([c, nCountyParts - 1]);
1364
- }
924
+ invertPlan(bLog = false) {
925
+ this._planByDistrictID = invertPlan(this._planByGeoID, this._container, bLog);
926
+ this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
1365
927
  }
1366
- // 07-17-21 - Enumerate # of times each split county is split
1367
- let splitCountiesWithSplits = [];
1368
- countyIndexesWithSplits.forEach(pair => {
1369
- const index = pair[0];
1370
- const splits = pair[1];
1371
- // Convert 1–N indices to FIPS codes
1372
- const fips = s.counties.fips[index];
1373
- if (fips) {
1374
- // Convert FIPS codes to names
1375
- const name = s.counties.nameFromFIPS(fips);
1376
- // Combine # of splits with names
1377
- if (name) {
1378
- const text = `${name} (${splits})`;
1379
- splitCountiesWithSplits.push(text);
1380
- }
1381
- }
1382
- });
1383
- // Sort by name
1384
- splitCountiesWithSplits.sort();
1385
- // End
1386
- // Convert county ordinals to FIPS codes
1387
- let splitCountiesFIPS = U.getSelectObjectKeys(s.counties.index, [...splitCounties]);
1388
- // THEN TAKE ACCOUNT OF THE COUNTY SPLITTING THAT IS EXPECTED (REQUIRED)
1389
- // Compute the total number of splits this way, in case any counties are split
1390
- // more than once. I.e., it's not just len(all_counties_split).
1391
- let nSplits = nPartitionsOverall - s.counties.nCounties;
1392
- // Determine the number of *unexpected* splits. NOTE: Prevent negative numbers,
1393
- // in case you have a plan the *doesn't* split counties that would have to be
1394
- // split due to their size.
1395
- unexpectedSplits = Math.max(0, nSplits - s.state.expectedSplits);
1396
- // Subtract off the total population that *has* to be affected by splits,
1397
- // because their counties are too big. NOTE: Again, prevent negative numbers,
1398
- // in case you have a plan the *doesn't* split counties that would have to be
1399
- // split due to their size.
1400
- unexpectedAffected = U.trim(Math.max(0, totalAffected - s.state.expectedAffected) / s.state.totalPop);
1401
- // Find the counties that are split *unexpectedly*. From all the counties that
1402
- // are split, remove those that *have* to be split, because they are bigger than
1403
- // a district.
1404
- let countiesSplitUnexpectedlyFIPS = [];
1405
- for (let fips of splitCountiesFIPS) {
1406
- if (!(U.arrayContains(s.state.tooBigFIPS, fips))) {
1407
- countiesSplitUnexpectedlyFIPS.push(fips);
928
+ initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
929
+ byGeoID() { return this._planByGeoID; }
930
+ byDistrictID() { return this._planByDistrictID; }
931
+ districtForGeoID(i) { return this._planByGeoID[i]; }
932
+ geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
933
+ }
934
+ exports.Plan = Plan;
935
+ function invertPlan(plan, container, bLog = false) {
936
+ let invertedPlan = {};
937
+ // Add a dummy 'unassigned' district
938
+ invertedPlan[S.NOT_ASSIGNED] = new Set();
939
+ // The feature assignments coming from DRA do not include unassigned ones.
940
+ // - In the DRA-calling context, there's an analytics session with a reference
941
+ // to the features. Loop over all the features to find the unassigned ones,
942
+ // and add them to the dummy unassigned district explicitly.
943
+ // - In the CLI-calling context, there's no session (yet) but the plan is complete.
944
+ if (!(container == undefined)) {
945
+ for (let i = 0; i < container.features.nFeatures(); i++) {
946
+ let f = container.features.featureByIndex(i);
947
+ let geoID = geoIDForFeature(f);
948
+ // If the feature is NOT explicitly assigned to a district, add the geoID
949
+ // to the dummy unassigned district 0.
950
+ if (!(U.keyExists(geoID, plan)))
951
+ invertedPlan[S.NOT_ASSIGNED].add(geoID);
1408
952
  }
1409
953
  }
1410
- // ... and convert the FIPS codes to county names.
1411
- for (let fips of countiesSplitUnexpectedlyFIPS) {
1412
- const name = s.counties.nameFromFIPS(fips);
1413
- // 07-06-20 - Guard in case the FIPS code isn't in the county name lookup
1414
- if (name)
1415
- countiesSplitUnexpectedly.push(name);
1416
- else {
1417
- if (bLog)
1418
- console.log("County is not in the FIPS-to-name lookup table: ", fips);
954
+ for (let geoID in plan) {
955
+ let districtID = plan[geoID];
956
+ // Make sure the set for the districtID exists
957
+ if (!(U.objectContains(invertedPlan, districtID))) {
958
+ invertedPlan[districtID] = new Set();
1419
959
  }
960
+ // Add the geoID to the districtID's set
961
+ invertedPlan[districtID].add(geoID);
962
+ if (U.isWaterOnly(geoID) && bLog)
963
+ console.log("Invert plan: Water-only feature still in plan!", geoID);
1420
964
  }
1421
- countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
1422
- test['score'] = U.trim(unexpectedAffected);
1423
- test['details']['nSplits'] = nSplits;
1424
- test['details']['unexpectedSplits'] = unexpectedSplits;
1425
- test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
1426
- // 02-23-21 - Added single-county district details
1427
- test['details']['expectedSplits'] = s.state.expectedSplits;
1428
- test['details']['tooBigName'] = s.state.tooBigName;
1429
- test['details']['singleCountyDistrictMax'] = s.state.singleCountyDistrictMax;
1430
- test['details']['nCountiesSplit'] = splitCountiesFIPS.length;
1431
- test['details']['nSingleCountyDistricts'] = singleCountyDistricts.length;
1432
- // 07-17-21 - Enumerate # of times each split county is split
1433
- test['details']['countiesWithSplits'] = splitCountiesWithSplits;
1434
- return test;
1435
- }
1436
- // NOTE - This function just creates an empty container that dra-client fills in when generating the UI
1437
- function doFindSplitVTDs(s, bLog = false) {
1438
- let test = s.getTest(6 /* T.Test.VTDSplits */);
1439
- let splitVTDs = [];
1440
- test['score'] = splitVTDs.length;
1441
- test['details']['splitVTDs'] = splitVTDs;
1442
- return test;
965
+ return invertedPlan;
1443
966
  }
1444
-
1445
-
1446
- /***/ }),
1447
-
1448
- /***/ "./src/compact.ts":
1449
- /*!************************!*\
1450
- !*** ./src/compact.ts ***!
1451
- \************************/
1452
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1453
-
1454
-
1455
- //
1456
- // COMPACT
1457
- //
1458
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1459
- if (k2 === undefined) k2 = k;
1460
- var desc = Object.getOwnPropertyDescriptor(m, k);
1461
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1462
- desc = { enumerable: true, get: function() { return m[k]; } };
1463
- }
1464
- Object.defineProperty(o, k2, desc);
1465
- }) : (function(o, m, k, k2) {
1466
- if (k2 === undefined) k2 = k;
1467
- o[k2] = m[k];
1468
- }));
1469
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
1470
- Object.defineProperty(o, "default", { enumerable: true, value: v });
1471
- }) : function(o, v) {
1472
- o["default"] = v;
1473
- });
1474
- var __importStar = (this && this.__importStar) || (function () {
1475
- var ownKeys = function(o) {
1476
- ownKeys = Object.getOwnPropertyNames || function (o) {
1477
- var ar = [];
1478
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
1479
- return ar;
1480
- };
1481
- return ownKeys(o);
1482
- };
1483
- return function (mod) {
1484
- if (mod && mod.__esModule) return mod;
1485
- var result = {};
1486
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1487
- __setModuleDefault(result, mod);
1488
- return result;
1489
- };
1490
- })();
1491
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1492
- exports.extractDistrictProperties = extractDistrictProperties;
1493
- const baseclient_1 = __webpack_require__(/*! @dra2020/baseclient */ "@dra2020/baseclient");
1494
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1495
- // The main compactness code is in the dra-analytics package.
1496
- // HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
1497
- function extractDistrictProperties(s, bLog = false) {
1498
- // NOTE - I am assuming that district IDs are integers 1–N
1499
- for (let i = 1; i <= s.state.nDistricts; i++) {
1500
- const poly = s.districts.getDistrictShapeByID(i);
1501
- // Guard against no shape for empty districts AND null shapes
1502
- if (isAShape(poly)) {
1503
- const area = baseclient_1.Poly.polyAreaFlat(poly);
1504
- const perimeter = baseclient_1.Poly.polyPerimeterFlat(poly);
1505
- const diameter = baseclient_1.Poly.polyDiameterFlat(poly);
1506
- let props = [0, 0, 0];
1507
- props[0 /* T.DistrictShapeProperty.Area */] = area;
1508
- props[1 /* T.DistrictShapeProperty.Diameter */] = diameter;
1509
- props[2 /* T.DistrictShapeProperty.Perimeter */] = perimeter;
1510
- s.districts.setGeoProperties(i, props);
1511
- // if (bLog) console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
1512
- }
967
+ // HELPERS
968
+ function processConfig(config) {
969
+ // Set the default the Census & redistricting cycle to 2010
970
+ if (!(U.keyExists('cycle', config))) {
971
+ config['cycle'] = 2010;
972
+ console.log("Cycle was not set explicitly on the session request.");
1513
973
  }
974
+ return config;
1514
975
  }
1515
- function isAShape(poly) {
1516
- if (poly == null)
1517
- return false;
1518
- if (baseclient_1.Poly.polyNull(poly))
1519
- return false;
1520
- return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
976
+ function packedFields(pfs, did) {
977
+ let pf = pfs[0];
978
+ if (!pf || !pf.dsGroup || !pf.dsGroup[did])
979
+ return [];
980
+ let fields = pf.dsGroup[did].fields[did];
981
+ const keys = Object.keys(fields);
982
+ return keys;
1521
983
  }
1522
- // SAVE THESE NOTES, IN CASE WE NEED TO REWORK HOW WE PERFORM THESE CALCS.
1523
- // THEY REFLECT HOW I DID THEM IN PYTHON.
1524
- //
1525
- // THE COMPACTNESS ANALYTICS NEED THE FOLLOWING DATA,
1526
- // IN ADDITION TO THE MAP (IDEALLY, GEO_IDS INDEXED BY DISTRICT_ID)
1527
- //
1528
- // Shapes by geo_id
1529
- //
1530
- // If we need/want to speed compactness calculations up, we'll need
1531
- // to calculate the perimeters and diameters (and areas) of districts implicitly,
1532
- // which will require identifying district boundaries initially and updating
1533
- // them incrementally as districts are (re)assigned.
1534
- //
1535
- // A district's boundary info is the set/list of features that constitute the
1536
- // district's border along with each boundary feature's neighbors. Hence, this
1537
- // requires a contiguity graph with the lengths of shared edges between features
1538
- // precomputed.
1539
- //
1540
- // NOTE - I can write up (if not implement) the logic for determining what shapes
1541
- // constitute a district's boundary. There are a few nuances.
1542
- //
1543
- // If we have to optimize like this when we generalize to mixed maps, the
1544
- // determination of "neighbors in the map" and the length of shared borders
1545
- // (for determining a district perimeters) becomes more complicated and dynamic.
1546
- //
1547
- // NOTE - Again, I can write up (if not implement) the logic for both of these.
1548
- // They are a bit tricky and require special preprocessing of the summary level
1549
- // hierarchy (which I also have a script for that we can repurpose).
1550
-
1551
-
1552
- /***/ }),
1553
-
1554
- /***/ "./src/equal.ts":
1555
- /*!**********************!*\
1556
- !*** ./src/equal.ts ***!
1557
- \**********************/
1558
- /***/ ((__unused_webpack_module, exports) => {
1559
-
1560
-
1561
- //
1562
- // EQUAL POPULATION
1563
- //
1564
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1565
- exports.doHasEqualPopulations = doHasEqualPopulations;
1566
- // MMD - This generalizes for variable #'s of reps per district.
1567
- // NOTE - This validity check is *derived* and depends on population deviation %
1568
- // being computed (above) and normalized in test log & scorecard generation.
1569
- function doHasEqualPopulations(s, bLog = false) {
1570
- let test = s.getTest(3 /* T.Test.EqualPopulation */);
1571
- // Get the normalized population deviation %
1572
- let popDevTest = s.getTest(4 /* T.Test.PopulationDeviation */);
1573
- const popDevPct = popDevTest['score'];
1574
- // const popDevNormalized: number = popDevTest['normalizedScore'] as number;
1575
- test['details']['deviation'] = popDevPct;
1576
- test['details']['thresholds'] = popDevTest['details']['scale'];
1577
- // Populate the N+1 summary "district" in district.statistics
1578
- let bEqualPop = s.districts.table.bEqualPop;
1579
- let summaryRow = s.districts.numberOfRows() - 1;
1580
- bEqualPop[summaryRow] = test['score'];
1581
- return test;
984
+ function unpackedFields(aggs, did, field) {
985
+ return aggs.map(agg => dra_types_1.PF.getPackedField(agg, did, field));
1582
986
  }
987
+ // END
1583
988
 
1584
989
 
1585
990
  /***/ }),
@@ -1611,11 +1016,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
1611
1016
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1612
1017
  exports.uncertaintyOfMembership = exports.effectiveSplits = exports.avgSVError = exports.isAntimajoritarian = exports.normalizePartisanBias = exports.ratePartisanBias = exports.estSeatProbability = exports.inferSelectedMinority = exports.fieldForFeature = exports.geoIDForFeature = void 0;
1613
1018
  __exportStar(__webpack_require__(/*! ./_api */ "./src/_api.ts"), exports);
1614
- var _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
1615
- Object.defineProperty(exports, "geoIDForFeature", ({ enumerable: true, get: function () { return _data_1.geoIDForFeature; } }));
1616
- Object.defineProperty(exports, "fieldForFeature", ({ enumerable: true, get: function () { return _data_1.fieldForFeature; } }));
1617
- Object.defineProperty(exports, "inferSelectedMinority", ({ enumerable: true, get: function () { return _data_1.inferSelectedMinority; } }));
1618
- __exportStar(__webpack_require__(/*! ./results */ "./src/results.ts"), exports);
1019
+ var data_1 = __webpack_require__(/*! ./data */ "./src/data.ts");
1020
+ Object.defineProperty(exports, "geoIDForFeature", ({ enumerable: true, get: function () { return data_1.geoIDForFeature; } }));
1021
+ Object.defineProperty(exports, "fieldForFeature", ({ enumerable: true, get: function () { return data_1.fieldForFeature; } }));
1022
+ var rpv_1 = __webpack_require__(/*! ./rpv */ "./src/rpv.ts");
1023
+ Object.defineProperty(exports, "inferSelectedMinority", ({ enumerable: true, get: function () { return rpv_1.inferSelectedMinority; } }));
1619
1024
  __exportStar(__webpack_require__(/*! ./types */ "./src/types.ts"), exports);
1620
1025
  __exportStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"), exports);
1621
1026
  // Re-export RPV types and COI splitting functions
@@ -1627,63 +1032,7 @@ exports.isAntimajoritarian = dra_analytics_1.Rate.isAntimajoritarian;
1627
1032
  exports.avgSVError = dra_analytics_1.Partisan.avgSVError;
1628
1033
  exports.effectiveSplits = dra_analytics_1.Splitting.effectiveSplits;
1629
1034
  exports.uncertaintyOfMembership = dra_analytics_1.Splitting.uncertaintyOfMembership;
1630
-
1631
-
1632
- /***/ }),
1633
-
1634
- /***/ "./src/minority.ts":
1635
- /*!*************************!*\
1636
- !*** ./src/minority.ts ***!
1637
- \*************************/
1638
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1639
-
1640
-
1641
- //
1642
- // PROTECTS MINORITIES
1643
- //
1644
- var __importDefault = (this && this.__importDefault) || function (mod) {
1645
- return (mod && mod.__esModule) ? mod : { "default": mod };
1646
- };
1647
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1648
- exports.getMajorityMinority = getMajorityMinority;
1649
- exports.getVRASection5 = getVRASection5;
1650
- exports.doAnalyzeRacialPolarization = doAnalyzeRacialPolarization;
1651
- const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
1652
- const majority_minority_json_1 = __importDefault(__webpack_require__(/*! ../static/majority-minority.json */ "./static/majority-minority.json"));
1653
- const vra5_preclearance_json_1 = __importDefault(__webpack_require__(/*! ../static/vra5-preclearance.json */ "./static/vra5-preclearance.json"));
1654
- // TODO - Update/revise this, when the update comes out in September:
1655
- // Sources for majority-minority info:
1656
- // - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
1657
- // - http://www.ncsl.org/Portals/1/Documents/Redistricting/Redistricting_2010.pdf (PP. 80–84)
1658
- // - https://www.justice.gov/crt/jurisdictions-previously-covered-section-5
1659
- function getMajorityMinority(s) {
1660
- const xx = s.state.xx;
1661
- const mMDict = majority_minority_json_1.default;
1662
- const stateMM = mMDict[xx];
1663
- return stateMM;
1664
- }
1665
- function getVRASection5(s) {
1666
- const xx = s.state.xx;
1667
- const vraPreDict = vra5_preclearance_json_1.default;
1668
- const stateVRAPre = vraPreDict[xx];
1669
- return stateVRAPre;
1670
- }
1671
- // RPV - pulled into a separate component 11-17-2020
1672
- function doAnalyzeRacialPolarization(s, districtID, groups, bLog = false) {
1673
- // Make sure that a minority is specified
1674
- if (!(groups.black || groups.hispanic || groups.pacific || groups.asian || groups.native || groups.minority))
1675
- return undefined;
1676
- const points = s.districts.extractVotesByDemographic(districtID, groups, bLog);
1677
- // Make sure the district is not empty & there are enough points
1678
- if (points === undefined)
1679
- return undefined;
1680
- if (points.comparison === undefined)
1681
- return undefined;
1682
- if (points.comparison.length <= 10)
1683
- return undefined;
1684
- return dra_analytics_1.Minority.analyzeRacialVoting(points, districtID, groups);
1685
- }
1686
- // RPV is in the dra-analytics package.
1035
+ // END
1687
1036
 
1688
1037
 
1689
1038
  /***/ }),
@@ -1732,72 +1081,67 @@ var __importStar = (this && this.__importStar) || (function () {
1732
1081
  };
1733
1082
  })();
1734
1083
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1735
- exports.doPreprocessData = doPreprocessData;
1084
+ exports.initialize = initialize;
1085
+ const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
1736
1086
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1737
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1738
- // NOTE - Do preprocessing separately, so the constructor returns quickly.
1739
- function doPreprocessData(s, bLog = false) {
1087
+ const D = __importStar(__webpack_require__(/*! ./data */ "./src/data.ts"));
1088
+ function initialize(s) {
1089
+ // NOTE - Do preprocessing separately, so the constructor returns quickly.
1740
1090
  // If necessary, do one-time preprocessing
1741
- if (!s.bOneTimeProcessingDone) {
1742
- doPreprocessCountyFeatures(s, bLog);
1743
- doPreprocessCensus(s, bLog);
1744
- // doPreprocessElection(s, bLog);
1745
- s.bOneTimeProcessingDone = true;
1091
+ if (!s.data.bOneTimeProcessingDone) {
1092
+ preprocessCountyFeatures(s.data, s.bLog);
1093
+ preprocessCensus(s.data, s.bLog);
1094
+ s.data.features.mapGeoIDsToFeatureIDs();
1095
+ s.data.bOneTimeProcessingDone = true;
1746
1096
  }
1747
- // Invert the plan by district ID
1748
- s.plan.invertPlan(bLog);
1749
- // Create a map of geoIDs to feature IDs
1750
- s.features.mapGeoIDsToFeatureIDs();
1097
+ s.data.plan.invertPlan(s.bLog);
1751
1098
  }
1752
1099
  // CREATE A FIPS CODE TO COUNTY NAME LOOKUP
1753
- function doPreprocessCountyFeatures(s, bLog = false) {
1100
+ function preprocessCountyFeatures(container, bLog = false) {
1754
1101
  let fipsCodes = [];
1755
1102
  // CREATE A MAP OF FIPS CODES TO NAMES
1756
- for (let i = 0; i < s.counties.nCounties; i++) {
1757
- let county = s.counties.countyByIndex(i);
1758
- let fips = s.counties.propertyForCounty(county, 'COUNTYFP');
1103
+ for (let i = 0; i < container.counties.nCounties; i++) {
1104
+ let county = container.counties.countyByIndex(i);
1105
+ let fips = container.counties.propertyForCounty(county, 'COUNTYFP');
1759
1106
  fipsCodes.push(fips);
1760
- let name = s.counties.propertyForCounty(county, 'NAME');
1761
- s.counties.mapFIPSToName(fips, name);
1107
+ let name = container.counties.propertyForCounty(county, 'NAME');
1108
+ container.counties.mapFIPSToName(fips, name);
1762
1109
  }
1763
1110
  // CREATE A FIPS CODE-ORDINAL MAP
1764
1111
  // Sort the FIPS codes in the county shapes
1765
1112
  fipsCodes = fipsCodes.sort();
1113
+ // NOTE - 02/05/25 - I'm not sure why we're adding the virtual county bucket here,
1114
+ // because we strip it off in creating the profile for analytics. To be safe, I'm
1115
+ // Keeping it here and stripping it off later.
1766
1116
  // Add a dummy county, for county-district splitting analysis.
1767
1117
  fipsCodes.unshift('000');
1768
- // NOTE - This was added for the legacy SPLITTING implementation.
1769
1118
  // Create the ID-ordinal map
1770
1119
  for (let i in fipsCodes) {
1771
- s.counties.index[fipsCodes[i]] = Number(i);
1120
+ container.counties.index[fipsCodes[i]] = Number(i);
1772
1121
  // 07-17-21 - For enumerating # of times each split county is split
1773
- s.counties.fips[Number(i)] = fipsCodes[i];
1122
+ container.counties.fips[Number(i)] = fipsCodes[i];
1774
1123
  }
1775
1124
  }
1776
1125
  // ANALYZE THE CENSUS BY COUNTY
1777
- function doPreprocessCensus(s, bLog = false) {
1778
- // The county-splitting analytic needs the following info, using NC as an example:
1779
- // '_stateTotal' = The total state population, e.g., 9,535,483 for NC's 2010 Census
1780
- // 'totalByCounty' = The total population by county FIPS code
1126
+ function preprocessCensus(container, bLog = false) {
1781
1127
  // SUM TOTAL POPULATION BY COUNTY
1782
1128
  let totalByCounty = {};
1783
1129
  // NOTE - This works w/o GEOIDs, because you're looping over all features.
1784
- for (let i = 0; i < s.features.nFeatures(); i++) {
1785
- let f = s.features.featureByIndex(i);
1130
+ for (let i = 0; i < container.features.nFeatures(); i++) {
1131
+ let f = container.features.featureByIndex(i);
1786
1132
  // 03-27-21
1787
1133
  let geoID = D.geoIDForFeature(f);
1788
- // let geoID: string = s.features.geoIDForFeature(f);
1789
1134
  // Skip water-only features
1790
1135
  if (!(U.isWaterOnly(geoID))) {
1791
1136
  // 03-27-21
1792
- const dk = s.features._keys["CENSUS" /* T.Dataset.CENSUS */];
1137
+ const dk = container.features._keys[T.Dataset.CENSUS];
1793
1138
  let value = D.fieldForFeature(f, dk, 0 /* T.FeatureField.TotalPop */);
1794
- // let value: number = s.features.fieldForFeature(f, T.Dataset.CENSUS, T.FeatureField.TotalPop);
1795
1139
  // Sum total population across the state
1796
- s.state.totalPop += value;
1140
+ container.state.totalPop += value;
1797
1141
  // Get the county FIPS code for the feature
1798
1142
  let countyFIPS = U.parseGeoID(geoID)['county'];
1799
1143
  // Ignore features when the county is unrecognized
1800
- if (U.keyExists(countyFIPS, s.counties.index)) {
1144
+ if (U.keyExists(countyFIPS, container.counties.index)) {
1801
1145
  // If a subtotal for the county doesn't exist, initialize one
1802
1146
  if (!(U.keyExists(countyFIPS, totalByCounty))) {
1803
1147
  totalByCounty[countyFIPS] = 0;
@@ -1815,40 +1159,21 @@ function doPreprocessCensus(s, bLog = false) {
1815
1159
  console.log("Skipping water-only feature in Census preprocessing:", geoID);
1816
1160
  }
1817
1161
  }
1818
- // NOTE - The above could be replaced, if I got totals on county.geojson.
1819
- /* Moved this to doPreprocessCountyFeatures() - 09-14-2020 to fix VA county mismatch bug
1820
- // CREATE A FIPS CODE-ORDINAL MAP
1821
-
1822
- // Get the county FIPS codes
1823
- let fipsCodes = U.getObjectKeys(totalByCounty);
1824
- // Sort the results
1825
- fipsCodes = fipsCodes.sort();
1826
-
1827
- // NOTE - This was added for the legacy SPLITTING implementation.
1828
- // Add a dummy county, for county-district splitting analysis.
1829
- fipsCodes.unshift('000');
1830
-
1831
- // Create the ID-ordinal map
1832
- for (let i in fipsCodes)
1833
- {
1834
- s.counties.index[fipsCodes[i]] = Number(i);
1835
- }
1836
- */
1837
1162
  // MAKE AN ARRAY OF TOTAL POPULATIONS BY COUNTY INDEX
1838
1163
  // Add an extra 0th virtual county bucket for county-district splitting analysis
1839
- let nCountyBuckets = s.counties.nCounties + 1;
1164
+ let nCountyBuckets = container.counties.nCounties + 1;
1840
1165
  let countyTotals = U.initArray(nCountyBuckets, 0);
1841
1166
  for (let fipsCode in totalByCounty) {
1842
- let i = s.counties.indexFromFIPS(fipsCode);
1167
+ let i = container.counties.indexFromFIPS(fipsCode);
1843
1168
  countyTotals[i] = totalByCounty[fipsCode];
1844
1169
  }
1845
- s.counties.totalPopulation = countyTotals;
1170
+ container.counties.totalPopulation = countyTotals;
1846
1171
  // MMD - Rationalized calculation of target size *per rep*
1847
- s.state.targetSize = s.state.totalPop / s.state.nReps;
1172
+ container.state.targetSize = Math.round(container.state.totalPop / container.state.nReps);
1848
1173
  // ANALYZE THE COUNTIES
1849
1174
  // MMD - This "these counties must be split" analysis does NOT generalize to heterogenous MMD.
1850
1175
  // 'target_size': 733499, # calc as total / districts
1851
- const targetSize = s.state.targetSize;
1176
+ const targetSize = container.state.targetSize;
1852
1177
  // let targetSize = Math.round(s.state.totalPop / s.state.nDistricts);
1853
1178
  // Find counties that are bigger than the target district size.
1854
1179
  // 'too_big' = The counties that are bigger than a district, e.g., ['Mecklenburg', 'Wake']
@@ -1861,7 +1186,7 @@ function doPreprocessCensus(s, bLog = false) {
1861
1186
  let expectedSplits = 0;
1862
1187
  let expectedAffected = 0;
1863
1188
  // Get the county FIPS codes
1864
- let fipsCodes = U.getObjectKeys(s.counties.index);
1189
+ let fipsCodes = U.getObjectKeys(container.counties.index);
1865
1190
  // Loop over the counties
1866
1191
  for (let county in fipsCodes) {
1867
1192
  let fipsCode = fipsCodes[county];
@@ -1877,37 +1202,166 @@ function doPreprocessCensus(s, bLog = false) {
1877
1202
  if (countySplits > 0) {
1878
1203
  countyAffected = totalByCounty[fipsCode] % targetSize;
1879
1204
  tooBigFIPS.push(fipsCode);
1880
- tooBigName.push(s.counties.nameFromFIPS(fipsCode));
1205
+ tooBigName.push(container.counties.nameFromFIPS(fipsCode));
1881
1206
  singleCountyDistrictMax += countySplits;
1882
1207
  }
1883
1208
  expectedSplits += countySplits;
1884
1209
  expectedAffected += countyAffected;
1885
1210
  }
1886
- s.state.tooBigFIPS = tooBigFIPS;
1887
- s.state.tooBigName = tooBigName;
1888
- s.state.expectedSplits = expectedSplits;
1889
- s.state.expectedAffected = expectedAffected;
1890
- s.state.singleCountyDistrictMax = singleCountyDistrictMax;
1211
+ container.state.tooBigFIPS = tooBigFIPS;
1212
+ container.state.tooBigName = tooBigName;
1213
+ container.state.expectedSplits = expectedSplits;
1214
+ container.state.expectedAffected = Math.round(expectedAffected);
1215
+ container.state.singleCountyDistrictMax = singleCountyDistrictMax;
1216
+ }
1217
+ // END
1218
+
1219
+
1220
+ /***/ }),
1221
+
1222
+ /***/ "./src/requirements.ts":
1223
+ /*!*****************************!*\
1224
+ !*** ./src/requirements.ts ***!
1225
+ \*****************************/
1226
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1227
+
1228
+
1229
+ //
1230
+ // MAP/PLAN REQUIREMENTS CHECKS ("CONSTRAINTS")
1231
+ //
1232
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1233
+ if (k2 === undefined) k2 = k;
1234
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1235
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1236
+ desc = { enumerable: true, get: function() { return m[k]; } };
1237
+ }
1238
+ Object.defineProperty(o, k2, desc);
1239
+ }) : (function(o, m, k, k2) {
1240
+ if (k2 === undefined) k2 = k;
1241
+ o[k2] = m[k];
1242
+ }));
1243
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
1244
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1245
+ }) : function(o, v) {
1246
+ o["default"] = v;
1247
+ });
1248
+ var __importStar = (this && this.__importStar) || (function () {
1249
+ var ownKeys = function(o) {
1250
+ ownKeys = Object.getOwnPropertyNames || function (o) {
1251
+ var ar = [];
1252
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
1253
+ return ar;
1254
+ };
1255
+ return ownKeys(o);
1256
+ };
1257
+ return function (mod) {
1258
+ if (mod && mod.__esModule) return mod;
1259
+ var result = {};
1260
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1261
+ __setModuleDefault(result, mod);
1262
+ return result;
1263
+ };
1264
+ })();
1265
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1266
+ exports.isComplete = isComplete;
1267
+ exports.emptyDistricts = emptyDistricts;
1268
+ exports.getUnassignedFeatures = getUnassignedFeatures;
1269
+ exports.isContiguous = isContiguous;
1270
+ exports.isEmbedded = isEmbedded;
1271
+ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
1272
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1273
+ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
1274
+ // COMPLETE-NESS
1275
+ /*
1276
+ * A plan is "complete" if all districts have non-zero population, and
1277
+ * all unassigned features have zero population.
1278
+ *
1279
+ * Note: This helper does not verify the correct number of districts:
1280
+ * That is the caller's responsibility.
1281
+ */
1282
+ function isComplete(popByDistricts) {
1283
+ if (popByDistricts[0] > 0)
1284
+ return false; // Unassigned pop
1285
+ for (let d = 1; d < popByDistricts.length; d++) {
1286
+ if (popByDistricts[d] <= 0)
1287
+ return false; // Empty district
1288
+ }
1289
+ return true;
1290
+ }
1291
+ function emptyDistricts(popByDistricts) {
1292
+ let emptyDistricts = [];
1293
+ for (let d = 1; d < popByDistricts.length; d++) {
1294
+ if (popByDistricts[d] <= 0)
1295
+ emptyDistricts.push(d); // Empty district
1296
+ }
1297
+ return emptyDistricts;
1298
+ }
1299
+ function getUnassignedFeatures(container) {
1300
+ // Simulate an old school test entry, to preserve this and downstream code
1301
+ let test = {};
1302
+ const bLog = container._session.bLog;
1303
+ let unassignedFeatures = [];
1304
+ // Is all population assigned to real districts?
1305
+ // If not, collect populated features (not water-only or uninhabited).
1306
+ if (container.aggregates.columns.totalPop[S.NOT_ASSIGNED] > 0) {
1307
+ const geoIDs = Array.from(container.plan.geoIDsForDistrictID(S.NOT_ASSIGNED));
1308
+ geoIDs.forEach(function (geoID) {
1309
+ if (U.isWaterOnly(geoID, container)) {
1310
+ if (bLog)
1311
+ console.log("Unassigned water-only feature ignored in completeness check: ", geoID);
1312
+ return;
1313
+ }
1314
+ if (U.isUninhabited(geoID, container)) {
1315
+ if (bLog)
1316
+ console.log("Uninhabited feature ignored in completeness check: ", geoID);
1317
+ return;
1318
+ }
1319
+ // Not water-only and inhabited
1320
+ unassignedFeatures.push(geoID);
1321
+ return;
1322
+ });
1323
+ }
1324
+ return unassignedFeatures;
1325
+ }
1326
+ // CONTIGUITY
1327
+ /*
1328
+ * A district is contiguous if all geo's in the set are connected
1329
+ * via the contiguity graph.
1330
+ *
1331
+ * Use this helper to test individual districts in dra-client.
1332
+ * AND all the district results together to test plan contiguity.
1333
+ */
1334
+ function isContiguous(geoids, graph, bLog = false) {
1335
+ return dra_analytics_1.Graph.isConnected(geoids, graph, bLog);
1336
+ }
1337
+ // FREE OF HOLES (NOT EMBEDDED)
1338
+ /*
1339
+ * A district is NOT a "donut hole" district:
1340
+ * - If any neighbor is 'OUT_OF_STATE'; or
1341
+ * - If there are 2 or more neighboring districts.
1342
+ *
1343
+ * Use this helper to test individual districts in dra-client.
1344
+ * AND all the district results together to test whether a plan is free of holes.
1345
+ */
1346
+ function isEmbedded(districtID, geoids, plan, graph, bLog = false) {
1347
+ return dra_analytics_1.Graph.isEmbedded(districtID, geoids, plan, graph, bLog);
1891
1348
  }
1892
- // PREPROCESS ELECTION RESULTS
1893
- // function doPreprocessElection(s: AnalyticsSession, bLog: boolean = false): void
1894
- // {
1895
- // if (bLog) console.log("Preprocessing election data ...");
1896
- // }
1349
+ // END
1897
1350
 
1898
1351
 
1899
1352
  /***/ }),
1900
1353
 
1901
- /***/ "./src/results.ts":
1902
- /*!************************!*\
1903
- !*** ./src/results.ts ***!
1904
- \************************/
1354
+ /***/ "./src/rpv.ts":
1355
+ /*!********************!*\
1356
+ !*** ./src/rpv.ts ***!
1357
+ \********************/
1905
1358
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
1906
1359
 
1907
1360
 
1908
1361
  //
1909
- // TEMPLATES FOR UNFORMATTED ANALYTICS RESULTS
1910
- //
1362
+ // RACIALLY POLARIZED VOTING (RPV) SUPPORT
1363
+ // The main RPV code is in the dra-analytics package.
1364
+ //
1911
1365
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1912
1366
  if (k2 === undefined) k2 = k;
1913
1367
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -1942,165 +1396,163 @@ var __importStar = (this && this.__importStar) || (function () {
1942
1396
  };
1943
1397
  })();
1944
1398
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1945
- exports.prepareRequirementsChecklist = prepareRequirementsChecklist;
1946
- exports.prepareDistrictStatistics = prepareDistrictStatistics;
1947
- exports.doAnalyzePostProcessing = doAnalyzePostProcessing;
1399
+ exports.doRPV = doRPV;
1400
+ exports.inferSelectedMinority = inferSelectedMinority;
1401
+ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
1402
+ const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
1948
1403
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1949
- const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
1950
- function prepareRequirementsChecklist(s, bLog = false) {
1951
- if (!(s.bPostProcessingDone)) {
1952
- doAnalyzePostProcessing(s);
1953
- }
1954
- // REQUIREMENTS CATEGORY
1955
- let paRequirements;
1956
- {
1957
- const completeTest = s.getTest(0 /* T.Test.Complete */);
1958
- const contiguousTest = s.getTest(1 /* T.Test.Contiguous */);
1959
- const freeOfHolesTest = s.getTest(2 /* T.Test.FreeOfHoles */);
1960
- const equalPopulationTest = s.getTest(3 /* T.Test.EqualPopulation */);
1961
- // Combine individual checks into an overall score
1962
- const completeMetric = completeTest['score'];
1963
- const contiguousMetric = contiguousTest['score'];
1964
- const freeOfHolesMetric = freeOfHolesTest['score'];
1965
- const equalPopulationMetric = equalPopulationTest['score'];
1966
- const reqScore = completeMetric && contiguousMetric && freeOfHolesMetric && equalPopulationMetric;
1967
- /* T.TriState
1968
- // NOTE - Until we add three-state support top to bottom in
1969
- // requirements/validations, map booleans to tri-states here.
1970
- const completeMetric = U.mapBooleanToTriState(completeTest['score'] as boolean);
1971
- const contiguousMetric = U.mapBooleanToTriState(contiguousTest['score'] as boolean);
1972
- const freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score'] as boolean);
1973
- const equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score'] as boolean);
1974
-
1975
- let reqScore: T.TriState = T.TriState.Green;
1976
- const checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
1977
- if (checks.includes(T.TriState.Yellow)) reqScore = T.TriState.Yellow;
1978
- if (checks.includes(T.TriState.Red)) reqScore = T.TriState.Red;
1979
- */
1980
- // Get values to support details entries
1981
- const unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
1982
- const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
1983
- const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
1984
- const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
1985
- const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
1986
- const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
1987
- // const xx: string = s.state.xx;
1988
- // const stateReqsDict: T.Dict = allStateReqs;
1989
- // const reqLinkToStateReqs: string = stateReqsDict[xx];
1990
- // Populate the category
1991
- paRequirements = {
1992
- score: reqScore,
1993
- metrics: {
1994
- complete: completeMetric,
1995
- contiguous: contiguousMetric,
1996
- freeOfHoles: freeOfHolesMetric,
1997
- equalPopulation: equalPopulationMetric
1998
- },
1999
- details: {
2000
- unassignedFeatures: unassignedFeaturesDetail,
2001
- emptyDistricts: emptyDistrictsDetail,
2002
- discontiguousDistricts: discontiguousDistrictsDetail,
2003
- embeddedDistricts: embeddedDistrictsDetail,
2004
- populationDeviation: populationDeviationDetail,
2005
- deviationThreshold: deviationThresholdDetail
2006
- },
2007
- datasets: {
2008
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
2009
- },
2010
- resources: {
2011
- // stateReqs: reqLinkToStateReqs
1404
+ const data_1 = __webpack_require__(/*! ./data */ "./src/data.ts");
1405
+ function doRPV(container, districtID, groups, bLog = false) {
1406
+ // Make sure that a minority is specified
1407
+ if (!(groups.black || groups.hispanic || groups.pacific || groups.asian || groups.native || groups.minority))
1408
+ return undefined;
1409
+ const points = extractVotesByDemographic(container, districtID, groups, bLog);
1410
+ // Make sure the district is not empty & there are enough points
1411
+ if (points === undefined)
1412
+ return undefined;
1413
+ if (points.comparison === undefined)
1414
+ return undefined;
1415
+ if (points.comparison.length <= 10)
1416
+ return undefined;
1417
+ return dra_analytics_1.Minority.analyzeRacialVoting(points, districtID, groups);
1418
+ }
1419
+ function extractVotesByDemographic(container, districtID, groups, bLog = false) {
1420
+ let ids = [];
1421
+ let comparisonPts = []; // Either White -or- (1 – <selected minority>) i.e., White++
1422
+ let hispanicPts = [];
1423
+ let blackPts = [];
1424
+ let pacificPts = [];
1425
+ let asianPts = [];
1426
+ let nativePts = [];
1427
+ let minorityPts = [];
1428
+ // Gather [demographic %, D %] points for the selected district ID
1429
+ let i = districtID;
1430
+ // HACK - For comparing a minority group to everyone else (vs. just White),
1431
+ // infer the *single* minority group that has been selected.
1432
+ const selectedMinority = inferSelectedMinority(groups);
1433
+ // Get the geoIDs assigned to the district
1434
+ // Guard against empty districts
1435
+ let geoIDs = container.plan.geoIDsForDistrictID(i);
1436
+ if (geoIDs && (geoIDs.size > 0)) {
1437
+ // ... loop over the geoIDs collecting the points for ecological regression
1438
+ geoIDs.forEach(function (geoID) {
1439
+ // Skip water-only features
1440
+ if (!(U.isWaterOnly(geoID))) {
1441
+ // Map from geoID to feature index
1442
+ let featureID = container.features.featureID(geoID);
1443
+ let f = container.features.featureByIndex(featureID);
1444
+ if (!(f == undefined)) {
1445
+ // Calculate the Dem two-party vote
1446
+ // 03-27-21
1447
+ const dkELECTION = container.features._keys[T.Dataset.ELECTION];
1448
+ const featureDem = (0, data_1.fieldForFeature)(f, dkELECTION, 7 /* T.FeatureField.DemVotes */);
1449
+ const featureRep = (0, data_1.fieldForFeature)(f, dkELECTION, 8 /* T.FeatureField.RepVotes */);
1450
+ if ((featureDem + featureRep) > 0) {
1451
+ const pctDem = featureDem / (featureDem + featureRep);
1452
+ // Calculate the VAP/CVAP percentages by demographic
1453
+ // 03-27-21
1454
+ const dkVAP = container.features._keys[T.Dataset.VAP];
1455
+ const totalVAP = (0, data_1.fieldForFeature)(f, dkVAP, 0 /* T.FeatureField.TotalPop */);
1456
+ if (totalVAP > 0) {
1457
+ // Gather all points, for debugging purposes ...
1458
+ const hispanicVAP = (0, data_1.fieldForFeature)(f, dkVAP, 3 /* T.FeatureField.HispanicPop */);
1459
+ const blackVAP = (0, data_1.fieldForFeature)(f, dkVAP, 2 /* T.FeatureField.BlackPop */);
1460
+ const pacificVAP = (0, data_1.fieldForFeature)(f, dkVAP, 5 /* T.FeatureField.PacificPop */);
1461
+ const asianVAP = (0, data_1.fieldForFeature)(f, dkVAP, 4 /* T.FeatureField.AsianPop */);
1462
+ const nativeVAP = (0, data_1.fieldForFeature)(f, dkVAP, 6 /* T.FeatureField.NativePop */);
1463
+ const pctHispanic = hispanicVAP / totalVAP;
1464
+ const pctBlack = blackVAP / totalVAP;
1465
+ const pctPacific = pacificVAP / totalVAP;
1466
+ const pctAsian = asianVAP / totalVAP;
1467
+ const pctNative = nativeVAP / totalVAP;
1468
+ const whiteVAP = (0, data_1.fieldForFeature)(f, dkVAP, 1 /* T.FeatureField.WhitePop */);
1469
+ const minorityVAP = totalVAP - whiteVAP;
1470
+ const pctMinority = minorityVAP / totalVAP;
1471
+ let pctComparison = whiteVAP / totalVAP;
1472
+ if (groups.invertSelection) {
1473
+ switch (selectedMinority) {
1474
+ case 'Hispanic': {
1475
+ pctComparison = (totalVAP - hispanicVAP) / totalVAP;
1476
+ break;
1477
+ }
1478
+ case 'Black': {
1479
+ pctComparison = (totalVAP - blackVAP) / totalVAP;
1480
+ break;
1481
+ }
1482
+ case 'Pacific': {
1483
+ pctComparison = (totalVAP - pacificVAP) / totalVAP;
1484
+ break;
1485
+ }
1486
+ case 'Asian': {
1487
+ pctComparison = (totalVAP - asianVAP) / totalVAP;
1488
+ break;
1489
+ }
1490
+ case 'Native': {
1491
+ pctComparison = (totalVAP - nativeVAP) / totalVAP;
1492
+ break;
1493
+ }
1494
+ case 'Minority': {
1495
+ pctComparison = (totalVAP - minorityVAP) / totalVAP;
1496
+ break;
1497
+ }
1498
+ default: {
1499
+ console.log("Selected minority not recognized!");
1500
+ break;
1501
+ }
1502
+ }
1503
+ }
1504
+ ids.push(geoID);
1505
+ comparisonPts.push({ x: pctComparison, y: pctDem }); // White -or- (1 – <selected minority>)
1506
+ hispanicPts.push({ x: pctHispanic, y: pctDem });
1507
+ blackPts.push({ x: pctBlack, y: pctDem });
1508
+ pacificPts.push({ x: pctPacific, y: pctDem });
1509
+ asianPts.push({ x: pctAsian, y: pctDem });
1510
+ nativePts.push({ x: pctNative, y: pctDem });
1511
+ minorityPts.push({ x: pctMinority, y: pctDem });
1512
+ }
1513
+ else {
1514
+ if (bLog)
1515
+ console.log("RPV: Total VAP not > 0.");
1516
+ }
1517
+ }
1518
+ else {
1519
+ if (bLog)
1520
+ console.log("RPV: D + R not > 0.");
1521
+ }
1522
+ }
2012
1523
  }
1524
+ });
1525
+ // ... but only keep points for the demographics that are going to be analyzed
1526
+ return {
1527
+ ids: ids,
1528
+ comparison: comparisonPts,
1529
+ minority: groups.minority ? minorityPts : [],
1530
+ black: groups.black ? blackPts : [],
1531
+ hispanic: groups.hispanic ? hispanicPts : [],
1532
+ pacific: groups.pacific ? pacificPts : [],
1533
+ asian: groups.asian ? asianPts : [],
1534
+ native: groups.native ? nativePts : []
2013
1535
  };
2014
1536
  }
2015
- return paRequirements;
1537
+ // If a district is empty
1538
+ return undefined;
2016
1539
  }
2017
- // Create a DistrictStatistics instance, deep copying the underlying values.
2018
- function prepareDistrictStatistics(s, bLog = false) {
2019
- if (!(s.bPostProcessingDone)) {
2020
- doAnalyzePostProcessing(s);
2021
- }
2022
- // Transpose the rows & columns, so rows are districts.
2023
- let dsTable = [];
2024
- for (let i = 0; i < s.districts.numberOfRows(); i++) {
2025
- const rawRow = {
2026
- districtID: i,
2027
- totalPop: s.districts.table.totalPop[i],
2028
- popDevPct: s.districts.table.popDevPct[i],
2029
- bEqualPop: s.districts.table.bEqualPop[i],
2030
- bNotEmpty: s.districts.table.bNotEmpty[i],
2031
- bContiguous: s.districts.table.bContiguous[i],
2032
- bNotEmbedded: s.districts.table.bNotEmbedded[i],
2033
- demPct: s.districts.table.demPct[i],
2034
- repPct: s.districts.table.repPct[i],
2035
- othPct: s.districts.table.otherPct[i],
2036
- whitePct: s.districts.table.whitePct[i],
2037
- totalVAP: s.districts.table.totalVAP[i],
2038
- minorityPct: s.districts.table.minorityPct[i],
2039
- blackPct: s.districts.table.blackPct[i],
2040
- hispanicPct: s.districts.table.hispanicPct[i],
2041
- pacificPct: s.districts.table.pacificPct[i],
2042
- asianPct: s.districts.table.asianPct[i],
2043
- nativePct: s.districts.table.nativePct[i]
2044
- };
2045
- const readyRow = U.deepCopy(rawRow);
2046
- dsTable.push(readyRow);
2047
- }
2048
- const dsDetails = {
2049
- // None at this time
2050
- };
2051
- const dsDatasets = {
2052
- shapes: U.deepCopy(s.config['descriptions']['SHAPES']), // 2020
2053
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
2054
- vap: U.deepCopy(s.config['descriptions']['VAP']),
2055
- election: U.deepCopy(s.config['descriptions']['ELECTION'])
2056
- };
2057
- const dsResources = {
2058
- // None at this time
2059
- };
2060
- const ds = {
2061
- table: dsTable,
2062
- details: dsDetails,
2063
- datasets: dsDatasets,
2064
- resources: dsResources
2065
- };
2066
- return ds;
2067
- }
2068
- // Postprocess analytics - Normalize numeric results and derive secondary tests.
2069
- // Do this after analytics have been run and before preparing a test log or scorecard.
2070
- function doAnalyzePostProcessing(s, bLog = false) {
2071
- // Just populate the normalized population deviation score in the test
2072
- const scorecard = s._scorecard;
2073
- let popDev = s.getTest(4 /* T.Test.PopulationDeviation */);
2074
- popDev['normalizedScore'] = scorecard.populationDeviation.normalized;
2075
- const datasets = {
2076
- shapes: U.deepCopy(s.config['descriptions']['SHAPES']), // 2020
2077
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
2078
- vap: U.deepCopy(s.config['descriptions']['VAP']),
2079
- election: U.deepCopy(s.config['descriptions']['ELECTION'])
2080
- };
2081
- scorecard.partisan.details['election'] = datasets.election;
2082
- scorecard.minority.details['vap'] = datasets.vap;
2083
- scorecard.details['shapes'] = datasets.shapes;
2084
- scorecard.details['census'] = datasets.census;
2085
- const simpleSplits = s.getTest(5 /* T.Test.UnexpectedCountySplits */);
2086
- scorecard.splitting.details['unexpectedAffected'] = simpleSplits['score'];
2087
- scorecard.splitting.details['nSplits'] = simpleSplits['details']['nSplits'];
2088
- scorecard.splitting.details['countiesSplitUnexpectedly'] = U.deepCopy(simpleSplits['details']['countiesSplitUnexpectedly']);
2089
- // 02-23-21 - Added single-county district details
2090
- scorecard.splitting.details['nTooBigCounties'] = simpleSplits['details']['expectedSplits'];
2091
- scorecard.splitting.details['tooBigName'] = simpleSplits['details']['tooBigName'];
2092
- scorecard.splitting.details['nCountiesSplit'] = simpleSplits['details']['nCountiesSplit'];
2093
- scorecard.splitting.details['nSingleCountyDistricts'] = simpleSplits['details']['nSingleCountyDistricts'];
2094
- scorecard.splitting.details['singleCountyDistrictMax'] = simpleSplits['details']['singleCountyDistrictMax'];
2095
- // 07-17-21 - Enumerate # of times each split county is split
2096
- scorecard.splitting.details['countiesWithSplits'] = simpleSplits['details']['countiesWithSplits'];
2097
- // NOTE - Add split precincts in dra-client directly
2098
- // Derive secondary tests
2099
- // Note - The only secondary test is 'roughly equal population (true/false)
2100
- (0, analyze_1.doDeriveSecondaryTests)(s, bLog);
2101
- // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
2102
- s.bPostProcessingDone = true;
1540
+ function inferSelectedMinority(groups) {
1541
+ if (groups.hispanic)
1542
+ return 'Hispanic';
1543
+ if (groups.black)
1544
+ return 'Black';
1545
+ if (groups.pacific)
1546
+ return 'Pacific';
1547
+ if (groups.asian)
1548
+ return 'Asian';
1549
+ if (groups.native)
1550
+ return 'Native';
1551
+ if (groups.minority)
1552
+ return 'Minority';
1553
+ return 'No minority selected!';
2103
1554
  }
1555
+ // END
2104
1556
 
2105
1557
 
2106
1558
  /***/ }),
@@ -2149,57 +1601,52 @@ var __importStar = (this && this.__importStar) || (function () {
2149
1601
  };
2150
1602
  })();
2151
1603
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2152
- exports.profilePlan = profilePlan;
2153
- exports.getStatewideDemographics = getStatewideDemographics;
1604
+ exports.makeProfile = makeProfile;
2154
1605
  exports.computeMetrics = computeMetrics;
2155
1606
  exports.rateKeyDimensions = rateKeyDimensions;
2156
1607
  exports.thunkScorecard = thunkScorecard;
2157
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2158
1608
  const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
1609
+ const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
1610
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1611
+ const D = __importStar(__webpack_require__(/*! ./data */ "./src/data.ts"));
2159
1612
  // PROFILE A PLAN
2160
1613
  const KEEP_DECIMALS = 6;
2161
- // MMD - Added # reps per district to the profile
2162
- function profilePlan(s, bLog = false) {
2163
- const state = s.state.xx;
2164
- const planName = s.title;
2165
- const nDistricts = s.state.nDistricts;
2166
- const nReps = s.state.nReps; // MMD
2167
- const nCounties = s.counties.nCounties;
2168
- const targetSize = Math.round(s.state.totalPop / nReps); // MMD
2169
- // const targetSize: number = Math.round(s.state.totalPop / nDistricts);
2170
- const popByDistrict = U.deepCopy(s.districts.table.totalPop.slice(1, -1));
2171
- const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
2172
- const splits = makeNakedCxD(s);
2173
- const summaryRow = s.districts.numberOfRows() - 1;
2174
- // 10-22-2020 - Converted Dem + Rep + Other = Total to two-party vote shares for analytics.
2175
- const demVote = s.districts.table.demVotes[summaryRow];
2176
- const repVote = s.districts.table.repVotes[summaryRow];
1614
+ function makeProfile(container) {
1615
+ const state = container.state.xx;
1616
+ const planName = container.title;
1617
+ const nDistricts = container.state.nDistricts;
1618
+ const nReps = container.state.nReps;
1619
+ const nCounties = container.counties.nCounties;
1620
+ const targetSize = container.state.targetSize;
1621
+ const popByDistrict = U.deepCopy(container.aggregates.columns.totalPop.slice(1, -1));
1622
+ // const geoPropsByDistrict: L.GeoProperties[] = makeArrayOfGeoProps(s, bLog); // No longer used
1623
+ const splits = _makeCxD(container, container._session.bLog);
1624
+ container.CxD = splits; // Squirrel CxD away for later "unexpected splits" analysis
1625
+ const demVote = container.aggregates.columns.DemVotes.at(-1);
1626
+ const repVote = container.aggregates.columns.RepVotes.at(-1);
2177
1627
  const statewideVf = U.trim(demVote / (demVote + repVote), KEEP_DECIMALS);
2178
1628
  let vpiArray = [];
2179
- const demVotes = U.deepCopy(s.districts.table.demVotes.slice(1, -1));
2180
- const repVotes = U.deepCopy(s.districts.table.repVotes.slice(1, -1));
2181
- for (let districtID = 1; districtID <= nDistricts; districtID++) {
2182
- const D = demVotes[districtID - 1];
2183
- const R = repVotes[districtID - 1];
1629
+ for (let d = 1; d <= nDistricts; d++) {
1630
+ const D = container.aggregates.columns.DemVotes[d];
1631
+ const R = container.aggregates.columns.RepVotes[d];
2184
1632
  const T = D + R;
2185
1633
  const v = (T > 0) ? U.trim(D / T, KEEP_DECIMALS) : 0;
2186
1634
  vpiArray.push(v);
2187
1635
  }
2188
- const statewideDemographics = getStatewideDemographics(s);
2189
- const demographicsByDistrict = getDemographicsByDistrict(s);
2190
- // MMD - Extended the profile for # reps per district
1636
+ const statewideDemographics = _makeStatewideDemographics(container, container._session.bLog);
1637
+ const demographicsByDistrict = _makeDemographicsByDistrict(container, container._session.bLog);
2191
1638
  const profile = {
2192
1639
  state: state,
2193
1640
  name: planName,
2194
1641
  nDistricts: nDistricts,
2195
- repsByDistrict: s.repsByDistrict, // MMD
1642
+ repsByDistrict: container.repsByDistrict,
2196
1643
  nCounties: nCounties,
2197
- bStateLeg: s.legislativeDistricts, // TODO - 2020
1644
+ bStateLeg: container.legislativeDistricts,
2198
1645
  population: {
2199
1646
  byDistrict: popByDistrict,
2200
1647
  targetSize: targetSize
2201
1648
  },
2202
- shapes: geoPropsByDistrict,
1649
+ shapes: [] /*geoPropsByDistrict*/,
2203
1650
  counties: splits,
2204
1651
  partisanship: {
2205
1652
  statewide: statewideVf,
@@ -2212,64 +1659,71 @@ function profilePlan(s, bLog = false) {
2212
1659
  };
2213
1660
  return profile;
2214
1661
  }
2215
- // NOTE - The CxD splits structure from _data.ts includes dummy districts for
2216
- // unassigned precincts & state summary and an extra 0 county. But dra-score takes
2217
- // a simple 1–D x 1–C splits array (zero-based, of course).
2218
- function makeNakedCxD(s, bLog = false) {
2219
- const adornedCxD = s.districts.table.countySplits;
2220
- let CxD = [];
2221
- // Remove the unassigned & total dummy "districts"
2222
- for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2223
- const splits = U.deepCopy(adornedCxD[districtID].slice(1));
2224
- CxD.push(splits);
2225
- }
2226
- return CxD;
2227
- }
2228
- function makeArrayOfGeoProps(s, bLog = false) {
2229
- let geometryByDistrict = [];
2230
- for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2231
- let districtProps = s.districts.getGeoProperties(districtID);
2232
- // Guard against no shape and no properties
2233
- if (districtProps) {
2234
- let a = U.trim(districtProps[0 /* T.DistrictShapeProperty.Area */], KEEP_DECIMALS);
2235
- let p = U.trim(districtProps[2 /* T.DistrictShapeProperty.Perimeter */], KEEP_DECIMALS);
2236
- let d = U.trim(districtProps[1 /* T.DistrictShapeProperty.Diameter */], KEEP_DECIMALS);
2237
- // Save each triple
2238
- geometryByDistrict.push({ area: a, perimeter: p, diameter: d });
2239
- }
2240
- }
2241
- return geometryByDistrict;
2242
- }
2243
- function getDemographicsByDistrict(s, bLog = false) {
1662
+ function _makeDemographicsByDistrict(container, bLog = false) {
2244
1663
  let demographicsArray = [];
2245
- // Remove the unassigned & total dummy "districts"
2246
- for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
1664
+ // Ignorethe unassigned & total dummy "districts"
1665
+ for (let d = 1; d <= container.state.nDistricts; d++) {
2247
1666
  const districtDemographics = {
2248
- white: U.trim(s.districts.table.whitePct[districtID], KEEP_DECIMALS),
2249
- minority: U.trim(s.districts.table.minorityPct[districtID], KEEP_DECIMALS),
2250
- black: U.trim(s.districts.table.blackPct[districtID], KEEP_DECIMALS),
2251
- hispanic: U.trim(s.districts.table.hispanicPct[districtID], KEEP_DECIMALS),
2252
- pacific: U.trim(s.districts.table.pacificPct[districtID], KEEP_DECIMALS),
2253
- asian: U.trim(s.districts.table.asianPct[districtID], KEEP_DECIMALS),
2254
- native: U.trim(s.districts.table.nativePct[districtID], KEEP_DECIMALS)
1667
+ white: U.trim(container.aggregates.columns.whitePct[d], KEEP_DECIMALS),
1668
+ minority: U.trim(container.aggregates.columns.minorityPct[d], KEEP_DECIMALS),
1669
+ black: U.trim(container.aggregates.columns.blackPct[d], KEEP_DECIMALS),
1670
+ hispanic: U.trim(container.aggregates.columns.hispanicPct[d], KEEP_DECIMALS),
1671
+ pacific: U.trim(container.aggregates.columns.pacificPct[d], KEEP_DECIMALS),
1672
+ asian: U.trim(container.aggregates.columns.asianPct[d], KEEP_DECIMALS),
1673
+ native: U.trim(container.aggregates.columns.nativePct[d], KEEP_DECIMALS)
2255
1674
  };
2256
1675
  demographicsArray.push(districtDemographics);
2257
1676
  }
2258
1677
  return demographicsArray;
2259
1678
  }
2260
- function getStatewideDemographics(s, bLog = false) {
2261
- const summaryRow = s.districts.numberOfRows() - 1;
1679
+ function _makeStatewideDemographics(container, bLog = false) {
1680
+ const summaryRow = container.state.nDistricts + 2 - 1; // Account for unassigned & total dummy "districts";
2262
1681
  const demographics = {
2263
- white: U.trim(s.districts.table.whitePct[summaryRow], KEEP_DECIMALS),
2264
- minority: U.trim(s.districts.table.minorityPct[summaryRow], KEEP_DECIMALS),
2265
- black: U.trim(s.districts.table.blackPct[summaryRow], KEEP_DECIMALS),
2266
- hispanic: U.trim(s.districts.table.hispanicPct[summaryRow], KEEP_DECIMALS),
2267
- pacific: U.trim(s.districts.table.pacificPct[summaryRow], KEEP_DECIMALS),
2268
- asian: U.trim(s.districts.table.asianPct[summaryRow], KEEP_DECIMALS),
2269
- native: U.trim(s.districts.table.nativePct[summaryRow], KEEP_DECIMALS)
1682
+ white: U.trim(container.aggregates.columns.whitePct[summaryRow], KEEP_DECIMALS),
1683
+ minority: U.trim(container.aggregates.columns.minorityPct[summaryRow], KEEP_DECIMALS),
1684
+ black: U.trim(container.aggregates.columns.blackPct[summaryRow], KEEP_DECIMALS),
1685
+ hispanic: U.trim(container.aggregates.columns.hispanicPct[summaryRow], KEEP_DECIMALS),
1686
+ pacific: U.trim(container.aggregates.columns.pacificPct[summaryRow], KEEP_DECIMALS),
1687
+ asian: U.trim(container.aggregates.columns.asianPct[summaryRow], KEEP_DECIMALS),
1688
+ native: U.trim(container.aggregates.columns.nativePct[summaryRow], KEEP_DECIMALS)
2270
1689
  };
2271
1690
  return demographics;
2272
1691
  }
1692
+ function _makeCxD(container, bLog = false) {
1693
+ let CxD = [];
1694
+ let nCountyBuckets = container.counties.nCounties;
1695
+ for (let d = 1; d <= container.state.nDistricts; d++) {
1696
+ let countySplits = U.initArray(nCountyBuckets, 0);
1697
+ let geoIDs = container.plan.geoIDsForDistrictID(d);
1698
+ if (geoIDs && (geoIDs.size > 0)) {
1699
+ geoIDs.forEach(function (geoID) {
1700
+ let featureID = container.features.featureID(geoID);
1701
+ let f = container.features.featureByIndex(featureID);
1702
+ if (f == undefined) {
1703
+ if (bLog)
1704
+ console.log("Skipping undefined feature in making CxD matrix: GEOID =", geoID, "Feature ID =", featureID);
1705
+ }
1706
+ else {
1707
+ const dkCENSUS = container.features._keys[T.Dataset.CENSUS];
1708
+ const featurePop = D.fieldForFeature(f, dkCENSUS, 0 /* T.FeatureField.TotalPop */);
1709
+ // Ignore features when the county is unrecognized
1710
+ const countyFIPS = U.parseGeoID(geoID)['county'];
1711
+ if (U.keyExists(countyFIPS, container.counties.index)) {
1712
+ // Total population by counties w/in a district
1713
+ let i = container.counties.indexFromFIPS(countyFIPS) - 1; // Remove the virtual county bucket
1714
+ countySplits[i] += featurePop;
1715
+ }
1716
+ else {
1717
+ if (bLog)
1718
+ console.log("Statistics: County not recognized:", geoID);
1719
+ }
1720
+ }
1721
+ });
1722
+ }
1723
+ CxD.push(countySplits);
1724
+ }
1725
+ return CxD;
1726
+ }
2273
1727
  // SCORE A PLAN using dra-analytics
2274
1728
  function computeMetrics(p, districtShapes, bLog = false) {
2275
1729
  if (bLog)
@@ -2356,13 +1810,12 @@ function thunkScorecard(newScorecard, bLog = false) {
2356
1810
  const scratchpad = newScorecard.scratchpad;
2357
1811
  // Partisan scorecard
2358
1812
  const pS = U.deepCopy(newScorecard.partisan);
1813
+ // NOTE - 02/05/26 - Stopped return experimental metrics in the scorecard.
2359
1814
  // EXPERIMENTAL
2360
- const lPropAlt = newScorecard.partisan.experimental.lProp;
2361
- if (lPropAlt)
2362
- pS.details['lProp'] = lPropAlt;
2363
- const lUE = newScorecard.partisan.experimental.lUE;
2364
- if (lUE)
2365
- pS.details['lUE'] = lUE;
1815
+ // const lPropAlt = newScorecard.partisan.experimental.lProp;
1816
+ // if (lPropAlt) pS.details['lProp'] = lPropAlt;
1817
+ // const lUE = newScorecard.partisan.experimental.lUE;
1818
+ // if (lUE) pS.details['lUE'] = lUE;
2366
1819
  // Minority scorecard
2367
1820
  const mS = U.deepCopy(newScorecard.minority);
2368
1821
  // Compactness scorecard
@@ -2420,6 +1873,7 @@ function thunkScorecard(newScorecard, bLog = false) {
2420
1873
  };
2421
1874
  return scorecard;
2422
1875
  }
1876
+ // END
2423
1877
 
2424
1878
 
2425
1879
  /***/ }),
@@ -2435,14 +1889,12 @@ function thunkScorecard(newScorecard, bLog = false) {
2435
1889
  // GLOBAL CONSTANTS
2436
1890
  //
2437
1891
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2438
- exports.OUT_OF_STATE = exports.NUMBER_OF_ITEMS_TO_REPORT = exports.NOT_ASSIGNED = exports.PRECISION = void 0;
1892
+ exports.OUT_OF_STATE = exports.NOT_ASSIGNED = exports.PRECISION = void 0;
2439
1893
  // Keep four decimal places for fractions [0–1], i.e.,
2440
1894
  // keep two decimal places for %'s [0–100].
2441
1895
  exports.PRECISION = 4;
2442
1896
  // The dummy district ID for features not assigned districts yet
2443
1897
  exports.NOT_ASSIGNED = 0;
2444
- // # of items to report as problematic (e.g., features, districts, etc.)
2445
- exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
2446
1898
  // The virtual geoID for "neighbors" in other states
2447
1899
  exports.OUT_OF_STATE = "OUT_OF_STATE";
2448
1900
 
@@ -2460,7 +1912,48 @@ exports.OUT_OF_STATE = "OUT_OF_STATE";
2460
1912
  // TYPE DEFINITIONS
2461
1913
  //
2462
1914
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1915
+ exports.StatisticsFields = exports.multiFields = exports.multiFieldPairs = exports.fieldMap = exports.Dataset = void 0;
2463
1916
  exports.fieldsFromFeatureField = fieldsFromFeatureField;
1917
+ var Dataset;
1918
+ (function (Dataset) {
1919
+ Dataset["SHAPES"] = "SHAPES";
1920
+ Dataset["CENSUS"] = "CENSUS";
1921
+ Dataset["VAP"] = "VAP";
1922
+ Dataset["ELECTION"] = "ELECTION";
1923
+ })(Dataset || (exports.Dataset = Dataset = {}));
1924
+ // MAPPING BETWEEN FIELDS OF VARIOUS TYPES
1925
+ // Map between packed field keys and buckets
1926
+ exports.fieldMap = {
1927
+ "CENSUS": {
1928
+ "Tot": "TotalPop"
1929
+ },
1930
+ "VAP": {
1931
+ "Tot": "TotalVAP",
1932
+ "Wh": "WhiteVAP",
1933
+ "Bl": "BlackVAP",
1934
+ "BlC": "BlackVAP",
1935
+ "His": "HispanicVAP",
1936
+ "Asn": "AsianVAP",
1937
+ "AsnC": "AsianVAP",
1938
+ "Pac": "PacificVAP",
1939
+ "PacC": "PacificVAP",
1940
+ "Nat": "NativeVAP",
1941
+ "NatC": "NativeVAP"
1942
+ },
1943
+ "ELECTION": {
1944
+ "Tot": "TotalVotes",
1945
+ "D": "DemVotes",
1946
+ "R": "RepVotes"
1947
+ }
1948
+ };
1949
+ // Pairs of packed/source fields that can correspond to the same bucket
1950
+ exports.multiFieldPairs = [
1951
+ ["Bl", "BlC"],
1952
+ ["Asn", "AsnC"],
1953
+ ["Pac", "PacC"],
1954
+ ["Nat", "NatC"]
1955
+ ];
1956
+ exports.multiFields = exports.multiFieldPairs.flat();
2464
1957
  const FieldsByFeatureField = [
2465
1958
  ["Tot"],
2466
1959
  ["Wh"],
@@ -2476,6 +1969,29 @@ const FieldsByFeatureField = [
2476
1969
  function fieldsFromFeatureField(ds, ff) {
2477
1970
  return FieldsByFeatureField[ff];
2478
1971
  }
1972
+ var StatisticsFields;
1973
+ (function (StatisticsFields) {
1974
+ StatisticsFields["districtID"] = "districtID";
1975
+ StatisticsFields["totalPop"] = "totalPop";
1976
+ StatisticsFields["popDevPct"] = "popDevPct";
1977
+ StatisticsFields["bEqualPop"] = "bEqualPop";
1978
+ StatisticsFields["bNotEmpty"] = "bNotEmpty";
1979
+ StatisticsFields["bContiguous"] = "bContiguous";
1980
+ StatisticsFields["bNotEmbedded"] = "bNotEmbedded";
1981
+ StatisticsFields["demPct"] = "demPct";
1982
+ StatisticsFields["repPct"] = "repPct";
1983
+ StatisticsFields["othPct"] = "othPct";
1984
+ StatisticsFields["totalVAP"] = "totalVAP";
1985
+ StatisticsFields["whitePct"] = "whitePct";
1986
+ StatisticsFields["minorityPct"] = "minorityPct";
1987
+ StatisticsFields["blackPct"] = "blackPct";
1988
+ StatisticsFields["hispanicPct"] = "hispanicPct";
1989
+ StatisticsFields["pacificPct"] = "pacificPct";
1990
+ StatisticsFields["asianPct"] = "asianPct";
1991
+ StatisticsFields["nativePct"] = "nativePct";
1992
+ })(StatisticsFields || (exports.StatisticsFields = StatisticsFields = {}));
1993
+ ;
1994
+ // END
2479
1995
 
2480
1996
 
2481
1997
  /***/ }),
@@ -2524,7 +2040,6 @@ var __importStar = (this && this.__importStar) || (function () {
2524
2040
  };
2525
2041
  })();
2526
2042
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2527
- exports.getDistrict = getDistrict;
2528
2043
  exports.parseGeoID = parseGeoID;
2529
2044
  exports.isWaterOnly = isWaterOnly;
2530
2045
  exports.isUninhabited = isUninhabited;
@@ -2535,6 +2050,10 @@ exports.minArray = minArray;
2535
2050
  exports.maxArray = maxArray;
2536
2051
  exports.initArray = initArray;
2537
2052
  exports.andArray = andArray;
2053
+ exports.addArrays = addArrays;
2054
+ exports.subtractArrays = subtractArrays;
2055
+ exports.divideArrays = divideArrays;
2056
+ exports.falseIndexes = falseIndexes;
2538
2057
  exports.keyExists = keyExists;
2539
2058
  exports.isObjectEmpty = isObjectEmpty;
2540
2059
  exports.isSetEmpty = isSetEmpty;
@@ -2544,26 +2063,14 @@ exports.getNumericObjectKeys = getNumericObjectKeys;
2544
2063
  exports.getSelectObjectKeys = getSelectObjectKeys;
2545
2064
  exports.arrayContains = arrayContains;
2546
2065
  exports.objectContains = objectContains;
2547
- exports.countEnumValues = countEnumValues;
2548
2066
  exports.shallowCopy = shallowCopy;
2549
2067
  exports.deepCopy = deepCopy;
2550
2068
  exports.depthof = depthof;
2069
+ exports.deepEqual = deepEqual;
2551
2070
  const dra_types_1 = __webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types");
2552
- const _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
2071
+ const D = __importStar(__webpack_require__(/*! ./data */ "./src/data.ts"));
2072
+ const T = __importStar(__webpack_require__(/*! ./types */ "./src/types.ts"));
2553
2073
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2554
- // PLAN HELPERS
2555
- // Get the districtID to which a geoID is assigned
2556
- function getDistrict(plan, geoID) {
2557
- // All geoIDs in a state *should be* assigned to a district (including the
2558
- // dummy "unassigned" district), but "water-only" features are sometimes missing
2559
- // from a map. This is also a guard against a bug in which a geoID has no district.
2560
- if (keyExists(geoID, plan)) {
2561
- return plan[geoID];
2562
- }
2563
- else {
2564
- return undefined;
2565
- }
2566
- }
2567
2074
  // WORKING WITH GEOIDS
2568
2075
  function parseGeoID(geoID) {
2569
2076
  let bVfeature = false;
@@ -2581,15 +2088,14 @@ function parseGeoID(geoID) {
2581
2088
  };
2582
2089
  return parts;
2583
2090
  }
2584
- // 08-13-2020 - Enhanced completeness checking.
2585
- function isWaterOnly(geoID, s) {
2091
+ function isWaterOnly(geoID, container) {
2586
2092
  const waterOnlySignature = 'ZZZZZZ';
2587
2093
  if (geoID.indexOf(waterOnlySignature) >= 0)
2588
2094
  return true;
2589
- if (s) {
2095
+ if (container) {
2590
2096
  // If called with a session, get the feature ...
2591
- const featureID = s.features.featureID(geoID);
2592
- const f /* T.GeoFeature */ = s.features.featureByIndex(featureID);
2097
+ const featureID = container.features.featureID(geoID);
2098
+ const f /* T.GeoFeature */ = container.features.featureByIndex(featureID);
2593
2099
  // ... and also check the 'ALAND' property
2594
2100
  const bNoLand = ((f.properties['ALAND10'] !== undefined) && (f.properties['ALAND10'] == 0)) ? true : false;
2595
2101
  return bNoLand;
@@ -2597,12 +2103,12 @@ function isWaterOnly(geoID, s) {
2597
2103
  else
2598
2104
  return false;
2599
2105
  }
2600
- function isUninhabited(geoID, s) {
2601
- const featureID = s.features.featureID(geoID);
2602
- const f /* T.GeoFeature */ = s.features.featureByIndex(featureID);
2106
+ function isUninhabited(geoID, container) {
2107
+ const featureID = container.features.featureID(geoID);
2108
+ const f /* T.GeoFeature */ = container.features.featureByIndex(featureID);
2603
2109
  // 03-27-21
2604
- const dk = s.features._keys["CENSUS" /* T.Dataset.CENSUS */];
2605
- const totalPop = (0, _data_1.fieldForFeature)(f, dk, 0 /* T.FeatureField.TotalPop */);
2110
+ const dk = container.features._keys[T.Dataset.CENSUS];
2111
+ const totalPop = D.fieldForFeature(f, dk, 0 /* T.FeatureField.TotalPop */);
2606
2112
  // const totalPop = s.features.fieldForFeature(f, T.Dataset.CENSUS, T.FeatureField.TotalPop);
2607
2113
  let bUninhabited = (totalPop > 0) ? false : true;
2608
2114
  // HACK for Kentucky's atypical data, so the official map shows as complete
@@ -2643,20 +2149,44 @@ function initArray(n, value) {
2643
2149
  function andArray(arr) {
2644
2150
  return arr.reduce(function (a, b) { return a && b; }, true);
2645
2151
  }
2152
+ function addArrays(a, b) {
2153
+ if (a.length !== b.length) {
2154
+ throw new Error(`Array lengths must match: ${a.length} !== ${b.length}`);
2155
+ }
2156
+ return a.map((val, i) => val + b[i]);
2157
+ }
2158
+ function subtractArrays(a, b) {
2159
+ if (a.length !== b.length) {
2160
+ throw new Error(`Array lengths must match: ${a.length} !== ${b.length}`);
2161
+ }
2162
+ return a.map((val, i) => val - b[i]);
2163
+ }
2164
+ function divideArrays(numerator, denominator) {
2165
+ if (numerator.length !== denominator.length) {
2166
+ throw new Error(`Array lengths must match: ${numerator.length} !== ${denominator.length}`);
2167
+ }
2168
+ return numerator.map((val, i) => denominator[i] <= 0 ? 0.0 : val / denominator[i]);
2169
+ }
2170
+ // NOTE - This helper excludes the first and last items in the array,
2171
+ // which are dummy unassigned and summary values, respectively.
2172
+ function falseIndexes(booleans) {
2173
+ return booleans.reduce((indexes, value, index) => {
2174
+ if (index > 0 && index < booleans.length - 1 && value === false) {
2175
+ indexes.push(index);
2176
+ }
2177
+ return indexes;
2178
+ }, []);
2179
+ }
2646
2180
  // WORKING WITH OBJECT KEYS/PROPERTIES
2647
- // Does an object have a key/property?
2648
2181
  function keyExists(k, o) {
2649
2182
  return k in o;
2650
2183
  }
2651
- // Does an object (dict) have any keys/properties?
2652
2184
  function isObjectEmpty(o) {
2653
2185
  return Object.keys(o).length === 0;
2654
2186
  }
2655
- // Does a Set have any members?
2656
2187
  function isSetEmpty(s) {
2657
2188
  return s.size === 0;
2658
2189
  }
2659
- // Does an array hold any items?
2660
2190
  function isArrayEmpty(a) {
2661
2191
  if (a === undefined || a.length == 0) {
2662
2192
  // array empty or does not exist
@@ -2666,7 +2196,6 @@ function isArrayEmpty(a) {
2666
2196
  return false;
2667
2197
  }
2668
2198
  }
2669
- // Get the keys for an object
2670
2199
  function getObjectKeys(o) {
2671
2200
  return Object.keys(o);
2672
2201
  }
@@ -2688,16 +2217,6 @@ function arrayContains(a, item) {
2688
2217
  function objectContains(o, key) {
2689
2218
  return (key in o);
2690
2219
  }
2691
- // ENUM HELPERS
2692
- // Source: https://stackoverflow.com/questions/38034673/determine-the-number-of-enum-elements-typescript
2693
- function countEnumValues(enumName) {
2694
- let count = 0;
2695
- for (let item in enumName) {
2696
- if (isNaN(Number(item)))
2697
- count++;
2698
- }
2699
- return count;
2700
- }
2701
2220
  // COPYING - Copied from dra-client/util.ts
2702
2221
  function shallowCopy(src) {
2703
2222
  if (Array.isArray(src))
@@ -2755,233 +2274,104 @@ function depthof(a) {
2755
2274
  }
2756
2275
  }
2757
2276
  }
2758
-
2759
-
2760
- /***/ }),
2761
-
2762
- /***/ "./src/valid.ts":
2763
- /*!**********************!*\
2764
- !*** ./src/valid.ts ***!
2765
- \**********************/
2766
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
2767
-
2768
-
2769
- //
2770
- // MAP/PLAN VALIDATIONS
2771
- //
2772
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
2773
- if (k2 === undefined) k2 = k;
2774
- var desc = Object.getOwnPropertyDescriptor(m, k);
2775
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2776
- desc = { enumerable: true, get: function() { return m[k]; } };
2277
+ // EQUALITY HELPERS
2278
+ function approxEqual(x, y, places = 2) {
2279
+ const tolerance = Math.pow(10, -places) * 0.5;
2280
+ return Math.abs(x - y) <= tolerance;
2281
+ }
2282
+ function deepEqual(obj1, obj2, path = '') {
2283
+ // Same reference
2284
+ if (obj1 === obj2)
2285
+ return true;
2286
+ // Type checking
2287
+ if (typeof obj1 !== typeof obj2) {
2288
+ console.log(`${path || 'root'}: type mismatch - ${typeof obj1} vs ${typeof obj2}`);
2289
+ return false;
2777
2290
  }
2778
- Object.defineProperty(o, k2, desc);
2779
- }) : (function(o, m, k, k2) {
2780
- if (k2 === undefined) k2 = k;
2781
- o[k2] = m[k];
2782
- }));
2783
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
2784
- Object.defineProperty(o, "default", { enumerable: true, value: v });
2785
- }) : function(o, v) {
2786
- o["default"] = v;
2787
- });
2788
- var __importStar = (this && this.__importStar) || (function () {
2789
- var ownKeys = function(o) {
2790
- ownKeys = Object.getOwnPropertyNames || function (o) {
2791
- var ar = [];
2792
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
2793
- return ar;
2794
- };
2795
- return ownKeys(o);
2796
- };
2797
- return function (mod) {
2798
- if (mod && mod.__esModule) return mod;
2799
- var result = {};
2800
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2801
- __setModuleDefault(result, mod);
2802
- return result;
2803
- };
2804
- })();
2805
- Object.defineProperty(exports, "__esModule", ({ value: true }));
2806
- exports.doIsComplete = doIsComplete;
2807
- exports.doIsContiguous = doIsContiguous;
2808
- exports.doIsFreeOfHoles = doIsFreeOfHoles;
2809
- const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2810
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2811
- //
2812
- // COMPLETE - Are all geo's assigned to a district, and do all districts have
2813
- // at least one geo assigned to them?
2814
- //
2815
- function doIsComplete(s, bLog = false) {
2816
- let test = s.getTest(0 /* T.Test.Complete */);
2817
- // Get the by-district results, including the dummy unassigned district,
2818
- // but ignoring the N+1 summary district
2819
- let bNotEmptyByDistrict = s.districts.table.bNotEmpty;
2820
- bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
2821
- // Are all features assigned to districts?
2822
- // Check the dummy district that holds any unassigned features.
2823
- let unassignedFeatures = [];
2824
- let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
2825
- let bAllNonWaterOnlyAssigned = bAllAssigned ? true : false;
2826
- if (!bAllAssigned) {
2827
- let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
2828
- unassignedFeatures = Array.from(unassignedDistrict);
2829
- // unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
2830
- // 08-13-2020 - Enhanced completeness checking.
2831
- // Are any of the unassigned features not water-only -or- inhabited?
2832
- // Check the official congressional maps for CT, KY, IL, and MI.
2833
- bAllNonWaterOnlyAssigned = !unassignedFeatures.some(function (geoID) {
2834
- if (U.isWaterOnly(geoID, s)) {
2835
- if (bLog)
2836
- console.log("Unassigned water-only feature ignored in completeness check: ", geoID);
2837
- return false;
2838
- }
2839
- if (U.isUninhabited(geoID, s)) {
2840
- if (bLog)
2841
- console.log("Uninhabited feature ignored in completeness check: ", geoID);
2842
- return false;
2291
+ // Handle null
2292
+ if (obj1 === null || obj2 === null) {
2293
+ console.log(`${path || 'root'}: ${obj1} vs ${obj2}`);
2294
+ return false;
2295
+ }
2296
+ // Atomic type comparisons
2297
+ if (typeof obj1 === 'string') {
2298
+ // Strings must be exactly equal
2299
+ if (obj1 !== obj2) {
2300
+ console.log(`${path || 'root'}: "${obj1}" vs "${obj2}"`);
2301
+ return false;
2302
+ }
2303
+ return true;
2304
+ }
2305
+ if (typeof obj1 === 'number') {
2306
+ // Numbers use approximate equality (2 decimal places)
2307
+ if (!approxEqual(obj1, obj2, 2)) {
2308
+ console.log(`${path || 'root'}: ${obj1} vs ${obj2} (difference: ${Math.abs(obj1 - obj2)})`);
2309
+ return false;
2310
+ }
2311
+ return true;
2312
+ }
2313
+ if (typeof obj1 === 'boolean') {
2314
+ // Booleans must be exactly equal
2315
+ if (obj1 !== obj2) {
2316
+ console.log(`${path || 'root'}: ${obj1} vs ${obj2}`);
2317
+ return false;
2318
+ }
2319
+ return true;
2320
+ }
2321
+ // If not an object type, fall back to strict equality
2322
+ if (typeof obj1 !== 'object') {
2323
+ if (obj1 !== obj2) {
2324
+ console.log(`${path || 'root'}: ${obj1} vs ${obj2}`);
2325
+ return false;
2326
+ }
2327
+ return true;
2328
+ }
2329
+ // Array handling
2330
+ if (Array.isArray(obj1) !== Array.isArray(obj2)) {
2331
+ console.log(`${path || 'root'}: array vs non-array`);
2332
+ return false;
2333
+ }
2334
+ if (Array.isArray(obj1)) {
2335
+ if (obj1.length !== obj2.length) {
2336
+ console.log(`${path || 'root'}: array length ${obj1.length} vs ${obj2.length}`);
2337
+ return false;
2338
+ }
2339
+ let isEqual = true;
2340
+ obj1.forEach((val, idx) => {
2341
+ const itemPath = `${path}[${idx}]`;
2342
+ if (!deepEqual(val, obj2[idx], itemPath)) {
2343
+ isEqual = false;
2843
2344
  }
2844
- // Not water-only and inhabited
2845
- return true;
2846
2345
  });
2847
- }
2848
- // Do all real districts have at least one feature assigned to them?
2849
- let emptyDistricts = [];
2850
- let bNoneEmpty = true;
2851
- bNotEmptyByDistrict = bNotEmptyByDistrict.slice(1);
2852
- let districtID = 1;
2853
- bNotEmptyByDistrict.forEach(function (bNotEmpty) {
2854
- if (!bNotEmpty) {
2855
- bNoneEmpty = false;
2856
- emptyDistricts.push(districtID);
2346
+ return isEqual;
2347
+ }
2348
+ // Object handling
2349
+ const keys1 = Object.keys(obj1);
2350
+ const keys2 = Object.keys(obj2);
2351
+ // Check for missing keys
2352
+ const allKeys = new Set([...keys1, ...keys2]);
2353
+ let isEqual = true;
2354
+ for (const key of allKeys) {
2355
+ const keyPath = path ? `${path}.${key}` : key;
2356
+ if (!(key in obj1)) {
2357
+ console.log(`${keyPath}: missing in first object`);
2358
+ isEqual = false;
2857
2359
  }
2858
- districtID += 1;
2859
- });
2860
- // Case 1 - One or more districts are missing:
2861
- // The # of enumerated districts minus the dummy NOT_ASSIGNED one should
2862
- // equal the number apportioned districts. This guards against a district
2863
- // not being included in a map that is imported.
2864
- //
2865
- // NOTE - I'm no longer checking for this, but DRA should!
2866
- // Case 2 - Or a district is explicitly named but empty:
2867
- // Note, this can happen if a district is created, and then all features
2868
- // are removed from it (in DRA).
2869
- // Populate the test entry
2870
- // 08-13-2020 - Revised completeness check:
2871
- // A plan is complete if:
2872
- // * All inhabited, not water-only districts are assigned to districts.
2873
- // * No districts are empty, i.e., each has one or more geos assigned to them.
2874
- // Note: We're not checking (in _data.ts) whether those geos are water-only
2875
- // or have any population, but that's a real edge case.
2876
- test['score'] = bAllNonWaterOnlyAssigned && bNoneEmpty;
2877
- // test['score'] = bAllAssigned && bNoneEmpty;
2878
- if (!bAllAssigned) {
2879
- test['details']['unassignedFeatures'] = unassignedFeatures;
2880
- }
2881
- if (!bNoneEmpty) {
2882
- test['details']['emptyDistricts'] = emptyDistricts;
2883
- }
2884
- // Populate the N+1 summary "district" in district.statistics
2885
- let bNotEmpty = s.districts.table.bNotEmpty;
2886
- let summaryRow = s.districts.numberOfRows() - 1;
2887
- bNotEmpty[summaryRow] = test['score'];
2888
- return test;
2889
- }
2890
- //
2891
- // CONTIGUOUS - Is each district in a plan fully connected?
2892
- //
2893
- // NOTE - To check "operational contiguity," we need to use a graph, i.e.,
2894
- // we can't rely on just the geometric contiguity of shapes in a shapefile.
2895
- //
2896
- // To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
2897
- //
2898
- function doIsContiguous(s, bLog = false) {
2899
- let test = s.getTest(1 /* T.Test.Contiguous */);
2900
- // Get the contiguity of each district. Ignore dummy unassigned district
2901
- // and the N+1 summary district.
2902
- let bContiguousByDistrict = s.districts.table.bContiguous;
2903
- bContiguousByDistrict = bContiguousByDistrict.slice(1, -1);
2904
- // If any real districts aren't contiguous, mark the plan as not contiguous
2905
- let bMapContiguous = U.andArray(bContiguousByDistrict);
2906
- // If the map is not contiguous, log the offending districts.
2907
- let discontiguousDistricts = [];
2908
- let districtID = 1;
2909
- bContiguousByDistrict.forEach(function (bDistrictContiguous) {
2910
- if (!bDistrictContiguous)
2911
- discontiguousDistricts.push(districtID);
2912
- districtID += 1;
2913
- });
2914
- // Populate the test entry
2915
- test['score'] = bMapContiguous;
2916
- if (!bMapContiguous) {
2917
- test['details'] = { 'discontiguousDistricts': discontiguousDistricts };
2918
- }
2919
- // Populate the N+1 summary "district" in district.statistics
2920
- let bContiguous = s.districts.table.bContiguous;
2921
- let summaryRow = s.districts.numberOfRows() - 1;
2922
- bContiguous[summaryRow] = test['score'];
2923
- return test;
2924
- }
2925
- //
2926
- // FREE OF HOLES - Are any districts fully embedded w/in another district?
2927
- //
2928
- // A district is NOT a "donut hole" district:
2929
- // - If any neighbor is 'OUT_OF_STATE'; or
2930
- // - If there are 2 or more neighboring districts.
2931
- //
2932
- // To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
2933
- // Buncombe County (37021), is a donut hole w/in District 3.
2934
- //
2935
- function doIsFreeOfHoles(s, bLog = false) {
2936
- let test = s.getTest(2 /* T.Test.FreeOfHoles */);
2937
- // Initialize values
2938
- let bFreeOfHoles = true;
2939
- let embeddedDistricts = [];
2940
- // Get the embeddedness of each district. Ignore dummy unassigned district
2941
- // and the N+1 summary district.
2942
- let bNotEmbeddedByDistrict = s.districts.table.bNotEmbedded;
2943
- bNotEmbeddedByDistrict = bNotEmbeddedByDistrict.slice(1, -1);
2944
- let districtID = 1;
2945
- bNotEmbeddedByDistrict.forEach(function (bDistrictNotEmbedded) {
2946
- if (!bDistrictNotEmbedded) {
2947
- embeddedDistricts.push(districtID);
2948
- bFreeOfHoles = false;
2360
+ else if (!(key in obj2)) {
2361
+ console.log(`${keyPath}: missing in second object`);
2362
+ isEqual = false;
2949
2363
  }
2950
- districtID += 1;
2951
- });
2952
- // Populate the test entry
2953
- test['score'] = bFreeOfHoles;
2954
- if (!bFreeOfHoles) {
2955
- test['details'] = { 'embeddedDistricts': embeddedDistricts };
2956
- }
2957
- // Populate the N+1 summary "district" in district.statistics
2958
- let bNotEmbedded = s.districts.table.bNotEmbedded;
2959
- let summaryRow = s.districts.numberOfRows() - 1;
2960
- bNotEmbedded[summaryRow] = test['score'];
2961
- return test;
2364
+ else {
2365
+ if (!deepEqual(obj1[key], obj2[key], keyPath)) {
2366
+ isEqual = false;
2367
+ }
2368
+ }
2369
+ }
2370
+ return isEqual;
2962
2371
  }
2372
+ // END
2963
2373
 
2964
2374
 
2965
- /***/ }),
2966
-
2967
- /***/ "./static/majority-minority.json":
2968
- /*!***************************************!*\
2969
- !*** ./static/majority-minority.json ***!
2970
- \***************************************/
2971
- /***/ ((module) => {
2972
-
2973
- module.exports = /*#__PURE__*/JSON.parse('{"AL":{"Black":[7],"Hispanic":[],"Pacific":[]},"AK":{"Black":[],"Hispanic":[],"Pacific":[]},"AZ":{"Black":[],"Hispanic":[2,4,7],"Pacific":[]},"AR":{"Black":[],"Hispanic":[],"Pacific":[]},"CA":{"Black":[13,37,43],"Hispanic":[17,18,20,21,28,31,32,34,35,38,39,43,45,47,51],"Pacific":[12,13,14,15,17,18,19,27,34,39,45,47,52]},"CO":{"Black":[],"Hispanic":[],"Pacific":[]},"CT":{"Black":[],"Hispanic":[],"Pacific":[]},"DC":{"Black":[],"Hispanic":[],"Pacific":[]},"DE":{"Black":[],"Hispanic":[],"Pacific":[]},"FL":{"Black":[5,20,24],"Hispanic":[18,21,25],"Pacific":[]},"GA":{"Black":[2,4,5,13],"Hispanic":[],"Pacific":[]},"HI":{"Black":[],"Hispanic":[],"Pacific":[2]},"ID":{"Black":[],"Hispanic":[1],"Pacific":[]},"IL":{"Black":[1,2,7],"Hispanic":[4],"Pacific":[]},"IN":{"Black":[7],"Hispanic":[],"Pacific":[]},"IA":{"Black":[],"Hispanic":[],"Pacific":[]},"KS":{"Black":[],"Hispanic":[],"Pacific":[]},"KY":{"Black":[],"Hispanic":[],"Pacific":[]},"LA":{"Black":[2],"Hispanic":[],"Pacific":[]},"ME":{"Black":[],"Hispanic":[],"Pacific":[]},"MD":{"Black":[4,7],"Hispanic":[],"Pacific":[]},"MA":{"Black":[],"Hispanic":[],"Pacific":[]},"MI":{"Black":[13,14],"Hispanic":[],"Pacific":[]},"MN":{"Black":[],"Hispanic":[],"Pacific":[]},"MS":{"Black":[2],"Hispanic":[],"Pacific":[]},"MO":{"Black":[1,5],"Hispanic":[],"Pacific":[]},"MT":{"Black":[],"Hispanic":[],"Pacific":[]},"NE":{"Black":[],"Hispanic":[],"Pacific":[]},"NV":{"Black":[],"Hispanic":[],"Pacific":[]},"NH":{"Black":[],"Hispanic":[],"Pacific":[]},"NJ":{"Black":[10,12],"Hispanic":[13],"Pacific":[6]},"NM":{"Black":[],"Hispanic":[2,13],"Pacific":[]},"NY":{"Black":[5,8,9,13],"Hispanic":[12,16],"Pacific":[6,7,10]},"NC":{"Black":[1,12],"Hispanic":[],"Pacific":[]},"ND":{"Black":[],"Hispanic":[],"Pacific":[]},"OH":{"Black":[3,11],"Hispanic":[],"Pacific":[]},"OK":{"Black":[],"Hispanic":[],"Pacific":[]},"OR":{"Black":[],"Hispanic":[],"Pacific":[]},"PA":{"Black":[2],"Hispanic":[],"Pacific":[]},"PR":{"Black":[],"Hispanic":[],"Pacific":[]},"RI":{"Black":[],"Hispanic":[],"Pacific":[]},"SC":{"Black":[6],"Hispanic":[],"Pacific":[]},"SD":{"Black":[],"Hispanic":[],"Pacific":[]},"TN":{"Black":[9],"Hispanic":[],"Pacific":[]},"TX":{"Black":[9,18,33],"Hispanic":[15,16,19,20,23,27,28,29,32],"Pacific":[]},"UT":{"Black":[],"Hispanic":[],"Pacific":[]},"VT":{"Black":[],"Hispanic":[],"Pacific":[]},"VA":{"Black":[3],"Hispanic":[],"Pacific":[]},"WA":{"Black":[],"Hispanic":[3],"Pacific":[9]},"WV":{"Black":[],"Hispanic":[],"Pacific":[]},"WI":{"Black":[4],"Hispanic":[],"Pacific":[]},"WY":{"Black":[],"Hispanic":[],"Pacific":[]}}');
2974
-
2975
- /***/ }),
2976
-
2977
- /***/ "./static/vra5-preclearance.json":
2978
- /*!***************************************!*\
2979
- !*** ./static/vra5-preclearance.json ***!
2980
- \***************************************/
2981
- /***/ ((module) => {
2982
-
2983
- module.exports = /*#__PURE__*/JSON.parse('{"AL":"all","AK":"all","AZ":"all","CA":"parts","FL":"parts","GA":"all","LA":"all","MI":"parts","MS":"all","NY":"parts","NC":"parts","SC":"all","SD":"parts","TX":"all","VA":"all"}');
2984
-
2985
2375
  /***/ }),
2986
2376
 
2987
2377
  /***/ "@dra2020/baseclient":