@dra2020/district-analytics 14.1.2 → 15.0.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.
@@ -44,16 +44,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
44
44
  Object.defineProperty(exports, "__esModule", ({ value: true }));
45
45
  exports.AnalyticsSession = void 0;
46
46
  const baseclient_1 = __webpack_require__(/*! @dra2020/baseclient */ "@dra2020/baseclient");
47
+ // LEGACY - DELETE
47
48
  // import * as DT from '@dra2020/dra-types';
48
- const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
49
+ // LEGACY - DELETE
50
+ // import * as Score from '@dra2020/dra-score';
51
+ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
49
52
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
50
53
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
54
+ // LEGACY - DELETE
55
+ // import { profilePlan, scorePlan, computeMetrics, rateKeyDimensions, thunkScorecard, compareScorecards } from './score'
51
56
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
52
57
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
53
58
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
54
59
  const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
55
60
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
61
+ const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
56
62
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
63
+ // LEGACY - DELETE
57
64
  // import * as S from './settings';
58
65
  class AnalyticsSession {
59
66
  constructor(SessionRequest) {
@@ -108,13 +115,69 @@ class AnalyticsSession {
108
115
  (0, analyze_1.doAnalyzeDistricts)(this, bLog);
109
116
  // This does a little stuff that didn't get factored out into dra-score and then dra-analytics
110
117
  (0, analyze_1.doAnalyzePlan)(this, bLog);
111
- // The main analytics are next
112
- // Run legacy analytics
118
+ // THE MAIN ANALYTICS ARE NEXT
119
+ // Even though we don't save the profile (as I thought we would),
120
+ // we allow it to be exported, so keep generating it, and use it
121
+ // to gather the inputs to new analytics (as well as the legacy).
113
122
  this._profile = (0, score_1.profilePlan)(this, bLog);
114
- this._scorecard = (0, score_1.scorePlan)(this, this._profile, bLog, overridesJSON);
115
- // TODO: Run new analytics
116
- // TODO: Compare the new & legacy scorecards
117
- // TODO: Set a scorecard
123
+ // LEGACY - DELETE
124
+ // let legacyScorecard = {} as Score.Scorecard;
125
+ let legacyScorecardAlt = {};
126
+ let newScorecard = {};
127
+ // LEGACY - DELETE: Run legacy analytics
128
+ /*
129
+ if (this.config.legacyanalytics)
130
+ {
131
+ if (bLog) console.log("Running legacy analytics ...");
132
+ legacyScorecard = scorePlan(this, this._profile, bLog, overridesJSON);
133
+ }
134
+ */
135
+ // Construct a new/alternate scorecard
136
+ // LEGACY - DELETE
137
+ // if (this.config.newanalytics)
138
+ // {
139
+ // if (bLog) console.log("Running new analytics ...");
140
+ newScorecard = (0, score_1.computeMetrics)(this._profile, this.getGoodShapes(), bLog); // w/o ratings
141
+ newScorecard = (0, score_1.rateKeyDimensions)(newScorecard, this._profile, bLog); // w/ ratings
142
+ legacyScorecardAlt = (0, score_1.thunkScorecard)(newScorecard, bLog);
143
+ // Add minority notes
144
+ legacyScorecardAlt.minority.details['majorityMinority'] = M.getMajorityMinority(this);
145
+ legacyScorecardAlt.minority.details['vraPreclearance'] = M.getVRASection5(this);
146
+ // LEGACY - DELETE
147
+ // // Compare the new & legacy scorecards
148
+ // compareScorecards(legacyScorecardAlt, legacyScorecard, bLog);
149
+ // }
150
+ // LEGACY - DELETE
151
+ // Use the new scorecard, after creating it
152
+ // this._scorecard = (this.config.newanalytics) ? legacyScorecardAlt : legacyScorecard;
153
+ this._scorecard = legacyScorecardAlt;
154
+ // Post-scorecard housekeeping - copied from scorePlan()
155
+ // LEGACY - DELETE
156
+ // if (this.config.newanalytics)
157
+ // {
158
+ // Before returning, create a dummy population deviation test, for
159
+ // doHasEqualPopulations() to use later.This is preserving the old calling sequence.
160
+ let test = this.getTest(4 /* PopulationDeviation */);
161
+ // Get the raw population deviation
162
+ const popDev = this._scorecard.populationDeviation.raw;
163
+ // Populate the test entry
164
+ test['score'] = popDev;
165
+ test['details'] = { 'maxDeviation': this._scorecard.populationDeviation.notes['maxDeviation'] };
166
+ // Populate the N+1 summary "district" in district.statistics
167
+ let totalPop = this.districts.table.totalPop;
168
+ let popDevPct = this.districts.table.popDevPct;
169
+ let totalVAP = this.districts.table.totalVAP;
170
+ const summaryRow = this.districts.numberOfRows() - 1;
171
+ totalPop[summaryRow] = this._profile.population.targetSize;
172
+ popDevPct[summaryRow] = popDev;
173
+ totalVAP[summaryRow] = Math.round(totalVAP[summaryRow] / this._profile.nDistricts);
174
+ // Added w/ new scorecard
175
+ // Use 'roughly' equal population from dra-analytics
176
+ let test2 = this.getTest(3 /* EqualPopulation */);
177
+ test2['score'] = newScorecard.populationDeviation.roughlyEqual;
178
+ // LEGACY - DELETE
179
+ // }
180
+ // END main analytics
118
181
  (0, results_1.doAnalyzePostProcessing)(this, bLog);
119
182
  }
120
183
  catch (e) {
@@ -124,6 +187,29 @@ class AnalyticsSession {
124
187
  }
125
188
  return true;
126
189
  }
190
+ getGoodShapes() {
191
+ const rawShapes = this.districts.getDistrictShapes();
192
+ // Filter the real shapes & throw everything else away
193
+ let goodShapes = {};
194
+ goodShapes['type'] = "FeatureCollection";
195
+ goodShapes['features'] = [];
196
+ for (let i = 0; i < rawShapes.features.length; i++) {
197
+ const shape = rawShapes.features[i];
198
+ if (isAShape(shape)) {
199
+ const d = baseclient_1.Poly.polyDescribe(shape);
200
+ let f = {
201
+ type: 'Feature',
202
+ properties: { districtID: `${i + 1}` },
203
+ geometry: {
204
+ type: (d.npoly > 1) ? 'MultiPolygon' : 'Polygon',
205
+ coordinates: shape.geometry.coordinates
206
+ }
207
+ };
208
+ goodShapes.features.push(f);
209
+ }
210
+ }
211
+ return goodShapes;
212
+ }
127
213
  // 11-03-2020 - Added for racially polarized voting analysis
128
214
  // NOTE - This assumes that analyzePlan() has been run!
129
215
  analyzeRacialPolarization(districtID, groups, bLog = false) {
@@ -196,15 +282,32 @@ class AnalyticsSession {
196
282
  // Return a pointer to the the test entry for this test
197
283
  return this.tests[testID];
198
284
  }
285
+ // LEGACY - Threshold defined in dra-analytics for new analytics
199
286
  // NOTE - Not sure why this has to be up here ...
200
287
  populationDeviationThreshold() {
201
- // NOTE - This assumes the plan has been profiled
202
- const scorer = new Score.Scorer();
203
- const threshold = scorer.populationDeviationThreshold(this.legislativeDistricts); // TODO - 2020
288
+ // LEGACY - DELETE
289
+ // let threshold: number;
290
+ // if (this.config.legacyanalytics && !this.config.newanalytics)
291
+ // {
292
+ // // NOTE - This assumes the plan has been profiled
293
+ // const scorer = new Score.Scorer();
294
+ // threshold = scorer.populationDeviationThreshold(this.legislativeDistricts); // TODO - 2020
295
+ // }
296
+ // else
297
+ // {
298
+ const threshold = dra_analytics_1.Rate.popdevThreshold(this.legislativeDistricts);
299
+ // }
204
300
  return threshold;
205
301
  }
206
302
  }
207
303
  exports.AnalyticsSession = AnalyticsSession;
304
+ function isAShape(poly) {
305
+ if (poly == null)
306
+ return false;
307
+ if (baseclient_1.Poly.polyNull(poly))
308
+ return false;
309
+ return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
310
+ }
208
311
 
209
312
 
210
313
  /***/ }),
@@ -1449,6 +1552,7 @@ function isAShape(poly) {
1449
1552
  return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
1450
1553
  }
1451
1554
  // SCORE KIWYSI COMPACTNESS
1555
+ // LEGACY
1452
1556
  function scoreKIWYSICompactness(s, bLog = false) {
1453
1557
  const rawShapes = s.districts.getDistrictShapes();
1454
1558
  // Filter the real shapes & throw everything else away
@@ -1528,17 +1632,26 @@ function doHasEqualPopulations(s, bLog = false) {
1528
1632
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
1529
1633
  const popDevPct = popDevTest['score'];
1530
1634
  const popDevNormalized = popDevTest['normalizedScore'];
1531
- // 09-19-2020 - Added to catch edge case of only one non-empty district
1532
- const p = s._profile;
1533
- const totPopByDistrict = p.population.byDistrict.filter(x => x > 0);
1534
- const bTwoOrMoreDistricts = (totPopByDistrict.length > 1) ? true : false;
1535
- // Populate the test entry
1536
- if (bTwoOrMoreDistricts && (popDevNormalized > 0)) {
1635
+ // LEGACY - DELETE: Has 'roughly' equal populations is calculated in dra-analytics
1636
+ /*
1637
+ if (s.config.legacyanalytics && !s.config.newanalytics)
1638
+ {
1639
+ // 09-19-2020 - Added to catch edge case of only one non-empty district
1640
+ const p = s._profile as L.Profile;
1641
+ const totPopByDistrict = p.population.byDistrict.filter(x => x > 0);
1642
+ const bTwoOrMoreDistricts: boolean = (totPopByDistrict.length > 1) ? true : false;
1643
+
1644
+ // Populate the test entry
1645
+ if (bTwoOrMoreDistricts && (popDevNormalized > 0))
1646
+ {
1537
1647
  test['score'] = true;
1538
- }
1539
- else {
1648
+ }
1649
+ else
1650
+ {
1540
1651
  test['score'] = false;
1652
+ }
1541
1653
  }
1654
+ */
1542
1655
  test['details']['deviation'] = popDevPct;
1543
1656
  test['details']['thresholds'] = popDevTest['details']['scale'];
1544
1657
  // Populate the N+1 summary "district" in district.statistics
@@ -1573,7 +1686,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
1573
1686
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
1574
1687
  };
1575
1688
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1576
- exports.uncertaintyOfMembership = exports.effectiveSplits = exports.avgSVError = exports.isAntimajoritarian = exports.ratePartisanBias = exports.inferSelectedMinority = exports.fieldForFeature = exports.geoIDForFeature = void 0;
1689
+ exports.uncertaintyOfMembership = exports.effectiveSplits = exports.avgSVError = exports.isAntimajoritarian = exports.ratePartisanBias = exports.estSeatProbability = exports.inferSelectedMinority = exports.fieldForFeature = exports.geoIDForFeature = void 0;
1577
1690
  __exportStar(__webpack_require__(/*! ./_api */ "./src/_api.ts"), exports);
1578
1691
  var _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
1579
1692
  Object.defineProperty(exports, "geoIDForFeature", ({ enumerable: true, get: function () { return _data_1.geoIDForFeature; } }));
@@ -1584,6 +1697,7 @@ __exportStar(__webpack_require__(/*! ./types */ "./src/types.ts"), exports);
1584
1697
  __exportStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"), exports);
1585
1698
  // Re-export RPV types and COI splitting functions
1586
1699
  const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
1700
+ exports.estSeatProbability = dra_analytics_1.Partisan.estSeatProbability;
1587
1701
  exports.ratePartisanBias = dra_analytics_1.Rate.ratePartisanBias;
1588
1702
  exports.isAntimajoritarian = dra_analytics_1.Rate.isAntimajoritarian;
1589
1703
  exports.avgSVError = dra_analytics_1.Rate.avgSVError;
@@ -2028,6 +2142,7 @@ function doAnalyzePostProcessing(s, bLog = false) {
2028
2142
  scorecard.splitting.details['countiesWithSplits'] = simpleSplits['details']['countiesWithSplits'];
2029
2143
  // NOTE - Add split precincts in dra-client directly
2030
2144
  // Derive secondary tests
2145
+ // Note - The only secondary test is 'roughly equal population (true/false)
2031
2146
  (0, analyze_1.doDeriveSecondaryTests)(s, bLog);
2032
2147
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
2033
2148
  s.bPostProcessingDone = true;
@@ -2067,11 +2182,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
2067
2182
  return result;
2068
2183
  };
2069
2184
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2070
- exports.scorePlan = exports.getStatewideDemographics = exports.profilePlan = void 0;
2071
- const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
2185
+ exports.thunkScorecard = exports.rateKeyDimensions = exports.computeMetrics = exports.getStatewideDemographics = exports.profilePlan = void 0;
2072
2186
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2073
- const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
2074
- const C = __importStar(__webpack_require__(/*! ./compact */ "./src/compact.ts"));
2187
+ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
2075
2188
  // PROFILE A PLAN
2076
2189
  const KEEP_DECIMALS = 6;
2077
2190
  function profilePlan(s, bLog = false) {
@@ -2183,50 +2296,555 @@ function getStatewideDemographics(s, bLog = false) {
2183
2296
  return demographics;
2184
2297
  }
2185
2298
  exports.getStatewideDemographics = getStatewideDemographics;
2186
- // SCORE A PLAN
2187
- function scorePlan(s, p, bLog = false, overridesJSON) {
2188
- let scorer = new Score.Scorer();
2189
- const scorecard = scorer.score(p, overridesJSON);
2190
- // Before returning, create a dummy population deviation test, for
2299
+ // SCORE A PLAN - Legacy using dra-score
2300
+ // LEGACY - DELETE
2301
+ /*
2302
+ export function scorePlan(s: AnalyticsSession, p: Score.Profile, bLog: boolean = false, overridesJSON?: any): Score.Scorecard
2303
+ {
2304
+ let scorer = new Score.Scorer();
2305
+ const scorecard: Score.Scorecard = scorer.score(p, overridesJSON);
2306
+
2307
+ // LEGACY POST-SCORECARD HOUSEKEEPING
2308
+ if (s.config.legacyanalytics && !s.config.newanalytics)
2309
+ {
2310
+ // Before returning, create a dummy population deviation test, for
2191
2311
  // doHasEqualPopulations() to use later.This is preserving the old calling sequence.
2192
- let test = s.getTest(4 /* PopulationDeviation */);
2312
+ let test = s.getTest(T.Test.PopulationDeviation) as T.TestEntry;
2313
+
2193
2314
  // Get the raw population deviation
2194
2315
  const popDev = scorecard.populationDeviation.raw;
2316
+
2195
2317
  // Populate the test entry
2196
2318
  test['score'] = popDev;
2197
2319
  test['details'] = { 'maxDeviation': scorecard.populationDeviation.notes['maxDeviation'] };
2320
+
2198
2321
  // Populate the N+1 summary "district" in district.statistics
2199
2322
  let totalPop = s.districts.table.totalPop;
2200
2323
  let popDevPct = s.districts.table.popDevPct;
2201
2324
  let totalVAP = s.districts.table.totalVAP;
2325
+
2202
2326
  const summaryRow = s.districts.numberOfRows() - 1;
2203
2327
  totalPop[summaryRow] = p.population.targetSize;
2204
2328
  popDevPct[summaryRow] = popDev;
2205
2329
  totalVAP[summaryRow] = Math.round(totalVAP[summaryRow] / p.nDistricts);
2206
- // Add minority notes
2207
- scorecard.minority.details['majorityMinority'] = M.getMajorityMinority(s);
2208
- scorecard.minority.details['vraPreclearance'] = M.getVRASection5(s);
2209
- try {
2210
- // Add KIWYSI compactness score
2211
- const kiwysiScores = C.scoreKIWYSICompactness(s, bLog);
2212
- // 10-21-2020 - Computing the average score here
2213
- const avgKIWYSIScore = Math.round(U.avgArray(kiwysiScores));
2214
- scorecard.compactness.details['kiwysi'] = avgKIWYSIScore;
2215
- // 10-21-2020 - Added KIWYSI scores to the by-district details
2216
- let byDistrict = scorecard.compactness.details['byDistrict'];
2217
- const nDistricts = byDistrict.length;
2218
- for (let districtID = 1; districtID <= nDistricts; districtID++) {
2219
- byDistrict[districtID - 1].kiwysiScore = kiwysiScores[districtID - 1];
2220
- }
2221
- }
2222
- catch (e) {
2223
- console.log("Exception caught scoring KIWYSI compactness.");
2224
- console.log(e.message);
2225
- // throw e;
2330
+
2331
+ } // end
2332
+
2333
+ // Add minority notes
2334
+ scorecard.minority.details['majorityMinority'] = M.getMajorityMinority(s);
2335
+ scorecard.minority.details['vraPreclearance'] = M.getVRASection5(s);
2336
+
2337
+ try
2338
+ {
2339
+ // Add KIWYSI compactness score
2340
+ const kiwysiScores: number[] = C.scoreKIWYSICompactness(s, bLog);
2341
+ // 10-21-2020 - Computing the average score here
2342
+ const avgKIWYSIScore: number = Math.round(U.avgArray(kiwysiScores));
2343
+ scorecard.compactness.details['kiwysi'] = avgKIWYSIScore;
2344
+
2345
+ // 10-21-2020 - Added KIWYSI scores to the by-district details
2346
+ let byDistrict: Score.CompactnessByDistrict[] = scorecard.compactness.details['byDistrict'];
2347
+ const nDistricts = byDistrict.length;
2348
+
2349
+ for (let districtID = 1; districtID <= nDistricts; districtID++)
2350
+ {
2351
+ byDistrict[districtID - 1].kiwysiScore = kiwysiScores[districtID - 1];
2352
+
2353
+ // 09-17-21 - Fix the normalized Polsby–Popper score!
2354
+ const rawPolsby = byDistrict[districtID - 1].rawPolsby;
2355
+ byDistrict[districtID - 1].normalizedPolsby = Rate.ratePolsby(rawPolsby);
2226
2356
  }
2357
+ }
2358
+ catch (e)
2359
+ {
2360
+ console.log("Exception caught scoring KIWYSI compactness.");
2361
+ console.log((<Error>e).message);
2362
+ // throw e;
2363
+ }
2364
+
2365
+ return scorecard;
2366
+ }
2367
+ */
2368
+ // SCORE A PLAN - New using dra-analytics
2369
+ function computeMetrics(p, districtShapes, bLog = false) {
2370
+ if (bLog)
2371
+ console.log("Computing metrics ...");
2372
+ const bLegislative = p.bStateLeg;
2373
+ // Calculate bias & responsiveness metrics ...
2374
+ const byDistrictVf = p.partisanship.byDistrict;
2375
+ const statewideVf = p.partisanship.statewide;
2376
+ let _pS = dra_analytics_1.Partisan.makePartisanScorecard(statewideVf, byDistrictVf, bLog);
2377
+ // Calculate minority representation metrics ...
2378
+ const statewideDemos = p.demographics.statewide;
2379
+ const byDistrictDemos = p.demographics.byDistrict;
2380
+ let _mS = dra_analytics_1.Minority.makeMinorityScorecard(statewideDemos, byDistrictDemos, bLog);
2381
+ // Calculate compactness metrics ...
2382
+ let _cS = dra_analytics_1.Compactness.makeCompactnessScorecard(districtShapes, bLog);
2383
+ // Calculate county-district splitting metrics ...
2384
+ const CxD = p.counties;
2385
+ const _sS = dra_analytics_1.Splitting.makeSplittingScorecard(CxD, bLog);
2386
+ // Calculate population deviation-related metrics ...
2387
+ const totPopByDistrict = p.population.byDistrict;
2388
+ const targetSize = p.population.targetSize;
2389
+ const _pdS = dra_analytics_1.Equal.makePopulationScorecard(totPopByDistrict, targetSize, bLegislative, bLog);
2390
+ const details = {};
2391
+ // Assemble the pieces into new scorecard
2392
+ const scorecard = {
2393
+ partisan: _pS,
2394
+ minority: _mS,
2395
+ compactness: _cS,
2396
+ splitting: _sS,
2397
+ populationDeviation: _pdS,
2398
+ details: details,
2399
+ scratchpad: {} // Hack to pass legacy values between processing steps
2400
+ };
2401
+ return scorecard;
2402
+ }
2403
+ exports.computeMetrics = computeMetrics;
2404
+ function rateKeyDimensions(scorecard, p, bLog = false) {
2405
+ if (bLog)
2406
+ console.log("Rating key dimensions ...");
2407
+ const bLegislative = p.bStateLeg;
2408
+ // Rate proportionality
2409
+ const statewideVf = p.partisanship.statewide;
2410
+ const Sf = scorecard.partisan.bias.estSf;
2411
+ scorecard.partisan.bias.score = dra_analytics_1.Rate.rateProportionality(scorecard.partisan.bias.deviation, statewideVf, Sf);
2412
+ // Rate competititveness
2413
+ scorecard.partisan.responsiveness.score = dra_analytics_1.Rate.rateCompetitiveness(scorecard.partisan.responsiveness.cDf);
2414
+ // Rate minority representation
2415
+ const rawOd = scorecard.minority.opportunityDistricts;
2416
+ const pOd = scorecard.minority.proportionalOpportunities;
2417
+ const rawCd = scorecard.minority.coalitionDistricts;
2418
+ const pCd = scorecard.minority.proportionalCoalitions;
2419
+ scorecard.minority.score = dra_analytics_1.Rate.rateMinorityRepresentation(rawOd, pOd, rawCd, pCd);
2420
+ // Rate compactness
2421
+ const avgReock = scorecard.compactness.avgReock;
2422
+ const avgPolsby = scorecard.compactness.avgPolsby;
2423
+ const reockRating = dra_analytics_1.Rate.rateReock(avgReock);
2424
+ const polsbyRating = dra_analytics_1.Rate.ratePolsby(avgPolsby);
2425
+ scorecard.compactness.score = dra_analytics_1.Rate.rateCompactness(reockRating, polsbyRating);
2426
+ // Rate county- & district-splitting
2427
+ const rawCountySplitting = scorecard.splitting.county;
2428
+ const rawDistrictSplitting = scorecard.splitting.district;
2429
+ const nCounties = p.nCounties;
2430
+ const nDistricts = p.nDistricts;
2431
+ const countyRating = dra_analytics_1.Rate.rateCountySplitting(rawCountySplitting, nCounties, nDistricts, bLegislative);
2432
+ const districtRating = dra_analytics_1.Rate.rateDistrictSplitting(rawDistrictSplitting, bLegislative);
2433
+ const unadjusted = dra_analytics_1.Rate.rateSplitting(countyRating, districtRating);
2434
+ scorecard.splitting.score = dra_analytics_1.Rate.adjustSplittingRating(unadjusted, rawCountySplitting, rawDistrictSplitting);
2435
+ // Rate population deviation
2436
+ const rawDeviation = scorecard.populationDeviation.deviation;
2437
+ scorecard.populationDeviation.score = dra_analytics_1.Rate.ratePopulationDeviation(rawDeviation, bLegislative);
2438
+ // Squirrel away normalized compactness & splitting ratings for the legacy scorecard
2439
+ const keep = {
2440
+ reockScore: reockRating,
2441
+ polsbyScore: polsbyRating,
2442
+ countyScore: countyRating,
2443
+ districtScore: districtRating
2444
+ };
2445
+ scorecard.scratchpad = keep;
2446
+ return scorecard;
2447
+ }
2448
+ exports.rateKeyDimensions = rateKeyDimensions;
2449
+ function thunkScorecard(newScorecard, bLog = false) {
2450
+ if (bLog)
2451
+ console.log("Thunking new scorecard into legacy structure ...");
2452
+ const scratchpad = newScorecard.scratchpad;
2453
+ // Partisan scorecard
2454
+ const pS = U.deepCopy(newScorecard.partisan);
2455
+ // Minority scorecard
2456
+ const mS = U.deepCopy(newScorecard.minority);
2457
+ // Compactness scorecard
2458
+ const reockM = {
2459
+ raw: newScorecard.compactness.avgReock,
2460
+ normalized: scratchpad.reockScore,
2461
+ notes: {}
2462
+ };
2463
+ const polsbyM = {
2464
+ raw: newScorecard.compactness.avgPolsby,
2465
+ normalized: scratchpad.polsbyScore,
2466
+ notes: {}
2467
+ };
2468
+ let cS = {
2469
+ score: newScorecard.compactness.score,
2470
+ reock: reockM,
2471
+ polsby: polsbyM,
2472
+ details: U.deepCopy(newScorecard.compactness.details)
2473
+ };
2474
+ // Relocate byDistrict compactness #'s
2475
+ cS.details.byDistrict = U.deepCopy(newScorecard.compactness.byDistrict);
2476
+ // Add KIWYSI compactness score
2477
+ cS.details['kiwysi'] = newScorecard.compactness.avgKWIWYSI;
2478
+ // Splitting scorecard
2479
+ const countyM = {
2480
+ raw: newScorecard.splitting.county,
2481
+ normalized: scratchpad.countyScore,
2482
+ notes: {}
2483
+ };
2484
+ const districtM = {
2485
+ raw: newScorecard.splitting.district,
2486
+ normalized: scratchpad.districtScore,
2487
+ notes: {}
2488
+ };
2489
+ const sS = {
2490
+ score: newScorecard.splitting.score,
2491
+ county: countyM,
2492
+ district: districtM,
2493
+ details: U.deepCopy(newScorecard.splitting.details)
2494
+ };
2495
+ // Population (equality) scorecard
2496
+ const pdM = {
2497
+ raw: newScorecard.populationDeviation.deviation,
2498
+ normalized: newScorecard.populationDeviation.score,
2499
+ notes: newScorecard.populationDeviation.notes
2500
+ };
2501
+ const pdS = pdM;
2502
+ const scorecard = {
2503
+ partisan: pS,
2504
+ minority: mS,
2505
+ compactness: cS,
2506
+ splitting: sS,
2507
+ populationDeviation: pdS,
2508
+ details: newScorecard.details
2509
+ };
2227
2510
  return scorecard;
2228
2511
  }
2229
- exports.scorePlan = scorePlan;
2512
+ exports.thunkScorecard = thunkScorecard;
2513
+ // LEGACY - DELETE
2514
+ /*
2515
+ export function compareScorecards(altLegacyScorecard: Score.Scorecard, legacyScorecard: Score.Scorecard, bLog: boolean = false): void
2516
+ {
2517
+ if (bLog) console.log("Comparing new & legacy scorecards ...");
2518
+
2519
+ // A pretty loose tolerance, because we're not trimming values in the new analytics
2520
+ const DECIMAL_PLACES = 2;
2521
+ const DEFAULT_TOLERANCE = toleranceFn(DECIMAL_PLACES);
2522
+
2523
+ // COMPARE PARTISAN SCORECARD
2524
+
2525
+ // Compare BIAS section
2526
+
2527
+ matchFloats("bestS", altLegacyScorecard.partisan.bias.bestS, legacyScorecard.partisan.bias.bestS, DEFAULT_TOLERANCE);
2528
+ matchFloats("bestSf", altLegacyScorecard.partisan.bias.bestSf, legacyScorecard.partisan.bias.bestSf, DEFAULT_TOLERANCE);
2529
+ matchFloats("estS", altLegacyScorecard.partisan.bias.estS, legacyScorecard.partisan.bias.estS, DEFAULT_TOLERANCE);
2530
+ matchFloats("estSf", altLegacyScorecard.partisan.bias.estSf, legacyScorecard.partisan.bias.estSf, DEFAULT_TOLERANCE);
2531
+ matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
2532
+ matchInts("proportionality/score", altLegacyScorecard.partisan.bias.score, legacyScorecard.partisan.bias.score);
2533
+
2534
+ matchFloats("tOf", altLegacyScorecard.partisan.bias.tOf as number, legacyScorecard.partisan.bias.tOf as number, DEFAULT_TOLERANCE);
2535
+ matchFloats("fptpS", altLegacyScorecard.partisan.bias.fptpS as number, legacyScorecard.partisan.bias.fptpS as number, DEFAULT_TOLERANCE);
2536
+ matchFloats("bS50", altLegacyScorecard.partisan.bias.bS50 as number, legacyScorecard.partisan.bias.bS50 as number, DEFAULT_TOLERANCE);
2537
+ matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
2538
+ matchUndefinableFloats("decl", altLegacyScorecard.partisan.bias.decl, legacyScorecard.partisan.bias.decl, toleranceFn(1));
2539
+
2540
+ let rvPointsAlt = altLegacyScorecard.partisan.bias.rvPoints;
2541
+ let rvPoints = legacyScorecard.partisan.bias.rvPoints;
2542
+ if (matchUndefinedness("rvPoints", rvPointsAlt, rvPoints)) {
2543
+ rvPointsAlt = rvPointsAlt as L.rVpoints;
2544
+ rvPoints = rvPoints as L.rVpoints;
2545
+ matchFloats("rvPoints/Sb", rvPointsAlt.Sb, rvPoints.Sb, DEFAULT_TOLERANCE);
2546
+ matchFloats("rvPoints/Ra", rvPointsAlt.Ra, rvPoints.Ra, DEFAULT_TOLERANCE);
2547
+ matchFloats("rvPoints/Rb", rvPointsAlt.Rb, rvPoints.Rb, DEFAULT_TOLERANCE);
2548
+ matchFloats("rvPoints/Va", rvPointsAlt.Va, rvPoints.Va, DEFAULT_TOLERANCE);
2549
+ matchFloats("rvPoints/Vb", rvPointsAlt.Vb, rvPoints.Vb, DEFAULT_TOLERANCE);
2550
+ }
2551
+
2552
+ matchFloats("gSym", altLegacyScorecard.partisan.bias.gSym as number, legacyScorecard.partisan.bias.gSym as number, DEFAULT_TOLERANCE);
2553
+ matchUndefinableFloats("gamma", altLegacyScorecard.partisan.bias.gamma, legacyScorecard.partisan.bias.gamma, DEFAULT_TOLERANCE);
2554
+ matchFloats("eG", altLegacyScorecard.partisan.bias.eG as number, legacyScorecard.partisan.bias.eG as number, DEFAULT_TOLERANCE);
2555
+ matchUndefinableFloats("bSV", altLegacyScorecard.partisan.bias.bSV, legacyScorecard.partisan.bias.bSV, DEFAULT_TOLERANCE);
2556
+ matchFloats("prop", altLegacyScorecard.partisan.bias.prop as number, legacyScorecard.partisan.bias.prop as number, DEFAULT_TOLERANCE);
2557
+ matchFloats("mMs", altLegacyScorecard.partisan.bias.mMs as number, legacyScorecard.partisan.bias.mMs as number, DEFAULT_TOLERANCE);
2558
+ matchFloats("mMd", altLegacyScorecard.partisan.bias.mMd as number, legacyScorecard.partisan.bias.mMd as number, DEFAULT_TOLERANCE);
2559
+ matchUndefinableFloats("lO", altLegacyScorecard.partisan.bias.lO, legacyScorecard.partisan.bias.lO, DEFAULT_TOLERANCE);
2560
+
2561
+ // Compare Impact section
2562
+
2563
+ matchFloats("unearnedS", altLegacyScorecard.partisan.impact.unearnedS, legacyScorecard.partisan.impact.unearnedS, DEFAULT_TOLERANCE);
2564
+ // Note - We don't use the impact score, so we don't compute it in the new scorecard.
2565
+ // matchInts("impact/score", altLegacyScorecard.partisan.impact.score as number, legacyScorecard.partisan.impact.score);
2566
+
2567
+ // Compare Responsiveness section
2568
+
2569
+ matchUndefinableFloats("bigR", altLegacyScorecard.partisan.responsiveness.bigR, legacyScorecard.partisan.responsiveness.bigR, DEFAULT_TOLERANCE);
2570
+ matchUndefinableFloats("littleR", altLegacyScorecard.partisan.responsiveness.littleR, legacyScorecard.partisan.responsiveness.littleR, DEFAULT_TOLERANCE);
2571
+ matchUndefinableFloats("mIR", altLegacyScorecard.partisan.responsiveness.mIR, legacyScorecard.partisan.responsiveness.mIR, DEFAULT_TOLERANCE);
2572
+ matchFloats("rD", altLegacyScorecard.partisan.responsiveness.rD as number, legacyScorecard.partisan.responsiveness.rD as number, DEFAULT_TOLERANCE);
2573
+ matchFloats("rDf", altLegacyScorecard.partisan.responsiveness.rDf as number, legacyScorecard.partisan.responsiveness.rDf as number, DEFAULT_TOLERANCE);
2574
+
2575
+ matchFloats("cSimple", altLegacyScorecard.partisan.responsiveness.cSimple, legacyScorecard.partisan.responsiveness.cSimple, DEFAULT_TOLERANCE);
2576
+ matchFloats("cD", altLegacyScorecard.partisan.responsiveness.cD, legacyScorecard.partisan.responsiveness.cD, DEFAULT_TOLERANCE);
2577
+ matchFloats("cDf", altLegacyScorecard.partisan.responsiveness.cDf, legacyScorecard.partisan.responsiveness.cDf, DEFAULT_TOLERANCE);
2578
+ matchInts("competitiveness/score", altLegacyScorecard.partisan.responsiveness.score as number, legacyScorecard.partisan.responsiveness.score);
2579
+
2580
+ matchPointArrays("dSVpoints", altLegacyScorecard.partisan.dSVpoints, legacyScorecard.partisan.dSVpoints, DEFAULT_TOLERANCE);
2581
+ matchPointArrays("rSVpoints", altLegacyScorecard.partisan.rSVpoints, legacyScorecard.partisan.rSVpoints, DEFAULT_TOLERANCE);
2582
+
2583
+ matchUndefinableFloats("averageDVf", altLegacyScorecard.partisan.averageDVf, legacyScorecard.partisan.averageDVf, DEFAULT_TOLERANCE);
2584
+ matchUndefinableFloats("averageRVf", altLegacyScorecard.partisan.averageRVf, legacyScorecard.partisan.averageRVf, DEFAULT_TOLERANCE);
2585
+ matchDicts("partisan/details", altLegacyScorecard.partisan.details, legacyScorecard.partisan.details);
2586
+
2587
+ // COMPARE MINORITY SCORECARD
2588
+
2589
+ matchObjectsAndArrays("pivotByDemographic", altLegacyScorecard.minority.pivotByDemographic, legacyScorecard.minority.pivotByDemographic);
2590
+ matchFloats("opportunityDistricts", altLegacyScorecard.minority.opportunityDistricts, legacyScorecard.minority.opportunityDistricts, DEFAULT_TOLERANCE);
2591
+ matchFloats("coalitionDistricts", altLegacyScorecard.minority.coalitionDistricts, legacyScorecard.minority.coalitionDistricts, DEFAULT_TOLERANCE);
2592
+ matchInts("minority/score", altLegacyScorecard.minority.score as number, legacyScorecard.minority.score);
2593
+ matchDicts("minority/details", altLegacyScorecard.minority.details, legacyScorecard.minority.details);
2594
+
2595
+ // COMPARE COMPACTNESS SCORECARD
2596
+
2597
+ matchFloats("reock", altLegacyScorecard.compactness.reock.raw, legacyScorecard.compactness.reock.raw, DEFAULT_TOLERANCE);
2598
+ matchInts("reock/normalized", altLegacyScorecard.compactness.reock.normalized, legacyScorecard.compactness.reock.normalized);
2599
+ matchFloats("polsby", altLegacyScorecard.compactness.polsby.raw, legacyScorecard.compactness.polsby.raw, DEFAULT_TOLERANCE);
2600
+ matchInts("polsby/normalized", altLegacyScorecard.compactness.polsby.normalized, legacyScorecard.compactness.polsby.normalized);
2601
+ matchInts("compactness/score", altLegacyScorecard.compactness.score as number, legacyScorecard.compactness.score);
2602
+ // Compare 'byDistrict' results separately
2603
+ const _altCompactnessByDistrict = Utils.deepCopy(altLegacyScorecard.compactness.details['byDistrict']);
2604
+ const _CompactnessByDistrict = Utils.deepCopy(legacyScorecard.compactness.details['byDistrict']);
2605
+ const _altCompactnessDetails = Utils.deepCopy(altLegacyScorecard.compactness.details);
2606
+ const _CompactnessDetails = Utils.deepCopy(legacyScorecard.compactness.details);
2607
+ delete _altCompactnessDetails['byDistrict'];
2608
+ delete _CompactnessDetails['byDistrict'];
2609
+ matchDicts("compactness/details", _altCompactnessDetails, _CompactnessDetails);
2610
+ matchCompactnessByDistrict("compactness/byDistrict", _altCompactnessByDistrict, _CompactnessByDistrict, DEFAULT_TOLERANCE);
2611
+
2612
+ // COMPARE SPLITTING SCORECARD
2613
+
2614
+ matchFloats("county", altLegacyScorecard.splitting.county.raw, legacyScorecard.splitting.county.raw, DEFAULT_TOLERANCE);
2615
+ matchInts("county/normalized", altLegacyScorecard.splitting.county.normalized, legacyScorecard.splitting.county.normalized);
2616
+ matchFloats("district", altLegacyScorecard.splitting.district.raw, legacyScorecard.splitting.district.raw, DEFAULT_TOLERANCE);
2617
+ matchInts("district/normalized", altLegacyScorecard.splitting.district.normalized, legacyScorecard.splitting.district.normalized);
2618
+ matchInts("splitting/score", altLegacyScorecard.splitting.score as number, legacyScorecard.splitting.score);
2619
+ matchDicts("splitting/details", altLegacyScorecard.splitting.details, legacyScorecard.splitting.details);
2620
+
2621
+ // COMPARE POPULATION SCORECARD
2622
+
2623
+ matchFloats("popdev", altLegacyScorecard.populationDeviation.raw, legacyScorecard.populationDeviation.raw, DEFAULT_TOLERANCE);
2624
+ matchInts("popdev/score", altLegacyScorecard.populationDeviation.normalized, legacyScorecard.populationDeviation.normalized);
2625
+ matchDicts("popdev/notes", altLegacyScorecard.populationDeviation.notes, legacyScorecard.populationDeviation.notes);
2626
+
2627
+ matchDicts("details", altLegacyScorecard.details, legacyScorecard.details);
2628
+ }
2629
+ */
2630
+ // LEGACY - DELETE
2631
+ /*
2632
+ // Matching helpers
2633
+
2634
+ function matchFloats(property: string, received: number, good: number, tolerance: number): boolean
2635
+ {
2636
+ if (Utils.areRoughlyEqual(received, good, tolerance)) {
2637
+ return true;
2638
+ }
2639
+ else {
2640
+ console.log(`${property} does not match: ${good} expected. Received ${received}.`);
2641
+ return false;
2642
+ }
2643
+ }
2644
+
2645
+ function toleranceFn (places: number): number{
2646
+ return 0.5 / Math.pow(10, places)
2647
+ }
2648
+
2649
+ function matchInts(property: string, received: number, good: number): boolean
2650
+ {
2651
+ if (received == good) {
2652
+ return true;
2653
+ }
2654
+ else {
2655
+ console.log(`${property} does not match: ${good} expected. Received ${received}.`);
2656
+ return false;
2657
+ }
2658
+ }
2659
+
2660
+ function matchUndefinableFloats(property: string, received: number | undefined, good: number | undefined, tolerance: number): boolean
2661
+ {
2662
+ if ((received === undefined) && (good === undefined)) return true;
2663
+ if ((received === undefined) || (good === undefined)) {
2664
+ console.log(`${property} does not match: ${good} expected. Received ${received}.`);
2665
+ return false;
2666
+ }
2667
+
2668
+ return matchFloats(property, received as number, good as number, tolerance);
2669
+ }
2670
+
2671
+ // https://stackoverflow.com/questions/13142968/deep-comparison-of-objects-arrays
2672
+ function matchObjectsAndArrays (property: string, received: object | undefined, good: object | undefined): boolean
2673
+ {
2674
+ if ((received === undefined) && (good === undefined)) return true; // Both undefined
2675
+
2676
+ if ((received === undefined) || (good === undefined)) { // One undefined but not the other
2677
+ console.log(`${property} does not match: ${good} expected. Received ${received}.`);
2678
+ return false;
2679
+ }
2680
+
2681
+ if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0)) return true; // Both empty
2682
+
2683
+ if (JSON.stringify(received) === JSON.stringify(good)) return true; // Contents match
2684
+
2685
+ console.log(`${property} objects or arrays do not match.`); // Contents don't match
2686
+ return false;
2687
+ }
2688
+
2689
+ function matchDicts (property: string, received: L.Dict, good: L.Dict): boolean
2690
+ {
2691
+ if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0)) return true; // Both empty
2692
+
2693
+ const receivedStr: string = JSON.stringify(received);
2694
+ const goodStr = JSON.stringify(good);
2695
+ if (receivedStr === goodStr) return true; // Contents match
2696
+
2697
+ console.log(`${property} does not match: ${goodStr} expected. Received ${receivedStr}.`); // Contents don't match
2698
+
2699
+ return false;
2700
+ }
2701
+
2702
+ function matchUndefinedness(property: string, received: any | undefined, good: any | undefined): boolean
2703
+ {
2704
+ if ((received === undefined) && (good === undefined)) return true;
2705
+
2706
+ if ((received === undefined) || (good === undefined)) return false;
2707
+
2708
+ return true;
2709
+ }
2710
+
2711
+ function matchPointArrays(property: string, received: L.SVpoint[], good: L.SVpoint[], tolerance: number): boolean
2712
+ {
2713
+ if (received.length != good.length)
2714
+ {
2715
+ console.log(`${property} does not match: Different number of points.`);
2716
+ return false;
2717
+ }
2718
+
2719
+ for (var i = 0; i < received.length; i++)
2720
+ {
2721
+ const vLabel: string = property + '/' + i.toString() + '/' + 'v';
2722
+ const sLabel: string = property + '/' + i.toString() + '/' + 's';
2723
+ if (!matchFloats(vLabel, received[i].v, good[i].v, tolerance)) return false;
2724
+ if (!matchFloats(sLabel, received[i].s, good[i].s, tolerance)) return false;
2725
+ }
2726
+
2727
+ return true;
2728
+ }
2729
+
2730
+ function matchCompactnessByDistrict(property: string, received: L.CompactnessByDistrict[], good: L.CompactnessByDistrict[], tolerance: number): boolean
2731
+ {
2732
+ if (received.length != good.length)
2733
+ {
2734
+ console.log(`${property} does not match: Different number of districts.`);
2735
+ return false;
2736
+ }
2737
+
2738
+ let bMismatched = false;
2739
+ for (var i = 0; i < received.length; i++)
2740
+ {
2741
+ const rawReock: string = property + '/' + i.toString() + '/' + 'rawReock';
2742
+ if (!matchFloats(rawReock, received[i].rawReock, good[i].rawReock, tolerance)) bMismatched = true;
2743
+
2744
+ const normalizedReock: string = property + '/' + i.toString() + '/' + 'normalizedReock';
2745
+ if (!matchInts(normalizedReock, received[i].normalizedReock, good[i].normalizedReock)) bMismatched = true;
2746
+
2747
+ const rawPolsby: string = property + '/' + i.toString() + '/' + 'rawPolsby';
2748
+ if (!matchFloats(rawPolsby, received[i].rawPolsby, good[i].rawPolsby, tolerance)) bMismatched = true;
2749
+
2750
+ // TODO
2751
+ // 09-17-21 - By-district Polsby–Popper ratings from dra-score in production are wrong!
2752
+ const normalizedPolsby: string = property + '/' + i.toString() + '/' + 'normalizedPolsby';
2753
+ if (!matchInts(normalizedPolsby, received[i].normalizedPolsby, good[i].normalizedPolsby)) bMismatched = true;
2754
+
2755
+ const kiwysiScore: string = property + '/' + i.toString() + '/' + 'kiwysiScore';
2756
+ if (!matchInts(kiwysiScore, received[i].kiwysiScore as number, good[i].kiwysiScore as number)) bMismatched = true;
2757
+ }
2758
+ if (bMismatched = true) return false;
2759
+
2760
+ return true;
2761
+ }
2762
+ */
2763
+ // LEGACY - DELETE
2764
+ // Not used, after all
2765
+ /* Modeled after David Sielaff's Jest extension 'toBeArrayWithValuesCloseTo'
2766
+
2767
+ function matchFloatArrays(property: string, received: Array<any>, expected: Array<any>, tolerance: number): boolean
2768
+ {
2769
+ // Note - Not sure what this check was for ...
2770
+ // if (expected.length == 0) {
2771
+ // return {
2772
+ // message: () => `expected arrays of same size`,
2773
+ // pass: received.length == 0
2774
+ // }
2775
+ // }
2776
+
2777
+ if (typeof expected[0] === "number")
2778
+ {
2779
+ if (typeof received[0] === "number") return matchArrays1d(property, received, expected, tolerance);
2780
+
2781
+ console.log(`${property} does not match: array doesn't contain numbers.`);
2782
+ return false;
2783
+ }
2784
+
2785
+ // Note - Ditto
2786
+ // if (expected[0].length == 0) {
2787
+ // return {
2788
+ // message: () => `expected arrays of same size`,
2789
+ // pass: received[0].length == 0
2790
+ // }
2791
+ // }
2792
+
2793
+ if (expected[0] instanceof Array && typeof expected[0][0] === "number")
2794
+ {
2795
+ if (received[0] instanceof Array && typeof received[0][0] === "number") return matchArrays2d(property, received, expected, tolerance);
2796
+
2797
+ console.log(`${property} does not match: arrays don't have the same dimensionality and content.`);
2798
+ return false;
2799
+ }
2800
+
2801
+ console.log(`${property}: Expected 1d or 2d arrays.`);
2802
+ return false;
2803
+ }
2804
+
2805
+ function matchArrays1d(property: string, received: number[], expected: number[], tolerance: number): boolean
2806
+ {
2807
+ if (received.length != expected.length)
2808
+ {
2809
+ console.log(`${property} does not match: arrays have different lengths.`);
2810
+ return false;
2811
+ }
2812
+
2813
+ for (var index = 0; index < received.length; index++)
2814
+ {
2815
+ const cell: string = property + '/' + index.toString();
2816
+ if (!matchFloats(cell, received[index], expected[index], tolerance)) return false;
2817
+ }
2818
+
2819
+ return true;
2820
+ }
2821
+
2822
+ function matchArrays2d(property: string, received: number[][], expected: number[][], tolerance: number): boolean
2823
+ {
2824
+ if (received.length != expected.length)
2825
+ {
2826
+ console.log(`${property} does not match: arrays have different lengths.`);
2827
+ return false;
2828
+ }
2829
+
2830
+ for (var index = 0; index < received.length; index++)
2831
+ {
2832
+ if (received[index].length != expected[index].length)
2833
+ {
2834
+ console.log(`${property} does not match: arrays have different lengths.`);
2835
+ return false;
2836
+ }
2837
+
2838
+ for (var inner = 0; inner < received[index].length; inner++)
2839
+ {
2840
+ const cell: string = property + '/' + index.toString() + '/' + inner.toString();
2841
+ if (!matchFloats(cell, received[index][inner], expected[index][inner], tolerance)) return false;
2842
+ }
2843
+ }
2844
+
2845
+ return true;
2846
+ }
2847
+ */
2230
2848
 
2231
2849
 
2232
2850
  /***/ }),
@@ -2242,24 +2860,27 @@ exports.scorePlan = scorePlan;
2242
2860
  // GLOBAL CONSTANTS
2243
2861
  //
2244
2862
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2245
- exports.DISTRICT_SPLITTING_WEIGHT = exports.COUNTY_SPLITTING_WEIGHT = exports.EQUAL_TOLERANCE = exports.OUT_OF_STATE = exports.NUMBER_OF_ITEMS_TO_REPORT = exports.NOT_ASSIGNED = exports.NORMALIZED_RANGE = exports.PRECISION = void 0;
2863
+ exports.OUT_OF_STATE = exports.NUMBER_OF_ITEMS_TO_REPORT = exports.NOT_ASSIGNED = exports.PRECISION = void 0;
2246
2864
  // Keep four decimal places for fractions [0–1], i.e.,
2247
2865
  // keep two decimal places for %'s [0–100].
2248
2866
  exports.PRECISION = 4;
2867
+ // LEGACY - Not used
2249
2868
  // Normalized scores [0-100]
2250
- exports.NORMALIZED_RANGE = 100;
2869
+ // export const NORMALIZED_RANGE: number = 100;
2251
2870
  // The dummy district ID for features not assigned districts yet
2252
2871
  exports.NOT_ASSIGNED = 0;
2253
2872
  // # of items to report as problematic (e.g., features, districts, etc.)
2254
2873
  exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
2255
2874
  // The virtual geoID for "neighbors" in other states
2256
2875
  exports.OUT_OF_STATE = "OUT_OF_STATE";
2876
+ // LEGACY - Not used
2257
2877
  // "Roughly equal" = average census block size / 2
2258
- const AVERAGE_BLOCK_SIZE = 30;
2259
- exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
2878
+ // const AVERAGE_BLOCK_SIZE = 30;
2879
+ // export const EQUAL_TOLERANCE: number = AVERAGE_BLOCK_SIZE / 2;
2880
+ // LEGACY - Not used
2260
2881
  // County & district splitting weights
2261
- exports.COUNTY_SPLITTING_WEIGHT = 0.8;
2262
- exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
2882
+ // export const COUNTY_SPLITTING_WEIGHT = 0.8;
2883
+ // export const DISTRICT_SPLITTING_WEIGHT = 1.0 - COUNTY_SPLITTING_WEIGHT;
2263
2884
  // 2020
2264
2885
  // export const SHAPES = "2010 VTD shapes";
2265
2886
 
@@ -2270,16 +2891,20 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
2270
2891
  /*!**********************!*\
2271
2892
  !*** ./src/types.ts ***!
2272
2893
  \**********************/
2273
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
2894
+ /***/ ((__unused_webpack_module, exports) => {
2274
2895
 
2275
2896
 
2276
2897
  //
2277
2898
  // TYPE DEFINITIONS
2278
2899
  //
2279
2900
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2280
- exports.estSeatProbability = void 0;
2281
- var dra_score_1 = __webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score");
2282
- Object.defineProperty(exports, "estSeatProbability", ({ enumerable: true, get: function () { return dra_score_1.estSeatProbability; } }));
2901
+ // LEGACY - DELETE
2902
+ // export
2903
+ // {
2904
+ // Profile, Scorecard,
2905
+ // // LEGACY - DELETE
2906
+ // // estSeatProbability
2907
+ // } from '@dra2020/dra-score';
2283
2908
  // END
2284
2909
 
2285
2910
 
@@ -2315,21 +2940,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
2315
2940
  return result;
2316
2941
  };
2317
2942
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2318
- exports.depthof = exports.deepCopy = exports.shallowCopy = exports.countEnumValues = exports.objectContains = exports.arrayContains = exports.getSelectObjectKeys = exports.getNumericObjectKeys = exports.getObjectKeys = exports.isArrayEmpty = exports.isSetEmpty = exports.isObjectEmpty = exports.keyExists = exports.andArray = exports.initArray = exports.maxArray = exports.minArray = exports.avgArray = exports.sumArray = exports.trim = exports.isUninhabited = exports.isWaterOnly = exports.parseGeoID = exports.getDistrict = exports.isOutOfState = exports.isInState = void 0;
2943
+ exports.depthof = exports.deepCopy = exports.shallowCopy = exports.countEnumValues = exports.objectContains = exports.arrayContains = exports.getSelectObjectKeys = exports.getNumericObjectKeys = exports.getObjectKeys = exports.isArrayEmpty = exports.isSetEmpty = exports.isObjectEmpty = exports.keyExists = exports.andArray = exports.initArray = exports.maxArray = exports.minArray = exports.avgArray = exports.sumArray = exports.trim = exports.isUninhabited = exports.isWaterOnly = exports.parseGeoID = exports.getDistrict = void 0;
2319
2944
  const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
2320
2945
  const _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
2321
2946
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2322
2947
  // PLAN HELPERS
2948
+ // LEGACY - Not used
2323
2949
  // Is a "neighbor" in state?
2324
- function isInState(geoID) {
2325
- return geoID != S.OUT_OF_STATE;
2326
- }
2327
- exports.isInState = isInState;
2950
+ // export function isInState(geoID: string): boolean
2951
+ // {
2952
+ // return geoID != S.OUT_OF_STATE;
2953
+ // }
2954
+ // LEGACY - Not used
2328
2955
  // Is a "neighbor" out of state?
2329
- function isOutOfState(geoID) {
2330
- return geoID == S.OUT_OF_STATE;
2331
- }
2332
- exports.isOutOfState = isOutOfState;
2956
+ // export function isOutOfState(geoID: string): boolean
2957
+ // {
2958
+ // return geoID == S.OUT_OF_STATE;
2959
+ // }
2333
2960
  // Get the districtID to which a geoID is assigned
2334
2961
  function getDistrict(plan, geoID) {
2335
2962
  // All geoIDs in a state *should be* assigned to a district (including the
@@ -2780,16 +3407,6 @@ module.exports = require("@dra2020/dra-analytics");
2780
3407
 
2781
3408
  /***/ }),
2782
3409
 
2783
- /***/ "@dra2020/dra-score":
2784
- /*!*************************************!*\
2785
- !*** external "@dra2020/dra-score" ***!
2786
- \*************************************/
2787
- /***/ ((module) => {
2788
-
2789
- module.exports = require("@dra2020/dra-score");
2790
-
2791
- /***/ }),
2792
-
2793
3410
  /***/ "@dra2020/dra-types":
2794
3411
  /*!*************************************!*\
2795
3412
  !*** external "@dra2020/dra-types" ***!