@dra2020/district-analytics 3.0.0 → 3.2.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.
@@ -121,6 +121,7 @@ const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
121
121
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
122
122
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
123
123
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
124
+ const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
124
125
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
125
126
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
126
127
  class AnalyticsSession {
@@ -140,45 +141,48 @@ class AnalyticsSession {
140
141
  this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
141
142
  this.plan = new D.Plan(this, SessionRequest['plan']);
142
143
  this.districts = new D.Districts(this, SessionRequest['districtShapes']);
143
- // TODO - Confirming that the graph includes OUT_OF_STATE neighbors
144
- // if (U.keyExists(S.OUT_OF_STATE, this.graph._graph)) {
145
- // console.log("Contiguity graph includes out-of-state neighbors.");
146
- // }
147
- // else {
148
- // console.log("Contiguity graph does NOT include out-of-state neighbors.");
149
- // }
150
- // NOTE: I've pulled these out of the individual analytics to here. Eventually,
151
- // we could want them to passed into an analytics session as data, along with
152
- // everything else. For now, this keeps branching out of the main code.
153
- results_1.doConfigureScales(this);
144
+ // TODO - SCORE: Toggle
145
+ if (this.useLegacy()) {
146
+ console.log("Using legacy district-analytics.");
147
+ // NOTE: I've pulled these out of the individual analytics to here. Eventually,
148
+ // we could want them to passed into an analytics session as data, along with
149
+ // everything else. For now, this keeps branching out of the main code.
150
+ results_1.doConfigureScales(this);
151
+ }
152
+ else {
153
+ console.log("Using dra-score analytics.");
154
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
155
+ results_1.doConfigureScales(this);
156
+ }
154
157
  }
155
158
  processConfig(config) {
156
159
  // NOTE - Session settings are required:
157
160
  // - Analytics suites can be defaulted to all with [], but
158
161
  // - Dataset keys must be explicitly specified with 'dataset'
159
- // TODO - DASHBOARD: Remove this mechanism. Always run everything.
160
- let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
161
- // If the config passed in has no suites = [], use the default suites
162
- if (U.isArrayEmpty(config['suites'])) {
163
- config['suites'] = defaultSuites;
164
- }
162
+ // TODO - SCORE: Delete
163
+ config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
165
164
  // Default the Census & redistricting cycle to 2010
166
165
  if (!(U.keyExists('cycle', config)))
167
166
  config['cycle'] = 2010;
168
167
  return config;
169
168
  }
169
+ // TODO - SCORE: Toggle = Invert this logic
170
+ useLegacy() {
171
+ // TODO - SCORE: Opt-out
172
+ return (U.keyExists('useScore', this.config) && (this.config['useScore'] != null) && (!(this.config['useScore']))) ? true : false;
173
+ // TODO - SCORE: Opt-in
174
+ // return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
175
+ }
170
176
  // Using the the data in the analytics session, calculate all the
171
177
  // analytics & validations, saving/updating the individual test results.
172
- analyzePlan(bLog = false) {
178
+ analyzePlan(bLog = false, overridesJSON) {
173
179
  try {
174
180
  preprocess_1.doPreprocessData(this, bLog);
175
181
  analyze_1.doAnalyzeDistricts(this, bLog);
176
182
  analyze_1.doAnalyzePlan(this, bLog);
177
183
  // TODO - SCORE
178
184
  this._profile = score_1.profilePlan(this, bLog);
179
- // this._profile = Score.sampleProfile;
180
- this._scorecard = score_1.scorePlan(this._profile, bLog);
181
- // TODO - SCORE
185
+ this._scorecard = score_1.scorePlan(this, this._profile, bLog, overridesJSON);
182
186
  results_1.doAnalyzePostProcessing(this, bLog);
183
187
  }
184
188
  catch (_a) {
@@ -199,13 +203,6 @@ class AnalyticsSession {
199
203
  getPlanScorecard(bLog = false) {
200
204
  return this._scorecard;
201
205
  }
202
- /* TODO - SCORE
203
- // NOTE - This assumes that analyzePlan() has been run!
204
- getPlanAnalytics(bLog: boolean = false): PlanAnalytics
205
- {
206
- return preparePlanAnalytics(this, bLog);
207
- }
208
- */
209
206
  // TODO - SCORE
210
207
  // NOTE - This assumes that analyzePlan() has been run!
211
208
  getRequirementsChecklist(bLog = false) {
@@ -214,19 +211,82 @@ class AnalyticsSession {
214
211
  // NOTE - This assumes that analyzePlan() has been run!
215
212
  getDiscontiguousDistrictFeatures(bLog = false) {
216
213
  // Get the (possibly empty) list of discontiguous district IDs
217
- let contiguousTest = this.getTest(1 /* Contiguous */);
218
- let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
214
+ const contiguousTest = this.getTest(1 /* Contiguous */);
215
+ const discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
219
216
  // Convert them into a (possibly empty) list of features
220
217
  let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
221
218
  if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
222
219
  for (let id of discontiguousDistrictIDs) {
223
220
  let poly = this.districts.getDistrictShapeByID(id);
224
- if (poly)
225
- discontiguousDistrictFeatures.features.push(poly);
221
+ if (poly) {
222
+ // If a district has a shape & it is not contiguous, by definition,
223
+ // it will be a Multipolygon, i.e., it will have multiple pieces, some
224
+ // possibly embedded w/in other districts. Get & add all the pieces.
225
+ const districtParts = geofeature_1.polyParts(poly);
226
+ discontiguousDistrictFeatures.features.push(...districtParts.features);
227
+ // discontiguousDistrictFeatures.features.push(poly);
228
+ }
226
229
  }
227
230
  }
228
231
  return discontiguousDistrictFeatures;
229
232
  }
233
+ // Comments clipped from dra-client geodistrict.ts.
234
+ // Discontiguous polygons are:
235
+ // 1. All polygons in a multi-polygon; and
236
+ // 2. All holes in a otherwise cohesive polygon.
237
+ // Note that all non-cohesive features are always simple polygons.
238
+ /*
239
+ let i: number, j: number;
240
+ let nPoly: number = 0;
241
+ for (i = 0;nPoly == 0 && i < this.cacheDistricts.features.length;i++)
242
+ {
243
+ let f = this.cacheDistricts.features[i];
244
+
245
+ if (f.geometry.type === 'MultiPolygon')
246
+ nPoly += f.geometry.coordinates.length;
247
+ else if (f.geometry.type === 'Polygon' && f.geometry.coordinates.length)
248
+ nPoly += (f.geometry.coordinates.length - 1);
249
+ }
250
+ if (nPoly)
251
+ {
252
+ this.cacheNoncohesive = {type: 'FeatureCollection', features: []};
253
+ let af: any = this.cacheNoncohesive.features;
254
+ let oUnique: any = {};
255
+
256
+ // First add discontiguous polygons
257
+ for (i = 0;i < this.cacheDistricts.features.length;i++)
258
+ {
259
+ let f = this.cacheDistricts.features[i];
260
+
261
+ if (f.geometry.type === 'MultiPolygon')
262
+ {
263
+ // Push all non-contiguous polygons
264
+ for (j = 0;j < f.geometry.coordinates.length;j++)
265
+ {
266
+ let p: any = f.geometry.coordinates[j];
267
+ oUnique[Hash.qhash(p[0])] = true;
268
+ af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: p}});
269
+ }
270
+ }
271
+ }
272
+
273
+ // Now add unique holes
274
+ for (i = 0;i < this.cacheDistricts.features.length;i++)
275
+ {
276
+ let f = this.cacheDistricts.features[i];
277
+
278
+ if (f.geometry.type === 'Polygon')
279
+ {
280
+ // Push all holes from this polygon
281
+ for (j = 1;j < f.geometry.coordinates.length;j++)
282
+ {
283
+ let p: any = f.geometry.coordinates[j];
284
+ if (oUnique[Hash.qhash(p)] === undefined)
285
+ af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: [p]}});
286
+ }
287
+ }
288
+ }
289
+ } */
230
290
  // HELPERS USED INTERNALLY
231
291
  // Get an individual test, so you can drive UI with the results.
232
292
  getTest(testID) {
@@ -242,9 +302,17 @@ class AnalyticsSession {
242
302
  // Return a pointer to the the test entry for this test
243
303
  return this.tests[testID];
244
304
  }
245
- // NOTE - Not sure why this has to be up here.
305
+ // NOTE - Not sure why this has to be up here ...
246
306
  populationDeviationThreshold() {
247
- return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
307
+ // TODO - SCORE: Toggle
308
+ if (this.useLegacy()) {
309
+ return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
310
+ }
311
+ else {
312
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
313
+ // NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
314
+ return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
315
+ }
248
316
  }
249
317
  }
250
318
  exports.AnalyticsSession = AnalyticsSession;
@@ -420,7 +488,6 @@ class Districts {
420
488
  let outerThis = this;
421
489
  // Default the pop dev % for the dummy Unassigned district to 0%.
422
490
  // Default the pop dev % for real (1–N) but empty districts to 100%.
423
- // TODO - SCORE
424
491
  let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
425
492
  // Get the geoIDs assigned to the district
426
493
  // Guard against empty districts
@@ -447,7 +514,6 @@ class Districts {
447
514
  // NOTE - SPLITTING
448
515
  // Total population by counties w/in a district,
449
516
  // except the dummy unassigned district 0
450
- // TODO - VFEATURE
451
517
  if (i > 0)
452
518
  countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
453
519
  // Democratic and Republican vote totals
@@ -621,10 +687,7 @@ class Districts {
621
687
  compact_1.extractDistrictProperties(this._session, bLog);
622
688
  }
623
689
  getCountyIndex(geoID) {
624
- // TODO - VFEATURE
625
690
  let countyFIPS = U.parseGeoID(geoID)['county'];
626
- // let countyGeoID = U.parseGeoID(geoID)['county'] as string;
627
- // let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
628
691
  let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
629
692
  return countyIndex;
630
693
  }
@@ -873,11 +936,45 @@ exports.doAnalyzeDistricts = doAnalyzeDistricts;
873
936
  // NOTE - I could make this table-driven, but I'm thinking that the explicit
874
937
  // calls might make chunking for aync easier.
875
938
  function doAnalyzePlan(s, bLog = false) {
876
- // TODO - Remove this mechanism. Always run all tests
877
- // Get the requested suites, and only execute those tests
878
- let requestedSuites = s.config['suites'];
879
- // Tests in the "Legal" suite, i.e., pass/ fail constraints
880
- if (requestedSuites.includes(0 /* Legal */)) {
939
+ // TODO - SCORE: Toggle
940
+ if (s.useLegacy()) {
941
+ // Disable most legacy analytics
942
+ // Get the requested suites, and only execute those tests
943
+ let requestedSuites = s.config['suites'];
944
+ // Tests in the "Legal" suite, i.e., pass/ fail constraints
945
+ if (requestedSuites.includes(0 /* Legal */)) {
946
+ s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
947
+ s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
948
+ s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
949
+ s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
950
+ // NOTE - I can't check whether a population deviation is legal or not, until
951
+ // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
952
+ // the given type of district (CD vs. LD). The EqualPopulation test is derived
953
+ // from PopulationDeviation, as part of scorecard or test log preparation.
954
+ // Create an empty test entry here though ...
955
+ s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
956
+ }
957
+ // Tests in the "Fair" suite
958
+ if (requestedSuites.includes(1 /* Fair */)) {
959
+ s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
960
+ s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
961
+ s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
962
+ s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
963
+ s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
964
+ s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
965
+ }
966
+ // Tests in the "Best" suite, i.e., criteria for better/worse
967
+ if (requestedSuites.includes(2 /* Best */)) {
968
+ s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
969
+ s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
970
+ s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
971
+ s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
972
+ s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
973
+ s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
974
+ }
975
+ }
976
+ else {
977
+ // TODO - SCORE: Except these. Continue to do these here vs. dra-score.
881
978
  s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
882
979
  s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
883
980
  s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
@@ -888,24 +985,8 @@ function doAnalyzePlan(s, bLog = false) {
888
985
  // from PopulationDeviation, as part of scorecard or test log preparation.
889
986
  // Create an empty test entry here though ...
890
987
  s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
891
- }
892
- // Tests in the "Fair" suite
893
- if (requestedSuites.includes(1 /* Fair */)) {
894
- s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
895
- s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
896
- s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
897
- s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
898
- s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
899
- s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
900
- }
901
- // Tests in the "Best" suite, i.e., criteria for better/worse
902
- if (requestedSuites.includes(2 /* Best */)) {
903
- s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
904
- s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
905
988
  s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
906
989
  s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
907
- s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
908
- s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
909
990
  }
910
991
  // Enable a Test Log and Scorecard to be generated
911
992
  s.bPlanAnalyzed = true;
@@ -1502,6 +1583,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
1502
1583
  Object.defineProperty(exports, "__esModule", { value: true });
1503
1584
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1504
1585
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1586
+ // TODO - SCORE: Delete
1505
1587
  function doPopulationDeviation(s, bLog = false) {
1506
1588
  let test = s.getTest(4 /* PopulationDeviation */);
1507
1589
  let targetSize = s.state.totalPop / s.state.nDistricts;
@@ -1540,8 +1622,8 @@ function doHasEqualPopulations(s, bLog = false) {
1540
1622
  let test = s.getTest(3 /* EqualPopulation */);
1541
1623
  // Get the normalized population deviation %
1542
1624
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
1543
- let popDevPct = popDevTest['score'];
1544
- let popDevNormalized = popDevTest['normalizedScore'];
1625
+ const popDevPct = popDevTest['score'];
1626
+ const popDevNormalized = popDevTest['normalizedScore'];
1545
1627
  // Populate the test entry
1546
1628
  if (popDevNormalized > 0) {
1547
1629
  test['score'] = true;
@@ -1583,6 +1665,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
1583
1665
  };
1584
1666
  Object.defineProperty(exports, "__esModule", { value: true });
1585
1667
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1668
+ // HELPER
1669
+ function polyParts(poly) {
1670
+ let parts = { type: 'FeatureCollection', features: [] };
1671
+ let af = parts.features;
1672
+ if (poly.geometry.type === 'MultiPolygon') {
1673
+ // Push all non-contiguous polygons
1674
+ for (let j = 0; j < poly.geometry.coordinates.length; j++) {
1675
+ let onePoly = poly.geometry.coordinates[j];
1676
+ af.push({ type: 'Feature', properties: { id: `${af.length + 1}` }, geometry: { type: 'Polygon', coordinates: onePoly } });
1677
+ }
1678
+ }
1679
+ else {
1680
+ parts.features.push(poly);
1681
+ }
1682
+ return parts;
1683
+ }
1684
+ exports.polyParts = polyParts;
1586
1685
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1587
1686
  // TODO - POLY: Confirm Cartesian calculations
1588
1687
  function gfArea(poly) {
@@ -1908,10 +2007,7 @@ function doPreprocessCensus(s, bLog = false) {
1908
2007
  // Sum total population across the state
1909
2008
  s.state.totalPop += value;
1910
2009
  // Get the county FIPS code for the feature
1911
- // TODO - VFEATURE
1912
2010
  let countyFIPS = U.parseGeoID(geoID)['county'];
1913
- // let county = U.parseGeoID(geoID)['county'] as string;
1914
- // let countyFIPS = U.getFIPSFromCountyGeoID(county);
1915
2011
  // If a subtotal for the county doesn't exist, initialize one
1916
2012
  if (!(U.keyExists(countyFIPS, totalByCounty))) {
1917
2013
  totalByCounty[countyFIPS] = 0;
@@ -1929,8 +2025,8 @@ function doPreprocessCensus(s, bLog = false) {
1929
2025
  let fipsCodes = U.getObjectKeys(totalByCounty);
1930
2026
  // Sort the results
1931
2027
  fipsCodes = fipsCodes.sort();
1932
- // TODO - SCORE: This was added for SPLITTING
1933
- // Add a dummy county, for county-district splitting analysis
2028
+ // NOTE - This was added for the legacy SPLITTING implementation.
2029
+ // Add a dummy county, for county-district splitting analysis.
1934
2030
  fipsCodes.unshift('000');
1935
2031
  // Create the ID-ordinal map
1936
2032
  for (let i in fipsCodes) {
@@ -2020,6 +2116,7 @@ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
2020
2116
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
2021
2117
  const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
2022
2118
  // Example
2119
+ // TODO - DELETE?
2023
2120
  let sampleRequirements = {
2024
2121
  score: 2 /* Red */,
2025
2122
  metrics: {
@@ -2043,6 +2140,7 @@ let sampleRequirements = {
2043
2140
  stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
2044
2141
  }
2045
2142
  };
2143
+ // TODO - DELETE
2046
2144
  let sampleCompactness = {
2047
2145
  score: 60,
2048
2146
  metrics: {
@@ -2055,6 +2153,7 @@ let sampleCompactness = {
2055
2153
  },
2056
2154
  resources: {}
2057
2155
  };
2156
+ // TODO - DELETE
2058
2157
  let sampleSplitting = {
2059
2158
  score: 73,
2060
2159
  metrics: {
@@ -2072,7 +2171,7 @@ let sampleSplitting = {
2072
2171
  datasets: {},
2073
2172
  resources: {}
2074
2173
  };
2075
- // TODO - PARTISAN: This category is still being fleshed out.
2174
+ // TODO - DELETE
2076
2175
  let samplePartisan = {
2077
2176
  score: 100,
2078
2177
  metrics: {
@@ -2087,7 +2186,7 @@ let samplePartisan = {
2087
2186
  planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2088
2187
  }
2089
2188
  };
2090
- // TODO - SCORE: Add a minority report
2189
+ // TODO - DELETE
2091
2190
  let sampleMinority = {
2092
2191
  score: null,
2093
2192
  metrics: {
@@ -2114,6 +2213,7 @@ let sampleMinority = {
2114
2213
  },
2115
2214
  resources: {}
2116
2215
  };
2216
+ // TODO - DELETE
2117
2217
  exports.samplePlanAnalytics = {
2118
2218
  requirements: sampleRequirements,
2119
2219
  compactness: sampleCompactness,
@@ -2122,232 +2222,6 @@ exports.samplePlanAnalytics = {
2122
2222
  partisan: samplePartisan,
2123
2223
  minority: sampleMinority
2124
2224
  };
2125
- /* TODO - SCORE
2126
- export function preparePlanAnalytics(s: AnalyticsSession, bLog: boolean = false): PlanAnalytics
2127
- {
2128
- if (!(s.bPostProcessingDone))
2129
- {
2130
- doAnalyzePostProcessing(s);
2131
- }
2132
-
2133
- // REQUIREMENTS CATEGORY
2134
- let paRequirements: RequirementsCategory;
2135
-
2136
- {
2137
- let completeTest = s.getTest(T.Test.Complete) as T.TestEntry;
2138
- let contiguousTest = s.getTest(T.Test.Contiguous) as T.TestEntry;
2139
- let freeOfHolesTest = s.getTest(T.Test.FreeOfHoles) as T.TestEntry;
2140
- let equalPopulationTest = s.getTest(T.Test.EqualPopulation) as T.TestEntry;
2141
-
2142
- // Combine individual checks into an overall score
2143
-
2144
- // TODO - DASHBOARD: Until we add three-state support top to bottom in
2145
- // requirements/validations, map booleans to tri-states here.
2146
- let completeMetric = U.mapBooleanToTriState(completeTest['score'] as boolean);
2147
- let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score'] as boolean);
2148
- let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score'] as boolean);
2149
- let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score'] as boolean);
2150
-
2151
- let reqScore: T.TriState = T.TriState.Green;
2152
- let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
2153
- if (checks.includes(T.TriState.Yellow)) reqScore = T.TriState.Yellow;
2154
- if (checks.includes(T.TriState.Red)) reqScore = T.TriState.Red;
2155
-
2156
- // Get values to support details entries
2157
- let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
2158
- let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
2159
- let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
2160
- let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
2161
-
2162
- let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
2163
- let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
2164
-
2165
- let xx: string = s.state.xx;
2166
- // TODO - JSON: Is there a better / easier way to work with the variable?
2167
- let stateReqsDict: T.Dict = allStateReqs;
2168
- let reqLinkToStateReqs: string = stateReqsDict[xx];
2169
-
2170
- // Populate the category
2171
- paRequirements = {
2172
- score: reqScore,
2173
- metrics: {
2174
- complete: completeMetric,
2175
- contiguous: contiguousMetric,
2176
- freeOfHoles: freeOfHolesMetric,
2177
- equalPopulation: equalPopulationMetric
2178
- },
2179
- details: {
2180
- unassignedFeatures: unassignedFeaturesDetail,
2181
- emptyDistricts: emptyDistrictsDetail,
2182
- discontiguousDistricts: discontiguousDistrictsDetail,
2183
- embeddedDistricts: embeddedDistrictsDetail,
2184
- populationDeviation: populationDeviationDetail,
2185
- deviationThreshold: deviationThresholdDetail
2186
- },
2187
- datasets: {
2188
- census: U.deepCopy(s.config['descriptions']['CENSUS']),
2189
- },
2190
- resources: {
2191
- stateReqs: reqLinkToStateReqs
2192
- }
2193
- }
2194
- }
2195
-
2196
- // COMPACTNESS CATEGORY
2197
- let paCompactness: CompactnessCategory;
2198
- {
2199
- let reockWeight = 0.5;
2200
- let polsbyWeight = 1.0 - reockWeight;
2201
-
2202
- let reockTest = s.getTest(T.Test.Reock) as T.TestEntry;
2203
- let polsbyTest = s.getTest(T.Test.PolsbyPopper) as T.TestEntry;
2204
-
2205
- let normalizedReock = reockTest['normalizedScore'] as number;
2206
- let normalizedPolsby = reockTest['normalizedScore'] as number;
2207
- let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
2208
-
2209
- let reockMetric = U.deepCopy(reockTest['score'] as number);
2210
- let polsbyMetric = U.deepCopy(polsbyTest['score'] as number);
2211
-
2212
- // Populate the category
2213
- paCompactness = {
2214
- score: compactnessScore,
2215
- metrics: {
2216
- reock: reockMetric,
2217
- polsby: polsbyMetric
2218
- },
2219
- details: {
2220
- // None at this time
2221
- },
2222
- datasets: {
2223
- // NOTE - DATASETS
2224
- shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2225
- // shapes: "2010 VTD shapes"
2226
- },
2227
- resources: {
2228
- // None at this time
2229
- }
2230
- }
2231
- }
2232
-
2233
- // SPLITTING CATEGORY
2234
-
2235
- let paSplitting: SplittingCategory
2236
-
2237
- {
2238
- let unexpectedCountySplittingTest = s.getTest(T.Test.UnexpectedCountySplits) as T.TestEntry;
2239
- let VTDSplitsTest = s.getTest(T.Test.VTDSplits) as T.TestEntry;
2240
- let countySplittingTest = s.getTest(T.Test.CountySplitting) as T.TestEntry;
2241
- let districtSplittingTest = s.getTest(T.Test.DistrictSplitting) as T.TestEntry;
2242
-
2243
- let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
2244
- let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
2245
-
2246
- let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
2247
- let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
2248
-
2249
- let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
2250
- let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
2251
-
2252
- let countySplittingNormalized = countySplittingTest['normalizedScore'] as number;
2253
- let districtSplittingNormalized = districtSplittingTest['normalizedScore'] as number;
2254
- let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
2255
- + (S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
2256
-
2257
- paSplitting = {
2258
- score: splittingScore,
2259
- metrics: {
2260
- sqEnt_DCreduced: SqEnt_DCreducedMetric,
2261
- sqEnt_CDreduced: SqEnt_CDreducedMetric
2262
- // NOTE - The un-reduced raw values
2263
- // sqEnt_DC : SqEnt_DCMetric,
2264
- // sqEnt_CD : SqEnt_CDMetric
2265
- },
2266
- details: {
2267
- countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
2268
- unexpectedAffected: unexpectedAffectedMetric,
2269
- nSplitVTDs: nVTDSplitsMetric,
2270
- splitVTDs: splitVTDsDetail
2271
- },
2272
- datasets: {
2273
- // None at this time
2274
- },
2275
- resources: {
2276
- // None at this time
2277
- }
2278
- }
2279
- }
2280
-
2281
- // PARTISAN CATEGORY
2282
- //
2283
- // TODO - PARTISAN: This category is still being fleshed out. Just an example below.
2284
- let paPartisan: PartisanCategory;
2285
-
2286
- {
2287
- paPartisan = {
2288
- score: 100,
2289
- metrics: {
2290
- partisanBias: 0.15,
2291
- responsiveness: 2.0
2292
- },
2293
- details: {},
2294
- datasets: {
2295
- election: "2016 Presidential, US Senate, Governor, and AG election results"
2296
- },
2297
- resources: {
2298
- planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2299
- }
2300
- }
2301
- }
2302
-
2303
- // MINORITY CATEGORY
2304
- //
2305
- // TODO - MINORITY: This category is still being fleshed out. Just an example below.
2306
- let paMinority: MinorityCategory;
2307
-
2308
- {
2309
- paMinority = {
2310
- score: null,
2311
- metrics: {
2312
- nBlack37to50: 1,
2313
- nBlackMajority: 12,
2314
- nHispanic37to50: 0,
2315
- nHispanicMajority: 0,
2316
- nPacific37to50: 0,
2317
- nPacificMajority: 0,
2318
- nAsian37to50: 0,
2319
- nAsianMajority: 0,
2320
- nNative37to50: 0,
2321
- nNativeMajority: 0,
2322
- nMinority37to50: 0,
2323
- nMinorityMajority: 0,
2324
-
2325
- averageDVoteShare: 0.90
2326
- },
2327
- details: {
2328
- vap: true,
2329
- comboCategories: true
2330
- },
2331
- datasets: {
2332
- vap: "2010 Voting Age Population"
2333
- },
2334
- resources: {}
2335
- }
2336
- }
2337
-
2338
- // PLAN ANALYTICS
2339
- let pa: PlanAnalytics = {
2340
- requirements: paRequirements,
2341
- compactness: paCompactness,
2342
- // TODO - Not implemented yet
2343
- splitting: paSplitting,
2344
- partisan: paPartisan,
2345
- minority: paMinority
2346
- }
2347
-
2348
- return pa;
2349
- }
2350
- */
2351
2225
  function prepareRequirementsChecklist(s, bLog = false) {
2352
2226
  if (!(s.bPostProcessingDone)) {
2353
2227
  doAnalyzePostProcessing(s);
@@ -2355,34 +2229,40 @@ function prepareRequirementsChecklist(s, bLog = false) {
2355
2229
  // REQUIREMENTS CATEGORY
2356
2230
  let paRequirements;
2357
2231
  {
2358
- let completeTest = s.getTest(0 /* Complete */);
2359
- let contiguousTest = s.getTest(1 /* Contiguous */);
2360
- let freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
2361
- let equalPopulationTest = s.getTest(3 /* EqualPopulation */);
2232
+ const completeTest = s.getTest(0 /* Complete */);
2233
+ const contiguousTest = s.getTest(1 /* Contiguous */);
2234
+ const freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
2235
+ const equalPopulationTest = s.getTest(3 /* EqualPopulation */);
2362
2236
  // Combine individual checks into an overall score
2363
- // TODO - DASHBOARD: Until we add three-state support top to bottom in
2237
+ // NOTE - Until we add three-state support top to bottom in
2364
2238
  // requirements/validations, map booleans to tri-states here.
2365
- let completeMetric = U.mapBooleanToTriState(completeTest['score']);
2366
- let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
2367
- let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
2368
- let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
2239
+ const completeMetric = U.mapBooleanToTriState(completeTest['score']);
2240
+ const contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
2241
+ const freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
2242
+ const equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
2369
2243
  let reqScore = 0 /* Green */;
2370
- let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
2244
+ const checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
2371
2245
  if (checks.includes(1 /* Yellow */))
2372
2246
  reqScore = 1 /* Yellow */;
2373
2247
  if (checks.includes(2 /* Red */))
2374
2248
  reqScore = 2 /* Red */;
2375
2249
  // Get values to support details entries
2376
- let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
2377
- let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
2378
- let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
2379
- let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
2380
- let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
2381
- let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
2382
- let xx = s.state.xx;
2250
+ const unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
2251
+ const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
2252
+ const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
2253
+ const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
2254
+ // TODO - SCORE: DELETE - This code is hooked correctly to use dra-score
2255
+ // const scorecard = s._scorecard as Score.Scorecard;
2256
+ // const populationDeviation = scorecard.best.populationDeviation.raw;
2257
+ const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
2258
+ // console.log("Population deviations =", populationDeviationDetail, populationDeviation);
2259
+ // const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
2260
+ const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
2261
+ // console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
2262
+ const xx = s.state.xx;
2383
2263
  // TODO - JSON: Is there a better / easier way to work with the variable?
2384
- let stateReqsDict = state_reqs_json_1.default;
2385
- let reqLinkToStateReqs = stateReqsDict[xx];
2264
+ const stateReqsDict = state_reqs_json_1.default;
2265
+ const reqLinkToStateReqs = stateReqsDict[xx];
2386
2266
  // Populate the category
2387
2267
  paRequirements = {
2388
2268
  score: reqScore,
@@ -2645,19 +2525,28 @@ exports.doConfigureScales = doConfigureScales;
2645
2525
  // Postprocess analytics - Normalize numeric results and derive secondary tests.
2646
2526
  // Do this after analytics have been run and before preparing a test log or scorecard.
2647
2527
  function doAnalyzePostProcessing(s, bLog = false) {
2648
- // Normalize the raw scores for all the numerics tests
2649
- let testResults = U.getNumericObjectKeys(testDefns);
2650
- for (let testID of testResults) {
2651
- if (testDefns[testID]['normalize']) {
2652
- let testResult = s.getTest(testID);
2653
- let rawScore = testResult['score'];
2654
- let normalizedScore;
2655
- normalizedScore = U.normalize(rawScore, s.testScales[testID]);
2656
- testResult['normalizedScore'] = normalizedScore;
2657
- // Add the scale used to normalize the raw score to the details
2658
- testResult['details']['scale'] = s.testScales[testID].scale;
2528
+ // TODO - SCORE: Toggle
2529
+ if (s.useLegacy()) {
2530
+ // Normalize the raw scores for all the numerics tests
2531
+ let testResults = U.getNumericObjectKeys(testDefns);
2532
+ for (let testID of testResults) {
2533
+ if (testDefns[testID]['normalize']) {
2534
+ let testResult = s.getTest(testID);
2535
+ let rawScore = testResult['score'];
2536
+ let normalizedScore;
2537
+ normalizedScore = U.normalize(rawScore, s.testScales[testID]);
2538
+ testResult['normalizedScore'] = normalizedScore;
2539
+ // Add the scale used to normalize the raw score to the details
2540
+ testResult['details']['scale'] = s.testScales[testID].scale;
2541
+ }
2659
2542
  }
2660
2543
  }
2544
+ else {
2545
+ // Just populate the normalized population deviation score in the test
2546
+ const scorecard = s._scorecard;
2547
+ let test = s.getTest(4 /* PopulationDeviation */);
2548
+ test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
2549
+ }
2661
2550
  // Derive secondary tests
2662
2551
  analyze_1.doDeriveSecondaryTests(s, bLog);
2663
2552
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
@@ -2740,13 +2629,11 @@ function makeNakedCxD(s, bLog = false) {
2740
2629
  let CxD = [];
2741
2630
  // Remove the unassigned & total dummy "districts"
2742
2631
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2743
- // TODO - SCORE: OH has an extra county!?!
2744
2632
  const splits = U.deepCopy(adornedCxD[districtID].slice(1));
2745
2633
  CxD.push(splits);
2746
2634
  }
2747
2635
  return CxD;
2748
2636
  }
2749
- // TODO - SCORE: Convert dict of geo props to array by district index
2750
2637
  function makeArrayOfGeoProps(s, bLog = false) {
2751
2638
  let geometryByDistrict = [];
2752
2639
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -2780,9 +2667,27 @@ function makeArrayOfDemographics(s, bLog = false) {
2780
2667
  return demographicsArray;
2781
2668
  }
2782
2669
  // SCORE A PLAN
2783
- function scorePlan(p, bLog = false) {
2670
+ function scorePlan(s, p, bLog = false, overridesJSON) {
2784
2671
  let scorer = new Score.Scorer();
2785
- return scorer.score(p);
2672
+ const scorecard = scorer.score(p, overridesJSON);
2673
+ // TODO - SCORE: Toggle: Before returning, create a dummy population deviation
2674
+ // test, for doHasEqualPopulations() to use later. This is preserving the old
2675
+ // calling sequence.
2676
+ let test = s.getTest(4 /* PopulationDeviation */);
2677
+ // TODO - SCORE: U.trim(popDev)???
2678
+ // Get the raw population deviation
2679
+ const popDev = scorecard.best.populationDeviation.raw;
2680
+ // Populate the test entry
2681
+ test['score'] = popDev;
2682
+ test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
2683
+ // Populate the N+1 summary "district" in district.statistics
2684
+ let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
2685
+ let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
2686
+ let summaryRow = s.districts.numberOfRows() - 1;
2687
+ totalPop[summaryRow] = p.populationProfile.targetSize;
2688
+ popDevPct[summaryRow] = popDev;
2689
+ //
2690
+ return scorecard;
2786
2691
  }
2787
2692
  exports.scorePlan = scorePlan;
2788
2693
 
@@ -2866,6 +2771,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
2866
2771
  return result;
2867
2772
  };
2868
2773
  Object.defineProperty(exports, "__esModule", { value: true });
2774
+ const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
2869
2775
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2870
2776
  // PLAN HELPERS
2871
2777
  // Is a "neighbor" in state?
@@ -2893,7 +2799,6 @@ function getDistrict(plan, geoID) {
2893
2799
  }
2894
2800
  exports.getDistrict = getDistrict;
2895
2801
  // WORKING WITH GEOIDS
2896
- // TODO - VFEATURE
2897
2802
  function parseGeoID(geoID) {
2898
2803
  let bVfeature = false;
2899
2804
  // Rewrite vfeature GEOIDs to enable lexical parsing of higher-level parts
@@ -2901,12 +2806,7 @@ function parseGeoID(geoID) {
2901
2806
  // Example: vfeature_39153153ASV_0_28e0bc2c8163e5982e1da2d61e2388a8325c575e
2902
2807
  if (geoID.indexOf('vfeature') >= 0) {
2903
2808
  bVfeature = true;
2904
- // Strip off leading 'vfeature_'
2905
- let parentGeoID = geoID.slice(9);
2906
- // Strip off trailing goo
2907
- const uPos = parentGeoID.indexOf('_');
2908
- parentGeoID = parentGeoID.slice(0, uPos);
2909
- geoID = parentGeoID;
2809
+ geoID = DT.vgeoidToGeoid(geoID);
2910
2810
  }
2911
2811
  const parts = {
2912
2812
  vfeature: bVfeature,
@@ -2914,29 +2814,9 @@ function parseGeoID(geoID) {
2914
2814
  county: geoID.substring(2, 5),
2915
2815
  rest: geoID.slice(5)
2916
2816
  };
2917
- // let l: number = geoID.length;
2918
- // if (l >= 11)
2919
- // {
2920
- // parts['tract'] = geoID.substring(0, 11);
2921
- // }
2922
- // if (l >= 12)
2923
- // {
2924
- // parts['bg'] = geoID.substring(0, 12);
2925
- // }
2926
- // if (l == 15)
2927
- // {
2928
- // parts['block'] = geoID;
2929
- // }
2930
2817
  return parts;
2931
2818
  }
2932
2819
  exports.parseGeoID = parseGeoID;
2933
- // TODO - VFEATURE
2934
- // export function getFIPSFromCountyGeoID(geoID: string): string
2935
- // {
2936
- // const fips = geoID.substring(2, 5);
2937
- // if (isNaN(Number(fips))) console.log("Non-numeric GEOID =", geoID);
2938
- // return fips;
2939
- // }
2940
2820
  function isWaterOnly(geoID) {
2941
2821
  let waterOnlySignature = 'ZZZZZZ';
2942
2822
  if (geoID.indexOf(waterOnlySignature) >= 0)
@@ -3386,6 +3266,17 @@ module.exports = require("@dra2020/dra-score");
3386
3266
 
3387
3267
  /***/ }),
3388
3268
 
3269
+ /***/ "@dra2020/dra-types":
3270
+ /*!*************************************!*\
3271
+ !*** external "@dra2020/dra-types" ***!
3272
+ \*************************************/
3273
+ /*! no static exports found */
3274
+ /***/ (function(module, exports) {
3275
+
3276
+ module.exports = require("@dra2020/dra-types");
3277
+
3278
+ /***/ }),
3279
+
3389
3280
  /***/ "@dra2020/poly":
3390
3281
  /*!********************************!*\
3391
3282
  !*** external "@dra2020/poly" ***!