@dra2020/district-analytics 14.1.0 → 14.1.3
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 +587 -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 +42 -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 +8 -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.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,8 +1673,10 @@ __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;
|
|
1679
|
+
exports.avgSVError = dra_analytics_1.Rate.avgSVError;
|
|
1582
1680
|
exports.effectiveSplits = dra_analytics_1.Splitting.effectiveSplits;
|
|
1583
1681
|
exports.uncertaintyOfMembership = dra_analytics_1.Splitting.uncertaintyOfMembership;
|
|
1584
1682
|
|
|
@@ -1641,28 +1739,6 @@ exports.doAnalyzeRacialPolarization = doAnalyzeRacialPolarization;
|
|
|
1641
1739
|
// 11-18-2020 - Moved RPV to racial-voting package.
|
|
1642
1740
|
|
|
1643
1741
|
|
|
1644
|
-
/***/ }),
|
|
1645
|
-
|
|
1646
|
-
/***/ "./src/political.ts":
|
|
1647
|
-
/*!**************************!*\
|
|
1648
|
-
!*** ./src/political.ts ***!
|
|
1649
|
-
\**************************/
|
|
1650
|
-
/***/ ((__unused_webpack_module, exports) => {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
//
|
|
1654
|
-
// FAIR/PROPORTIONAL
|
|
1655
|
-
//
|
|
1656
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1657
|
-
exports.fptpWin = void 0;
|
|
1658
|
-
function fptpWin(demPct) {
|
|
1659
|
-
// Vote shares should be fractions in the range [0.0 – 1.0]
|
|
1660
|
-
//assert((demPct <= 1.0) && (demPct >= 0.0));
|
|
1661
|
-
return ((demPct > 0.5) ? 1 : 0);
|
|
1662
|
-
}
|
|
1663
|
-
exports.fptpWin = fptpWin;
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
1742
|
/***/ }),
|
|
1667
1743
|
|
|
1668
1744
|
/***/ "./src/preprocess.ts":
|
|
@@ -2042,6 +2118,7 @@ function doAnalyzePostProcessing(s, bLog = false) {
|
|
|
2042
2118
|
scorecard.splitting.details['countiesWithSplits'] = simpleSplits['details']['countiesWithSplits'];
|
|
2043
2119
|
// NOTE - Add split precincts in dra-client directly
|
|
2044
2120
|
// Derive secondary tests
|
|
2121
|
+
// Note - The only secondary test is 'roughly equal population (true/false)
|
|
2045
2122
|
(0, analyze_1.doDeriveSecondaryTests)(s, bLog);
|
|
2046
2123
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
2047
2124
|
s.bPostProcessingDone = true;
|
|
@@ -2081,11 +2158,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
2081
2158
|
return result;
|
|
2082
2159
|
};
|
|
2083
2160
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2084
|
-
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;
|
|
2085
2162
|
const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
|
|
2086
2163
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
2087
2164
|
const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
|
|
2088
2165
|
const C = __importStar(__webpack_require__(/*! ./compact */ "./src/compact.ts"));
|
|
2166
|
+
const dra_analytics_1 = __webpack_require__(/*! @dra2020/dra-analytics */ "@dra2020/dra-analytics");
|
|
2089
2167
|
// PROFILE A PLAN
|
|
2090
2168
|
const KEEP_DECIMALS = 6;
|
|
2091
2169
|
function profilePlan(s, bLog = false) {
|
|
@@ -2197,26 +2275,30 @@ function getStatewideDemographics(s, bLog = false) {
|
|
|
2197
2275
|
return demographics;
|
|
2198
2276
|
}
|
|
2199
2277
|
exports.getStatewideDemographics = getStatewideDemographics;
|
|
2200
|
-
// SCORE A PLAN
|
|
2278
|
+
// SCORE A PLAN - Legacy using dra-score
|
|
2279
|
+
// LEGACY
|
|
2201
2280
|
function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
2202
2281
|
let scorer = new Score.Scorer();
|
|
2203
2282
|
const scorecard = scorer.score(p, overridesJSON);
|
|
2204
|
-
//
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
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
|
|
2220
2302
|
// Add minority notes
|
|
2221
2303
|
scorecard.minority.details['majorityMinority'] = M.getMajorityMinority(s);
|
|
2222
2304
|
scorecard.minority.details['vraPreclearance'] = M.getVRASection5(s);
|
|
@@ -2241,6 +2323,432 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
|
2241
2323
|
return scorecard;
|
|
2242
2324
|
}
|
|
2243
2325
|
exports.scorePlan = scorePlan;
|
|
2326
|
+
// SCORE A PLAN - New using dra-analytics
|
|
2327
|
+
function computeMetrics(p, districtShapes, bLog = false) {
|
|
2328
|
+
if (bLog)
|
|
2329
|
+
console.log("Computing metrics ...");
|
|
2330
|
+
const bLegislative = p.bStateLeg;
|
|
2331
|
+
// Calculate bias & responsiveness metrics ...
|
|
2332
|
+
const byDistrictVf = p.partisanship.byDistrict;
|
|
2333
|
+
const statewideVf = p.partisanship.statewide;
|
|
2334
|
+
let _pS = dra_analytics_1.Partisan.makePartisanScorecard(statewideVf, byDistrictVf, bLog);
|
|
2335
|
+
// Calculate minority representation metrics ...
|
|
2336
|
+
const statewideDemos = p.demographics.statewide;
|
|
2337
|
+
const byDistrictDemos = p.demographics.byDistrict;
|
|
2338
|
+
let _mS = dra_analytics_1.Minority.makeMinorityScorecard(statewideDemos, byDistrictDemos, bLog);
|
|
2339
|
+
// Calculate compactness metrics ...
|
|
2340
|
+
let _cS = dra_analytics_1.Compactness.makeCompactnessScorecard(districtShapes, bLog);
|
|
2341
|
+
// Calculate county-district splitting metrics ...
|
|
2342
|
+
const CxD = p.counties;
|
|
2343
|
+
const _sS = dra_analytics_1.Splitting.makeSplittingScorecard(CxD, bLog);
|
|
2344
|
+
// Calculate population deviation-related metrics ...
|
|
2345
|
+
const totPopByDistrict = p.population.byDistrict;
|
|
2346
|
+
const targetSize = p.population.targetSize;
|
|
2347
|
+
const _pdS = dra_analytics_1.Equal.makePopulationScorecard(totPopByDistrict, targetSize, bLegislative, bLog);
|
|
2348
|
+
const details = {};
|
|
2349
|
+
// Assemble the pieces into new scorecard
|
|
2350
|
+
const scorecard = {
|
|
2351
|
+
partisan: _pS,
|
|
2352
|
+
minority: _mS,
|
|
2353
|
+
compactness: _cS,
|
|
2354
|
+
splitting: _sS,
|
|
2355
|
+
populationDeviation: _pdS,
|
|
2356
|
+
details: details,
|
|
2357
|
+
scratchpad: {} // Hack to pass legacy values between processing steps
|
|
2358
|
+
};
|
|
2359
|
+
return scorecard;
|
|
2360
|
+
}
|
|
2361
|
+
exports.computeMetrics = computeMetrics;
|
|
2362
|
+
function rateKeyDimensions(scorecard, p, bLog = false) {
|
|
2363
|
+
if (bLog)
|
|
2364
|
+
console.log("Rating key dimensions ...");
|
|
2365
|
+
const bLegislative = p.bStateLeg;
|
|
2366
|
+
// Rate proportionality
|
|
2367
|
+
const statewideVf = p.partisanship.statewide;
|
|
2368
|
+
const Sf = scorecard.partisan.bias.estSf;
|
|
2369
|
+
scorecard.partisan.bias.score = dra_analytics_1.Rate.rateProportionality(scorecard.partisan.bias.deviation, statewideVf, Sf);
|
|
2370
|
+
// Rate competititveness
|
|
2371
|
+
scorecard.partisan.responsiveness.score = dra_analytics_1.Rate.rateCompetitiveness(scorecard.partisan.responsiveness.cDf);
|
|
2372
|
+
// Rate minority representation
|
|
2373
|
+
const rawOd = scorecard.minority.opportunityDistricts;
|
|
2374
|
+
const pOd = scorecard.minority.proportionalOpportunities;
|
|
2375
|
+
const rawCd = scorecard.minority.coalitionDistricts;
|
|
2376
|
+
const pCd = scorecard.minority.proportionalCoalitions;
|
|
2377
|
+
scorecard.minority.score = dra_analytics_1.Rate.rateMinorityRepresentation(rawOd, pOd, rawCd, pCd);
|
|
2378
|
+
// Rate compactness
|
|
2379
|
+
const avgReock = scorecard.compactness.avgReock;
|
|
2380
|
+
const avgPolsby = scorecard.compactness.avgPolsby;
|
|
2381
|
+
const reockRating = dra_analytics_1.Rate.rateReock(avgReock);
|
|
2382
|
+
const polsbyRating = dra_analytics_1.Rate.ratePolsby(avgPolsby);
|
|
2383
|
+
scorecard.compactness.score = dra_analytics_1.Rate.rateCompactness(reockRating, polsbyRating);
|
|
2384
|
+
// Rate county- & district-splitting
|
|
2385
|
+
const rawCountySplitting = scorecard.splitting.county;
|
|
2386
|
+
const rawDistrictSplitting = scorecard.splitting.district;
|
|
2387
|
+
const nCounties = p.nCounties;
|
|
2388
|
+
const nDistricts = p.nDistricts;
|
|
2389
|
+
const countyRating = dra_analytics_1.Rate.rateCountySplitting(rawCountySplitting, nCounties, nDistricts, bLegislative);
|
|
2390
|
+
const districtRating = dra_analytics_1.Rate.rateDistrictSplitting(rawDistrictSplitting, bLegislative);
|
|
2391
|
+
const unadjusted = dra_analytics_1.Rate.rateSplitting(countyRating, districtRating);
|
|
2392
|
+
scorecard.splitting.score = dra_analytics_1.Rate.adjustSplittingRating(unadjusted, rawCountySplitting, rawDistrictSplitting);
|
|
2393
|
+
// Rate population deviation
|
|
2394
|
+
const rawDeviation = scorecard.populationDeviation.deviation;
|
|
2395
|
+
scorecard.populationDeviation.score = dra_analytics_1.Rate.ratePopulationDeviation(rawDeviation, bLegislative);
|
|
2396
|
+
// Squirrel away normalized compactness & splitting ratings for the legacy scorecard
|
|
2397
|
+
const keep = {
|
|
2398
|
+
reockScore: reockRating,
|
|
2399
|
+
polsbyScore: polsbyRating,
|
|
2400
|
+
countyScore: countyRating,
|
|
2401
|
+
districtScore: districtRating
|
|
2402
|
+
};
|
|
2403
|
+
scorecard.scratchpad = keep;
|
|
2404
|
+
return scorecard;
|
|
2405
|
+
}
|
|
2406
|
+
exports.rateKeyDimensions = rateKeyDimensions;
|
|
2407
|
+
function thunkScorecard(newScorecard, bLog = false) {
|
|
2408
|
+
if (bLog)
|
|
2409
|
+
console.log("Thunking new scorecard into legacy structure ...");
|
|
2410
|
+
const scratchpad = newScorecard.scratchpad;
|
|
2411
|
+
// Partisan scorecard
|
|
2412
|
+
const pS = U.deepCopy(newScorecard.partisan);
|
|
2413
|
+
// Minority scorecard
|
|
2414
|
+
const mS = U.deepCopy(newScorecard.minority);
|
|
2415
|
+
// Compactness scorecard
|
|
2416
|
+
const reockM = {
|
|
2417
|
+
raw: newScorecard.compactness.avgReock,
|
|
2418
|
+
normalized: scratchpad.reockScore,
|
|
2419
|
+
notes: {}
|
|
2420
|
+
};
|
|
2421
|
+
const polsbyM = {
|
|
2422
|
+
raw: newScorecard.compactness.avgPolsby,
|
|
2423
|
+
normalized: scratchpad.polsbyScore,
|
|
2424
|
+
notes: {}
|
|
2425
|
+
};
|
|
2426
|
+
let cS = {
|
|
2427
|
+
score: newScorecard.compactness.score,
|
|
2428
|
+
reock: reockM,
|
|
2429
|
+
polsby: polsbyM,
|
|
2430
|
+
details: U.deepCopy(newScorecard.compactness.details)
|
|
2431
|
+
};
|
|
2432
|
+
// Relocate byDistrict compactness #'s
|
|
2433
|
+
cS.details.byDistrict = U.deepCopy(newScorecard.compactness.byDistrict);
|
|
2434
|
+
// Add KIWYSI compactness score
|
|
2435
|
+
cS.details['kiwysi'] = newScorecard.compactness.avgKWIWYSI;
|
|
2436
|
+
// Splitting scorecard
|
|
2437
|
+
const countyM = {
|
|
2438
|
+
raw: newScorecard.splitting.county,
|
|
2439
|
+
normalized: scratchpad.countyScore,
|
|
2440
|
+
notes: {}
|
|
2441
|
+
};
|
|
2442
|
+
const districtM = {
|
|
2443
|
+
raw: newScorecard.splitting.district,
|
|
2444
|
+
normalized: scratchpad.districtScore,
|
|
2445
|
+
notes: {}
|
|
2446
|
+
};
|
|
2447
|
+
const sS = {
|
|
2448
|
+
score: newScorecard.splitting.score,
|
|
2449
|
+
county: countyM,
|
|
2450
|
+
district: districtM,
|
|
2451
|
+
details: U.deepCopy(newScorecard.splitting.details)
|
|
2452
|
+
};
|
|
2453
|
+
// Population (equality) scorecard
|
|
2454
|
+
const pdM = {
|
|
2455
|
+
raw: newScorecard.populationDeviation.deviation,
|
|
2456
|
+
normalized: newScorecard.populationDeviation.score,
|
|
2457
|
+
notes: newScorecard.populationDeviation.notes
|
|
2458
|
+
};
|
|
2459
|
+
const pdS = pdM;
|
|
2460
|
+
const scorecard = {
|
|
2461
|
+
partisan: pS,
|
|
2462
|
+
minority: mS,
|
|
2463
|
+
compactness: cS,
|
|
2464
|
+
splitting: sS,
|
|
2465
|
+
populationDeviation: pdS,
|
|
2466
|
+
details: newScorecard.details
|
|
2467
|
+
};
|
|
2468
|
+
return scorecard;
|
|
2469
|
+
}
|
|
2470
|
+
exports.thunkScorecard = thunkScorecard;
|
|
2471
|
+
function compareScorecards(altLegacyScorecard, legacyScorecard, bLog = false) {
|
|
2472
|
+
if (bLog)
|
|
2473
|
+
console.log("Comparing new & legacy scorecards ...");
|
|
2474
|
+
// A pretty loose tolerance, because we're not trimming values in the new analytics
|
|
2475
|
+
const DECIMAL_PLACES = 2;
|
|
2476
|
+
const DEFAULT_TOLERANCE = toleranceFn(DECIMAL_PLACES);
|
|
2477
|
+
// COMPARE PARTISAN SCORECARD
|
|
2478
|
+
// Compare BIAS section
|
|
2479
|
+
matchFloats("bestS", altLegacyScorecard.partisan.bias.bestS, legacyScorecard.partisan.bias.bestS, DEFAULT_TOLERANCE);
|
|
2480
|
+
matchFloats("bestSf", altLegacyScorecard.partisan.bias.bestSf, legacyScorecard.partisan.bias.bestSf, DEFAULT_TOLERANCE);
|
|
2481
|
+
matchFloats("estS", altLegacyScorecard.partisan.bias.estS, legacyScorecard.partisan.bias.estS, DEFAULT_TOLERANCE);
|
|
2482
|
+
matchFloats("estSf", altLegacyScorecard.partisan.bias.estSf, legacyScorecard.partisan.bias.estSf, DEFAULT_TOLERANCE);
|
|
2483
|
+
matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
|
|
2484
|
+
matchInts("proportionality/score", altLegacyScorecard.partisan.bias.score, legacyScorecard.partisan.bias.score);
|
|
2485
|
+
matchFloats("tOf", altLegacyScorecard.partisan.bias.tOf, legacyScorecard.partisan.bias.tOf, DEFAULT_TOLERANCE);
|
|
2486
|
+
matchFloats("fptpS", altLegacyScorecard.partisan.bias.fptpS, legacyScorecard.partisan.bias.fptpS, DEFAULT_TOLERANCE);
|
|
2487
|
+
matchFloats("bS50", altLegacyScorecard.partisan.bias.bS50, legacyScorecard.partisan.bias.bS50, DEFAULT_TOLERANCE);
|
|
2488
|
+
matchFloats("deviation", altLegacyScorecard.partisan.bias.deviation, legacyScorecard.partisan.bias.deviation, DEFAULT_TOLERANCE);
|
|
2489
|
+
matchUndefinableFloats("decl", altLegacyScorecard.partisan.bias.decl, legacyScorecard.partisan.bias.decl, toleranceFn(1));
|
|
2490
|
+
let rvPointsAlt = altLegacyScorecard.partisan.bias.rvPoints;
|
|
2491
|
+
let rvPoints = legacyScorecard.partisan.bias.rvPoints;
|
|
2492
|
+
if (matchUndefinedness("rvPoints", rvPointsAlt, rvPoints)) {
|
|
2493
|
+
rvPointsAlt = rvPointsAlt;
|
|
2494
|
+
rvPoints = rvPoints;
|
|
2495
|
+
matchFloats("rvPoints/Sb", rvPointsAlt.Sb, rvPoints.Sb, DEFAULT_TOLERANCE);
|
|
2496
|
+
matchFloats("rvPoints/Ra", rvPointsAlt.Ra, rvPoints.Ra, DEFAULT_TOLERANCE);
|
|
2497
|
+
matchFloats("rvPoints/Rb", rvPointsAlt.Rb, rvPoints.Rb, DEFAULT_TOLERANCE);
|
|
2498
|
+
matchFloats("rvPoints/Va", rvPointsAlt.Va, rvPoints.Va, DEFAULT_TOLERANCE);
|
|
2499
|
+
matchFloats("rvPoints/Vb", rvPointsAlt.Vb, rvPoints.Vb, DEFAULT_TOLERANCE);
|
|
2500
|
+
}
|
|
2501
|
+
matchFloats("gSym", altLegacyScorecard.partisan.bias.gSym, legacyScorecard.partisan.bias.gSym, DEFAULT_TOLERANCE);
|
|
2502
|
+
matchUndefinableFloats("gamma", altLegacyScorecard.partisan.bias.gamma, legacyScorecard.partisan.bias.gamma, DEFAULT_TOLERANCE);
|
|
2503
|
+
matchFloats("eG", altLegacyScorecard.partisan.bias.eG, legacyScorecard.partisan.bias.eG, DEFAULT_TOLERANCE);
|
|
2504
|
+
matchUndefinableFloats("bSV", altLegacyScorecard.partisan.bias.bSV, legacyScorecard.partisan.bias.bSV, DEFAULT_TOLERANCE);
|
|
2505
|
+
matchFloats("prop", altLegacyScorecard.partisan.bias.prop, legacyScorecard.partisan.bias.prop, DEFAULT_TOLERANCE);
|
|
2506
|
+
matchFloats("mMs", altLegacyScorecard.partisan.bias.mMs, legacyScorecard.partisan.bias.mMs, DEFAULT_TOLERANCE);
|
|
2507
|
+
matchFloats("mMd", altLegacyScorecard.partisan.bias.mMd, legacyScorecard.partisan.bias.mMd, DEFAULT_TOLERANCE);
|
|
2508
|
+
matchUndefinableFloats("lO", altLegacyScorecard.partisan.bias.lO, legacyScorecard.partisan.bias.lO, DEFAULT_TOLERANCE);
|
|
2509
|
+
// Compare Impact section
|
|
2510
|
+
matchFloats("unearnedS", altLegacyScorecard.partisan.impact.unearnedS, legacyScorecard.partisan.impact.unearnedS, DEFAULT_TOLERANCE);
|
|
2511
|
+
// Note - We don't use the impact score, so we don't compute it in the new scorecard.
|
|
2512
|
+
// matchInts("impact/score", altLegacyScorecard.partisan.impact.score as number, legacyScorecard.partisan.impact.score);
|
|
2513
|
+
// Compare Responsiveness section
|
|
2514
|
+
matchUndefinableFloats("bigR", altLegacyScorecard.partisan.responsiveness.bigR, legacyScorecard.partisan.responsiveness.bigR, DEFAULT_TOLERANCE);
|
|
2515
|
+
matchUndefinableFloats("littleR", altLegacyScorecard.partisan.responsiveness.littleR, legacyScorecard.partisan.responsiveness.littleR, DEFAULT_TOLERANCE);
|
|
2516
|
+
matchUndefinableFloats("mIR", altLegacyScorecard.partisan.responsiveness.mIR, legacyScorecard.partisan.responsiveness.mIR, DEFAULT_TOLERANCE);
|
|
2517
|
+
matchFloats("rD", altLegacyScorecard.partisan.responsiveness.rD, legacyScorecard.partisan.responsiveness.rD, DEFAULT_TOLERANCE);
|
|
2518
|
+
matchFloats("rDf", altLegacyScorecard.partisan.responsiveness.rDf, legacyScorecard.partisan.responsiveness.rDf, DEFAULT_TOLERANCE);
|
|
2519
|
+
matchFloats("cSimple", altLegacyScorecard.partisan.responsiveness.cSimple, legacyScorecard.partisan.responsiveness.cSimple, DEFAULT_TOLERANCE);
|
|
2520
|
+
matchFloats("cD", altLegacyScorecard.partisan.responsiveness.cD, legacyScorecard.partisan.responsiveness.cD, DEFAULT_TOLERANCE);
|
|
2521
|
+
matchFloats("cDf", altLegacyScorecard.partisan.responsiveness.cDf, legacyScorecard.partisan.responsiveness.cDf, DEFAULT_TOLERANCE);
|
|
2522
|
+
matchInts("competitiveness/score", altLegacyScorecard.partisan.responsiveness.score, legacyScorecard.partisan.responsiveness.score);
|
|
2523
|
+
matchPointArrays("dSVpoints", altLegacyScorecard.partisan.dSVpoints, legacyScorecard.partisan.dSVpoints, DEFAULT_TOLERANCE);
|
|
2524
|
+
matchPointArrays("rSVpoints", altLegacyScorecard.partisan.rSVpoints, legacyScorecard.partisan.rSVpoints, DEFAULT_TOLERANCE);
|
|
2525
|
+
matchUndefinableFloats("averageDVf", altLegacyScorecard.partisan.averageDVf, legacyScorecard.partisan.averageDVf, DEFAULT_TOLERANCE);
|
|
2526
|
+
matchUndefinableFloats("averageRVf", altLegacyScorecard.partisan.averageRVf, legacyScorecard.partisan.averageRVf, DEFAULT_TOLERANCE);
|
|
2527
|
+
matchDicts("partisan/details", altLegacyScorecard.partisan.details, legacyScorecard.partisan.details);
|
|
2528
|
+
// COMPARE MINORITY SCORECARD
|
|
2529
|
+
matchObjectsAndArrays("pivotByDemographic", altLegacyScorecard.minority.pivotByDemographic, legacyScorecard.minority.pivotByDemographic);
|
|
2530
|
+
matchFloats("opportunityDistricts", altLegacyScorecard.minority.opportunityDistricts, legacyScorecard.minority.opportunityDistricts, DEFAULT_TOLERANCE);
|
|
2531
|
+
matchFloats("coalitionDistricts", altLegacyScorecard.minority.coalitionDistricts, legacyScorecard.minority.coalitionDistricts, DEFAULT_TOLERANCE);
|
|
2532
|
+
matchInts("minority/score", altLegacyScorecard.minority.score, legacyScorecard.minority.score);
|
|
2533
|
+
matchDicts("minority/details", altLegacyScorecard.minority.details, legacyScorecard.minority.details);
|
|
2534
|
+
// COMPARE COMPACTNESS SCORECARD
|
|
2535
|
+
matchFloats("reock", altLegacyScorecard.compactness.reock.raw, legacyScorecard.compactness.reock.raw, DEFAULT_TOLERANCE);
|
|
2536
|
+
matchInts("reock/normalized", altLegacyScorecard.compactness.reock.normalized, legacyScorecard.compactness.reock.normalized);
|
|
2537
|
+
matchFloats("polsby", altLegacyScorecard.compactness.polsby.raw, legacyScorecard.compactness.polsby.raw, DEFAULT_TOLERANCE);
|
|
2538
|
+
matchInts("polsby/normalized", altLegacyScorecard.compactness.polsby.normalized, legacyScorecard.compactness.polsby.normalized);
|
|
2539
|
+
matchInts("compactness/score", altLegacyScorecard.compactness.score, legacyScorecard.compactness.score);
|
|
2540
|
+
// Compare 'byDistrict' results separately
|
|
2541
|
+
const _altCompactnessByDistrict = dra_analytics_1.Utils.deepCopy(altLegacyScorecard.compactness.details['byDistrict']);
|
|
2542
|
+
const _CompactnessByDistrict = dra_analytics_1.Utils.deepCopy(legacyScorecard.compactness.details['byDistrict']);
|
|
2543
|
+
const _altCompactnessDetails = dra_analytics_1.Utils.deepCopy(altLegacyScorecard.compactness.details);
|
|
2544
|
+
const _CompactnessDetails = dra_analytics_1.Utils.deepCopy(legacyScorecard.compactness.details);
|
|
2545
|
+
delete _altCompactnessDetails['byDistrict'];
|
|
2546
|
+
delete _CompactnessDetails['byDistrict'];
|
|
2547
|
+
matchDicts("compactness/details", _altCompactnessDetails, _CompactnessDetails);
|
|
2548
|
+
matchCompactnessByDistrict("compactness/byDistrict", _altCompactnessByDistrict, _CompactnessByDistrict, DEFAULT_TOLERANCE);
|
|
2549
|
+
// COMPARE SPLITTING SCORECARD
|
|
2550
|
+
matchFloats("county", altLegacyScorecard.splitting.county.raw, legacyScorecard.splitting.county.raw, DEFAULT_TOLERANCE);
|
|
2551
|
+
matchInts("county/normalized", altLegacyScorecard.splitting.county.normalized, legacyScorecard.splitting.county.normalized);
|
|
2552
|
+
matchFloats("district", altLegacyScorecard.splitting.district.raw, legacyScorecard.splitting.district.raw, DEFAULT_TOLERANCE);
|
|
2553
|
+
matchInts("district/normalized", altLegacyScorecard.splitting.district.normalized, legacyScorecard.splitting.district.normalized);
|
|
2554
|
+
matchInts("splitting/score", altLegacyScorecard.splitting.score, legacyScorecard.splitting.score);
|
|
2555
|
+
matchDicts("splitting/details", altLegacyScorecard.splitting.details, legacyScorecard.splitting.details);
|
|
2556
|
+
// COMPARE POPULATION SCORECARD
|
|
2557
|
+
matchFloats("popdev", altLegacyScorecard.populationDeviation.raw, legacyScorecard.populationDeviation.raw, DEFAULT_TOLERANCE);
|
|
2558
|
+
matchInts("popdev/score", altLegacyScorecard.populationDeviation.normalized, legacyScorecard.populationDeviation.normalized);
|
|
2559
|
+
matchDicts("popdev/notes", altLegacyScorecard.populationDeviation.notes, legacyScorecard.populationDeviation.notes);
|
|
2560
|
+
matchDicts("details", altLegacyScorecard.details, legacyScorecard.details);
|
|
2561
|
+
}
|
|
2562
|
+
exports.compareScorecards = compareScorecards;
|
|
2563
|
+
// Matching helpers
|
|
2564
|
+
function matchFloats(property, received, good, tolerance) {
|
|
2565
|
+
if (dra_analytics_1.Utils.areRoughlyEqual(received, good, tolerance)) {
|
|
2566
|
+
return true;
|
|
2567
|
+
}
|
|
2568
|
+
else {
|
|
2569
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2570
|
+
return false;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
function toleranceFn(places) {
|
|
2574
|
+
return 0.5 / Math.pow(10, places);
|
|
2575
|
+
}
|
|
2576
|
+
function matchInts(property, received, good) {
|
|
2577
|
+
if (received == good) {
|
|
2578
|
+
return true;
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2582
|
+
return false;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function matchUndefinableFloats(property, received, good, tolerance) {
|
|
2586
|
+
if ((received === undefined) && (good === undefined))
|
|
2587
|
+
return true;
|
|
2588
|
+
if ((received === undefined) || (good === undefined)) {
|
|
2589
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2590
|
+
return false;
|
|
2591
|
+
}
|
|
2592
|
+
return matchFloats(property, received, good, tolerance);
|
|
2593
|
+
}
|
|
2594
|
+
// https://stackoverflow.com/questions/13142968/deep-comparison-of-objects-arrays
|
|
2595
|
+
function matchObjectsAndArrays(property, received, good) {
|
|
2596
|
+
if ((received === undefined) && (good === undefined))
|
|
2597
|
+
return true; // Both undefined
|
|
2598
|
+
if ((received === undefined) || (good === undefined)) { // One undefined but not the other
|
|
2599
|
+
console.log(`${property} does not match: ${good} expected. Received ${received}.`);
|
|
2600
|
+
return false;
|
|
2601
|
+
}
|
|
2602
|
+
if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0))
|
|
2603
|
+
return true; // Both empty
|
|
2604
|
+
if (JSON.stringify(received) === JSON.stringify(good))
|
|
2605
|
+
return true; // Contents match
|
|
2606
|
+
console.log(`${property} objects or arrays do not match.`); // Contents don't match
|
|
2607
|
+
return false;
|
|
2608
|
+
}
|
|
2609
|
+
function matchDicts(property, received, good) {
|
|
2610
|
+
if ((Object.keys(received).length === 0) && (Object.keys(good).length === 0))
|
|
2611
|
+
return true; // Both empty
|
|
2612
|
+
const receivedStr = JSON.stringify(received);
|
|
2613
|
+
const goodStr = JSON.stringify(good);
|
|
2614
|
+
if (receivedStr === goodStr)
|
|
2615
|
+
return true; // Contents match
|
|
2616
|
+
console.log(`${property} does not match: ${goodStr} expected. Received ${receivedStr}.`); // Contents don't match
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
function matchUndefinedness(property, received, good) {
|
|
2620
|
+
if ((received === undefined) && (good === undefined))
|
|
2621
|
+
return true;
|
|
2622
|
+
if ((received === undefined) || (good === undefined))
|
|
2623
|
+
return false;
|
|
2624
|
+
return true;
|
|
2625
|
+
}
|
|
2626
|
+
function matchPointArrays(property, received, good, tolerance) {
|
|
2627
|
+
if (received.length != good.length) {
|
|
2628
|
+
console.log(`${property} does not match: Different number of points.`);
|
|
2629
|
+
return false;
|
|
2630
|
+
}
|
|
2631
|
+
for (var i = 0; i < received.length; i++) {
|
|
2632
|
+
const vLabel = property + '/' + i.toString() + '/' + 'v';
|
|
2633
|
+
const sLabel = property + '/' + i.toString() + '/' + 's';
|
|
2634
|
+
if (!matchFloats(vLabel, received[i].v, good[i].v, tolerance))
|
|
2635
|
+
return false;
|
|
2636
|
+
if (!matchFloats(sLabel, received[i].s, good[i].s, tolerance))
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
return true;
|
|
2640
|
+
}
|
|
2641
|
+
function matchCompactnessByDistrict(property, received, good, tolerance) {
|
|
2642
|
+
if (received.length != good.length) {
|
|
2643
|
+
console.log(`${property} does not match: Different number of districts.`);
|
|
2644
|
+
return false;
|
|
2645
|
+
}
|
|
2646
|
+
let bMismatched = false;
|
|
2647
|
+
for (var i = 0; i < received.length; i++) {
|
|
2648
|
+
const rawReock = property + '/' + i.toString() + '/' + 'rawReock';
|
|
2649
|
+
if (!matchFloats(rawReock, received[i].rawReock, good[i].rawReock, tolerance))
|
|
2650
|
+
bMismatched = true;
|
|
2651
|
+
const normalizedReock = property + '/' + i.toString() + '/' + 'normalizedReock';
|
|
2652
|
+
if (!matchInts(normalizedReock, received[i].normalizedReock, good[i].normalizedReock))
|
|
2653
|
+
bMismatched = true;
|
|
2654
|
+
const rawPolsby = property + '/' + i.toString() + '/' + 'rawPolsby';
|
|
2655
|
+
if (!matchFloats(rawPolsby, received[i].rawPolsby, good[i].rawPolsby, tolerance))
|
|
2656
|
+
bMismatched = true;
|
|
2657
|
+
// 09-17-21 - By-district Polsby–Popper ratings from dra-score in production are wrong!
|
|
2658
|
+
// const normalizedPolsby: string = property + '/' + i.toString() + '/' + 'normalizedPolsby';
|
|
2659
|
+
// if (!matchInts(normalizedPolsby, received[i].normalizedPolsby, good[i].normalizedPolsby)) bMismatched = true;
|
|
2660
|
+
const kiwysiScore = property + '/' + i.toString() + '/' + 'kiwysiScore';
|
|
2661
|
+
if (!matchInts(kiwysiScore, received[i].kiwysiScore, good[i].kiwysiScore))
|
|
2662
|
+
bMismatched = true;
|
|
2663
|
+
}
|
|
2664
|
+
if (bMismatched = true)
|
|
2665
|
+
return false;
|
|
2666
|
+
return true;
|
|
2667
|
+
}
|
|
2668
|
+
// Not used, after all
|
|
2669
|
+
/* Modeled after David Sielaff's Jest extension 'toBeArrayWithValuesCloseTo'
|
|
2670
|
+
|
|
2671
|
+
function matchFloatArrays(property: string, received: Array<any>, expected: Array<any>, tolerance: number): boolean
|
|
2672
|
+
{
|
|
2673
|
+
// Note - Not sure what this check was for ...
|
|
2674
|
+
// if (expected.length == 0) {
|
|
2675
|
+
// return {
|
|
2676
|
+
// message: () => `expected arrays of same size`,
|
|
2677
|
+
// pass: received.length == 0
|
|
2678
|
+
// }
|
|
2679
|
+
// }
|
|
2680
|
+
|
|
2681
|
+
if (typeof expected[0] === "number")
|
|
2682
|
+
{
|
|
2683
|
+
if (typeof received[0] === "number") return matchArrays1d(property, received, expected, tolerance);
|
|
2684
|
+
|
|
2685
|
+
console.log(`${property} does not match: array doesn't contain numbers.`);
|
|
2686
|
+
return false;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// Note - Ditto
|
|
2690
|
+
// if (expected[0].length == 0) {
|
|
2691
|
+
// return {
|
|
2692
|
+
// message: () => `expected arrays of same size`,
|
|
2693
|
+
// pass: received[0].length == 0
|
|
2694
|
+
// }
|
|
2695
|
+
// }
|
|
2696
|
+
|
|
2697
|
+
if (expected[0] instanceof Array && typeof expected[0][0] === "number")
|
|
2698
|
+
{
|
|
2699
|
+
if (received[0] instanceof Array && typeof received[0][0] === "number") return matchArrays2d(property, received, expected, tolerance);
|
|
2700
|
+
|
|
2701
|
+
console.log(`${property} does not match: arrays don't have the same dimensionality and content.`);
|
|
2702
|
+
return false;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
console.log(`${property}: Expected 1d or 2d arrays.`);
|
|
2706
|
+
return false;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function matchArrays1d(property: string, received: number[], expected: number[], tolerance: number): boolean
|
|
2710
|
+
{
|
|
2711
|
+
if (received.length != expected.length)
|
|
2712
|
+
{
|
|
2713
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2714
|
+
return false;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
for (var index = 0; index < received.length; index++)
|
|
2718
|
+
{
|
|
2719
|
+
const cell: string = property + '/' + index.toString();
|
|
2720
|
+
if (!matchFloats(cell, received[index], expected[index], tolerance)) return false;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
return true;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function matchArrays2d(property: string, received: number[][], expected: number[][], tolerance: number): boolean
|
|
2727
|
+
{
|
|
2728
|
+
if (received.length != expected.length)
|
|
2729
|
+
{
|
|
2730
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2731
|
+
return false;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
for (var index = 0; index < received.length; index++)
|
|
2735
|
+
{
|
|
2736
|
+
if (received[index].length != expected[index].length)
|
|
2737
|
+
{
|
|
2738
|
+
console.log(`${property} does not match: arrays have different lengths.`);
|
|
2739
|
+
return false;
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
for (var inner = 0; inner < received[index].length; inner++)
|
|
2743
|
+
{
|
|
2744
|
+
const cell: string = property + '/' + index.toString() + '/' + inner.toString();
|
|
2745
|
+
if (!matchFloats(cell, received[index][inner], expected[index][inner], tolerance)) return false;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
return true;
|
|
2750
|
+
}
|
|
2751
|
+
*/
|
|
2244
2752
|
|
|
2245
2753
|
|
|
2246
2754
|
/***/ }),
|
|
@@ -2256,24 +2764,27 @@ exports.scorePlan = scorePlan;
|
|
|
2256
2764
|
// GLOBAL CONSTANTS
|
|
2257
2765
|
//
|
|
2258
2766
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2259
|
-
exports.
|
|
2767
|
+
exports.OUT_OF_STATE = exports.NUMBER_OF_ITEMS_TO_REPORT = exports.NOT_ASSIGNED = exports.PRECISION = void 0;
|
|
2260
2768
|
// Keep four decimal places for fractions [0–1], i.e.,
|
|
2261
2769
|
// keep two decimal places for %'s [0–100].
|
|
2262
2770
|
exports.PRECISION = 4;
|
|
2771
|
+
// LEGACY - Not used
|
|
2263
2772
|
// Normalized scores [0-100]
|
|
2264
|
-
|
|
2773
|
+
// export const NORMALIZED_RANGE: number = 100;
|
|
2265
2774
|
// The dummy district ID for features not assigned districts yet
|
|
2266
2775
|
exports.NOT_ASSIGNED = 0;
|
|
2267
2776
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
2268
2777
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
2269
2778
|
// The virtual geoID for "neighbors" in other states
|
|
2270
2779
|
exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
2780
|
+
// LEGACY - Not used
|
|
2271
2781
|
// "Roughly equal" = average census block size / 2
|
|
2272
|
-
const AVERAGE_BLOCK_SIZE = 30;
|
|
2273
|
-
|
|
2782
|
+
// const AVERAGE_BLOCK_SIZE = 30;
|
|
2783
|
+
// export const EQUAL_TOLERANCE: number = AVERAGE_BLOCK_SIZE / 2;
|
|
2784
|
+
// LEGACY - Not used
|
|
2274
2785
|
// County & district splitting weights
|
|
2275
|
-
|
|
2276
|
-
|
|
2786
|
+
// export const COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
2787
|
+
// export const DISTRICT_SPLITTING_WEIGHT = 1.0 - COUNTY_SPLITTING_WEIGHT;
|
|
2277
2788
|
// 2020
|
|
2278
2789
|
// export const SHAPES = "2010 VTD shapes";
|
|
2279
2790
|
|
|
@@ -2284,16 +2795,13 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
|
2284
2795
|
/*!**********************!*\
|
|
2285
2796
|
!*** ./src/types.ts ***!
|
|
2286
2797
|
\**********************/
|
|
2287
|
-
/***/ ((__unused_webpack_module, exports
|
|
2798
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
2288
2799
|
|
|
2289
2800
|
|
|
2290
2801
|
//
|
|
2291
2802
|
// TYPE DEFINITIONS
|
|
2292
2803
|
//
|
|
2293
2804
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2294
|
-
exports.estSeatProbability = void 0;
|
|
2295
|
-
var dra_score_1 = __webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score");
|
|
2296
|
-
Object.defineProperty(exports, "estSeatProbability", ({ enumerable: true, get: function () { return dra_score_1.estSeatProbability; } }));
|
|
2297
2805
|
// END
|
|
2298
2806
|
|
|
2299
2807
|
|
|
@@ -2329,21 +2837,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
2329
2837
|
return result;
|
|
2330
2838
|
};
|
|
2331
2839
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2332
|
-
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 =
|
|
2840
|
+
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;
|
|
2333
2841
|
const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
|
|
2334
2842
|
const _data_1 = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
2335
2843
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
2336
2844
|
// PLAN HELPERS
|
|
2845
|
+
// LEGACY - Not used
|
|
2337
2846
|
// Is a "neighbor" in state?
|
|
2338
|
-
function isInState(geoID)
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2847
|
+
// export function isInState(geoID: string): boolean
|
|
2848
|
+
// {
|
|
2849
|
+
// return geoID != S.OUT_OF_STATE;
|
|
2850
|
+
// }
|
|
2851
|
+
// LEGACY - Not used
|
|
2342
2852
|
// Is a "neighbor" out of state?
|
|
2343
|
-
function isOutOfState(geoID)
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2853
|
+
// export function isOutOfState(geoID: string): boolean
|
|
2854
|
+
// {
|
|
2855
|
+
// return geoID == S.OUT_OF_STATE;
|
|
2856
|
+
// }
|
|
2347
2857
|
// Get the districtID to which a geoID is assigned
|
|
2348
2858
|
function getDistrict(plan, geoID) {
|
|
2349
2859
|
// All geoIDs in a state *should be* assigned to a district (including the
|