@dra2020/district-analytics 14.1.1 → 14.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.
- package/dist/district-analytics.js +591 -77
- package/dist/district-analytics.js.map +1 -1
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/all.d.ts +22 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/compactness.d.ts +47 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/general.d.ts +11 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/graph.d.ts +14 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/minority.d.ts +90 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/partisan.d.ts +64 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/population.d.ts +7 -0
- package/dist/node_modules/@dra2020/dra-analytics/lib/types/splitting.d.ts +9 -0
- package/dist/src/_api.d.ts +3 -2
- package/dist/src/_data.d.ts +3 -3
- package/dist/src/index.d.ts +7 -6
- package/dist/src/legacy-types.d.ts +163 -0
- package/dist/src/minority.d.ts +2 -2
- package/dist/src/political.d.ts +1 -1
- package/dist/src/score.d.ts +7 -0
- package/dist/src/settings.d.ts +0 -4
- package/dist/src/types.d.ts +1 -6
- package/dist/src/utils.d.ts +0 -2
- package/package.json +2 -2
|
@@ -46,6 +46,7 @@ exports.AnalyticsSession = void 0;
|
|
|
46
46
|
const baseclient_1 = __webpack_require__(/*! @dra2020/baseclient */ "@dra2020/baseclient");
|
|
47
47
|
// import * as DT from '@dra2020/dra-types';
|
|
48
48
|
const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
|
|
49
|
+
const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
|
|
49
50
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
50
51
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
51
52
|
const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
|
|
@@ -53,6 +54,7 @@ const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
|
53
54
|
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
54
55
|
const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
|
|
55
56
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
57
|
+
const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
|
|
56
58
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
57
59
|
// import * as S from './settings';
|
|
58
60
|
class AnalyticsSession {
|
|
@@ -106,9 +108,61 @@ class AnalyticsSession {
|
|
|
106
108
|
return false;
|
|
107
109
|
(0, preprocess_1.doPreprocessData)(this, bLog);
|
|
108
110
|
(0, analyze_1.doAnalyzeDistricts)(this, bLog);
|
|
111
|
+
// This does a little stuff that didn't get factored out into dra-score and then dra-analytics
|
|
109
112
|
(0, analyze_1.doAnalyzePlan)(this, bLog);
|
|
113
|
+
// THE MAIN ANALYTICS ARE NEXT
|
|
114
|
+
// Even though we don't save the profile (as I thought we would),
|
|
115
|
+
// we allow it to be exported, so keep generating it, and use it
|
|
116
|
+
// to gather the inputs to new analytics (as well as the legacy).
|
|
110
117
|
this._profile = (0, score_1.profilePlan)(this, bLog);
|
|
111
|
-
|
|
118
|
+
let legacyScorecard = {};
|
|
119
|
+
let legacyScorecardAlt = {};
|
|
120
|
+
let newScorecard = {};
|
|
121
|
+
// LEGACY - Run legacy analytics
|
|
122
|
+
if (this.config.legacyanalytics) {
|
|
123
|
+
if (bLog)
|
|
124
|
+
console.log("Running legacy analytics ...");
|
|
125
|
+
legacyScorecard = (0, score_1.scorePlan)(this, this._profile, bLog, overridesJSON);
|
|
126
|
+
}
|
|
127
|
+
// Construct a new/alternate scorecard
|
|
128
|
+
if (this.config.newanalytics) {
|
|
129
|
+
if (bLog)
|
|
130
|
+
console.log("Running new analytics ...");
|
|
131
|
+
newScorecard = (0, score_1.computeMetrics)(this._profile, this.getGoodShapes(), bLog); // w/o ratings
|
|
132
|
+
newScorecard = (0, score_1.rateKeyDimensions)(newScorecard, this._profile, bLog); // w/ ratings
|
|
133
|
+
legacyScorecardAlt = (0, score_1.thunkScorecard)(newScorecard, bLog);
|
|
134
|
+
// Add minority notes
|
|
135
|
+
legacyScorecardAlt.minority.details['majorityMinority'] = M.getMajorityMinority(this);
|
|
136
|
+
legacyScorecardAlt.minority.details['vraPreclearance'] = M.getVRASection5(this);
|
|
137
|
+
// Compare the new & legacy scorecards
|
|
138
|
+
(0, score_1.compareScorecards)(legacyScorecardAlt, legacyScorecard, bLog);
|
|
139
|
+
}
|
|
140
|
+
// Use the new scorecard, after creating it
|
|
141
|
+
this._scorecard = (this.config.newanalytics) ? legacyScorecardAlt : legacyScorecard;
|
|
142
|
+
// Post-scorecard housekeeping - copied from scorePlan()
|
|
143
|
+
if (this.config.newanalytics) {
|
|
144
|
+
// Before returning, create a dummy population deviation test, for
|
|
145
|
+
// doHasEqualPopulations() to use later.This is preserving the old calling sequence.
|
|
146
|
+
let test = this.getTest(4 /* PopulationDeviation */);
|
|
147
|
+
// Get the raw population deviation
|
|
148
|
+
const popDev = this._scorecard.populationDeviation.raw;
|
|
149
|
+
// Populate the test entry
|
|
150
|
+
test['score'] = popDev;
|
|
151
|
+
test['details'] = { 'maxDeviation': this._scorecard.populationDeviation.notes['maxDeviation'] };
|
|
152
|
+
// Populate the N+1 summary "district" in district.statistics
|
|
153
|
+
let totalPop = this.districts.table.totalPop;
|
|
154
|
+
let popDevPct = this.districts.table.popDevPct;
|
|
155
|
+
let totalVAP = this.districts.table.totalVAP;
|
|
156
|
+
const summaryRow = this.districts.numberOfRows() - 1;
|
|
157
|
+
totalPop[summaryRow] = this._profile.population.targetSize;
|
|
158
|
+
popDevPct[summaryRow] = popDev;
|
|
159
|
+
totalVAP[summaryRow] = Math.round(totalVAP[summaryRow] / this._profile.nDistricts);
|
|
160
|
+
// Added w/ new scorecard
|
|
161
|
+
// Use 'roughly' equal population from dra-analytics
|
|
162
|
+
let test2 = this.getTest(3 /* EqualPopulation */);
|
|
163
|
+
test2['score'] = newScorecard.populationDeviation.roughlyEqual;
|
|
164
|
+
}
|
|
165
|
+
// END main analytics
|
|
112
166
|
(0, results_1.doAnalyzePostProcessing)(this, bLog);
|
|
113
167
|
}
|
|
114
168
|
catch (e) {
|
|
@@ -118,6 +172,29 @@ class AnalyticsSession {
|
|
|
118
172
|
}
|
|
119
173
|
return true;
|
|
120
174
|
}
|
|
175
|
+
getGoodShapes() {
|
|
176
|
+
const rawShapes = this.districts.getDistrictShapes();
|
|
177
|
+
// Filter the real shapes & throw everything else away
|
|
178
|
+
let goodShapes = {};
|
|
179
|
+
goodShapes['type'] = "FeatureCollection";
|
|
180
|
+
goodShapes['features'] = [];
|
|
181
|
+
for (let i = 0; i < rawShapes.features.length; i++) {
|
|
182
|
+
const shape = rawShapes.features[i];
|
|
183
|
+
if (isAShape(shape)) {
|
|
184
|
+
const d = baseclient_1.Poly.polyDescribe(shape);
|
|
185
|
+
let f = {
|
|
186
|
+
type: 'Feature',
|
|
187
|
+
properties: { districtID: `${i + 1}` },
|
|
188
|
+
geometry: {
|
|
189
|
+
type: (d.npoly > 1) ? 'MultiPolygon' : 'Polygon',
|
|
190
|
+
coordinates: shape.geometry.coordinates
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
goodShapes.features.push(f);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return goodShapes;
|
|
197
|
+
}
|
|
121
198
|
// 11-03-2020 - Added for racially polarized voting analysis
|
|
122
199
|
// NOTE - This assumes that analyzePlan() has been run!
|
|
123
200
|
analyzeRacialPolarization(districtID, groups, bLog = false) {
|
|
@@ -190,15 +267,29 @@ class AnalyticsSession {
|
|
|
190
267
|
// Return a pointer to the the test entry for this test
|
|
191
268
|
return this.tests[testID];
|
|
192
269
|
}
|
|
270
|
+
// LEGACY - Threshold defined in dra-analytics for new analytics
|
|
193
271
|
// NOTE - Not sure why this has to be up here ...
|
|
194
272
|
populationDeviationThreshold() {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
273
|
+
let threshold;
|
|
274
|
+
if (this.config.legacyanalytics && !this.config.newanalytics) {
|
|
275
|
+
// NOTE - This assumes the plan has been profiled
|
|
276
|
+
const scorer = new Score.Scorer();
|
|
277
|
+
threshold = scorer.populationDeviationThreshold(this.legislativeDistricts); // TODO - 2020
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
threshold = dra_analytics_1.Rate.popdevThreshold(this.legislativeDistricts);
|
|
281
|
+
}
|
|
198
282
|
return threshold;
|
|
199
283
|
}
|
|
200
284
|
}
|
|
201
285
|
exports.AnalyticsSession = AnalyticsSession;
|
|
286
|
+
function isAShape(poly) {
|
|
287
|
+
if (poly == null)
|
|
288
|
+
return false;
|
|
289
|
+
if (baseclient_1.Poly.polyNull(poly))
|
|
290
|
+
return false;
|
|
291
|
+
return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
|
|
292
|
+
}
|
|
202
293
|
|
|
203
294
|
|
|
204
295
|
/***/ }),
|
|
@@ -238,7 +329,8 @@ const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2
|
|
|
238
329
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
239
330
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
240
331
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
241
|
-
const
|
|
332
|
+
const dra_analytics_2 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
|
|
333
|
+
// import { fptpWin } from './political'
|
|
242
334
|
// DEBUG COUNTERS
|
|
243
335
|
let nMissingDataset = 0;
|
|
244
336
|
let nMissingProperty = 0;
|
|
@@ -472,7 +564,7 @@ class Districts {
|
|
|
472
564
|
demPct = demVotes / totVotes;
|
|
473
565
|
repPct = repVotes / totVotes;
|
|
474
566
|
othPct = othVotes / totVotes;
|
|
475
|
-
DemSeat =
|
|
567
|
+
DemSeat = dra_analytics_2.Partisan.fptpWin(demPct);
|
|
476
568
|
}
|
|
477
569
|
// Total minority VAP
|
|
478
570
|
let minorityPop = totalVAP - whitePop;
|
|
@@ -1442,6 +1534,7 @@ function isAShape(poly) {
|
|
|
1442
1534
|
return poly.geometry && poly.geometry.coordinates && !U.isArrayEmpty(poly.geometry.coordinates);
|
|
1443
1535
|
}
|
|
1444
1536
|
// SCORE KIWYSI COMPACTNESS
|
|
1537
|
+
// LEGACY
|
|
1445
1538
|
function scoreKIWYSICompactness(s, bLog = false) {
|
|
1446
1539
|
const rawShapes = s.districts.getDistrictShapes();
|
|
1447
1540
|
// Filter the real shapes & throw everything else away
|
|
@@ -1521,17 +1614,20 @@ function doHasEqualPopulations(s, bLog = false) {
|
|
|
1521
1614
|
let popDevTest = s.getTest(4 /* PopulationDeviation */);
|
|
1522
1615
|
const popDevPct = popDevTest['score'];
|
|
1523
1616
|
const popDevNormalized = popDevTest['normalizedScore'];
|
|
1524
|
-
//
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
test
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1617
|
+
// LEGACY - Has 'roughly' equal populations is calculated in dra-analytics
|
|
1618
|
+
if (s.config.legacyanalytics && !s.config.newanalytics) {
|
|
1619
|
+
// 09-19-2020 - Added to catch edge case of only one non-empty district
|
|
1620
|
+
const p = s._profile;
|
|
1621
|
+
const totPopByDistrict = p.population.byDistrict.filter(x => x > 0);
|
|
1622
|
+
const bTwoOrMoreDistricts = (totPopByDistrict.length > 1) ? true : false;
|
|
1623
|
+
// Populate the test entry
|
|
1624
|
+
if (bTwoOrMoreDistricts && (popDevNormalized > 0)) {
|
|
1625
|
+
test['score'] = true;
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
test['score'] = false;
|
|
1629
|
+
}
|
|
1630
|
+
} // end
|
|
1535
1631
|
test['details']['deviation'] = popDevPct;
|
|
1536
1632
|
test['details']['thresholds'] = popDevTest['details']['scale'];
|
|
1537
1633
|
// Populate the N+1 summary "district" in district.statistics
|
|
@@ -1566,7 +1662,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
1566
1662
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
1567
1663
|
};
|
|
1568
1664
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1569
|
-
exports.uncertaintyOfMembership = exports.effectiveSplits = exports.avgSVError = exports.isAntimajoritarian = exports.ratePartisanBias = exports.inferSelectedMinority = exports.fieldForFeature = exports.geoIDForFeature = void 0;
|
|
1665
|
+
exports.uncertaintyOfMembership = exports.effectiveSplits = exports.avgSVError = exports.isAntimajoritarian = exports.ratePartisanBias = exports.estSeatProbability = exports.inferSelectedMinority = exports.fieldForFeature = exports.geoIDForFeature = void 0;
|
|
1570
1666
|
__exportStar(__webpack_require__(/*! ./_api */ "./src/_api.ts"), exports);
|
|
1571
1667
|
var _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
1572
1668
|
Object.defineProperty(exports, "geoIDForFeature", ({ enumerable: true, get: function () { return _data_1.geoIDForFeature; } }));
|
|
@@ -1577,6 +1673,7 @@ __exportStar(__webpack_require__(/*! ./types */ "./src/types.ts"), exports);
|
|
|
1577
1673
|
__exportStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"), exports);
|
|
1578
1674
|
// Re-export RPV types and COI splitting functions
|
|
1579
1675
|
const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
|
|
1676
|
+
exports.estSeatProbability = dra_analytics_1.Partisan.estSeatProbability;
|
|
1580
1677
|
exports.ratePartisanBias = dra_analytics_1.Rate.ratePartisanBias;
|
|
1581
1678
|
exports.isAntimajoritarian = dra_analytics_1.Rate.isAntimajoritarian;
|
|
1582
1679
|
exports.avgSVError = dra_analytics_1.Rate.avgSVError;
|
|
@@ -1642,28 +1739,6 @@ exports.doAnalyzeRacialPolarization = doAnalyzeRacialPolarization;
|
|
|
1642
1739
|
// 11-18-2020 - Moved RPV to racial-voting package.
|
|
1643
1740
|
|
|
1644
1741
|
|
|
1645
|
-
/***/ }),
|
|
1646
|
-
|
|
1647
|
-
/***/ "./src/political.ts":
|
|
1648
|
-
/*!**************************!*\
|
|
1649
|
-
!*** ./src/political.ts ***!
|
|
1650
|
-
\**************************/
|
|
1651
|
-
/***/ ((__unused_webpack_module, exports) => {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
//
|
|
1655
|
-
// FAIR/PROPORTIONAL
|
|
1656
|
-
//
|
|
1657
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1658
|
-
exports.fptpWin = void 0;
|
|
1659
|
-
function fptpWin(demPct) {
|
|
1660
|
-
// Vote shares should be fractions in the range [0.0 – 1.0]
|
|
1661
|
-
//assert((demPct <= 1.0) && (demPct >= 0.0));
|
|
1662
|
-
return ((demPct > 0.5) ? 1 : 0);
|
|
1663
|
-
}
|
|
1664
|
-
exports.fptpWin = fptpWin;
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
1742
|
/***/ }),
|
|
1668
1743
|
|
|
1669
1744
|
/***/ "./src/preprocess.ts":
|
|
@@ -2043,6 +2118,7 @@ function doAnalyzePostProcessing(s, bLog = false) {
|
|
|
2043
2118
|
scorecard.splitting.details['countiesWithSplits'] = simpleSplits['details']['countiesWithSplits'];
|
|
2044
2119
|
// NOTE - Add split precincts in dra-client directly
|
|
2045
2120
|
// Derive secondary tests
|
|
2121
|
+
// Note - The only secondary test is 'roughly equal population (true/false)
|
|
2046
2122
|
(0, analyze_1.doDeriveSecondaryTests)(s, bLog);
|
|
2047
2123
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
2048
2124
|
s.bPostProcessingDone = true;
|
|
@@ -2082,11 +2158,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
2082
2158
|
return result;
|
|
2083
2159
|
};
|
|
2084
2160
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2085
|
-
exports.scorePlan = exports.getStatewideDemographics = exports.profilePlan = void 0;
|
|
2161
|
+
exports.compareScorecards = exports.thunkScorecard = exports.rateKeyDimensions = exports.computeMetrics = exports.scorePlan = exports.getStatewideDemographics = exports.profilePlan = void 0;
|
|
2086
2162
|
const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
|
|
2087
2163
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
2088
2164
|
const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
|
|
2089
2165
|
const C = __importStar(__webpack_require__(/*! ./compact */ "./src/compact.ts"));
|
|
2166
|
+
const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
|
|
2090
2167
|
// PROFILE A PLAN
|
|
2091
2168
|
const KEEP_DECIMALS = 6;
|
|
2092
2169
|
function profilePlan(s, bLog = false) {
|
|
@@ -2198,26 +2275,30 @@ function getStatewideDemographics(s, bLog = false) {
|
|
|
2198
2275
|
return demographics;
|
|
2199
2276
|
}
|
|
2200
2277
|
exports.getStatewideDemographics = getStatewideDemographics;
|
|
2201
|
-
// SCORE A PLAN
|
|
2278
|
+
// SCORE A PLAN - Legacy using dra-score
|
|
2279
|
+
// LEGACY
|
|
2202
2280
|
function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
2203
2281
|
let scorer = new Score.Scorer();
|
|
2204
2282
|
const scorecard = scorer.score(p, overridesJSON);
|
|
2205
|
-
//
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2283
|
+
// LEGACY POST-SCORECARD HOUSEKEEPING
|
|
2284
|
+
if (s.config.legacyanalytics && !s.config.newanalytics) {
|
|
2285
|
+
// Before returning, create a dummy population deviation test, for
|
|
2286
|
+
// doHasEqualPopulations() to use later.This is preserving the old calling sequence.
|
|
2287
|
+
let test = s.getTest(4 /* PopulationDeviation */);
|
|
2288
|
+
// Get the raw population deviation
|
|
2289
|
+
const popDev = scorecard.populationDeviation.raw;
|
|
2290
|
+
// Populate the test entry
|
|
2291
|
+
test['score'] = popDev;
|
|
2292
|
+
test['details'] = { 'maxDeviation': scorecard.populationDeviation.notes['maxDeviation'] };
|
|
2293
|
+
// Populate the N+1 summary "district" in district.statistics
|
|
2294
|
+
let totalPop = s.districts.table.totalPop;
|
|
2295
|
+
let popDevPct = s.districts.table.popDevPct;
|
|
2296
|
+
let totalVAP = s.districts.table.totalVAP;
|
|
2297
|
+
const summaryRow = s.districts.numberOfRows() - 1;
|
|
2298
|
+
totalPop[summaryRow] = p.population.targetSize;
|
|
2299
|
+
popDevPct[summaryRow] = popDev;
|
|
2300
|
+
totalVAP[summaryRow] = Math.round(totalVAP[summaryRow] / p.nDistricts);
|
|
2301
|
+
} // end
|
|
2221
2302
|
// Add minority notes
|
|
2222
2303
|
scorecard.minority.details['majorityMinority'] = M.getMajorityMinority(s);
|
|
2223
2304
|
scorecard.minority.details['vraPreclearance'] = M.getVRASection5(s);
|
|
@@ -2232,6 +2313,9 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
|
2232
2313
|
const nDistricts = byDistrict.length;
|
|
2233
2314
|
for (let districtID = 1; districtID <= nDistricts; districtID++) {
|
|
2234
2315
|
byDistrict[districtID - 1].kiwysiScore = kiwysiScores[districtID - 1];
|
|
2316
|
+
// 09-17-21 - Fix the normalized Polsby–Popper score!
|
|
2317
|
+
const rawPolsby = byDistrict[districtID - 1].rawPolsby;
|
|
2318
|
+
byDistrict[districtID - 1].normalizedPolsby = dra_analytics_1.Rate.ratePolsby(rawPolsby);
|
|
2235
2319
|
}
|
|
2236
2320
|
}
|
|
2237
2321
|
catch (e) {
|
|
@@ -2242,6 +2326,434 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
|
2242
2326
|
return scorecard;
|
|
2243
2327
|
}
|
|
2244
2328
|
exports.scorePlan = scorePlan;
|
|
2329
|
+
// SCORE A PLAN - New using dra-analytics
|
|
2330
|
+
function computeMetrics(p, districtShapes, bLog = false) {
|
|
2331
|
+
if (bLog)
|
|
2332
|
+
console.log("Computing metrics ...");
|
|
2333
|
+
const bLegislative = p.bStateLeg;
|
|
2334
|
+
// Calculate bias & responsiveness metrics ...
|
|
2335
|
+
const byDistrictVf = p.partisanship.byDistrict;
|
|
2336
|
+
const statewideVf = p.partisanship.statewide;
|
|
2337
|
+
let _pS = dra_analytics_1.Partisan.makePartisanScorecard(statewideVf, byDistrictVf, bLog);
|
|
2338
|
+
// Calculate minority representation metrics ...
|
|
2339
|
+
const statewideDemos = p.demographics.statewide;
|
|
2340
|
+
const byDistrictDemos = p.demographics.byDistrict;
|
|
2341
|
+
let _mS = dra_analytics_1.Minority.makeMinorityScorecard(statewideDemos, byDistrictDemos, bLog);
|
|
2342
|
+
// Calculate compactness metrics ...
|
|
2343
|
+
let _cS = dra_analytics_1.Compactness.makeCompactnessScorecard(districtShapes, bLog);
|
|
2344
|
+
// Calculate county-district splitting metrics ...
|
|
2345
|
+
const CxD = p.counties;
|
|
2346
|
+
const _sS = dra_analytics_1.Splitting.makeSplittingScorecard(CxD, bLog);
|
|
2347
|
+
// Calculate population deviation-related metrics ...
|
|
2348
|
+
const totPopByDistrict = p.population.byDistrict;
|
|
2349
|
+
const targetSize = p.population.targetSize;
|
|
2350
|
+
const _pdS = dra_analytics_1.Equal.makePopulationScorecard(totPopByDistrict, targetSize, bLegislative, bLog);
|
|
2351
|
+
const details = {};
|
|
2352
|
+
// Assemble the pieces into new scorecard
|
|
2353
|
+
const scorecard = {
|
|
2354
|
+
partisan: _pS,
|
|
2355
|
+
minority: _mS,
|
|
2356
|
+
compactness: _cS,
|
|
2357
|
+
splitting: _sS,
|
|
2358
|
+
populationDeviation: _pdS,
|
|
2359
|
+
details: details,
|
|
2360
|
+
scratchpad: {} // Hack to pass legacy values between processing steps
|
|
2361
|
+
};
|
|
2362
|
+
return scorecard;
|
|
2363
|
+
}
|
|
2364
|
+
exports.computeMetrics = computeMetrics;
|
|
2365
|
+
function rateKeyDimensions(scorecard, p, bLog = false) {
|
|
2366
|
+
if (bLog)
|
|
2367
|
+
console.log("Rating key dimensions ...");
|
|
2368
|
+
const bLegislative = p.bStateLeg;
|
|
2369
|
+
// Rate proportionality
|
|
2370
|
+
const statewideVf = p.partisanship.statewide;
|
|
2371
|
+
const Sf = scorecard.partisan.bias.estSf;
|
|
2372
|
+
scorecard.partisan.bias.score = dra_analytics_1.Rate.rateProportionality(scorecard.partisan.bias.deviation, statewideVf, Sf);
|
|
2373
|
+
// Rate competititveness
|
|
2374
|
+
scorecard.partisan.responsiveness.score = dra_analytics_1.Rate.rateCompetitiveness(scorecard.partisan.responsiveness.cDf);
|
|
2375
|
+
// Rate minority representation
|
|
2376
|
+
const rawOd = scorecard.minority.opportunityDistricts;
|
|
2377
|
+
const pOd = scorecard.minority.proportionalOpportunities;
|
|
2378
|
+
const rawCd = scorecard.minority.coalitionDistricts;
|
|
2379
|
+
const pCd = scorecard.minority.proportionalCoalitions;
|
|
2380
|
+
scorecard.minority.score = dra_analytics_1.Rate.rateMinorityRepresentation(rawOd, pOd, rawCd, pCd);
|
|
2381
|
+
// Rate compactness
|
|
2382
|
+
const avgReock = scorecard.compactness.avgReock;
|
|
2383
|
+
const avgPolsby = scorecard.compactness.avgPolsby;
|
|
2384
|
+
const reockRating = dra_analytics_1.Rate.rateReock(avgReock);
|
|
2385
|
+
const polsbyRating = dra_analytics_1.Rate.ratePolsby(avgPolsby);
|
|
2386
|
+
scorecard.compactness.score = dra_analytics_1.Rate.rateCompactness(reockRating, polsbyRating);
|
|
2387
|
+
// Rate county- & district-splitting
|
|
2388
|
+
const rawCountySplitting = scorecard.splitting.county;
|
|
2389
|
+
const rawDistrictSplitting = scorecard.splitting.district;
|
|
2390
|
+
const nCounties = p.nCounties;
|
|
2391
|
+
const nDistricts = p.nDistricts;
|
|
2392
|
+
const countyRating = dra_analytics_1.Rate.rateCountySplitting(rawCountySplitting, nCounties, nDistricts, bLegislative);
|
|
2393
|
+
const districtRating = dra_analytics_1.Rate.rateDistrictSplitting(rawDistrictSplitting, bLegislative);
|
|
2394
|
+
const unadjusted = dra_analytics_1.Rate.rateSplitting(countyRating, districtRating);
|
|
2395
|
+
scorecard.splitting.score = dra_analytics_1.Rate.adjustSplittingRating(unadjusted, rawCountySplitting, rawDistrictSplitting);
|
|
2396
|
+
// Rate population deviation
|
|
2397
|
+
const rawDeviation = scorecard.populationDeviation.deviation;
|
|
2398
|
+
scorecard.populationDeviation.score = dra_analytics_1.Rate.ratePopulationDeviation(rawDeviation, bLegislative);
|
|
2399
|
+
// Squirrel away normalized compactness & splitting ratings for the legacy scorecard
|
|
2400
|
+
const keep = {
|
|
2401
|
+
reockScore: reockRating,
|
|
2402
|
+
polsbyScore: polsbyRating,
|
|
2403
|
+
countyScore: countyRating,
|
|
2404
|
+
districtScore: districtRating
|
|
2405
|
+
};
|
|
2406
|
+
scorecard.scratchpad = keep;
|
|
2407
|
+
return scorecard;
|
|
2408
|
+
}
|
|
2409
|
+
exports.rateKeyDimensions = rateKeyDimensions;
|
|
2410
|
+
function thunkScorecard(newScorecard, bLog = false) {
|
|
2411
|
+
if (bLog)
|
|
2412
|
+
console.log("Thunking new scorecard into legacy structure ...");
|
|
2413
|
+
const scratchpad = newScorecard.scratchpad;
|
|
2414
|
+
// Partisan scorecard
|
|
2415
|
+
const pS = U.deepCopy(newScorecard.partisan);
|
|
2416
|
+
// Minority scorecard
|
|
2417
|
+
const mS = U.deepCopy(newScorecard.minority);
|
|
2418
|
+
// Compactness scorecard
|
|
2419
|
+
const reockM = {
|
|
2420
|
+
raw: newScorecard.compactness.avgReock,
|
|
2421
|
+
normalized: scratchpad.reockScore,
|
|
2422
|
+
notes: {}
|
|
2423
|
+
};
|
|
2424
|
+
const polsbyM = {
|
|
2425
|
+
raw: newScorecard.compactness.avgPolsby,
|
|
2426
|
+
normalized: scratchpad.polsbyScore,
|
|
2427
|
+
notes: {}
|
|
2428
|
+
};
|
|
2429
|
+
let cS = {
|
|
2430
|
+
score: newScorecard.compactness.score,
|
|
2431
|
+
reock: reockM,
|
|
2432
|
+
polsby: polsbyM,
|
|
2433
|
+
details: U.deepCopy(newScorecard.compactness.details)
|
|
2434
|
+
};
|
|
2435
|
+
// Relocate byDistrict compactness #'s
|
|
2436
|
+
cS.details.byDistrict = U.deepCopy(newScorecard.compactness.byDistrict);
|
|
2437
|
+
// Add KIWYSI compactness score
|
|
2438
|
+
cS.details['kiwysi'] = newScorecard.compactness.avgKWIWYSI;
|
|
2439
|
+
// Splitting scorecard
|
|
2440
|
+
const countyM = {
|
|
2441
|
+
raw: newScorecard.splitting.county,
|
|
2442
|
+
normalized: scratchpad.countyScore,
|
|
2443
|
+
notes: {}
|
|
2444
|
+
};
|
|
2445
|
+
const districtM = {
|
|
2446
|
+
raw: newScorecard.splitting.district,
|
|
2447
|
+
normalized: scratchpad.districtScore,
|
|
2448
|
+
notes: {}
|
|
2449
|
+
};
|
|
2450
|
+
const sS = {
|
|
2451
|
+
score: newScorecard.splitting.score,
|
|
2452
|
+
county: countyM,
|
|
2453
|
+
district: districtM,
|
|
2454
|
+
details: U.deepCopy(newScorecard.splitting.details)
|
|
2455
|
+
};
|
|
2456
|
+
// Population (equality) scorecard
|
|
2457
|
+
const pdM = {
|
|
2458
|
+
raw: newScorecard.populationDeviation.deviation,
|
|
2459
|
+
normalized: newScorecard.populationDeviation.score,
|
|
2460
|
+
notes: newScorecard.populationDeviation.notes
|
|
2461
|
+
};
|
|
2462
|
+
const pdS = pdM;
|
|
2463
|
+
const scorecard = {
|
|
2464
|
+
partisan: pS,
|
|
2465
|
+
minority: mS,
|
|
2466
|
+
compactness: cS,
|
|
2467
|
+
splitting: sS,
|
|
2468
|
+
populationDeviation: pdS,
|
|
2469
|
+
details: newScorecard.details
|
|
2470
|
+
};
|
|
2471
|
+
return scorecard;
|
|
2472
|
+
}
|
|
2473
|
+
exports.thunkScorecard = thunkScorecard;
|
|
2474
|
+
function compareScorecards(altLegacyScorecard, legacyScorecard, bLog = false) {
|
|
2475
|
+
if (bLog)
|
|
2476
|
+
console.log("Comparing new & legacy scorecards ...");
|
|
2477
|
+
// A pretty loose tolerance, because we're not trimming values in the new analytics
|
|
2478
|
+
const DECIMAL_PLACES = 2;
|
|
2479
|
+
const DEFAULT_TOLERANCE = toleranceFn(DECIMAL_PLACES);
|
|
2480
|
+
// COMPARE PARTISAN SCORECARD
|
|
2481
|
+
// Compare BIAS section
|
|
2482
|
+
matchFloats("bestS", altLegacyScorecard.partisan.bias.bestS, legacyScorecard.partisan.bias.bestS, DEFAULT_TOLERANCE);
|
|
2483
|
+
matchFloats("bestSf", altLegacyScorecard.partisan.bias.bestSf, legacyScorecard.partisan.bias.bestSf, DEFAULT_TOLERANCE);
|
|
2484
|
+
matchFloats("estS", altLegacyScorecard.partisan.bias.estS, legacyScorecard.partisan.bias.estS, DEFAULT_TOLERANCE);
|
|
2485
|
+
matchFloats("estSf", altLegacyScorecard.partisan.bias.estSf, legacyScorecard.partisan.bias.estSf, DEFAULT_TOLERANCE);
|
|
2486
|
+
matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
|
|
2487
|
+
matchInts("proportionality/score", altLegacyScorecard.partisan.bias.score, legacyScorecard.partisan.bias.score);
|
|
2488
|
+
matchFloats("tOf", altLegacyScorecard.partisan.bias.tOf, legacyScorecard.partisan.bias.tOf, DEFAULT_TOLERANCE);
|
|
2489
|
+
matchFloats("fptpS", altLegacyScorecard.partisan.bias.fptpS, legacyScorecard.partisan.bias.fptpS, DEFAULT_TOLERANCE);
|
|
2490
|
+
matchFloats("bS50", altLegacyScorecard.partisan.bias.bS50, legacyScorecard.partisan.bias.bS50, DEFAULT_TOLERANCE);
|
|
2491
|
+
matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
|
|
2492
|
+
matchUndefinableFloats("decl", altLegacyScorecard.partisan.bias.decl, legacyScorecard.partisan.bias.decl, toleranceFn(1));
|
|
2493
|
+
let rvPointsAlt = altLegacyScorecard.partisan.bias.rvPoints;
|
|
2494
|
+
let rvPoints = legacyScorecard.partisan.bias.rvPoints;
|
|
2495
|
+
if (matchUndefinedness("rvPoints", rvPointsAlt, rvPoints)) {
|
|
2496
|
+
rvPointsAlt = rvPointsAlt;
|
|
2497
|
+
rvPoints = rvPoints;
|
|
2498
|
+
matchFloats("rvPoints/Sb", rvPointsAlt.Sb, rvPoints.Sb, DEFAULT_TOLERANCE);
|
|
2499
|
+
matchFloats("rvPoints/Ra", rvPointsAlt.Ra, rvPoints.Ra, DEFAULT_TOLERANCE);
|
|
2500
|
+
matchFloats("rvPoints/Rb", rvPointsAlt.Rb, rvPoints.Rb, DEFAULT_TOLERANCE);
|
|
2501
|
+
matchFloats("rvPoints/Va", rvPointsAlt.Va, rvPoints.Va, DEFAULT_TOLERANCE);
|
|
2502
|
+
matchFloats("rvPoints/Vb", rvPointsAlt.Vb, rvPoints.Vb, DEFAULT_TOLERANCE);
|
|
2503
|
+
}
|
|
2504
|
+
matchFloats("gSym", altLegacyScorecard.partisan.bias.gSym, legacyScorecard.partisan.bias.gSym, DEFAULT_TOLERANCE);
|
|
2505
|
+
matchUndefinableFloats("gamma", altLegacyScorecard.partisan.bias.gamma, legacyScorecard.partisan.bias.gamma, DEFAULT_TOLERANCE);
|
|
2506
|
+
matchFloats("eG", altLegacyScorecard.partisan.bias.eG, legacyScorecard.partisan.bias.eG, DEFAULT_TOLERANCE);
|
|
2507
|
+
matchUndefinableFloats("bSV", altLegacyScorecard.partisan.bias.bSV, legacyScorecard.partisan.bias.bSV, DEFAULT_TOLERANCE);
|
|
2508
|
+
matchFloats("prop", altLegacyScorecard.partisan.bias.prop, legacyScorecard.partisan.bias.prop, DEFAULT_TOLERANCE);
|
|
2509
|
+
matchFloats("mMs", altLegacyScorecard.partisan.bias.mMs, legacyScorecard.partisan.bias.mMs, DEFAULT_TOLERANCE);
|
|
2510
|
+
matchFloats("mMd", altLegacyScorecard.partisan.bias.mMd, legacyScorecard.partisan.bias.mMd, DEFAULT_TOLERANCE);
|
|
2511
|
+
matchUndefinableFloats("lO", altLegacyScorecard.partisan.bias.lO, legacyScorecard.partisan.bias.lO, DEFAULT_TOLERANCE);
|
|
2512
|
+
// Compare Impact section
|
|
2513
|
+
matchFloats("unearnedS", altLegacyScorecard.partisan.impact.unearnedS, legacyScorecard.partisan.impact.unearnedS, DEFAULT_TOLERANCE);
|
|
2514
|
+
// Note - We don't use the impact score, so we don't compute it in the new scorecard.
|
|
2515
|
+
// matchInts("impact/score", altLegacyScorecard.partisan.impact.score as number, legacyScorecard.partisan.impact.score);
|
|
2516
|
+
// Compare Responsiveness section
|
|
2517
|
+
matchUndefinableFloats("bigR", altLegacyScorecard.partisan.responsiveness.bigR, legacyScorecard.partisan.responsiveness.bigR, DEFAULT_TOLERANCE);
|
|
2518
|
+
matchUndefinableFloats("littleR", altLegacyScorecard.partisan.responsiveness.littleR, legacyScorecard.partisan.responsiveness.littleR, DEFAULT_TOLERANCE);
|
|
2519
|
+
matchUndefinableFloats("mIR", altLegacyScorecard.partisan.responsiveness.mIR, legacyScorecard.partisan.responsiveness.mIR, DEFAULT_TOLERANCE);
|
|
2520
|
+
matchFloats("rD", altLegacyScorecard.partisan.responsiveness.rD, legacyScorecard.partisan.responsiveness.rD, DEFAULT_TOLERANCE);
|
|
2521
|
+
matchFloats("rDf", altLegacyScorecard.partisan.responsiveness.rDf, legacyScorecard.partisan.responsiveness.rDf, DEFAULT_TOLERANCE);
|
|
2522
|
+
matchFloats("cSimple", altLegacyScorecard.partisan.responsiveness.cSimple, legacyScorecard.partisan.responsiveness.cSimple, DEFAULT_TOLERANCE);
|
|
2523
|
+
matchFloats("cD", altLegacyScorecard.partisan.responsiveness.cD, legacyScorecard.partisan.responsiveness.cD, DEFAULT_TOLERANCE);
|
|
2524
|
+
matchFloats("cDf", altLegacyScorecard.partisan.responsiveness.cDf, legacyScorecard.partisan.responsiveness.cDf, DEFAULT_TOLERANCE);
|
|
2525
|
+
matchInts("competitiveness/score", altLegacyScorecard.partisan.responsiveness.score, legacyScorecard.partisan.responsiveness.score);
|
|
2526
|
+
matchPointArrays("dSVpoints", altLegacyScorecard.partisan.dSVpoints, legacyScorecard.partisan.dSVpoints, DEFAULT_TOLERANCE);
|
|
2527
|
+
matchPointArrays("rSVpoints", altLegacyScorecard.partisan.rSVpoints, legacyScorecard.partisan.rSVpoints, DEFAULT_TOLERANCE);
|
|
2528
|
+
matchUndefinableFloats("averageDVf", altLegacyScorecard.partisan.averageDVf, legacyScorecard.partisan.averageDVf, DEFAULT_TOLERANCE);
|
|
2529
|
+
matchUndefinableFloats("averageRVf", altLegacyScorecard.partisan.averageRVf, legacyScorecard.partisan.averageRVf, DEFAULT_TOLERANCE);
|
|
2530
|
+
matchDicts("partisan/details", altLegacyScorecard.partisan.details, legacyScorecard.partisan.details);
|
|
2531
|
+
// COMPARE MINORITY SCORECARD
|
|
2532
|
+
matchObjectsAndArrays("pivotByDemographic", altLegacyScorecard.minority.pivotByDemographic, legacyScorecard.minority.pivotByDemographic);
|
|
2533
|
+
matchFloats("opportunityDistricts", altLegacyScorecard.minority.opportunityDistricts, legacyScorecard.minority.opportunityDistricts, DEFAULT_TOLERANCE);
|
|
2534
|
+
matchFloats("coalitionDistricts", altLegacyScorecard.minority.coalitionDistricts, legacyScorecard.minority.coalitionDistricts, DEFAULT_TOLERANCE);
|
|
2535
|
+
matchInts("minority/score", altLegacyScorecard.minority.score, legacyScorecard.minority.score);
|
|
2536
|
+
matchDicts("minority/details", altLegacyScorecard.minority.details, legacyScorecard.minority.details);
|
|
2537
|
+
// COMPARE COMPACTNESS SCORECARD
|
|
2538
|
+
matchFloats("reock", altLegacyScorecard.compactness.reock.raw, legacyScorecard.compactness.reock.raw, DEFAULT_TOLERANCE);
|
|
2539
|
+
matchInts("reock/normalized", altLegacyScorecard.compactness.reock.normalized, legacyScorecard.compactness.reock.normalized);
|
|
2540
|
+
matchFloats("polsby", altLegacyScorecard.compactness.polsby.raw, legacyScorecard.compactness.polsby.raw, DEFAULT_TOLERANCE);
|
|
2541
|
+
matchInts("polsby/normalized", altLegacyScorecard.compactness.polsby.normalized, legacyScorecard.compactness.polsby.normalized);
|
|
2542
|
+
matchInts("compactness/score", altLegacyScorecard.compactness.score, legacyScorecard.compactness.score);
|
|
2543
|
+
// Compare 'byDistrict' results separately
|
|
2544
|
+
const _altCompactnessByDistrict = dra_analytics_1.Utils.deepCopy(altLegacyScorecard.compactness.details['byDistrict']);
|
|
2545
|
+
const _CompactnessByDistrict = dra_analytics_1.Utils.deepCopy(legacyScorecard.compactness.details['byDistrict']);
|
|
2546
|
+
const _altCompactnessDetails = dra_analytics_1.Utils.deepCopy(altLegacyScorecard.compactness.details);
|
|
2547
|
+
const _CompactnessDetails = dra_analytics_1.Utils.deepCopy(legacyScorecard.compactness.details);
|
|
2548
|
+
delete _altCompactnessDetails['byDistrict'];
|
|
2549
|
+
delete _CompactnessDetails['byDistrict'];
|
|
2550
|
+
matchDicts("compactness/details", _altCompactnessDetails, _CompactnessDetails);
|
|
2551
|
+
matchCompactnessByDistrict("compactness/byDistrict", _altCompactnessByDistrict, _CompactnessByDistrict, DEFAULT_TOLERANCE);
|
|
2552
|
+
// COMPARE SPLITTING SCORECARD
|
|
2553
|
+
matchFloats("county", altLegacyScorecard.splitting.county.raw, legacyScorecard.splitting.county.raw, DEFAULT_TOLERANCE);
|
|
2554
|
+
matchInts("county/normalized", altLegacyScorecard.splitting.county.normalized, legacyScorecard.splitting.county.normalized);
|
|
2555
|
+
matchFloats("district", altLegacyScorecard.splitting.district.raw, legacyScorecard.splitting.district.raw, DEFAULT_TOLERANCE);
|
|
2556
|
+
matchInts("district/normalized", altLegacyScorecard.splitting.district.normalized, legacyScorecard.splitting.district.normalized);
|
|
2557
|
+
matchInts("splitting/score", altLegacyScorecard.splitting.score, legacyScorecard.splitting.score);
|
|
2558
|
+
matchDicts("splitting/details", altLegacyScorecard.splitting.details, legacyScorecard.splitting.details);
|
|
2559
|
+
// COMPARE POPULATION SCORECARD
|
|
2560
|
+
matchFloats("popdev", altLegacyScorecard.populationDeviation.raw, legacyScorecard.populationDeviation.raw, DEFAULT_TOLERANCE);
|
|
2561
|
+
matchInts("popdev/score", altLegacyScorecard.populationDeviation.normalized, legacyScorecard.populationDeviation.normalized);
|
|
2562
|
+
matchDicts("popdev/notes", altLegacyScorecard.populationDeviation.notes, legacyScorecard.populationDeviation.notes);
|
|
2563
|
+
matchDicts("details", altLegacyScorecard.details, legacyScorecard.details);
|
|
2564
|
+
}
|
|
2565
|
+
exports.compareScorecards = compareScorecards;
|
|
2566
|
+
// Matching helpers
|
|
2567
|
+
function matchFloats(property, received, good, tolerance) {
|
|
2568
|
+
if (dra_analytics_1.Utils.areRoughlyEqual(received, good, tolerance)) {
|
|
2569
|
+
return true;
|
|
2570
|
+
}
|
|
2571
|
+
else {
|
|
2572
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2573
|
+
return false;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
function toleranceFn(places) {
|
|
2577
|
+
return 0.5 / Math.pow(10, places);
|
|
2578
|
+
}
|
|
2579
|
+
function matchInts(property, received, good) {
|
|
2580
|
+
if (received == good) {
|
|
2581
|
+
return true;
|
|
2582
|
+
}
|
|
2583
|
+
else {
|
|
2584
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2585
|
+
return false;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
function matchUndefinableFloats(property, received, good, tolerance) {
|
|
2589
|
+
if ((received === undefined) && (good === undefined))
|
|
2590
|
+
return true;
|
|
2591
|
+
if ((received === undefined) || (good === undefined)) {
|
|
2592
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2593
|
+
return false;
|
|
2594
|
+
}
|
|
2595
|
+
return matchFloats(property, received, good, tolerance);
|
|
2596
|
+
}
|
|
2597
|
+
// https://stackoverflow.com/questions/13142968/deep-comparison-of-objects-arrays
|
|
2598
|
+
function matchObjectsAndArrays(property, received, good) {
|
|
2599
|
+
if ((received === undefined) && (good === undefined))
|
|
2600
|
+
return true; // Both undefined
|
|
2601
|
+
if ((received === undefined) || (good === undefined)) { // One undefined but not the other
|
|
2602
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0))
|
|
2606
|
+
return true; // Both empty
|
|
2607
|
+
if (JSON.stringify(received) === JSON.stringify(good))
|
|
2608
|
+
return true; // Contents match
|
|
2609
|
+
console.log(`${property} objects or arrays do not match.`); // Contents don't match
|
|
2610
|
+
return false;
|
|
2611
|
+
}
|
|
2612
|
+
function matchDicts(property, received, good) {
|
|
2613
|
+
if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0))
|
|
2614
|
+
return true; // Both empty
|
|
2615
|
+
const receivedStr = JSON.stringify(received);
|
|
2616
|
+
const goodStr = JSON.stringify(good);
|
|
2617
|
+
if (receivedStr === goodStr)
|
|
2618
|
+
return true; // Contents match
|
|
2619
|
+
console.log(`${property} does not match: ${goodStr} expected. Received ${receivedStr}.`); // Contents don't match
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
function matchUndefinedness(property, received, good) {
|
|
2623
|
+
if ((received === undefined) && (good === undefined))
|
|
2624
|
+
return true;
|
|
2625
|
+
if ((received === undefined) || (good === undefined))
|
|
2626
|
+
return false;
|
|
2627
|
+
return true;
|
|
2628
|
+
}
|
|
2629
|
+
function matchPointArrays(property, received, good, tolerance) {
|
|
2630
|
+
if (received.length != good.length) {
|
|
2631
|
+
console.log(`${property} does not match: Different number of points.`);
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
for (var i = 0; i < received.length; i++) {
|
|
2635
|
+
const vLabel = property + '/' + i.toString() + '/' + 'v';
|
|
2636
|
+
const sLabel = property + '/' + i.toString() + '/' + 's';
|
|
2637
|
+
if (!matchFloats(vLabel, received[i].v, good[i].v, tolerance))
|
|
2638
|
+
return false;
|
|
2639
|
+
if (!matchFloats(sLabel, received[i].s, good[i].s, tolerance))
|
|
2640
|
+
return false;
|
|
2641
|
+
}
|
|
2642
|
+
return true;
|
|
2643
|
+
}
|
|
2644
|
+
function matchCompactnessByDistrict(property, received, good, tolerance) {
|
|
2645
|
+
if (received.length != good.length) {
|
|
2646
|
+
console.log(`${property} does not match: Different number of districts.`);
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
let bMismatched = false;
|
|
2650
|
+
for (var i = 0; i < received.length; i++) {
|
|
2651
|
+
const rawReock = property + '/' + i.toString() + '/' + 'rawReock';
|
|
2652
|
+
if (!matchFloats(rawReock, received[i].rawReock, good[i].rawReock, tolerance))
|
|
2653
|
+
bMismatched = true;
|
|
2654
|
+
const normalizedReock = property + '/' + i.toString() + '/' + 'normalizedReock';
|
|
2655
|
+
if (!matchInts(normalizedReock, received[i].normalizedReock, good[i].normalizedReock))
|
|
2656
|
+
bMismatched = true;
|
|
2657
|
+
const rawPolsby = property + '/' + i.toString() + '/' + 'rawPolsby';
|
|
2658
|
+
if (!matchFloats(rawPolsby, received[i].rawPolsby, good[i].rawPolsby, tolerance))
|
|
2659
|
+
bMismatched = true;
|
|
2660
|
+
// TODO
|
|
2661
|
+
// 09-17-21 - By-district Polsby–Popper ratings from dra-score in production are wrong!
|
|
2662
|
+
const normalizedPolsby = property + '/' + i.toString() + '/' + 'normalizedPolsby';
|
|
2663
|
+
if (!matchInts(normalizedPolsby, received[i].normalizedPolsby, good[i].normalizedPolsby))
|
|
2664
|
+
bMismatched = true;
|
|
2665
|
+
const kiwysiScore = property + '/' + i.toString() + '/' + 'kiwysiScore';
|
|
2666
|
+
if (!matchInts(kiwysiScore, received[i].kiwysiScore, good[i].kiwysiScore))
|
|
2667
|
+
bMismatched = true;
|
|
2668
|
+
}
|
|
2669
|
+
if (bMismatched = true)
|
|
2670
|
+
return false;
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
// Not used, after all
|
|
2674
|
+
/* Modeled after David Sielaff's Jest extension 'toBeArrayWithValuesCloseTo'
|
|
2675
|
+
|
|
2676
|
+
function matchFloatArrays(property: string, received: Array<any>, expected: Array<any>, tolerance: number): boolean
|
|
2677
|
+
{
|
|
2678
|
+
// Note - Not sure what this check was for ...
|
|
2679
|
+
// if (expected.length == 0) {
|
|
2680
|
+
// return {
|
|
2681
|
+
// message: () => `expected arrays of same size`,
|
|
2682
|
+
// pass: received.length == 0
|
|
2683
|
+
// }
|
|
2684
|
+
// }
|
|
2685
|
+
|
|
2686
|
+
if (typeof expected[0] === "number")
|
|
2687
|
+
{
|
|
2688
|
+
if (typeof received[0] === "number") return matchArrays1d(property, received, expected, tolerance);
|
|
2689
|
+
|
|
2690
|
+
console.log(`${property} does not match: array doesn't contain numbers.`);
|
|
2691
|
+
return false;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
// Note - Ditto
|
|
2695
|
+
// if (expected[0].length == 0) {
|
|
2696
|
+
// return {
|
|
2697
|
+
// message: () => `expected arrays of same size`,
|
|
2698
|
+
// pass: received[0].length == 0
|
|
2699
|
+
// }
|
|
2700
|
+
// }
|
|
2701
|
+
|
|
2702
|
+
if (expected[0] instanceof Array && typeof expected[0][0] === "number")
|
|
2703
|
+
{
|
|
2704
|
+
if (received[0] instanceof Array && typeof received[0][0] === "number") return matchArrays2d(property, received, expected, tolerance);
|
|
2705
|
+
|
|
2706
|
+
console.log(`${property} does not match: arrays don't have the same dimensionality and content.`);
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
console.log(`${property}: Expected 1d or 2d arrays.`);
|
|
2711
|
+
return false;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
function matchArrays1d(property: string, received: number[], expected: number[], tolerance: number): boolean
|
|
2715
|
+
{
|
|
2716
|
+
if (received.length != expected.length)
|
|
2717
|
+
{
|
|
2718
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
for (var index = 0; index < received.length; index++)
|
|
2723
|
+
{
|
|
2724
|
+
const cell: string = property + '/' + index.toString();
|
|
2725
|
+
if (!matchFloats(cell, received[index], expected[index], tolerance)) return false;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
function matchArrays2d(property: string, received: number[][], expected: number[][], tolerance: number): boolean
|
|
2732
|
+
{
|
|
2733
|
+
if (received.length != expected.length)
|
|
2734
|
+
{
|
|
2735
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2736
|
+
return false;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
for (var index = 0; index < received.length; index++)
|
|
2740
|
+
{
|
|
2741
|
+
if (received[index].length != expected[index].length)
|
|
2742
|
+
{
|
|
2743
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
for (var inner = 0; inner < received[index].length; inner++)
|
|
2748
|
+
{
|
|
2749
|
+
const cell: string = property + '/' + index.toString() + '/' + inner.toString();
|
|
2750
|
+
if (!matchFloats(cell, received[index][inner], expected[index][inner], tolerance)) return false;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
return true;
|
|
2755
|
+
}
|
|
2756
|
+
*/
|
|
2245
2757
|
|
|
2246
2758
|
|
|
2247
2759
|
/***/ }),
|
|
@@ -2257,24 +2769,27 @@ exports.scorePlan = scorePlan;
|
|
|
2257
2769
|
// GLOBAL CONSTANTS
|
|
2258
2770
|
//
|
|
2259
2771
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2260
|
-
exports.
|
|
2772
|
+
exports.OUT_OF_STATE = exports.NUMBER_OF_ITEMS_TO_REPORT = exports.NOT_ASSIGNED = exports.PRECISION = void 0;
|
|
2261
2773
|
// Keep four decimal places for fractions [0–1], i.e.,
|
|
2262
2774
|
// keep two decimal places for %'s [0–100].
|
|
2263
2775
|
exports.PRECISION = 4;
|
|
2776
|
+
// LEGACY - Not used
|
|
2264
2777
|
// Normalized scores [0-100]
|
|
2265
|
-
|
|
2778
|
+
// export const NORMALIZED_RANGE: number = 100;
|
|
2266
2779
|
// The dummy district ID for features not assigned districts yet
|
|
2267
2780
|
exports.NOT_ASSIGNED = 0;
|
|
2268
2781
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
2269
2782
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
2270
2783
|
// The virtual geoID for "neighbors" in other states
|
|
2271
2784
|
exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
2785
|
+
// LEGACY - Not used
|
|
2272
2786
|
// "Roughly equal" = average census block size / 2
|
|
2273
|
-
const AVERAGE_BLOCK_SIZE = 30;
|
|
2274
|
-
|
|
2787
|
+
// const AVERAGE_BLOCK_SIZE = 30;
|
|
2788
|
+
// export const EQUAL_TOLERANCE: number = AVERAGE_BLOCK_SIZE / 2;
|
|
2789
|
+
// LEGACY - Not used
|
|
2275
2790
|
// County & district splitting weights
|
|
2276
|
-
|
|
2277
|
-
|
|
2791
|
+
// export const COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
2792
|
+
// export const DISTRICT_SPLITTING_WEIGHT = 1.0 - COUNTY_SPLITTING_WEIGHT;
|
|
2278
2793
|
// 2020
|
|
2279
2794
|
// export const SHAPES = "2010 VTD shapes";
|
|
2280
2795
|
|
|
@@ -2285,16 +2800,13 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
|
2285
2800
|
/*!**********************!*\
|
|
2286
2801
|
!*** ./src/types.ts ***!
|
|
2287
2802
|
\**********************/
|
|
2288
|
-
/***/ ((__unused_webpack_module, exports
|
|
2803
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
2289
2804
|
|
|
2290
2805
|
|
|
2291
2806
|
//
|
|
2292
2807
|
// TYPE DEFINITIONS
|
|
2293
2808
|
//
|
|
2294
2809
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2295
|
-
exports.estSeatProbability = void 0;
|
|
2296
|
-
var dra_score_1 = __webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score");
|
|
2297
|
-
Object.defineProperty(exports, "estSeatProbability", ({ enumerable: true, get: function () { return dra_score_1.estSeatProbability; } }));
|
|
2298
2810
|
// END
|
|
2299
2811
|
|
|
2300
2812
|
|
|
@@ -2330,21 +2842,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
2330
2842
|
return result;
|
|
2331
2843
|
};
|
|
2332
2844
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2333
|
-
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 =
|
|
2845
|
+
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;
|
|
2334
2846
|
const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
|
|
2335
2847
|
const _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
2336
2848
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
2337
2849
|
// PLAN HELPERS
|
|
2850
|
+
// LEGACY - Not used
|
|
2338
2851
|
// Is a "neighbor" in state?
|
|
2339
|
-
function isInState(geoID)
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2852
|
+
// export function isInState(geoID: string): boolean
|
|
2853
|
+
// {
|
|
2854
|
+
// return geoID != S.OUT_OF_STATE;
|
|
2855
|
+
// }
|
|
2856
|
+
// LEGACY - Not used
|
|
2343
2857
|
// Is a "neighbor" out of state?
|
|
2344
|
-
function isOutOfState(geoID)
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2858
|
+
// export function isOutOfState(geoID: string): boolean
|
|
2859
|
+
// {
|
|
2860
|
+
// return geoID == S.OUT_OF_STATE;
|
|
2861
|
+
// }
|
|
2348
2862
|
// Get the districtID to which a geoID is assigned
|
|
2349
2863
|
function getDistrict(plan, geoID) {
|
|
2350
2864
|
// All geoIDs in a state *should be* assigned to a district (including the
|