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