@dra2020/district-analytics 3.0.1 → 3.3.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.
@@ -116,11 +116,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
116
116
  return result;
117
117
  };
118
118
  Object.defineProperty(exports, "__esModule", { value: true });
119
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
119
120
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
120
121
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
121
122
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
122
123
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
123
124
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
125
+ const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
124
126
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
125
127
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
126
128
  class AnalyticsSession {
@@ -140,45 +142,48 @@ class AnalyticsSession {
140
142
  this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
141
143
  this.plan = new D.Plan(this, SessionRequest['plan']);
142
144
  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);
145
+ // TODO - SCORE: Toggle
146
+ if (this.useLegacy()) {
147
+ console.log("Using legacy district-analytics.");
148
+ // NOTE: I've pulled these out of the individual analytics to here. Eventually,
149
+ // we could want them to passed into an analytics session as data, along with
150
+ // everything else. For now, this keeps branching out of the main code.
151
+ results_1.doConfigureScales(this);
152
+ }
153
+ else {
154
+ console.log("Using dra-score analytics.");
155
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
156
+ results_1.doConfigureScales(this);
157
+ }
154
158
  }
155
159
  processConfig(config) {
156
160
  // NOTE - Session settings are required:
157
161
  // - Analytics suites can be defaulted to all with [], but
158
162
  // - 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
- }
163
+ // TODO - SCORE: Delete
164
+ config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
165
165
  // Default the Census & redistricting cycle to 2010
166
166
  if (!(U.keyExists('cycle', config)))
167
167
  config['cycle'] = 2010;
168
168
  return config;
169
169
  }
170
+ // TODO - SCORE: Toggle = Invert this logic
171
+ useLegacy() {
172
+ // TODO - SCORE: Opt-out
173
+ return (U.keyExists('useScore', this.config) && (this.config['useScore'] != null) && (!(this.config['useScore']))) ? true : false;
174
+ // TODO - SCORE: Opt-in
175
+ // return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
176
+ }
170
177
  // Using the the data in the analytics session, calculate all the
171
178
  // analytics & validations, saving/updating the individual test results.
172
- analyzePlan(bLog = false) {
179
+ analyzePlan(bLog = false, overridesJSON) {
173
180
  try {
174
181
  preprocess_1.doPreprocessData(this, bLog);
175
182
  analyze_1.doAnalyzeDistricts(this, bLog);
176
183
  analyze_1.doAnalyzePlan(this, bLog);
177
184
  // TODO - SCORE
178
185
  this._profile = score_1.profilePlan(this, bLog);
179
- // this._profile = Score.sampleProfile;
180
- this._scorecard = score_1.scorePlan(this._profile, bLog);
181
- // TODO - SCORE
186
+ this._scorecard = score_1.scorePlan(this, this._profile, bLog, overridesJSON);
182
187
  results_1.doAnalyzePostProcessing(this, bLog);
183
188
  }
184
189
  catch (_a) {
@@ -199,13 +204,6 @@ class AnalyticsSession {
199
204
  getPlanScorecard(bLog = false) {
200
205
  return this._scorecard;
201
206
  }
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
207
  // TODO - SCORE
210
208
  // NOTE - This assumes that analyzePlan() has been run!
211
209
  getRequirementsChecklist(bLog = false) {
@@ -214,19 +212,82 @@ class AnalyticsSession {
214
212
  // NOTE - This assumes that analyzePlan() has been run!
215
213
  getDiscontiguousDistrictFeatures(bLog = false) {
216
214
  // Get the (possibly empty) list of discontiguous district IDs
217
- let contiguousTest = this.getTest(1 /* Contiguous */);
218
- let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
215
+ const contiguousTest = this.getTest(1 /* Contiguous */);
216
+ const discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
219
217
  // Convert them into a (possibly empty) list of features
220
218
  let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
221
219
  if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
222
220
  for (let id of discontiguousDistrictIDs) {
223
221
  let poly = this.districts.getDistrictShapeByID(id);
224
- if (poly)
225
- discontiguousDistrictFeatures.features.push(poly);
222
+ if (poly) {
223
+ // If a district has a shape & it is not contiguous, by definition,
224
+ // it will be a Multipolygon, i.e., it will have multiple pieces, some
225
+ // possibly embedded w/in other districts. Get & add all the pieces.
226
+ const districtParts = geofeature_1.polyParts(poly);
227
+ discontiguousDistrictFeatures.features.push(...districtParts.features);
228
+ // discontiguousDistrictFeatures.features.push(poly);
229
+ }
226
230
  }
227
231
  }
228
232
  return discontiguousDistrictFeatures;
229
233
  }
234
+ // Comments clipped from dra-client geodistrict.ts.
235
+ // Discontiguous polygons are:
236
+ // 1. All polygons in a multi-polygon; and
237
+ // 2. All holes in a otherwise cohesive polygon.
238
+ // Note that all non-cohesive features are always simple polygons.
239
+ /*
240
+ let i: number, j: number;
241
+ let nPoly: number = 0;
242
+ for (i = 0;nPoly == 0 && i < this.cacheDistricts.features.length;i++)
243
+ {
244
+ let f = this.cacheDistricts.features[i];
245
+
246
+ if (f.geometry.type === 'MultiPolygon')
247
+ nPoly += f.geometry.coordinates.length;
248
+ else if (f.geometry.type === 'Polygon' && f.geometry.coordinates.length)
249
+ nPoly += (f.geometry.coordinates.length - 1);
250
+ }
251
+ if (nPoly)
252
+ {
253
+ this.cacheNoncohesive = {type: 'FeatureCollection', features: []};
254
+ let af: any = this.cacheNoncohesive.features;
255
+ let oUnique: any = {};
256
+
257
+ // First add discontiguous polygons
258
+ for (i = 0;i < this.cacheDistricts.features.length;i++)
259
+ {
260
+ let f = this.cacheDistricts.features[i];
261
+
262
+ if (f.geometry.type === 'MultiPolygon')
263
+ {
264
+ // Push all non-contiguous polygons
265
+ for (j = 0;j < f.geometry.coordinates.length;j++)
266
+ {
267
+ let p: any = f.geometry.coordinates[j];
268
+ oUnique[Hash.qhash(p[0])] = true;
269
+ af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: p}});
270
+ }
271
+ }
272
+ }
273
+
274
+ // Now add unique holes
275
+ for (i = 0;i < this.cacheDistricts.features.length;i++)
276
+ {
277
+ let f = this.cacheDistricts.features[i];
278
+
279
+ if (f.geometry.type === 'Polygon')
280
+ {
281
+ // Push all holes from this polygon
282
+ for (j = 1;j < f.geometry.coordinates.length;j++)
283
+ {
284
+ let p: any = f.geometry.coordinates[j];
285
+ if (oUnique[Hash.qhash(p)] === undefined)
286
+ af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: [p]}});
287
+ }
288
+ }
289
+ }
290
+ } */
230
291
  // HELPERS USED INTERNALLY
231
292
  // Get an individual test, so you can drive UI with the results.
232
293
  getTest(testID) {
@@ -242,9 +303,21 @@ class AnalyticsSession {
242
303
  // Return a pointer to the the test entry for this test
243
304
  return this.tests[testID];
244
305
  }
245
- // NOTE - Not sure why this has to be up here.
306
+ // NOTE - Not sure why this has to be up here ...
246
307
  populationDeviationThreshold() {
247
- return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
308
+ // TODO - SCORE: Toggle
309
+ if (this.useLegacy()) {
310
+ return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
311
+ }
312
+ else {
313
+ // NOTE - This assumes the plan has been profiled
314
+ const scorer = new Score.Scorer();
315
+ const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
316
+ return popdev;
317
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
318
+ // NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
319
+ // return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
320
+ }
248
321
  }
249
322
  }
250
323
  exports.AnalyticsSession = AnalyticsSession;
@@ -420,7 +493,6 @@ class Districts {
420
493
  let outerThis = this;
421
494
  // Default the pop dev % for the dummy Unassigned district to 0%.
422
495
  // Default the pop dev % for real (1–N) but empty districts to 100%.
423
- // TODO - SCORE
424
496
  let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
425
497
  // Get the geoIDs assigned to the district
426
498
  // Guard against empty districts
@@ -447,7 +519,6 @@ class Districts {
447
519
  // NOTE - SPLITTING
448
520
  // Total population by counties w/in a district,
449
521
  // except the dummy unassigned district 0
450
- // TODO - VFEATURE
451
522
  if (i > 0)
452
523
  countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
453
524
  // Democratic and Republican vote totals
@@ -621,10 +692,7 @@ class Districts {
621
692
  compact_1.extractDistrictProperties(this._session, bLog);
622
693
  }
623
694
  getCountyIndex(geoID) {
624
- // TODO - VFEATURE
625
695
  let countyFIPS = U.parseGeoID(geoID)['county'];
626
- // let countyGeoID = U.parseGeoID(geoID)['county'] as string;
627
- // let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
628
696
  let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
629
697
  return countyIndex;
630
698
  }
@@ -873,11 +941,45 @@ exports.doAnalyzeDistricts = doAnalyzeDistricts;
873
941
  // NOTE - I could make this table-driven, but I'm thinking that the explicit
874
942
  // calls might make chunking for aync easier.
875
943
  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 */)) {
944
+ // TODO - SCORE: Toggle
945
+ if (s.useLegacy()) {
946
+ // Disable most legacy analytics
947
+ // Get the requested suites, and only execute those tests
948
+ let requestedSuites = s.config['suites'];
949
+ // Tests in the "Legal" suite, i.e., pass/ fail constraints
950
+ if (requestedSuites.includes(0 /* Legal */)) {
951
+ s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
952
+ s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
953
+ s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
954
+ s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
955
+ // NOTE - I can't check whether a population deviation is legal or not, until
956
+ // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
957
+ // the given type of district (CD vs. LD). The EqualPopulation test is derived
958
+ // from PopulationDeviation, as part of scorecard or test log preparation.
959
+ // Create an empty test entry here though ...
960
+ s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
961
+ }
962
+ // Tests in the "Fair" suite
963
+ if (requestedSuites.includes(1 /* Fair */)) {
964
+ s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
965
+ s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
966
+ s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
967
+ s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
968
+ s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
969
+ s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
970
+ }
971
+ // Tests in the "Best" suite, i.e., criteria for better/worse
972
+ if (requestedSuites.includes(2 /* Best */)) {
973
+ s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
974
+ s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
975
+ s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
976
+ s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
977
+ s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
978
+ s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
979
+ }
980
+ }
981
+ else {
982
+ // TODO - SCORE: Except these. Continue to do these here vs. dra-score.
881
983
  s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
882
984
  s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
883
985
  s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
@@ -888,24 +990,8 @@ function doAnalyzePlan(s, bLog = false) {
888
990
  // from PopulationDeviation, as part of scorecard or test log preparation.
889
991
  // Create an empty test entry here though ...
890
992
  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
993
  s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
906
994
  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
995
  }
910
996
  // Enable a Test Log and Scorecard to be generated
911
997
  s.bPlanAnalyzed = true;
@@ -1502,6 +1588,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
1502
1588
  Object.defineProperty(exports, "__esModule", { value: true });
1503
1589
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1504
1590
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1591
+ // TODO - SCORE: Delete
1505
1592
  function doPopulationDeviation(s, bLog = false) {
1506
1593
  let test = s.getTest(4 /* PopulationDeviation */);
1507
1594
  let targetSize = s.state.totalPop / s.state.nDistricts;
@@ -1540,8 +1627,8 @@ function doHasEqualPopulations(s, bLog = false) {
1540
1627
  let test = s.getTest(3 /* EqualPopulation */);
1541
1628
  // Get the normalized population deviation %
1542
1629
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
1543
- let popDevPct = popDevTest['score'];
1544
- let popDevNormalized = popDevTest['normalizedScore'];
1630
+ const popDevPct = popDevTest['score'];
1631
+ const popDevNormalized = popDevTest['normalizedScore'];
1545
1632
  // Populate the test entry
1546
1633
  if (popDevNormalized > 0) {
1547
1634
  test['score'] = true;
@@ -1583,6 +1670,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
1583
1670
  };
1584
1671
  Object.defineProperty(exports, "__esModule", { value: true });
1585
1672
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1673
+ // HELPER
1674
+ function polyParts(poly) {
1675
+ let parts = { type: 'FeatureCollection', features: [] };
1676
+ let af = parts.features;
1677
+ if (poly.geometry.type === 'MultiPolygon') {
1678
+ // Push all non-contiguous polygons
1679
+ for (let j = 0; j < poly.geometry.coordinates.length; j++) {
1680
+ let onePoly = poly.geometry.coordinates[j];
1681
+ af.push({ type: 'Feature', properties: { id: `${af.length + 1}` }, geometry: { type: 'Polygon', coordinates: onePoly } });
1682
+ }
1683
+ }
1684
+ else {
1685
+ parts.features.push(poly);
1686
+ }
1687
+ return parts;
1688
+ }
1689
+ exports.polyParts = polyParts;
1586
1690
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1587
1691
  // TODO - POLY: Confirm Cartesian calculations
1588
1692
  function gfArea(poly) {
@@ -1908,10 +2012,7 @@ function doPreprocessCensus(s, bLog = false) {
1908
2012
  // Sum total population across the state
1909
2013
  s.state.totalPop += value;
1910
2014
  // Get the county FIPS code for the feature
1911
- // TODO - VFEATURE
1912
2015
  let countyFIPS = U.parseGeoID(geoID)['county'];
1913
- // let county = U.parseGeoID(geoID)['county'] as string;
1914
- // let countyFIPS = U.getFIPSFromCountyGeoID(county);
1915
2016
  // If a subtotal for the county doesn't exist, initialize one
1916
2017
  if (!(U.keyExists(countyFIPS, totalByCounty))) {
1917
2018
  totalByCounty[countyFIPS] = 0;
@@ -1929,8 +2030,8 @@ function doPreprocessCensus(s, bLog = false) {
1929
2030
  let fipsCodes = U.getObjectKeys(totalByCounty);
1930
2031
  // Sort the results
1931
2032
  fipsCodes = fipsCodes.sort();
1932
- // TODO - SCORE: This was added for SPLITTING
1933
- // Add a dummy county, for county-district splitting analysis
2033
+ // NOTE - This was added for the legacy SPLITTING implementation.
2034
+ // Add a dummy county, for county-district splitting analysis.
1934
2035
  fipsCodes.unshift('000');
1935
2036
  // Create the ID-ordinal map
1936
2037
  for (let i in fipsCodes) {
@@ -2020,6 +2121,7 @@ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
2020
2121
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
2021
2122
  const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
2022
2123
  // Example
2124
+ // TODO - DELETE?
2023
2125
  let sampleRequirements = {
2024
2126
  score: 2 /* Red */,
2025
2127
  metrics: {
@@ -2043,6 +2145,7 @@ let sampleRequirements = {
2043
2145
  stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
2044
2146
  }
2045
2147
  };
2148
+ // TODO - DELETE
2046
2149
  let sampleCompactness = {
2047
2150
  score: 60,
2048
2151
  metrics: {
@@ -2055,6 +2158,7 @@ let sampleCompactness = {
2055
2158
  },
2056
2159
  resources: {}
2057
2160
  };
2161
+ // TODO - DELETE
2058
2162
  let sampleSplitting = {
2059
2163
  score: 73,
2060
2164
  metrics: {
@@ -2072,7 +2176,7 @@ let sampleSplitting = {
2072
2176
  datasets: {},
2073
2177
  resources: {}
2074
2178
  };
2075
- // TODO - PARTISAN: This category is still being fleshed out.
2179
+ // TODO - DELETE
2076
2180
  let samplePartisan = {
2077
2181
  score: 100,
2078
2182
  metrics: {
@@ -2087,7 +2191,7 @@ let samplePartisan = {
2087
2191
  planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2088
2192
  }
2089
2193
  };
2090
- // TODO - SCORE: Add a minority report
2194
+ // TODO - DELETE
2091
2195
  let sampleMinority = {
2092
2196
  score: null,
2093
2197
  metrics: {
@@ -2114,6 +2218,7 @@ let sampleMinority = {
2114
2218
  },
2115
2219
  resources: {}
2116
2220
  };
2221
+ // TODO - DELETE
2117
2222
  exports.samplePlanAnalytics = {
2118
2223
  requirements: sampleRequirements,
2119
2224
  compactness: sampleCompactness,
@@ -2122,232 +2227,6 @@ exports.samplePlanAnalytics = {
2122
2227
  partisan: samplePartisan,
2123
2228
  minority: sampleMinority
2124
2229
  };
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
2230
  function prepareRequirementsChecklist(s, bLog = false) {
2352
2231
  if (!(s.bPostProcessingDone)) {
2353
2232
  doAnalyzePostProcessing(s);
@@ -2355,34 +2234,40 @@ function prepareRequirementsChecklist(s, bLog = false) {
2355
2234
  // REQUIREMENTS CATEGORY
2356
2235
  let paRequirements;
2357
2236
  {
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 */);
2237
+ const completeTest = s.getTest(0 /* Complete */);
2238
+ const contiguousTest = s.getTest(1 /* Contiguous */);
2239
+ const freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
2240
+ const equalPopulationTest = s.getTest(3 /* EqualPopulation */);
2362
2241
  // Combine individual checks into an overall score
2363
- // TODO - DASHBOARD: Until we add three-state support top to bottom in
2242
+ // NOTE - Until we add three-state support top to bottom in
2364
2243
  // 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']);
2244
+ const completeMetric = U.mapBooleanToTriState(completeTest['score']);
2245
+ const contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
2246
+ const freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
2247
+ const equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
2369
2248
  let reqScore = 0 /* Green */;
2370
- let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
2249
+ const checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
2371
2250
  if (checks.includes(1 /* Yellow */))
2372
2251
  reqScore = 1 /* Yellow */;
2373
2252
  if (checks.includes(2 /* Red */))
2374
2253
  reqScore = 2 /* Red */;
2375
2254
  // 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;
2255
+ const unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
2256
+ const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
2257
+ const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
2258
+ const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
2259
+ // TODO - SCORE: DELETE - This code is hooked correctly to use dra-score
2260
+ // const scorecard = s._scorecard as Score.Scorecard;
2261
+ // const populationDeviation = scorecard.best.populationDeviation.raw;
2262
+ const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
2263
+ // console.log("Population deviations =", populationDeviationDetail, populationDeviation);
2264
+ // const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
2265
+ const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
2266
+ // console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
2267
+ const xx = s.state.xx;
2383
2268
  // 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];
2269
+ const stateReqsDict = state_reqs_json_1.default;
2270
+ const reqLinkToStateReqs = stateReqsDict[xx];
2386
2271
  // Populate the category
2387
2272
  paRequirements = {
2388
2273
  score: reqScore,
@@ -2645,19 +2530,30 @@ exports.doConfigureScales = doConfigureScales;
2645
2530
  // Postprocess analytics - Normalize numeric results and derive secondary tests.
2646
2531
  // Do this after analytics have been run and before preparing a test log or scorecard.
2647
2532
  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;
2533
+ // TODO - SCORE: Toggle
2534
+ if (s.useLegacy()) {
2535
+ // Normalize the raw scores for all the numerics tests
2536
+ let testResults = U.getNumericObjectKeys(testDefns);
2537
+ for (let testID of testResults) {
2538
+ if (testDefns[testID]['normalize']) {
2539
+ let testResult = s.getTest(testID);
2540
+ let rawScore = testResult['score'];
2541
+ let normalizedScore;
2542
+ normalizedScore = U.normalize(rawScore, s.testScales[testID]);
2543
+ testResult['normalizedScore'] = normalizedScore;
2544
+ // Add the scale used to normalize the raw score to the details
2545
+ testResult['details']['scale'] = s.testScales[testID].scale;
2546
+ }
2659
2547
  }
2660
2548
  }
2549
+ else {
2550
+ // Just populate the normalized population deviation score in the test
2551
+ const scorecard = s._scorecard;
2552
+ let test = s.getTest(4 /* PopulationDeviation */);
2553
+ test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
2554
+ // TODO - DELETE
2555
+ // test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
2556
+ }
2661
2557
  // Derive secondary tests
2662
2558
  analyze_1.doDeriveSecondaryTests(s, bLog);
2663
2559
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
@@ -2702,7 +2598,7 @@ function profilePlan(s, bLog = false) {
2702
2598
  const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
2703
2599
  const splits = makeNakedCxD(s);
2704
2600
  const summaryRow = s.districts.numberOfRows() - 1;
2705
- const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
2601
+ const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
2706
2602
  const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
2707
2603
  const demographicsByDistrict = makeArrayOfDemographics(s);
2708
2604
  const profile = {
@@ -2740,13 +2636,11 @@ function makeNakedCxD(s, bLog = false) {
2740
2636
  let CxD = [];
2741
2637
  // Remove the unassigned & total dummy "districts"
2742
2638
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2743
- // TODO - SCORE: OH has an extra county!?!
2744
2639
  const splits = U.deepCopy(adornedCxD[districtID].slice(1));
2745
2640
  CxD.push(splits);
2746
2641
  }
2747
2642
  return CxD;
2748
2643
  }
2749
- // TODO - SCORE: Convert dict of geo props to array by district index
2750
2644
  function makeArrayOfGeoProps(s, bLog = false) {
2751
2645
  let geometryByDistrict = [];
2752
2646
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -2780,9 +2674,30 @@ function makeArrayOfDemographics(s, bLog = false) {
2780
2674
  return demographicsArray;
2781
2675
  }
2782
2676
  // SCORE A PLAN
2783
- function scorePlan(p, bLog = false) {
2677
+ function scorePlan(s, p, bLog = false, overridesJSON) {
2784
2678
  let scorer = new Score.Scorer();
2785
- return scorer.score(p);
2679
+ const scorecard = scorer.score(p, overridesJSON);
2680
+ // TODO - SCORE: Toggle: Before returning, create a dummy population deviation
2681
+ // test, for doHasEqualPopulations() to use later. This is preserving the old
2682
+ // calling sequence.
2683
+ let test = s.getTest(4 /* PopulationDeviation */);
2684
+ // TODO - SCORE: U.trim(popDev)???
2685
+ // const popDev = scorecard.best.populationDeviation.raw;
2686
+ // Get the raw population deviation
2687
+ const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
2688
+ // Populate the test entry
2689
+ test['score'] = popDev;
2690
+ test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
2691
+ // TODO - DELETE
2692
+ // test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
2693
+ // Populate the N+1 summary "district" in district.statistics
2694
+ let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
2695
+ let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
2696
+ let summaryRow = s.districts.numberOfRows() - 1;
2697
+ totalPop[summaryRow] = p.populationProfile.targetSize;
2698
+ popDevPct[summaryRow] = popDev;
2699
+ //
2700
+ return scorecard;
2786
2701
  }
2787
2702
  exports.scorePlan = scorePlan;
2788
2703
 
@@ -2866,6 +2781,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
2866
2781
  return result;
2867
2782
  };
2868
2783
  Object.defineProperty(exports, "__esModule", { value: true });
2784
+ const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
2869
2785
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2870
2786
  // PLAN HELPERS
2871
2787
  // Is a "neighbor" in state?
@@ -2893,7 +2809,6 @@ function getDistrict(plan, geoID) {
2893
2809
  }
2894
2810
  exports.getDistrict = getDistrict;
2895
2811
  // WORKING WITH GEOIDS
2896
- // TODO - VFEATURE
2897
2812
  function parseGeoID(geoID) {
2898
2813
  let bVfeature = false;
2899
2814
  // Rewrite vfeature GEOIDs to enable lexical parsing of higher-level parts
@@ -2901,12 +2816,7 @@ function parseGeoID(geoID) {
2901
2816
  // Example: vfeature_39153153ASV_0_28e0bc2c8163e5982e1da2d61e2388a8325c575e
2902
2817
  if (geoID.indexOf('vfeature') >= 0) {
2903
2818
  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;
2819
+ geoID = DT.vgeoidToGeoid(geoID);
2910
2820
  }
2911
2821
  const parts = {
2912
2822
  vfeature: bVfeature,
@@ -2914,29 +2824,9 @@ function parseGeoID(geoID) {
2914
2824
  county: geoID.substring(2, 5),
2915
2825
  rest: geoID.slice(5)
2916
2826
  };
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
2827
  return parts;
2931
2828
  }
2932
2829
  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
2830
  function isWaterOnly(geoID) {
2941
2831
  let waterOnlySignature = 'ZZZZZZ';
2942
2832
  if (geoID.indexOf(waterOnlySignature) >= 0)
@@ -3386,6 +3276,17 @@ module.exports = require("@dra2020/dra-score");
3386
3276
 
3387
3277
  /***/ }),
3388
3278
 
3279
+ /***/ "@dra2020/dra-types":
3280
+ /*!*************************************!*\
3281
+ !*** external "@dra2020/dra-types" ***!
3282
+ \*************************************/
3283
+ /*! no static exports found */
3284
+ /***/ (function(module, exports) {
3285
+
3286
+ module.exports = require("@dra2020/dra-types");
3287
+
3288
+ /***/ }),
3289
+
3389
3290
  /***/ "@dra2020/poly":
3390
3291
  /*!********************************!*\
3391
3292
  !*** external "@dra2020/poly" ***!