@dra2020/district-analytics 3.3.0 → 4.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/README.md +1 -0
- package/dist/cli.js +1065 -268
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +272 -112
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/results.d.ts +0 -90
- package/dist/src/settings.d.ts +1 -0
- package/dist/src/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -89432,7 +89432,7 @@ function reduceCSplits(CxD, districtTotals) {
|
|
|
89432
89432
|
for (let i = 1; i < nD; i++) {
|
|
89433
89433
|
let split_total = CxDreducedC[i][j];
|
|
89434
89434
|
if (split_total > 0) {
|
|
89435
|
-
if (areRoughlyEqual(split_total, districtTotals[i - 1])) {
|
|
89435
|
+
if (U.areRoughlyEqual(split_total, districtTotals[i - 1], S.EQUAL_TOLERANCE)) {
|
|
89436
89436
|
CxDreducedC[0][j] += split_total;
|
|
89437
89437
|
CxDreducedC[i][j] = 0;
|
|
89438
89438
|
}
|
|
@@ -89455,7 +89455,7 @@ function reduceDSplits(CxD, countyTotals) {
|
|
|
89455
89455
|
for (let j = 1; j < nC; j++) {
|
|
89456
89456
|
let split_total = CxDreducedD[i][j];
|
|
89457
89457
|
if (split_total > 0) {
|
|
89458
|
-
if (areRoughlyEqual(split_total, countyTotals[j - 1])) {
|
|
89458
|
+
if (U.areRoughlyEqual(split_total, countyTotals[j - 1], S.EQUAL_TOLERANCE)) {
|
|
89459
89459
|
CxDreducedD[i][0] += split_total;
|
|
89460
89460
|
CxDreducedD[i][j] = 0;
|
|
89461
89461
|
}
|
|
@@ -89519,13 +89519,6 @@ function calcDistrictFractions(CxD, districtTotals) {
|
|
|
89519
89519
|
return g;
|
|
89520
89520
|
}
|
|
89521
89521
|
exports.calcDistrictFractions = calcDistrictFractions;
|
|
89522
|
-
// Deal with decimal census "counts" due to disagg/re-agg
|
|
89523
|
-
function areRoughlyEqual(x, y) {
|
|
89524
|
-
let delta = Math.abs(x - y);
|
|
89525
|
-
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
89526
|
-
return result;
|
|
89527
|
-
}
|
|
89528
|
-
exports.areRoughlyEqual = areRoughlyEqual;
|
|
89529
89522
|
function splitScore(splits) {
|
|
89530
89523
|
let e;
|
|
89531
89524
|
if (splits.length > 0) {
|
|
@@ -89633,7 +89626,7 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
|
|
|
89633
89626
|
// where A is the area of the district and D is the diameter of the minimum
|
|
89634
89627
|
// bounding circle.
|
|
89635
89628
|
//
|
|
89636
|
-
// Polsby-Popper is the primary measure of the
|
|
89629
|
+
// Polsby-Popper is the primary measure of the indentation of district shapes,
|
|
89637
89630
|
// calculated as the “the ratio of the area of the district to the area of a circle
|
|
89638
89631
|
// whose circumference is equal to the perimeter of the district.”
|
|
89639
89632
|
//
|
|
@@ -89797,10 +89790,10 @@ exports.scorePolsbyPopper = scorePolsbyPopper;
|
|
|
89797
89790
|
/*!*************************!*\
|
|
89798
89791
|
!*** ./src/config.json ***!
|
|
89799
89792
|
\*************************/
|
|
89800
|
-
/*! exports provided: partisan, traditionalPrinciples, default */
|
|
89793
|
+
/*! exports provided: partisan, minority, traditionalPrinciples, default */
|
|
89801
89794
|
/***/ (function(module) {
|
|
89802
89795
|
|
|
89803
|
-
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"overall\":{\"range\":[0,0.67],\"weight\":25},\"marginal\":{\"range\":[0,0.85],\"weight\":75},\"range\":[0.45,0.55],\"distribution\":[0.25,0.75],\"weight\":[0,20]},\"weight\":[100,80]},\"traditionalPrinciples\":{\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50},\"weight\":[0,50]},\"splitting\":{\"county\":{\"range\":[1,1.71],\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"range\":[1,1.5],\"weight\":50},\"weight\":[0,50]},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]],\"weight\":[0,0]},\"weight\":[0,20]}}");
|
|
89796
|
+
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"overall\":{\"range\":[0,0.67],\"weight\":25},\"marginal\":{\"range\":[0,0.85],\"weight\":75},\"range\":[0.45,0.55],\"distribution\":[0.25,0.75],\"weight\":[0,20]},\"bonus\":2,\"weight\":[100,80]},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":0.15,\"bonus\":20},\"traditionalPrinciples\":{\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50},\"weight\":[0,50]},\"splitting\":{\"county\":{\"range\":[1,1.71],\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"range\":[1,1.5],\"weight\":50},\"weight\":[0,50]},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]],\"weight\":[0,0]},\"weight\":[0,20]}}");
|
|
89804
89797
|
|
|
89805
89798
|
/***/ }),
|
|
89806
89799
|
|
|
@@ -89842,6 +89835,11 @@ function impactWeight(context, overridesJSON) {
|
|
|
89842
89835
|
return iW;
|
|
89843
89836
|
}
|
|
89844
89837
|
exports.impactWeight = impactWeight;
|
|
89838
|
+
function winnerBonus(overridesJSON) {
|
|
89839
|
+
const bonus = config_json_1.default.partisan.bonus;
|
|
89840
|
+
return bonus;
|
|
89841
|
+
}
|
|
89842
|
+
exports.winnerBonus = winnerBonus;
|
|
89845
89843
|
// The maximum # of unearned seats that scores positively
|
|
89846
89844
|
function unearnedThreshold(overridesJSON) {
|
|
89847
89845
|
const threshold = config_json_1.default.partisan.impact.threshold;
|
|
@@ -89883,6 +89881,32 @@ function overallCompetitivenessWeight(overridesJSON) {
|
|
|
89883
89881
|
return ocW;
|
|
89884
89882
|
}
|
|
89885
89883
|
exports.overallCompetitivenessWeight = overallCompetitivenessWeight;
|
|
89884
|
+
// MINORITY
|
|
89885
|
+
function minorityOpportunityRange(overridesJSON) {
|
|
89886
|
+
const range = config_json_1.default.minority.range;
|
|
89887
|
+
return range;
|
|
89888
|
+
}
|
|
89889
|
+
exports.minorityOpportunityRange = minorityOpportunityRange;
|
|
89890
|
+
function minorityOpportunityDistribution(overridesJSON) {
|
|
89891
|
+
const dist = config_json_1.default.minority.distribution;
|
|
89892
|
+
return dist;
|
|
89893
|
+
}
|
|
89894
|
+
exports.minorityOpportunityDistribution = minorityOpportunityDistribution;
|
|
89895
|
+
function minorityShift(overridesJSON) {
|
|
89896
|
+
const shift = config_json_1.default.minority.shift;
|
|
89897
|
+
return shift;
|
|
89898
|
+
}
|
|
89899
|
+
exports.minorityShift = minorityShift;
|
|
89900
|
+
function opportunityDistrictBonus(overridesJSON) {
|
|
89901
|
+
const bonus = config_json_1.default.minority.bonus;
|
|
89902
|
+
return bonus;
|
|
89903
|
+
}
|
|
89904
|
+
exports.opportunityDistrictBonus = opportunityDistrictBonus;
|
|
89905
|
+
function minorityBonus(overridesJSON) {
|
|
89906
|
+
const bonus = config_json_1.default.minority.bonus;
|
|
89907
|
+
return bonus;
|
|
89908
|
+
}
|
|
89909
|
+
exports.minorityBonus = minorityBonus;
|
|
89886
89910
|
// TRADITIONAL DISTRICTING PRINCIPLES
|
|
89887
89911
|
function compactnessWeight(context, overridesJSON) {
|
|
89888
89912
|
const cW = config_json_1.default.traditionalPrinciples.compactness.weight[context];
|
|
@@ -90068,8 +90092,137 @@ function __export(m) {
|
|
|
90068
90092
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90069
90093
|
__export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
|
|
90070
90094
|
var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
|
|
90071
|
-
exports.sampleProfile = types_1.sampleProfile;
|
|
90072
90095
|
exports.sampleScorecard = types_1.sampleScorecard;
|
|
90096
|
+
exports.sampleProfile = types_1.sampleProfile;
|
|
90097
|
+
|
|
90098
|
+
|
|
90099
|
+
/***/ }),
|
|
90100
|
+
|
|
90101
|
+
/***/ "./src/minority.ts":
|
|
90102
|
+
/*!*************************!*\
|
|
90103
|
+
!*** ./src/minority.ts ***!
|
|
90104
|
+
\*************************/
|
|
90105
|
+
/*! no static exports found */
|
|
90106
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
90107
|
+
|
|
90108
|
+
"use strict";
|
|
90109
|
+
|
|
90110
|
+
//
|
|
90111
|
+
// SCORING FOR MINORITY OPPORTUNITY
|
|
90112
|
+
//
|
|
90113
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
90114
|
+
if (mod && mod.__esModule) return mod;
|
|
90115
|
+
var result = {};
|
|
90116
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
90117
|
+
result["default"] = mod;
|
|
90118
|
+
return result;
|
|
90119
|
+
};
|
|
90120
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90121
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
90122
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
90123
|
+
const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
|
|
90124
|
+
const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
|
|
90125
|
+
function evalMinorityOpportunity(p, bLog = false) {
|
|
90126
|
+
// Calculate average Democratic win share
|
|
90127
|
+
const VfArray = p.partisanProfile.vfArray;
|
|
90128
|
+
const DWins = VfArray.filter(x => x > 0.5);
|
|
90129
|
+
const averageDVf = (DWins.length > 0) ? U.avgArray(DWins) : undefined;
|
|
90130
|
+
// Determine proportional minority districts by demographic (ignore 'White')
|
|
90131
|
+
const districtsByDemo = calcDistrictsByDemo(p.demographicProfile.stateMfArray.slice(1), p.nDistricts);
|
|
90132
|
+
// Initialize arrays for results
|
|
90133
|
+
const offset = 1; // Don't process 'White'
|
|
90134
|
+
const nDemos = 6 /* Native */ + 1 - offset; // Ditto
|
|
90135
|
+
const nBuckets = 2; // 37–50% and > 50%
|
|
90136
|
+
const demosByDistrict = p.demographicProfile.mfArrayByDistrict;
|
|
90137
|
+
let bucketsByDemo = new Array(nDemos);
|
|
90138
|
+
for (let j = 0; j < nDemos; j++) {
|
|
90139
|
+
bucketsByDemo[j] = [0, 0];
|
|
90140
|
+
}
|
|
90141
|
+
let opptyByDemo = U.initArray(nDemos, 0.0);
|
|
90142
|
+
// For each district
|
|
90143
|
+
for (let i = 0; i < p.nDistricts; i++) {
|
|
90144
|
+
// Find the opportunities for minority representation
|
|
90145
|
+
for (let j = 0; j < nDemos; j++) {
|
|
90146
|
+
const proportion = U.deepCopy(demosByDistrict[i][j + offset]);
|
|
90147
|
+
if (exceedsMinimumThreshold(proportion)) {
|
|
90148
|
+
// Bucket opportunity districts
|
|
90149
|
+
const bucket = (exceedsMaximumThreshold(proportion)) ? 1 : 0;
|
|
90150
|
+
bucketsByDemo[j][bucket] += 1;
|
|
90151
|
+
// Accumulate seat probabilities
|
|
90152
|
+
opptyByDemo[j] += estMinorityOpportunity(proportion);
|
|
90153
|
+
}
|
|
90154
|
+
}
|
|
90155
|
+
}
|
|
90156
|
+
// Sum the # of opportunity districts - ignore total minority
|
|
90157
|
+
const oD = U.sumArray(opptyByDemo.slice(1));
|
|
90158
|
+
// Sum the # of proportion districts - ignore total minority
|
|
90159
|
+
const pD = U.sumArray(districtsByDemo.slice(1));
|
|
90160
|
+
// Score opportunity
|
|
90161
|
+
const score = scoreMinority(oD, pD);
|
|
90162
|
+
let mS = {
|
|
90163
|
+
report: {
|
|
90164
|
+
averageDVf: averageDVf,
|
|
90165
|
+
bucketsByDemographic: bucketsByDemo
|
|
90166
|
+
},
|
|
90167
|
+
// nOpportunity1: l1,
|
|
90168
|
+
// nOpportunity2: l2,
|
|
90169
|
+
nProportional: pD,
|
|
90170
|
+
opportunityDistricts: oD,
|
|
90171
|
+
score: score,
|
|
90172
|
+
details: {} // TODO - Add notes
|
|
90173
|
+
};
|
|
90174
|
+
return mS;
|
|
90175
|
+
}
|
|
90176
|
+
exports.evalMinorityOpportunity = evalMinorityOpportunity;
|
|
90177
|
+
function scoreMinority(oD, pD) {
|
|
90178
|
+
const bonus = C.minorityBonus();
|
|
90179
|
+
const score = (pD > 0) ? Math.round((Math.min(oD, pD) / pD) * bonus) : 0;
|
|
90180
|
+
return score;
|
|
90181
|
+
}
|
|
90182
|
+
exports.scoreMinority = scoreMinority;
|
|
90183
|
+
function calcDistrictsByDemo(MfArray, N) {
|
|
90184
|
+
const districtsByDemo = MfArray.map(v => calcProportionalDistricts(v, N));
|
|
90185
|
+
return districtsByDemo;
|
|
90186
|
+
}
|
|
90187
|
+
exports.calcDistrictsByDemo = calcDistrictsByDemo;
|
|
90188
|
+
// NOTE - Shift minority proportions up, so 37% minority scores like 52% share,
|
|
90189
|
+
// but use the uncompressed seat probability distribution. This makes a 37%
|
|
90190
|
+
// district have a ~70% chance of winning, and a 50% district have a >99% chance.
|
|
90191
|
+
// Below 37 % has no chance.
|
|
90192
|
+
// NOTE - Sam Wang suggest 90% probability for a 37% district. That seems a little
|
|
90193
|
+
// too abrupt and all or nothing, so I backed off to the ~70%.
|
|
90194
|
+
//
|
|
90195
|
+
function estMinorityOpportunity(Mf) {
|
|
90196
|
+
// NOTE - Switch to compress the probability distribution
|
|
90197
|
+
const bCompress = false;
|
|
90198
|
+
const dist = bCompress ? C.minorityOpportunityDistribution() : [0.0, 1.0];
|
|
90199
|
+
const range = C.minorityOpportunityRange();
|
|
90200
|
+
const _normalizer = new normalize_1.Normalizer(Mf);
|
|
90201
|
+
const shift = C.minorityShift();
|
|
90202
|
+
_normalizer.wipNum += shift;
|
|
90203
|
+
_normalizer.clip(dist[C.BEG], dist[C.END]);
|
|
90204
|
+
_normalizer.unitize(dist[C.BEG], dist[C.END]);
|
|
90205
|
+
const oppty = (Mf < range[C.BEG]) ? 0.0 : partisan_1.estSeatProbability(_normalizer.wipNum, dist);
|
|
90206
|
+
return oppty;
|
|
90207
|
+
}
|
|
90208
|
+
exports.estMinorityOpportunity = estMinorityOpportunity;
|
|
90209
|
+
// HELPERS
|
|
90210
|
+
function exceedsMinimumThreshold(Mf) {
|
|
90211
|
+
const threshold = C.minorityOpportunityRange()[C.BEG];
|
|
90212
|
+
return Mf >= threshold;
|
|
90213
|
+
}
|
|
90214
|
+
function exceedsMaximumThreshold(Mf) {
|
|
90215
|
+
const threshold = C.minorityOpportunityRange()[C.END];
|
|
90216
|
+
return Mf > threshold;
|
|
90217
|
+
}
|
|
90218
|
+
function calcProportionalDistricts(proportion, nDistricts) {
|
|
90219
|
+
// TODO - Bump up to get a statewide proportion on the bubble to rate one district?
|
|
90220
|
+
const roundUp = 0.0;
|
|
90221
|
+
const fractional = proportion * nDistricts;
|
|
90222
|
+
const integral = Math.round(fractional + roundUp);
|
|
90223
|
+
return integral;
|
|
90224
|
+
}
|
|
90225
|
+
exports.calcProportionalDistricts = calcProportionalDistricts;
|
|
90073
90226
|
|
|
90074
90227
|
|
|
90075
90228
|
/***/ }),
|
|
@@ -90196,85 +90349,136 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
|
|
|
90196
90349
|
// NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
|
|
90197
90350
|
// NOTE - I do not (cannot) assume that the values are sorted.
|
|
90198
90351
|
// SCORE BIAS & COMPETITIVENESS
|
|
90199
|
-
/* SCORECARD FIELDS
|
|
90200
|
-
|
|
90201
|
-
*
|
|
90202
|
-
|
|
90203
|
-
|
|
90204
|
-
* S# [
|
|
90205
|
-
* S% [
|
|
90206
|
-
*
|
|
90207
|
-
*
|
|
90208
|
-
*
|
|
90209
|
-
|
|
90210
|
-
*
|
|
90211
|
-
*
|
|
90212
|
-
*
|
|
90213
|
-
|
|
90214
|
-
|
|
90215
|
-
*
|
|
90216
|
-
*
|
|
90217
|
-
*
|
|
90218
|
-
*
|
|
90219
|
-
*
|
|
90220
|
-
*
|
|
90221
|
-
|
|
90222
|
-
|
|
90352
|
+
/* SCORECARD FIELDS:
|
|
90353
|
+
|
|
90354
|
+
* ??? [statewideV] (V) = the average statewide two-party vote for Democrats
|
|
90355
|
+
|
|
90356
|
+
|
|
90357
|
+
* ^S# [bestS] = the Democratic seats closest to proportional
|
|
90358
|
+
* ^S% [bestSf] = the corresponding Democratic seat share
|
|
90359
|
+
* S! [fptpS] = the estimated number of Democratic seats using first past the post
|
|
90360
|
+
* S# [estS] (S_V) = the estimated Democratic seats, using seat probabilities
|
|
90361
|
+
* S% [estSf] = the estimated Democratic seat share fraction, calculated as S# / N
|
|
90362
|
+
|
|
90363
|
+
* BS_50 [bS50] = Seat bias as a fraction of N <<< TODO - RENAME
|
|
90364
|
+
* BV_50 [bV50] = Votes bias as a fraction <<< TODO - RENAME
|
|
90365
|
+
* decl [decl] = Declination
|
|
90366
|
+
* GS [gSym] = Global symmetry <<< TODO - RENAME
|
|
90367
|
+
|
|
90368
|
+
* EG [EG] = Efficiency gap as a fraction
|
|
90369
|
+
* BS_V [bSV] = Seats bias @ <V> (geometric) <<< TODO
|
|
90370
|
+
* PR [prop] = Disproportionality <<< TODO - RENAME
|
|
90371
|
+
* MM [mMs] = Mean – median difference using statewide Vf
|
|
90372
|
+
* TO [tOf] = Turnout bias
|
|
90373
|
+
* MM' [mMd] = Mean – median difference using average district v
|
|
90374
|
+
* LO [LO] = Lopsided outcomes
|
|
90375
|
+
|
|
90376
|
+
* B% [bias]= the bias calculated as S% – ^S%
|
|
90377
|
+
* B$ [bias.score] = the bias score normalized [0–100]
|
|
90378
|
+
|
|
90379
|
+
* UE# [unearnedS] = the number of unearned seats (R = positive; D = negative)
|
|
90380
|
+
* I$ [impact.score] -- the unearned seats normalized [0–100] as impact
|
|
90381
|
+
|
|
90382
|
+
* R [bigR] = Overall responsiveness or winner’s bonus
|
|
90383
|
+
* r [littleR] = The point responsiveness at V% (the slope of the S(V) curve at <V>)
|
|
90384
|
+
* MIR [MIR] = Minimal inverse responsiveness
|
|
90385
|
+
* rD [rD] = the estimated # of responsive districts (using probabilities)
|
|
90386
|
+
* rD% [rDf] = the estimated # of responsive districts, as a fraction of N
|
|
90387
|
+
|
|
90388
|
+
* C [c] = the number of districts that fall into the range [45–55%]
|
|
90389
|
+
* cD [cD] = the estimated # of competitive districts, using probabilities & a narrower [0.25–0.75] range
|
|
90390
|
+
* cD% [cDf] = the estimated # of competitive districts, as a fraction of N
|
|
90391
|
+
* beg/end [mRange] = the 1–N indices for the first and last district that separate the likely result and the proportional result
|
|
90392
|
+
* Md [mD] = the competitiveness of the marginal districts
|
|
90393
|
+
* Md% [mDf] = the probability that the marginal seats will flip, so S% => ^S%
|
|
90394
|
+
* C$ [competitiveness.score] = the competitiveness score normalized [0–100]
|
|
90395
|
+
|
|
90396
|
+
* <P$ [score] = the combined partisan score used to compare plans *across* states
|
|
90397
|
+
* >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
|
|
90223
90398
|
|
|
90224
90399
|
*/
|
|
90225
|
-
function scorePartisan(Vf, VfArray,
|
|
90400
|
+
function scorePartisan(Vf, VfArray, options) {
|
|
90401
|
+
const bAlternateMetrics = options.alternates;
|
|
90402
|
+
const bConstrained = options.constrained;
|
|
90403
|
+
const shift = options.shift;
|
|
90226
90404
|
const N = VfArray.length;
|
|
90227
|
-
const
|
|
90228
|
-
const
|
|
90229
|
-
const
|
|
90405
|
+
const bestS = bestSeats(N, Vf);
|
|
90406
|
+
const bestSf = bestSeatShare(bestS, N);
|
|
90407
|
+
const fptpS = bAlternateMetrics ? estFPTPSeats(VfArray) : undefined;
|
|
90230
90408
|
const range = bConstrained ? C.competitiveDistribution() : undefined;
|
|
90231
|
-
const
|
|
90232
|
-
const
|
|
90233
|
-
const
|
|
90234
|
-
const biasScore =
|
|
90235
|
-
const
|
|
90236
|
-
const impactScore = scoreImpact(
|
|
90237
|
-
|
|
90238
|
-
|
|
90239
|
-
const
|
|
90240
|
-
const
|
|
90241
|
-
const
|
|
90409
|
+
const estS = estSeats(VfArray, range);
|
|
90410
|
+
const estSf = estSeatShare(estS, N);
|
|
90411
|
+
const bias = estBias(estSf, bestSf);
|
|
90412
|
+
const biasScore = scorebias(bias, Vf, estSf);
|
|
90413
|
+
const unearnedS = estUnearnedSeats(bestS, estS);
|
|
90414
|
+
const impactScore = scoreImpact(unearnedS, Vf, N);
|
|
90415
|
+
// Calculate additional alternate metrics for reference
|
|
90416
|
+
// NOTE - Use the uncompressed seat probability function
|
|
90417
|
+
const inferredSVpoints = bAlternateMetrics ? inferSVpoints(Vf, VfArray, shift) : undefined;
|
|
90418
|
+
const TOf = bAlternateMetrics ? calcTurnoutBias(Vf, VfArray) : undefined;
|
|
90419
|
+
const Bs50 = bAlternateMetrics ? estPartisanBias(inferredSVpoints, N) : undefined;
|
|
90420
|
+
const Bs50f = (!(Bs50 === undefined)) ? U.trim(Bs50 / N) : undefined;
|
|
90421
|
+
const Bv50f = bAlternateMetrics ? estVotesBias(inferredSVpoints, N) : undefined;
|
|
90422
|
+
const decl = bAlternateMetrics ? calcDeclination(VfArray) : undefined;
|
|
90423
|
+
const gSym = bAlternateMetrics ? calcGlobalSymmetry(inferredSVpoints, Bs50f) : undefined;
|
|
90424
|
+
const EG = bAlternateMetrics ? calcEfficiencyGap(Vf, estSf) : undefined;
|
|
90425
|
+
const BsGf = bAlternateMetrics ? estGeometricSeatsBias(Vf, inferredSVpoints) : undefined;
|
|
90426
|
+
const prop = bAlternateMetrics ? calcDisproportionality(Vf, estSf) : undefined;
|
|
90427
|
+
const mMs = bAlternateMetrics ? estMeanMedianDifference(VfArray, Vf) : undefined;
|
|
90428
|
+
const mMd = bAlternateMetrics ? estMeanMedianDifference(VfArray) : undefined;
|
|
90429
|
+
const LO = bAlternateMetrics ? calcLopsidedOutcomes(VfArray) : undefined;
|
|
90430
|
+
// Calculate alternate responsiveness metrics for reference
|
|
90431
|
+
const bigR = bAlternateMetrics ? calcBigR(Vf, estSf) : undefined;
|
|
90432
|
+
const littleR = bAlternateMetrics ? estResponsiveness(Vf, inferredSVpoints) : undefined;
|
|
90433
|
+
const MIR = bAlternateMetrics ? calcMinimalInverseResponsiveness(Vf, littleR) : undefined;
|
|
90434
|
+
const rD = (!bConstrained || bAlternateMetrics) ? estResponsiveDistricts(VfArray) : undefined;
|
|
90435
|
+
const rDf = bAlternateMetrics ? estResponsiveDistrictsShare(rD, N) : undefined;
|
|
90242
90436
|
const Cn = countCompetitiveDistricts(VfArray);
|
|
90243
|
-
const
|
|
90244
|
-
const
|
|
90437
|
+
const cD = bConstrained ? estCompetitiveDistricts(VfArray) : rD;
|
|
90438
|
+
const cDf = estCompetitiveDistrictsShare(cD, N);
|
|
90245
90439
|
const Mrange = findMarginalDistricts(Vf, VfArray, N);
|
|
90246
90440
|
const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
|
|
90247
90441
|
const Mdf = estMarginalCompetitiveShare(Md, Mrange);
|
|
90248
|
-
const competitivenessScore = scoreCompetitiveness(Mdf,
|
|
90249
|
-
|
|
90250
|
-
|
|
90251
|
-
|
|
90252
|
-
|
|
90253
|
-
|
|
90254
|
-
|
|
90255
|
-
Bias: Bias,
|
|
90442
|
+
const competitivenessScore = scoreCompetitiveness(Mdf, cDf);
|
|
90443
|
+
let biasScoring = {
|
|
90444
|
+
bestS: bestS,
|
|
90445
|
+
bestSf: bestSf,
|
|
90446
|
+
estS: estS,
|
|
90447
|
+
estSf: estSf,
|
|
90448
|
+
bias: bias,
|
|
90256
90449
|
score: biasScore
|
|
90257
90450
|
};
|
|
90258
90451
|
const impactScoring = {
|
|
90259
|
-
|
|
90452
|
+
unearnedS: unearnedS,
|
|
90260
90453
|
score: impactScore
|
|
90261
90454
|
};
|
|
90262
90455
|
let competitiveScoring = {
|
|
90263
|
-
|
|
90264
|
-
|
|
90265
|
-
|
|
90266
|
-
|
|
90267
|
-
|
|
90268
|
-
|
|
90269
|
-
Mrange: Mrange,
|
|
90270
|
-
Md: Md,
|
|
90271
|
-
Mdf: Mdf,
|
|
90456
|
+
cSimple: Cn,
|
|
90457
|
+
cD: cD,
|
|
90458
|
+
cDf: cDf,
|
|
90459
|
+
mRange: Mrange,
|
|
90460
|
+
mD: Md,
|
|
90461
|
+
mDf: Mdf,
|
|
90272
90462
|
score: competitivenessScore
|
|
90273
90463
|
};
|
|
90274
|
-
if (
|
|
90275
|
-
|
|
90276
|
-
|
|
90277
|
-
|
|
90464
|
+
if (bAlternateMetrics) {
|
|
90465
|
+
biasScoring.tOf = TOf;
|
|
90466
|
+
biasScoring.fptpS = fptpS;
|
|
90467
|
+
biasScoring.bS50 = Bs50f;
|
|
90468
|
+
biasScoring.bV50 = Bv50f;
|
|
90469
|
+
biasScoring.decl = decl;
|
|
90470
|
+
biasScoring.gSym = gSym;
|
|
90471
|
+
biasScoring.eG = EG;
|
|
90472
|
+
biasScoring.bSV = BsGf;
|
|
90473
|
+
biasScoring.prop = prop;
|
|
90474
|
+
biasScoring.mMs = mMs;
|
|
90475
|
+
biasScoring.mMd = mMd;
|
|
90476
|
+
biasScoring.lO = LO;
|
|
90477
|
+
competitiveScoring.bigR = bigR;
|
|
90478
|
+
competitiveScoring.littleR = littleR;
|
|
90479
|
+
competitiveScoring.mIR = MIR;
|
|
90480
|
+
competitiveScoring.rD = rD;
|
|
90481
|
+
competitiveScoring.rDf = rDf;
|
|
90278
90482
|
}
|
|
90279
90483
|
// Weight bias, impact, and competitiveness into partisan scores to compare
|
|
90280
90484
|
// plans across states and within a state.
|
|
@@ -90288,7 +90492,8 @@ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
|
|
|
90288
90492
|
impact: impactScoring,
|
|
90289
90493
|
competitiveness: competitiveScoring,
|
|
90290
90494
|
score: acrossStatesPartisanScore,
|
|
90291
|
-
score2: withinStatesPartisanScore
|
|
90495
|
+
score2: withinStatesPartisanScore,
|
|
90496
|
+
details: {} // TODO - Add notes
|
|
90292
90497
|
};
|
|
90293
90498
|
return s;
|
|
90294
90499
|
}
|
|
@@ -90301,35 +90506,49 @@ function weightPartisan(bS, iS, cS, context) {
|
|
|
90301
90506
|
return score;
|
|
90302
90507
|
}
|
|
90303
90508
|
exports.weightPartisan = weightPartisan;
|
|
90304
|
-
function
|
|
90305
|
-
|
|
90306
|
-
|
|
90307
|
-
exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
|
|
90308
|
-
function printPartisanScorecardRow(xx, name, N, Vf, s) {
|
|
90309
|
-
console.log('%s, %s, %i, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %f, %i, %i, %f, %f, %i, %i, %i', xx, name, N, U.trim(1 / N), Vf, s.bias.BestS, s.bias.BestSf, s.bias.FptpS, s.bias.ProbableS, s.bias.ProbableSf, s.bias.Bias, s.bias.score, s.impact.UnearnedS, s.impact.score, s.competitiveness.R, s.competitiveness.Rd, s.competitiveness.Rdf, s.competitiveness.C, s.competitiveness.Cd, s.competitiveness.Cdf, s.competitiveness.Mrange[C.BEG], s.competitiveness.Mrange[C.END], s.competitiveness.Md, s.competitiveness.Mdf, s.competitiveness.score, s.score, s.score2);
|
|
90509
|
+
function extraBonus(Vf) {
|
|
90510
|
+
const okExtra = (0.5 - Vf) * (C.winnerBonus() - 1.0);
|
|
90511
|
+
return U.trim(okExtra);
|
|
90310
90512
|
}
|
|
90311
|
-
exports.
|
|
90312
|
-
function
|
|
90513
|
+
exports.extraBonus = extraBonus;
|
|
90514
|
+
function scorebias(rawBias, Vf, Sf) {
|
|
90313
90515
|
if (isAntimajoritarian(Vf, Sf)) {
|
|
90314
90516
|
return 0;
|
|
90315
90517
|
}
|
|
90316
90518
|
else {
|
|
90317
|
-
|
|
90519
|
+
// Adjust bias to incorporate an acceptable winner's bonus based on Vf
|
|
90520
|
+
const extra = extraBonus(Vf);
|
|
90521
|
+
const adjusted = adjustBias(Vf, rawBias, extra);
|
|
90522
|
+
// Then normalize
|
|
90523
|
+
const _normalizer = new normalize_1.Normalizer(adjusted);
|
|
90318
90524
|
const worst = C.biasRange()[C.BEG];
|
|
90319
90525
|
const best = C.biasRange()[C.END];
|
|
90526
|
+
_normalizer.positive();
|
|
90320
90527
|
_normalizer.clip(worst, best);
|
|
90321
90528
|
_normalizer.unitize(worst, best);
|
|
90322
90529
|
_normalizer.invert();
|
|
90323
|
-
// _normalizer.decay();
|
|
90324
90530
|
_normalizer.rescale();
|
|
90325
90531
|
const score = _normalizer.normalizedNum;
|
|
90326
90532
|
return score;
|
|
90327
90533
|
}
|
|
90328
90534
|
}
|
|
90329
|
-
exports.
|
|
90535
|
+
exports.scorebias = scorebias;
|
|
90536
|
+
// Adjust bias to account for a winner's bonus
|
|
90537
|
+
function adjustBias(Vf, bias, extra) {
|
|
90538
|
+
if (Vf > 0.5)
|
|
90539
|
+
return Math.min(bias - extra, 0);
|
|
90540
|
+
else
|
|
90541
|
+
return Math.max(bias - extra, 0);
|
|
90542
|
+
}
|
|
90543
|
+
exports.adjustBias = adjustBias;
|
|
90330
90544
|
// Normalize unearned seats
|
|
90331
|
-
function scoreImpact(rawUE) {
|
|
90332
|
-
|
|
90545
|
+
function scoreImpact(rawUE, Vf, N) {
|
|
90546
|
+
// Adjust impact to incorporate an acceptable winner's bonus based on Vf
|
|
90547
|
+
const extra = extraBonus(Vf);
|
|
90548
|
+
const adjustedBias = adjustBias(Vf, rawUE / N, extra);
|
|
90549
|
+
const adjustedImpact = adjustedBias * N;
|
|
90550
|
+
// Then normalize
|
|
90551
|
+
const _normalizer = new normalize_1.Normalizer(adjustedImpact);
|
|
90333
90552
|
const worst = C.unearnedThreshold();
|
|
90334
90553
|
const best = 0.0;
|
|
90335
90554
|
_normalizer.positive();
|
|
@@ -90381,11 +90600,11 @@ const { erf } = __webpack_require__(/*! mathjs */ "./node_modules/mathjs/main/es
|
|
|
90381
90600
|
// Estimate the probability of a seat win for district, given a Vf
|
|
90382
90601
|
function estSeatProbability(Vf, range) {
|
|
90383
90602
|
if (range) {
|
|
90603
|
+
// If a range is provided, it defines end points of a compressed probability
|
|
90604
|
+
// distribution. These *aren't* the points where races start or stop being
|
|
90605
|
+
// contested, just the end points of a distribution that yields the desired
|
|
90606
|
+
// probabilities in the typical competitive range [45-55%].
|
|
90384
90607
|
const _normalizer = new normalize_1.Normalizer(Vf);
|
|
90385
|
-
// The end points of a compressed probability distribution
|
|
90386
|
-
// NOTE - These aren't the points where races start or stop being contested,
|
|
90387
|
-
// just the end points of a distribution that yields the desired behavior
|
|
90388
|
-
// in the typical competitive range [45-55%].
|
|
90389
90608
|
const distBeg = range[C.BEG];
|
|
90390
90609
|
const distEnd = range[C.END];
|
|
90391
90610
|
_normalizer.clip(distBeg, distEnd);
|
|
@@ -90393,6 +90612,7 @@ function estSeatProbability(Vf, range) {
|
|
|
90393
90612
|
return seatProbabilityFn(_normalizer.wipNum);
|
|
90394
90613
|
}
|
|
90395
90614
|
else {
|
|
90615
|
+
// Otherwise, use the full probability distribution.
|
|
90396
90616
|
return seatProbabilityFn(Vf);
|
|
90397
90617
|
}
|
|
90398
90618
|
}
|
|
@@ -90407,19 +90627,27 @@ function estDistrictResponsiveness(Vf) {
|
|
|
90407
90627
|
return U.trim(1.0 - 4.0 * Math.pow((estSeatProbability(Vf) - 0.5), 2));
|
|
90408
90628
|
}
|
|
90409
90629
|
exports.estDistrictResponsiveness = estDistrictResponsiveness;
|
|
90410
|
-
function inferSVpoints(Vf, VfArray, range) {
|
|
90630
|
+
function inferSVpoints(Vf, VfArray, shift, range) {
|
|
90411
90631
|
const nDistricts = VfArray.length;
|
|
90412
90632
|
let SVpoints = [];
|
|
90413
90633
|
for (let shiftedVf of shiftRange()) {
|
|
90414
|
-
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
|
|
90415
|
-
const shiftedSf =
|
|
90634
|
+
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf, shift);
|
|
90635
|
+
const shiftedSf = estSeats(shiftedVPI, range) / nDistricts;
|
|
90416
90636
|
SVpoints.push({ v: shiftedVf, s: shiftedSf });
|
|
90637
|
+
// TODO - Why can't I trim these? Why does that only break the Hypotheticals?!?
|
|
90638
|
+
// SVpoints.push({v: U.trim(Number(shiftedVf)), s: shiftedSf});
|
|
90417
90639
|
}
|
|
90418
90640
|
return SVpoints;
|
|
90419
90641
|
}
|
|
90420
90642
|
exports.inferSVpoints = inferSVpoints;
|
|
90421
|
-
|
|
90422
|
-
|
|
90643
|
+
function shiftDistricts(Vf, VfArray, shiftedVf, shift) {
|
|
90644
|
+
if (shift == 0 /* Proportional */)
|
|
90645
|
+
return shiftProportionally(Vf, VfArray, shiftedVf);
|
|
90646
|
+
else
|
|
90647
|
+
return shiftUniformly(Vf, VfArray, shiftedVf);
|
|
90648
|
+
}
|
|
90649
|
+
// Shift districts proportionally
|
|
90650
|
+
function shiftProportionally(Vf, VfArray, shiftedVf) {
|
|
90423
90651
|
let shiftedVfArray;
|
|
90424
90652
|
if (shiftedVf < Vf) {
|
|
90425
90653
|
// Shift down: D's to R's
|
|
@@ -90437,18 +90665,26 @@ function shiftDistricts(Vf, VfArray, shiftedVf) {
|
|
|
90437
90665
|
}
|
|
90438
90666
|
return shiftedVfArray;
|
|
90439
90667
|
}
|
|
90440
|
-
//
|
|
90668
|
+
// Shift districts uniformly
|
|
90669
|
+
function shiftUniformly(Vf, VfArray, shiftedVf) {
|
|
90670
|
+
const shift = shiftedVf - Vf;
|
|
90671
|
+
const shiftedVfArray = VfArray.map((v => v + shift));
|
|
90672
|
+
return shiftedVfArray;
|
|
90673
|
+
}
|
|
90674
|
+
// Generate a range of v's in 1/2% increments
|
|
90441
90675
|
function shiftRange() {
|
|
90442
|
-
const range = [];
|
|
90443
|
-
const
|
|
90444
|
-
const upper = 75 / 100;
|
|
90676
|
+
const range = [0.25, 0.75];
|
|
90677
|
+
const axisRange = [];
|
|
90445
90678
|
const step = (1 / 100) / 2;
|
|
90446
|
-
for (let v =
|
|
90447
|
-
|
|
90679
|
+
for (let v = range[0]; v <= range[1] + S.EPSILON; v += step) {
|
|
90680
|
+
axisRange.push(v);
|
|
90448
90681
|
}
|
|
90449
|
-
return
|
|
90682
|
+
return axisRange;
|
|
90450
90683
|
}
|
|
90451
|
-
// ESTIMATE BIAS
|
|
90684
|
+
// ESTIMATE BIAS
|
|
90685
|
+
//
|
|
90686
|
+
// NOTE: By convention, '+' = R bias; '-' = D bias.
|
|
90687
|
+
//
|
|
90452
90688
|
// ^S# - The # of Democratic seats closest to proportional @ statewide Vf
|
|
90453
90689
|
// The "expected number of seats" from http://bit.ly/2Fcuf4q
|
|
90454
90690
|
function bestSeats(N, Vf) {
|
|
@@ -90460,17 +90696,17 @@ function bestSeatShare(bestS, N) {
|
|
|
90460
90696
|
return U.trim(bestS / N);
|
|
90461
90697
|
}
|
|
90462
90698
|
exports.bestSeatShare = bestSeatShare;
|
|
90463
|
-
// S# - The estimated # of Democratic seats
|
|
90464
|
-
function
|
|
90699
|
+
// S# - The estimated # of Democratic seats, using seat probabilities
|
|
90700
|
+
function estSeats(VfArray, range) {
|
|
90465
90701
|
// Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
|
|
90466
90702
|
return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
|
|
90467
90703
|
}
|
|
90468
|
-
exports.
|
|
90469
|
-
// S% - The estimated Democratic seat share fraction
|
|
90470
|
-
function
|
|
90471
|
-
return U.trim(
|
|
90704
|
+
exports.estSeats = estSeats;
|
|
90705
|
+
// S% - The estimated Democratic seat share fraction
|
|
90706
|
+
function estSeatShare(estS, N) {
|
|
90707
|
+
return U.trim(estS / N);
|
|
90472
90708
|
}
|
|
90473
|
-
exports.
|
|
90709
|
+
exports.estSeatShare = estSeatShare;
|
|
90474
90710
|
// F# - The estimated number of Democratic seats using first past the post
|
|
90475
90711
|
function estFPTPSeats(VfArray) {
|
|
90476
90712
|
// Python: sum([1.0 for vpi in vpi_by_district if (vpi > 0.5)])
|
|
@@ -90484,34 +90720,33 @@ function estFPTPSeats(VfArray) {
|
|
|
90484
90720
|
}));
|
|
90485
90721
|
}
|
|
90486
90722
|
exports.estFPTPSeats = estFPTPSeats;
|
|
90487
|
-
// B% -
|
|
90488
|
-
function estBias(
|
|
90489
|
-
return U.trim(
|
|
90723
|
+
// B% - The bias calculated as ^S% — S%
|
|
90724
|
+
function estBias(estSf, bestSf) {
|
|
90725
|
+
return U.trim(bestSf - estSf);
|
|
90490
90726
|
}
|
|
90491
90727
|
exports.estBias = estBias;
|
|
90492
90728
|
// UE# - The estimated # of unearned seats
|
|
90493
90729
|
// UE_# from http://bit.ly/2Fcuf4q
|
|
90494
|
-
function estUnearnedSeats(
|
|
90495
|
-
|
|
90496
|
-
return U.trim(best - probable);
|
|
90730
|
+
function estUnearnedSeats(proportional, probable) {
|
|
90731
|
+
return U.trim(proportional - probable);
|
|
90497
90732
|
}
|
|
90498
90733
|
exports.estUnearnedSeats = estUnearnedSeats;
|
|
90499
90734
|
// ESTIMATE RESPONSIVENESS ("COMPETITIVE")
|
|
90500
90735
|
// R# - Estimate responsiveness at the statewide vote share
|
|
90501
90736
|
function estResponsiveness(Vf, inferredSVpoints) {
|
|
90502
|
-
|
|
90503
|
-
//
|
|
90504
|
-
|
|
90505
|
-
|
|
90506
|
-
|
|
90507
|
-
|
|
90508
|
-
|
|
90509
|
-
|
|
90510
|
-
return
|
|
90737
|
+
let R = undefined;
|
|
90738
|
+
// NOTE - Seat values are already fractions [0.0–1.0] here.
|
|
90739
|
+
const lowerPt = findBracketingLowerVf(Vf, inferredSVpoints);
|
|
90740
|
+
const upperPt = findBracketingUpperVf(Vf, inferredSVpoints);
|
|
90741
|
+
if (!(U.areRoughlyEqual((upperPt.v - lowerPt.v), 0, S.EPSILON))) {
|
|
90742
|
+
R = ((upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v));
|
|
90743
|
+
R = U.trim(R);
|
|
90744
|
+
}
|
|
90745
|
+
return R;
|
|
90511
90746
|
}
|
|
90512
90747
|
exports.estResponsiveness = estResponsiveness;
|
|
90513
90748
|
// Find the S(V) point that brackets a Vf value on the lower end
|
|
90514
|
-
function
|
|
90749
|
+
function findBracketingLowerVf(Vf, inferredSVpoints) {
|
|
90515
90750
|
let lowerPt = inferredSVpoints[0];
|
|
90516
90751
|
let smallerPoints = [];
|
|
90517
90752
|
for (let pt of inferredSVpoints) {
|
|
@@ -90527,7 +90762,7 @@ function findLowerBracket(Vf, inferredSVpoints) {
|
|
|
90527
90762
|
return lowerPt;
|
|
90528
90763
|
}
|
|
90529
90764
|
// Find the S(V) point that brackets a Vf value on the upper end
|
|
90530
|
-
function
|
|
90765
|
+
function findBracketingUpperVf(Vf, inferredSVpoints) {
|
|
90531
90766
|
let upperPt = inferredSVpoints[-1];
|
|
90532
90767
|
for (let pt of inferredSVpoints) {
|
|
90533
90768
|
if (pt.v >= Vf) {
|
|
@@ -90538,15 +90773,44 @@ function findUpperBracket(Vf, inferredSVpoints) {
|
|
|
90538
90773
|
}
|
|
90539
90774
|
return upperPt;
|
|
90540
90775
|
}
|
|
90541
|
-
//
|
|
90776
|
+
// The corresponding functions via the Sf y-axis (vs. Vf x-axis)
|
|
90777
|
+
// Find the S(V) point that brackets a Sf value on the lower end
|
|
90778
|
+
function findBracketingLowerSf(Sf, inferredSVpoints) {
|
|
90779
|
+
let lowerPt = inferredSVpoints[0];
|
|
90780
|
+
let smallerPoints = [];
|
|
90781
|
+
for (let pt of inferredSVpoints) {
|
|
90782
|
+
if (pt.s <= Sf) {
|
|
90783
|
+
smallerPoints.push(pt);
|
|
90784
|
+
}
|
|
90785
|
+
else {
|
|
90786
|
+
break;
|
|
90787
|
+
}
|
|
90788
|
+
}
|
|
90789
|
+
// The last smaller point
|
|
90790
|
+
lowerPt = smallerPoints.slice(-1)[0];
|
|
90791
|
+
return lowerPt;
|
|
90792
|
+
}
|
|
90793
|
+
// Find the S(V) point that brackets a Sf value on the upper end
|
|
90794
|
+
function findBracketingUpperSf(Sf, inferredSVpoints) {
|
|
90795
|
+
let upperPt = inferredSVpoints[-1];
|
|
90796
|
+
for (let pt of inferredSVpoints) {
|
|
90797
|
+
if (pt.s >= Sf) {
|
|
90798
|
+
// The first bigger point
|
|
90799
|
+
upperPt = { v: pt.v, s: pt.s };
|
|
90800
|
+
break;
|
|
90801
|
+
}
|
|
90802
|
+
}
|
|
90803
|
+
return upperPt;
|
|
90804
|
+
}
|
|
90805
|
+
// rD - Estimate the number of responsive districts, given a set of Vf's
|
|
90542
90806
|
function estResponsiveDistricts(VfArray) {
|
|
90543
90807
|
// Python: sum([est_district_responsiveness(vpi) for vpi in vpi_by_district])
|
|
90544
90808
|
return U.trim(U.sumArray(VfArray.map(v => estDistrictResponsiveness(v))));
|
|
90545
90809
|
}
|
|
90546
90810
|
exports.estResponsiveDistricts = estResponsiveDistricts;
|
|
90547
|
-
//
|
|
90548
|
-
function estResponsiveDistrictsShare(
|
|
90549
|
-
return U.trim(
|
|
90811
|
+
// rD% - The estimated # of responsive districts, as a fraction of N
|
|
90812
|
+
function estResponsiveDistrictsShare(rD, N) {
|
|
90813
|
+
return U.trim(rD / N);
|
|
90550
90814
|
}
|
|
90551
90815
|
exports.estResponsiveDistrictsShare = estResponsiveDistrictsShare;
|
|
90552
90816
|
// ESTIMATE COMPETITIVENESS (ENHANCED)
|
|
@@ -90558,7 +90822,7 @@ exports.countCompetitiveDistricts = countCompetitiveDistricts;
|
|
|
90558
90822
|
function isCompetitive(v) {
|
|
90559
90823
|
return ((v >= C.competitiveRange()[C.BEG]) && (v <= C.competitiveRange()[C.END])) ? 1 : 0;
|
|
90560
90824
|
}
|
|
90561
|
-
//
|
|
90825
|
+
// cD - The estimated # of competitive districts
|
|
90562
90826
|
function estCompetitiveDistricts(VfArray) {
|
|
90563
90827
|
return U.trim(U.sumArray(VfArray.map(v => estDistrictCompetitiveness(v))));
|
|
90564
90828
|
}
|
|
@@ -90578,9 +90842,9 @@ function estDistrictCompetitiveness(Vf) {
|
|
|
90578
90842
|
return dC;
|
|
90579
90843
|
}
|
|
90580
90844
|
exports.estDistrictCompetitiveness = estDistrictCompetitiveness;
|
|
90581
|
-
//
|
|
90582
|
-
function estCompetitiveDistrictsShare(
|
|
90583
|
-
return U.trim(
|
|
90845
|
+
// cD% - The estimated # of competitive districts, as a fraction of N
|
|
90846
|
+
function estCompetitiveDistrictsShare(cD, N) {
|
|
90847
|
+
return U.trim(cD / N);
|
|
90584
90848
|
}
|
|
90585
90849
|
exports.estCompetitiveDistrictsShare = estCompetitiveDistrictsShare;
|
|
90586
90850
|
// Md - The estimated # of "marginal" districts in and around the likely FPTP
|
|
@@ -90628,6 +90892,328 @@ function findMarginalDistricts(Vf, VfArray, N) {
|
|
|
90628
90892
|
return [minId, maxId];
|
|
90629
90893
|
}
|
|
90630
90894
|
exports.findMarginalDistricts = findMarginalDistricts;
|
|
90895
|
+
// ADVANCED/ALTERNATE METRICS FOR REFERENCE
|
|
90896
|
+
function calcTurnoutBias(statewideVf, VfArray) {
|
|
90897
|
+
const districtAvg = U.avgArray(VfArray);
|
|
90898
|
+
const turnoutBias = statewideVf - districtAvg;
|
|
90899
|
+
return U.trim(turnoutBias);
|
|
90900
|
+
}
|
|
90901
|
+
exports.calcTurnoutBias = calcTurnoutBias;
|
|
90902
|
+
// PARTISAN BIAS - I'm using John Nagle's simple seat bias below, which is what
|
|
90903
|
+
// PlanScore is doing:
|
|
90904
|
+
//
|
|
90905
|
+
// "Partisan bias is the difference between each party’s seat share and 50 %
|
|
90906
|
+
// in a hypothetical, perfectly tied election.For example, if a party would
|
|
90907
|
+
// win 55 % of a plan’s districts if it received 50 % of the statewide vote,
|
|
90908
|
+
// then the plan would have a bias of 5 % in this party’s favor.To calculate
|
|
90909
|
+
// partisan bias, the observed vote share in each district is shifted by the
|
|
90910
|
+
// amount necessary to simulate a tied statewide election.Each party’s seat
|
|
90911
|
+
// share in this hypothetical election is then determined. The difference
|
|
90912
|
+
// between each party’s seat share and 50 % is partisan bias."
|
|
90913
|
+
//
|
|
90914
|
+
// This is *not* King's & others' geometric partisan bias metric per se.
|
|
90915
|
+
// That is below.
|
|
90916
|
+
function estPartisanBias(inferredSVpoints, nDistricts) {
|
|
90917
|
+
return estSeatBias(inferredSVpoints, nDistricts);
|
|
90918
|
+
}
|
|
90919
|
+
exports.estPartisanBias = estPartisanBias;
|
|
90920
|
+
// SEATS BIAS -- John Nagle's simple seat bias @ 50% (alpha), a fractional # of seats.
|
|
90921
|
+
function estSeatBias(inferredSVpoints, nDistricts) {
|
|
90922
|
+
const half = 0.5;
|
|
90923
|
+
const tolerance = 0.001;
|
|
90924
|
+
let dSeats = 0;
|
|
90925
|
+
for (let pt of inferredSVpoints) {
|
|
90926
|
+
if (U.areRoughlyEqual(pt.v, half, tolerance)) {
|
|
90927
|
+
dSeats = pt.s * nDistricts;
|
|
90928
|
+
break;
|
|
90929
|
+
}
|
|
90930
|
+
}
|
|
90931
|
+
const rSeats = nDistricts - dSeats;
|
|
90932
|
+
const Bs = (rSeats - dSeats) / 2.0;
|
|
90933
|
+
// NOTE - That is the same as (N/2) - S(0.5).
|
|
90934
|
+
// const BsAlt = (nDistricts / 2.0) - dSeats;
|
|
90935
|
+
return U.trim(Bs);
|
|
90936
|
+
}
|
|
90937
|
+
exports.estSeatBias = estSeatBias;
|
|
90938
|
+
// VOTES BIAS -- John Nagle's simple vote bias @ 50% (alpha2), a percentage.
|
|
90939
|
+
function estVotesBias(inferredSVpoints, nDistricts) {
|
|
90940
|
+
let extraVf = 0.0;
|
|
90941
|
+
// Interpolate the extra Vf required @ Sf = 0.5
|
|
90942
|
+
const lowerPt = findBracketingLowerSf(0.5, inferredSVpoints);
|
|
90943
|
+
const upperPt = findBracketingUpperSf(0.5, inferredSVpoints);
|
|
90944
|
+
if ((upperPt.s - lowerPt.s) != 0) {
|
|
90945
|
+
const ratio = (upperPt.v - lowerPt.v) / (upperPt.s - lowerPt.s);
|
|
90946
|
+
const deltaS = 0.5 - lowerPt.s;
|
|
90947
|
+
extraVf = lowerPt.v + (ratio * deltaS) - 0.5;
|
|
90948
|
+
}
|
|
90949
|
+
extraVf = U.trim(extraVf);
|
|
90950
|
+
return extraVf;
|
|
90951
|
+
}
|
|
90952
|
+
exports.estVotesBias = estVotesBias;
|
|
90953
|
+
// GEOMETRIC SEATS BIAS (@ V = statewide vote share)
|
|
90954
|
+
function estGeometricSeatsBias(Vf, inferredSVpoints) {
|
|
90955
|
+
const bgsSVpoints = inferGeometricSeatsBiasPoints(inferredSVpoints);
|
|
90956
|
+
// Interpolate the seat fraction @ Vf
|
|
90957
|
+
const lowerPt = findBracketingLowerVf(Vf, bgsSVpoints);
|
|
90958
|
+
const upperPt = findBracketingUpperVf(Vf, bgsSVpoints);
|
|
90959
|
+
const ratio = (upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v);
|
|
90960
|
+
const deltaV = Vf - lowerPt.v;
|
|
90961
|
+
const deltaS = ratio * deltaV;
|
|
90962
|
+
const BsGf = lowerPt.s + deltaS;
|
|
90963
|
+
return U.trim(BsGf);
|
|
90964
|
+
}
|
|
90965
|
+
exports.estGeometricSeatsBias = estGeometricSeatsBias;
|
|
90966
|
+
function inferGeometricSeatsBiasPoints(inferredSVpoints) {
|
|
90967
|
+
const nPoints = inferredSVpoints.length;
|
|
90968
|
+
const inverseSVpoints = invertSVPoints(inferredSVpoints);
|
|
90969
|
+
let bgsSVpoints = [];
|
|
90970
|
+
for (let i = 0; i < nPoints; i++) {
|
|
90971
|
+
const Vf = inferredSVpoints[i].v;
|
|
90972
|
+
const sD = inferredSVpoints[i].s;
|
|
90973
|
+
const sR = inverseSVpoints[i].s;
|
|
90974
|
+
const BsGf = 0.5 * (sR - sD);
|
|
90975
|
+
bgsSVpoints.push({ v: Vf, s: BsGf });
|
|
90976
|
+
}
|
|
90977
|
+
return bgsSVpoints;
|
|
90978
|
+
}
|
|
90979
|
+
exports.inferGeometricSeatsBiasPoints = inferGeometricSeatsBiasPoints;
|
|
90980
|
+
function invertSVPoints(inferredSVpoints) {
|
|
90981
|
+
let invertedSVpoints = [];
|
|
90982
|
+
for (let pt of inferredSVpoints) {
|
|
90983
|
+
const Vd = pt.v;
|
|
90984
|
+
const Sd = pt.s;
|
|
90985
|
+
const Vr = U.trim(1.0 - Vd);
|
|
90986
|
+
const Sr = 1.0 - Sd;
|
|
90987
|
+
invertedSVpoints.push({ v: Vr, s: Sr });
|
|
90988
|
+
}
|
|
90989
|
+
invertedSVpoints.sort(function (a, b) {
|
|
90990
|
+
return a.v - b.v;
|
|
90991
|
+
});
|
|
90992
|
+
return invertedSVpoints;
|
|
90993
|
+
}
|
|
90994
|
+
exports.invertSVPoints = invertSVPoints;
|
|
90995
|
+
// EFFICIENCY GAP -- note the formulation used. Also, to accommodate turnout bias,
|
|
90996
|
+
// we would need to have D & R votes, not just shares.
|
|
90997
|
+
function calcEfficiencyGap(Vf, Sf, shareType = 0 /* Democratic */) {
|
|
90998
|
+
let efficiencyGap;
|
|
90999
|
+
if (shareType == 1 /* Republican */) {
|
|
91000
|
+
// NOTE - This is the common formulation:
|
|
91001
|
+
//
|
|
91002
|
+
// EG = (Sf – 0.5) – (2 × (Vf – 0.5))
|
|
91003
|
+
//
|
|
91004
|
+
// in which it is implied that '-' = R bias; '+' = D bias.
|
|
91005
|
+
efficiencyGap = U.trim((Sf - 0.5) - (2.0 * (Vf - 0.5)));
|
|
91006
|
+
}
|
|
91007
|
+
else {
|
|
91008
|
+
// NOTE - This is the alternate formulation in which '+' = R bias; '-' = D bias,
|
|
91009
|
+
// which is consistent with all our other metrics.
|
|
91010
|
+
efficiencyGap = U.trim((2.0 * (Vf - 0.5)) - (Sf - 0.5));
|
|
91011
|
+
}
|
|
91012
|
+
return U.trim(efficiencyGap);
|
|
91013
|
+
}
|
|
91014
|
+
exports.calcEfficiencyGap = calcEfficiencyGap;
|
|
91015
|
+
// MEAN–MEDIAN DIFFERENCE
|
|
91016
|
+
//
|
|
91017
|
+
// From PlanScore.org: "The mean-median difference is a party’s median vote share
|
|
91018
|
+
// minus its mean vote share, across all of a plan’s districts. For example, if
|
|
91019
|
+
// a party has a median vote share of 45 % and a mean vote share of 50 %, then
|
|
91020
|
+
// the plan has a mean - median difference of 5 % against this party. When the
|
|
91021
|
+
// mean and the median diverge significantly, the district distribution is skewed
|
|
91022
|
+
// in favor of one party and against its opponent. Conversely, when the mean and
|
|
91023
|
+
// the median are close, the district distribution is more symmetric."
|
|
91024
|
+
//
|
|
91025
|
+
// From Princeton Gerrymandering Project: "The mean-median difference is calculated
|
|
91026
|
+
// by subtracting the average vote share of either party across all districts from
|
|
91027
|
+
// the median vote share of the same party across all districts. A negative mean -
|
|
91028
|
+
// median difference indicates that the examined party has an advantage; a positive
|
|
91029
|
+
// difference indicates that the examined party is disadvantaged."
|
|
91030
|
+
//
|
|
91031
|
+
// So:
|
|
91032
|
+
// * With D VPI, '+' = R bias; '-' = D bias <<< We're using this convention.
|
|
91033
|
+
// * With R VPI, '-' = R bias; '+' = D bias.
|
|
91034
|
+
function estMeanMedianDifference(VfArray, Vf) {
|
|
91035
|
+
const meanVf = Vf ? Vf : U.avgArray(VfArray);
|
|
91036
|
+
const medianVf = U.medianArray(VfArray);
|
|
91037
|
+
// NOTE - Switched order to get the signs correct
|
|
91038
|
+
const difference = meanVf - medianVf;
|
|
91039
|
+
// const difference: number = medianVf - meanVf;
|
|
91040
|
+
return U.trim(difference);
|
|
91041
|
+
}
|
|
91042
|
+
exports.estMeanMedianDifference = estMeanMedianDifference;
|
|
91043
|
+
// HELPERS FOR DECLINATION & LOPSIDED OUTCOMES
|
|
91044
|
+
// Key r(v) points, defined in Fig. 19:
|
|
91045
|
+
// * VfArray are Democratic seat shares (by convention).
|
|
91046
|
+
// * But the x-axis of r(v) graphs us Republican seat share.
|
|
91047
|
+
// * So, you have to invert the D/R axis for Vb; and
|
|
91048
|
+
// * Invert the D/R probabilities for Va.
|
|
91049
|
+
function keyRVpoints(VfArray) {
|
|
91050
|
+
const nDistricts = VfArray.length;
|
|
91051
|
+
const estS = estSeats(VfArray);
|
|
91052
|
+
const Sb = estSeatShare(estS, nDistricts);
|
|
91053
|
+
// TODO - Understand why the corresponding V to Sb is always @ 0.5.
|
|
91054
|
+
// John Nagle: "This is the dividing vote for party A vs party B wins defined
|
|
91055
|
+
// by Warrington.My modification just puts fractions of districts to the each side."
|
|
91056
|
+
const Rb = Sb / 2;
|
|
91057
|
+
const Ra = (1 + Sb) / 2;
|
|
91058
|
+
const Vb = 1.0 - (U.sumArray(VfArray.map(v => estSeatProbability(v) * v))) / estS;
|
|
91059
|
+
const Va = (U.sumArray(VfArray.map(v => estSeatProbability(1 - v) * (1 - v)))) / (nDistricts - estS);
|
|
91060
|
+
const keyPoints = {
|
|
91061
|
+
Sb: Sb,
|
|
91062
|
+
Ra: Ra,
|
|
91063
|
+
Rb: Rb,
|
|
91064
|
+
Va: Va,
|
|
91065
|
+
Vb: Vb
|
|
91066
|
+
};
|
|
91067
|
+
return keyPoints;
|
|
91068
|
+
}
|
|
91069
|
+
exports.keyRVpoints = keyRVpoints;
|
|
91070
|
+
function isASweep(Sf, nDistricts) {
|
|
91071
|
+
const oneDistrict = 1 / nDistricts;
|
|
91072
|
+
const bSweep = ((Sf > (1 - oneDistrict)) || (Sf < oneDistrict)) ? true : false;
|
|
91073
|
+
return bSweep;
|
|
91074
|
+
}
|
|
91075
|
+
exports.isASweep = isASweep;
|
|
91076
|
+
function radiansToDegrees(radians) {
|
|
91077
|
+
const degrees = radians * (180 / Math.PI);
|
|
91078
|
+
return degrees;
|
|
91079
|
+
}
|
|
91080
|
+
exports.radiansToDegrees = radiansToDegrees;
|
|
91081
|
+
// DECLINATION
|
|
91082
|
+
//
|
|
91083
|
+
// Declination is calculated using the key r(v) points, defined in Fig. 19.
|
|
91084
|
+
// Note that district vote shares are D shares, so party A = Rep & B = Dem.
|
|
91085
|
+
function calcDeclination(VfArray) {
|
|
91086
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91087
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91088
|
+
const bTooFewDistricts = (VfArray.length < 5) ? true : false;
|
|
91089
|
+
const bVaAt50 = (U.areRoughlyEqual((Va - 0.5), 0.0, S.EPSILON)) ? true : false;
|
|
91090
|
+
const bVbAt50 = (U.areRoughlyEqual((0.5 - Vb), 0.0, S.EPSILON)) ? true : false;
|
|
91091
|
+
let decl;
|
|
91092
|
+
if (bSweep || bTooFewDistricts || bVaAt50 || bVbAt50) {
|
|
91093
|
+
decl = undefined;
|
|
91094
|
+
}
|
|
91095
|
+
else {
|
|
91096
|
+
const lTan = (Sb - Rb) / (0.5 - Vb);
|
|
91097
|
+
const rTan = (Ra - Sb) / (Va - 0.5);
|
|
91098
|
+
const lAngle = radiansToDegrees(Math.atan(lTan));
|
|
91099
|
+
const rAngle = radiansToDegrees(Math.atan(rTan));
|
|
91100
|
+
decl = rAngle - lAngle;
|
|
91101
|
+
decl = U.trim(decl);
|
|
91102
|
+
}
|
|
91103
|
+
return decl;
|
|
91104
|
+
}
|
|
91105
|
+
exports.calcDeclination = calcDeclination;
|
|
91106
|
+
// LOPSIDED OUTCOMES
|
|
91107
|
+
//
|
|
91108
|
+
// This is a measure of packing bias is:
|
|
91109
|
+
//
|
|
91110
|
+
// LO = (1⁄2 - vB) - (vA – 1⁄2) Eq. 5.4.1 on P. 26
|
|
91111
|
+
//
|
|
91112
|
+
// "The ideal for this measure is that the excess vote share for districts
|
|
91113
|
+
// won by party A averaged over those districts equals the excess vote share
|
|
91114
|
+
// for districts won by party B averaged over those districts.
|
|
91115
|
+
// A positive value of LO indicates greater packing of party B voters and,
|
|
91116
|
+
// therefore, indicates a bias in favor of party A."
|
|
91117
|
+
function calcLopsidedOutcomes(VfArray) {
|
|
91118
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91119
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91120
|
+
let LO;
|
|
91121
|
+
if (bSweep) {
|
|
91122
|
+
LO = undefined;
|
|
91123
|
+
}
|
|
91124
|
+
else {
|
|
91125
|
+
LO = (0.5 - Vb) - (Va - 0.5);
|
|
91126
|
+
LO = U.trim(LO);
|
|
91127
|
+
}
|
|
91128
|
+
return LO;
|
|
91129
|
+
}
|
|
91130
|
+
exports.calcLopsidedOutcomes = calcLopsidedOutcomes;
|
|
91131
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91132
|
+
// GLOBAL SYMMETRY - Fig. 17 in Section 5.1
|
|
91133
|
+
function calcGlobalSymmetry(inferredSVpoints, S50V) {
|
|
91134
|
+
const invertedSVpoints = invertSVPoints(inferredSVpoints);
|
|
91135
|
+
let gSym = 0.0;
|
|
91136
|
+
for (let i in inferredSVpoints) {
|
|
91137
|
+
gSym += Math.abs(inferredSVpoints[i].s - invertedSVpoints[i].s) / 2;
|
|
91138
|
+
}
|
|
91139
|
+
const sign = (S50V < 0) ? -1 : 1;
|
|
91140
|
+
gSym *= sign;
|
|
91141
|
+
return U.trim(gSym);
|
|
91142
|
+
}
|
|
91143
|
+
exports.calcGlobalSymmetry = calcGlobalSymmetry;
|
|
91144
|
+
// RAW DISPROPORTIONALITY
|
|
91145
|
+
//
|
|
91146
|
+
// gamma = Sf – Vf : Eq.C.1.1 on P. 42
|
|
91147
|
+
function calcDisproportionality(Vf, Sf) {
|
|
91148
|
+
const gamma = Vf - Sf;
|
|
91149
|
+
// const gamma = Sf - Vf;
|
|
91150
|
+
return U.trim(gamma);
|
|
91151
|
+
}
|
|
91152
|
+
exports.calcDisproportionality = calcDisproportionality;
|
|
91153
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91154
|
+
// BIG 'R': Defined in Footnote 22 on P. 10
|
|
91155
|
+
function calcBigR(Vf, Sf) {
|
|
91156
|
+
let bigR = undefined;
|
|
91157
|
+
if (!(U.areRoughlyEqual(Vf, 0.5, S.EPSILON))) {
|
|
91158
|
+
bigR = (Sf - 0.5) / (Vf - 0.5);
|
|
91159
|
+
bigR = U.trim(bigR);
|
|
91160
|
+
}
|
|
91161
|
+
return bigR;
|
|
91162
|
+
}
|
|
91163
|
+
exports.calcBigR = calcBigR;
|
|
91164
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91165
|
+
// MINIMAL INVERSE RESPONSIVENESS
|
|
91166
|
+
//
|
|
91167
|
+
// zeta = (1 / r) - (1 / r_sub_max) : Eq. 5.2.1
|
|
91168
|
+
//
|
|
91169
|
+
// where r_sub_max = 10 or 20 for balanced and unbalanced states, respectively.
|
|
91170
|
+
function calcMinimalInverseResponsiveness(Vf, r) {
|
|
91171
|
+
let MIR = undefined;
|
|
91172
|
+
if (!(U.areRoughlyEqual(r, 0, S.EPSILON))) {
|
|
91173
|
+
const bBalanced = isBalanced(Vf);
|
|
91174
|
+
const ideal = bBalanced ? 0.1 : 0.2;
|
|
91175
|
+
MIR = (1 / r) - ideal;
|
|
91176
|
+
MIR = U.trim(MIR);
|
|
91177
|
+
}
|
|
91178
|
+
return MIR;
|
|
91179
|
+
}
|
|
91180
|
+
exports.calcMinimalInverseResponsiveness = calcMinimalInverseResponsiveness;
|
|
91181
|
+
function isBalanced(Vf) {
|
|
91182
|
+
const [lower, upper] = C.competitiveRange();
|
|
91183
|
+
const bBalanced = ((Vf > upper) || (Vf < lower)) ? false : true;
|
|
91184
|
+
return bBalanced;
|
|
91185
|
+
}
|
|
91186
|
+
// HELPERS
|
|
91187
|
+
function printPartisanScorecardHeader() {
|
|
91188
|
+
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, <P$');
|
|
91189
|
+
}
|
|
91190
|
+
exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
|
|
91191
|
+
function printPartisanScorecardRow(xx, name, N, Vf, s) {
|
|
91192
|
+
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i', xx, // 1
|
|
91193
|
+
name, // 2
|
|
91194
|
+
N, // 3
|
|
91195
|
+
Vf, // 4
|
|
91196
|
+
s.bias.bestS, // 5
|
|
91197
|
+
s.bias.estS, // 6
|
|
91198
|
+
s.bias.bias, // 7
|
|
91199
|
+
s.bias.score, s.impact.unearnedS, // 9
|
|
91200
|
+
s.impact.score, s.competitiveness.cSimple, // 11
|
|
91201
|
+
s.competitiveness.cD, s.competitiveness.mD, s.competitiveness.score, s.score // 15
|
|
91202
|
+
);
|
|
91203
|
+
}
|
|
91204
|
+
exports.printPartisanScorecardRow = printPartisanScorecardRow;
|
|
91205
|
+
// Generate partisan details (Table 1)
|
|
91206
|
+
function printPartisanDetailsHeader() {
|
|
91207
|
+
console.log('XX, <V>, S(<V>), S50V, V50S, Decl, B_G, EG, Beta, l-gamma, mM, TO, mM\', LO, R, r, Zeta');
|
|
91208
|
+
}
|
|
91209
|
+
exports.printPartisanDetailsHeader = printPartisanDetailsHeader;
|
|
91210
|
+
function printPartisanDetailsRow(xx, name, N, Vf, s) {
|
|
91211
|
+
console.log('%s, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f', xx, Vf, s.bias.estSf, s.bias.bS50, s.bias.bV50, s.bias.decl, s.bias.gSym, s.bias.eG, s.bias.bSV, // Beta
|
|
91212
|
+
s.bias.prop, // Lower-gamma
|
|
91213
|
+
s.bias.mMs, s.bias.tOf, s.bias.mMd, s.bias.lO, s.competitiveness.bigR, s.competitiveness.littleR, s.competitiveness.mIR // Zeta
|
|
91214
|
+
);
|
|
91215
|
+
}
|
|
91216
|
+
exports.printPartisanDetailsRow = printPartisanDetailsRow;
|
|
90631
91217
|
|
|
90632
91218
|
|
|
90633
91219
|
/***/ }),
|
|
@@ -90652,18 +91238,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
90652
91238
|
return result;
|
|
90653
91239
|
};
|
|
90654
91240
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90655
|
-
const
|
|
91241
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
90656
91242
|
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
90657
91243
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
90658
91244
|
const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
|
|
90659
91245
|
const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
|
|
90660
91246
|
const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
|
|
90661
|
-
|
|
91247
|
+
const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
|
|
90662
91248
|
function scorePlan(p, overridesJSON) {
|
|
90663
91249
|
// TRADITIONAL DISTRICTING PRINCIPLES ("best") subcategories - compactness, splitting, population deviation
|
|
90664
91250
|
// Compactness
|
|
90665
|
-
const reockM = compact_1.doReock(p.compactnessProfile.
|
|
90666
|
-
const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.
|
|
91251
|
+
const reockM = compact_1.doReock(p.compactnessProfile.geometryByDistrict);
|
|
91252
|
+
const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.geometryByDistrict);
|
|
90667
91253
|
const compactnessScore = weightCompactness(reockM.normalized, polsbyM.normalized);
|
|
90668
91254
|
const cS = {
|
|
90669
91255
|
score: compactnessScore,
|
|
@@ -90671,7 +91257,7 @@ function scorePlan(p, overridesJSON) {
|
|
|
90671
91257
|
polsby: polsbyM
|
|
90672
91258
|
};
|
|
90673
91259
|
// Splitting
|
|
90674
|
-
const CxD = p.splittingProfile.
|
|
91260
|
+
const CxD = p.splittingProfile.countyPopByDistrict;
|
|
90675
91261
|
const dT = cohesive_1.totalDistricts(CxD);
|
|
90676
91262
|
const cT = cohesive_1.totalCounties(CxD);
|
|
90677
91263
|
const countyM = cohesive_1.doCountySplittingReduced(CxD, dT, cT);
|
|
@@ -90683,7 +91269,7 @@ function scorePlan(p, overridesJSON) {
|
|
|
90683
91269
|
district: districtM
|
|
90684
91270
|
};
|
|
90685
91271
|
// Population deviation
|
|
90686
|
-
const pdS = equal_1.doPopulationDeviation(p.populationProfile.
|
|
91272
|
+
const pdS = equal_1.doPopulationDeviation(p.populationProfile.totalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
|
|
90687
91273
|
// Combine traditional principles into a score to compare plans w/in a state
|
|
90688
91274
|
const tpScore = weightTradtionalPrinciples(cS.score, sS.score, pdS.normalized, 1 /* WithinAState */);
|
|
90689
91275
|
// Populate the "best" traditional principles scorecard
|
|
@@ -90691,18 +91277,29 @@ function scorePlan(p, overridesJSON) {
|
|
|
90691
91277
|
score: tpScore,
|
|
90692
91278
|
compactness: cS,
|
|
90693
91279
|
splitting: sS,
|
|
90694
|
-
populationDeviation: pdS
|
|
91280
|
+
populationDeviation: pdS,
|
|
91281
|
+
details: {} // TODO - Add notes
|
|
90695
91282
|
};
|
|
90696
91283
|
// PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
|
|
90697
|
-
const
|
|
91284
|
+
const options = {
|
|
91285
|
+
alternates: true,
|
|
91286
|
+
constrained: true,
|
|
91287
|
+
shift: 0 /* Proportional */
|
|
91288
|
+
};
|
|
91289
|
+
const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.vfArray, options);
|
|
90698
91290
|
// Combine the partisan/partisan & traditional principles/best scores into an overall
|
|
90699
91291
|
// score for comparing plans w/in a state
|
|
90700
|
-
|
|
91292
|
+
let score = weightOverall(pS.score2, tpS.score, 1 /* WithinAState */);
|
|
91293
|
+
const mS = minority_1.evalMinorityOpportunity(p);
|
|
91294
|
+
// Add minority bonus, keeping score it to the range [0–100]
|
|
91295
|
+
score = mixinMinorityBonus(score, mS.score);
|
|
90701
91296
|
// Roll up an overall scorecard
|
|
90702
91297
|
const scorecard = {
|
|
90703
91298
|
partisan: pS,
|
|
91299
|
+
minority: mS,
|
|
90704
91300
|
traditionalPrinciples: tpS,
|
|
90705
|
-
score: score
|
|
91301
|
+
score: score,
|
|
91302
|
+
details: {} // TODO - Add notes
|
|
90706
91303
|
};
|
|
90707
91304
|
return scorecard;
|
|
90708
91305
|
}
|
|
@@ -90730,19 +91327,39 @@ function weightTradtionalPrinciples(cS, sS, pdS, context) {
|
|
|
90730
91327
|
return score;
|
|
90731
91328
|
}
|
|
90732
91329
|
exports.weightTradtionalPrinciples = weightTradtionalPrinciples;
|
|
90733
|
-
function
|
|
91330
|
+
function weightOverall(pS, tpS, context) {
|
|
90734
91331
|
const pW = C.partisanWeight(context);
|
|
90735
91332
|
const tpW = C.traditionalPrinciplesWeight(context);
|
|
90736
91333
|
const score = Math.round(((pS * pW) + (tpS * tpW)) / (pW + tpW));
|
|
90737
91334
|
return score;
|
|
90738
91335
|
}
|
|
90739
|
-
exports.
|
|
91336
|
+
exports.weightOverall = weightOverall;
|
|
91337
|
+
function mixinMinorityBonus(score, minorityBonus) {
|
|
91338
|
+
const modifiedScore = Math.min(score + minorityBonus, S.NORMALIZED_RANGE);
|
|
91339
|
+
return modifiedScore;
|
|
91340
|
+
}
|
|
91341
|
+
exports.mixinMinorityBonus = mixinMinorityBonus;
|
|
90740
91342
|
function printScorecardHeader() {
|
|
90741
|
-
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$,
|
|
91343
|
+
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, >P$, Rc, Rc$, Pc, Pc$, G$, Cs, Cs$, Ds, Ds$, S$, Eq, Eq$, T$, Od, M$, $$$');
|
|
90742
91344
|
}
|
|
90743
91345
|
exports.printScorecardHeader = printScorecardHeader;
|
|
90744
91346
|
function printScorecardRow(xx, name, N, Vf, s) {
|
|
90745
|
-
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i, %
|
|
91347
|
+
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i, %f, %i, %f, %i, %i, %f, %i, %f, %i, %i, %f, %i, %i, %f, %i, %i', xx, // 1
|
|
91348
|
+
name, // 2
|
|
91349
|
+
N, // 3
|
|
91350
|
+
Vf, // 4
|
|
91351
|
+
s.partisan.bias.bestS, // 5
|
|
91352
|
+
s.partisan.bias.estS, // 6
|
|
91353
|
+
s.partisan.bias.bias, // 7
|
|
91354
|
+
s.partisan.bias.score, s.partisan.impact.unearnedS, // 9
|
|
91355
|
+
s.partisan.impact.score, s.partisan.competitiveness.cSimple, // 11
|
|
91356
|
+
s.partisan.competitiveness.cD, s.partisan.competitiveness.mD, s.partisan.competitiveness.score, s.partisan.score2, // 15
|
|
91357
|
+
s.traditionalPrinciples.compactness.reock.raw, s.traditionalPrinciples.compactness.reock.normalized, s.traditionalPrinciples.compactness.polsby.raw, s.traditionalPrinciples.compactness.polsby.normalized, s.traditionalPrinciples.compactness.score, s.traditionalPrinciples.splitting.county.raw, s.traditionalPrinciples.splitting.county.normalized, s.traditionalPrinciples.splitting.district.raw, s.traditionalPrinciples.splitting.district.normalized, s.traditionalPrinciples.splitting.score, s.traditionalPrinciples.populationDeviation.raw, s.traditionalPrinciples.populationDeviation.normalized, s.traditionalPrinciples.score,
|
|
91358
|
+
// s.minority.nOpportunity1,
|
|
91359
|
+
// s.minority.nOpportunity2,
|
|
91360
|
+
s.minority.opportunityDistricts,
|
|
91361
|
+
// s.minority.nProportional,
|
|
91362
|
+
s.minority.score, s.score);
|
|
90746
91363
|
}
|
|
90747
91364
|
exports.printScorecardRow = printScorecardRow;
|
|
90748
91365
|
|
|
@@ -90841,11 +91458,31 @@ function maxArray(arr) {
|
|
|
90841
91458
|
return Math.max(...arr);
|
|
90842
91459
|
}
|
|
90843
91460
|
exports.maxArray = maxArray;
|
|
91461
|
+
// Modified from https://jsfiddle.net/Lucky500/3sy5au0c/
|
|
91462
|
+
function medianArray(arr) {
|
|
91463
|
+
if (arr.length === 0)
|
|
91464
|
+
return 0;
|
|
91465
|
+
arr.sort(function (a, b) {
|
|
91466
|
+
return a - b;
|
|
91467
|
+
});
|
|
91468
|
+
var half = Math.floor(arr.length / 2);
|
|
91469
|
+
if (arr.length % 2)
|
|
91470
|
+
return arr[half];
|
|
91471
|
+
return (arr[half - 1] + arr[half]) / 2.0;
|
|
91472
|
+
}
|
|
91473
|
+
exports.medianArray = medianArray;
|
|
90844
91474
|
function initArray(n, value) {
|
|
90845
91475
|
return Array.from(Array(n), () => value);
|
|
90846
91476
|
}
|
|
90847
91477
|
exports.initArray = initArray;
|
|
90848
91478
|
// MISCELLANEOUS
|
|
91479
|
+
// Deal with decimal census "counts" due to disagg/re-agg
|
|
91480
|
+
function areRoughlyEqual(x, y, tolerance) {
|
|
91481
|
+
let delta = Math.abs(x - y);
|
|
91482
|
+
let result = (delta < tolerance) ? true : false;
|
|
91483
|
+
return result;
|
|
91484
|
+
}
|
|
91485
|
+
exports.areRoughlyEqual = areRoughlyEqual;
|
|
90849
91486
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
90850
91487
|
function trim(fullFraction, digits = undefined) {
|
|
90851
91488
|
if (digits == 0) {
|
|
@@ -90886,7 +91523,7 @@ exports.deepCopy = deepCopy;
|
|
|
90886
91523
|
/*! exports provided: state, planName, nDistricts, nCounties, legislativeDistricts, populationProfile, compactnessProfile, splittingProfile, partisanProfile, demographicProfile, default */
|
|
90887
91524
|
/***/ (function(module) {
|
|
90888
91525
|
|
|
90889
|
-
module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"
|
|
91526
|
+
module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"NC 116th Congressional\",\"nDistricts\":13,\"nCounties\":100,\"legislativeDistricts\":false,\"populationProfile\":{\"totalPopByDistrict\":[733499,733499,733498,733499,733499,733498,733499,733499,733498,733499,733499,733498,733499],\"targetSize\":733499},\"compactnessProfile\":{\"geometryByDistrict\":[[1.577393519870778,10.159603194222512,2.6574239327900613],[0.7058443860242223,7.806818205304456,1.7632772181381422],[2.9200833695730037,9.972779463090355,2.733997011824872],[0.1913918462294299,3.5460738146162076,0.9224411598627837],[1.039017192939366,6.4920493759205495,2.1060599512212406],[1.0334601456545682,6.466840626614781,1.6394147493749138],[1.6241884494008865,7.988593581066711,1.9529170345835551],[0.7706777049448708,6.980189925133433,2.188634395661598],[0.9976766364327585,8.350880983965949,2.5604199958307654],[0.6762837622380331,5.8543721706585545,1.7264608756890525],[1.719230725216499,10.37139670690812,3.1586859214584764],[0.11329839503948308,2.2954988975477066,0.5379072920877442],[0.48371245467321944,5.3384017624136435,1.4517375375301198]]},\"splittingProfile\":{\"countyPopByDistrict\":[[0,0,0,0,0,0,0,21282,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240299,56552,0,0,0,12197,0,59916,0,0,54691,0,0,0,24669,0,0,0,0,0,0,0,0,0,0,0,0,24505,0,0,0,0,0,0,22099,0,0,0,0,0,0,0,80880,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45422,0,20972,13228,0,0,0,56787,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60619,0,0,0,0,0,0,0,114678,0,0,0,0,0,0,0,109332,0,0,0,0,0,0,0,0,0,0,0,0,95840,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,328583,0,0,0,0,0,24447,0,0],[0,0,0,0,0,0,47759,0,0,0,0,0,0,0,9980,66469,0,0,0,0,14793,0,0,0,103505,0,23547,33920,0,0,0,0,0,0,0,0,0,0,0,21362,0,0,0,0,0,0,0,5810,0,0,0,10153,0,59495,0,0,0,0,0,0,0,0,0,0,0,0,177772,0,13144,40661,0,13453,0,87268,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4407,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27288,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,133801,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,572410,0,0,0,0,0,0,0,0],[0,37198,11155,0,27281,17797,0,0,0,0,0,0,0,0,0,0,0,9499,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,350670,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47401,73673,0,0,0,0,0,0,0,0,51079,0,69340,0,38406,0],[151131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23719,0,63505,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,162418,0,0,0,0,0,0,0,0,0,0,0,57866,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39464,0,0,141752,0,0,93643,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,8981,107431,0,0,0,0,0,0,0,0,0,0,0,0,0,58098,0,0,0,0,0,0,58505,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59546,0,0,0,0,0,0,0,0,0,0,0,0,0,202667,0,0,0,0,0,52217,0,0,0,0,0,0,0,0,0,0,63431,0,0,0,0,0,0,0,0,0,0,0,0,0,122623,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,178011,0,0,0,0,0,0,0,0,0,0,0,0,243476,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46952,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27798,88247,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88430,0,0,0,60585,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,26948,0,0,0,0,26209,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75955,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,186130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46639,134168,0,0,0,0,36157,0,0,0,0,0,0,201292,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,111849,0,0,0,0,0,0,144859,0,0,0,0,98078,0,0,0,0,0,0,0,0,0,0,0,0,206086,0,0,0,0,0,0,0,0,0,0,0,0,6042,0,0,0,0,0,78265,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20510,0,0,0,0,0,67810,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,126469,90912,0,83029,0,0,0,0,0,27444,0,10587,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8861,0,0,0,0,0,59036,106740,0,0,0,0,40271,0,0,0,0,0,44996,33922,20764,0,0,15579,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13981,33090,0,0,0,0,0,0,0,0,0,0,0,17818],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,733498,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,162878,41240,0,0,0,0,0,0,0,0,0,0,325988,0,0,0,0,0,0,0,153395,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,49998,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]},\"partisanProfile\":{\"statewideVf\":0.488799,\"vfArray\":[0.38133475250631976,0.3849820112175976,0.3932778243643098,0.4232536822067312,0.42479562842958446,0.43309446651496813,0.43760621455968074,0.43980028165892326,0.4545677480810613,0.4642133701735491,0.6853270858231137,0.6938881738049054,0.6953207231271951]},\"demographicProfile\":{\"stateMfArray\":[0.684371246819619,0.31562875318038097,0.21168281993226215,0.06787156278984616,0.0012540930000187486,0.024136155044881008,0.017483272326632705],\"mfArrayByDistrict\":[[0.46201994876162167,0.5379800512383783,0.44446912914151915,0.065885219684988,0.0009221497550043815,0.0218600624522836,0.01298238746863721],[0.7062964810448031,0.2937035189551968,0.19676408791341676,0.07140508797387993,0.0009787472035794184,0.02027027027027027,0.011283934941653062],[0.7125415968218078,0.2874584031781921,0.21181863608495496,0.05374539732717163,0.002106863519897368,0.01628256424250371,0.010404964330393058],[0.6162003899079305,0.3837996100920695,0.22386087117385056,0.08687191382180923,0.0012060108434249284,0.07153885560599377,0.010091002234143982],[0.7766842620090415,0.22331573799095852,0.14015703622760559,0.06690518783542039,0.0007790124871119258,0.013001753659331847,0.007680851626320752],[0.7111584046318987,0.2888415953681012,0.19851813634865356,0.07148666466027488,0.0008061395588804333,0.013621967123837368,0.010186021181764765],[0.7086112059348308,0.2913887940651691,0.20229594619799895,0.06953648210647081,0.001149028868250555,0.009700759801937688,0.014166663733974303],[0.6697822118227867,0.33021778817721326,0.2240088599271958,0.07249565635824667,0.002909229825482943,0.022877125445843145,0.01884938491094157],[0.6406346877484486,0.35936531225155144,0.19622106825202063,0.059419121439330605,0.0010389455633486816,0.02217720538538736,0.08616917683114156],[0.8192767260107778,0.18072327398922214,0.11582505713398979,0.045060025957170666,0.0006806703721468273,0.014117738340433936,0.008000521964844963],[0.8956213645578734,0.1043786354421266,0.032662784268536554,0.04329373425036473,0.0012436075643032956,0.010267513422177209,0.019651410943402076],[0.46574079494234033,0.5342592050576597,0.36183998712169996,0.12151883451384417,0.001650032195750161,0.050651598079962536,0.010997775566352515],[0.6979595109954735,0.3020404890045265,0.21171009017357523,0.0572352710553516,0.0008767865416830024,0.028566846063370996,0.009525252165235058]]}}");
|
|
90890
91527
|
|
|
90891
91528
|
/***/ }),
|
|
90892
91529
|
|
|
@@ -90894,10 +91531,10 @@ module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"Sample profile\",\
|
|
|
90894
91531
|
/*!************************************************!*\
|
|
90895
91532
|
!*** ./testdata/samples/sample-scorecard.json ***!
|
|
90896
91533
|
\************************************************/
|
|
90897
|
-
/*! exports provided: score, traditionalPrinciples,
|
|
91534
|
+
/*! exports provided: score, partisan, minority, traditionalPrinciples, details, default */
|
|
90898
91535
|
/***/ (function(module) {
|
|
90899
91536
|
|
|
90900
|
-
module.exports = JSON.parse("{\"score\":5,\"traditionalPrinciples\":{\"score\":18,\"compactness\":{\"score\":35,\"reock\":{\"raw\":0.3373,\"normalized\":35,\"notes\":{}},\"polsby\":{\"raw\":0.2418,\"normalized\":35,\"notes\":{}}},\"splitting\":{\"score\":0,\"county\":{\"raw\":1.1474,\"normalized\":0,\"notes\":{}},\"district\":{\"raw\":1.4839,\"normalized\":3,\"notes\":{}}},\"populationDeviation\":{\"raw\":0.016,\"normalized\":0,\"notes\":{\"maxDeviation\":11693}}
|
|
91537
|
+
module.exports = JSON.parse("{\"score\":5,\"partisan\":{\"bias\":{\"bestS\":7,\"bestSf\":0.5385,\"estS\":4.1925,\"estSf\":0.3225,\"bias\":0.216,\"tOf\":-0.0023,\"fptpS\":3,\"bS50\":0.2172,\"bV50\":0.045,\"decl\":36.5164,\"gSym\":6.6602,\"eG\":0.2846,\"bSV\":0.2098,\"prop\":0.2695,\"mMd\":0.0593,\"mMs\":0.057,\"lO\":0.1106,\"score\":0},\"impact\":{\"unearnedS\":2.8075,\"score\":0},\"competitiveness\":{\"bigR\":-16.926,\"littleR\":4.3523,\"mIR\":0.1298,\"rD\":4.0207,\"rDf\":0.3093,\"cSimple\":6,\"cD\":0.7266,\"cDf\":0.0559,\"mRange\":[5,11],\"mD\":0.7134,\"mDf\":0.1019,\"score\":10},\"score\":3,\"score2\":2,\"details\":{\"election\":\"2016 Presidential, US Senate, Governor, and AG election results\"}},\"minority\":{\"report\":{\"bucketsByDemographic\":[[10,14],[3,0],[1,9],[0,0],[0,0],[0,0]],\"averageDVf\":0.6737588682747838},\"nProportional\":18,\"opportunityDistricts\":12.9424,\"score\":14,\"details\":{\"vap\":\"2010 Voting Age Population\"}},\"traditionalPrinciples\":{\"score\":18,\"compactness\":{\"score\":35,\"reock\":{\"raw\":0.3373,\"normalized\":35,\"notes\":{}},\"polsby\":{\"raw\":0.2418,\"normalized\":35,\"notes\":{}}},\"splitting\":{\"score\":0,\"county\":{\"raw\":1.1474,\"normalized\":0,\"notes\":{}},\"district\":{\"raw\":1.4839,\"normalized\":3,\"notes\":{}}},\"populationDeviation\":{\"raw\":0.016,\"normalized\":0,\"notes\":{\"maxDeviation\":11693}},\"details\":{\"countiesSplitUnexpectedly\":[\"Bladen\",\"Buncombe\",\"Catawba\",\"Cumberland\",\"Durham\",\"Guilford\",\"Iredell\",\"Johnston\",\"Pitt\",\"Rowan\",\"Wilson\"],\"unexpectedAffected\":0.3096,\"nSplitVTDs\":12,\"splitVTDs\":[\"VTD-01\",\"VTD-02\",\"VTD-03\",\"VTD-04\",\"VTD-05\",\"VTD-06\",\"VTD-07\",\"VTD-08\",\"VTD-09\",\"VTD-10\",\"VTD-11\",\"VTD-12\"],\"shapes\":\"2010 VTD shapes\",\"census\":\"2010 Census Total Population\"}},\"details\":{}}");
|
|
90901
91538
|
|
|
90902
91539
|
/***/ })
|
|
90903
91540
|
|
|
@@ -101942,6 +102579,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
101942
102579
|
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
|
|
101943
102580
|
const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
|
|
101944
102581
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
102582
|
+
// TODO - SCORE: Remove legacy code
|
|
101945
102583
|
// Measures of compactness compare district shapes to various ideally compact
|
|
101946
102584
|
// benchmarks, such as circles. All else equal, more compact districts are better.
|
|
101947
102585
|
//
|
|
@@ -102237,6 +102875,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
102237
102875
|
};
|
|
102238
102876
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
102239
102877
|
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
|
|
102878
|
+
// TODO - SCORE: Remove legacy code
|
|
102240
102879
|
// HELPER
|
|
102241
102880
|
function polyParts(poly) {
|
|
102242
102881
|
let parts = { type: 'FeatureCollection', features: [] };
|
|
@@ -102366,6 +103005,7 @@ exports.gfDiameter = gfDiameter;
|
|
|
102366
103005
|
// PROTECTS MINORITIES
|
|
102367
103006
|
//
|
|
102368
103007
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
103008
|
+
// TODO - SCORE: Remove legacy code
|
|
102369
103009
|
function doMajorityMinorityDistricts(s, bLog = false) {
|
|
102370
103010
|
let test = s.getTest(16 /* MajorityMinorityDistricts */);
|
|
102371
103011
|
if (bLog)
|
|
@@ -102404,6 +103044,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
102404
103044
|
const assert_1 = __webpack_require__(/*! assert */ "assert");
|
|
102405
103045
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
102406
103046
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
103047
|
+
// TODO - SCORE: Remove legacy code
|
|
102407
103048
|
// Partisan analytics need the following data:
|
|
102408
103049
|
//
|
|
102409
103050
|
// An "election model" by geo_id, where each item has 4 pieces of data:
|
|
@@ -102660,116 +103301,238 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
102660
103301
|
};
|
|
102661
103302
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
102662
103303
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
103304
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
102663
103305
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
102664
103306
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
102665
103307
|
const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
|
|
102666
|
-
//
|
|
102667
|
-
|
|
102668
|
-
|
|
102669
|
-
|
|
102670
|
-
|
|
102671
|
-
|
|
102672
|
-
|
|
102673
|
-
|
|
102674
|
-
|
|
102675
|
-
|
|
102676
|
-
|
|
102677
|
-
|
|
102678
|
-
|
|
102679
|
-
|
|
102680
|
-
|
|
102681
|
-
|
|
102682
|
-
|
|
102683
|
-
|
|
102684
|
-
|
|
102685
|
-
|
|
102686
|
-
|
|
102687
|
-
|
|
102688
|
-
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
102689
|
-
}
|
|
103308
|
+
// PLAN ANALYTICS
|
|
103309
|
+
/* TODO - DELETE
|
|
103310
|
+
export type RequirementsCategory = {
|
|
103311
|
+
score: T.TriState;
|
|
103312
|
+
metrics: {
|
|
103313
|
+
complete: T.TriState;
|
|
103314
|
+
contiguous: T.TriState;
|
|
103315
|
+
freeOfHoles: T.TriState;
|
|
103316
|
+
equalPopulation: T.TriState;
|
|
103317
|
+
};
|
|
103318
|
+
details: {
|
|
103319
|
+
unassignedFeatures: string[]; // A possibly empty list of GEOIDs
|
|
103320
|
+
emptyDistricts: number[]; // A possibly empty list of district IDs
|
|
103321
|
+
discontiguousDistricts: number[]; // Ditto
|
|
103322
|
+
embeddedDistricts: number[]; // Ditto
|
|
103323
|
+
populationDeviation: number; // A fraction [0.0 – 1.0] to represent as a %
|
|
103324
|
+
deviationThreshold: number; // A fraction [0.0 – 1.0] to represent as a %
|
|
103325
|
+
};
|
|
103326
|
+
datasets: T.Datasets;
|
|
103327
|
+
resources: {
|
|
103328
|
+
stateReqs: string;
|
|
103329
|
+
};
|
|
102690
103330
|
};
|
|
102691
|
-
|
|
102692
|
-
|
|
102693
|
-
|
|
102694
|
-
|
|
102695
|
-
|
|
102696
|
-
|
|
102697
|
-
|
|
102698
|
-
|
|
102699
|
-
|
|
102700
|
-
|
|
102701
|
-
|
|
102702
|
-
|
|
103331
|
+
*/
|
|
103332
|
+
/* TODO - DELETE
|
|
103333
|
+
export type CompactnessCategory = {
|
|
103334
|
+
score: number; // An integer score [0–100]
|
|
103335
|
+
metrics: {
|
|
103336
|
+
reock: number; // A decimal number [0.0–1.0]
|
|
103337
|
+
polsby: number; // A decimal number [0.0–1.0]
|
|
103338
|
+
};
|
|
103339
|
+
details: {
|
|
103340
|
+
// None at this time
|
|
103341
|
+
};
|
|
103342
|
+
datasets: T.Datasets;
|
|
103343
|
+
resources: {
|
|
103344
|
+
// None at this time
|
|
103345
|
+
};
|
|
102703
103346
|
};
|
|
102704
|
-
|
|
102705
|
-
|
|
102706
|
-
|
|
102707
|
-
|
|
102708
|
-
|
|
102709
|
-
|
|
102710
|
-
|
|
102711
|
-
|
|
102712
|
-
|
|
102713
|
-
|
|
102714
|
-
|
|
102715
|
-
|
|
102716
|
-
|
|
102717
|
-
|
|
102718
|
-
|
|
102719
|
-
|
|
102720
|
-
|
|
103347
|
+
*/
|
|
103348
|
+
/* TODO - DELETE
|
|
103349
|
+
export type SplittingCategory = {
|
|
103350
|
+
score: number; // An integer score [0–100]
|
|
103351
|
+
metrics: {
|
|
103352
|
+
sqEnt_DCreduced: number, // A decimal number [1.0 – < 2.0]
|
|
103353
|
+
sqEnt_CDreduced: number // A decimal number [1.0 – < 2.0]
|
|
103354
|
+
};
|
|
103355
|
+
details: {
|
|
103356
|
+
countiesSplitUnexpectedly: string[], // A possibly empty list of county names
|
|
103357
|
+
unexpectedAffected: number, // A fraction [0.0 – 1.0] to represent as a %
|
|
103358
|
+
nSplitVTDs: number, // An integer, possibly 0
|
|
103359
|
+
splitVTDs: string[] // A possibly empty list of GEOIDs
|
|
103360
|
+
};
|
|
103361
|
+
datasets: T.Datasets;
|
|
103362
|
+
resources: {
|
|
103363
|
+
// None at this time
|
|
103364
|
+
};
|
|
102721
103365
|
};
|
|
102722
|
-
|
|
102723
|
-
|
|
102724
|
-
|
|
102725
|
-
|
|
102726
|
-
|
|
102727
|
-
|
|
102728
|
-
|
|
102729
|
-
|
|
102730
|
-
|
|
102731
|
-
|
|
102732
|
-
|
|
102733
|
-
|
|
102734
|
-
|
|
102735
|
-
|
|
102736
|
-
};
|
|
102737
|
-
// TODO - DELETE
|
|
102738
|
-
let sampleMinority = {
|
|
102739
|
-
score: null,
|
|
102740
|
-
metrics: {
|
|
102741
|
-
nBlack37to50: 1,
|
|
102742
|
-
nBlackMajority: 12,
|
|
102743
|
-
nHispanic37to50: 0,
|
|
102744
|
-
nHispanicMajority: 0,
|
|
102745
|
-
nPacific37to50: 0,
|
|
102746
|
-
nPacificMajority: 0,
|
|
102747
|
-
nAsian37to50: 0,
|
|
102748
|
-
nAsianMajority: 0,
|
|
102749
|
-
nNative37to50: 0,
|
|
102750
|
-
nNativeMajority: 0,
|
|
102751
|
-
nMinority37to50: 0,
|
|
102752
|
-
nMinorityMajority: 0,
|
|
102753
|
-
averageDVoteShare: 0.90
|
|
102754
|
-
},
|
|
102755
|
-
details: {
|
|
102756
|
-
vap: true,
|
|
102757
|
-
comboCategories: true
|
|
102758
|
-
},
|
|
102759
|
-
datasets: {
|
|
102760
|
-
vap: "2010 Voting Age Population"
|
|
102761
|
-
},
|
|
102762
|
-
resources: {}
|
|
103366
|
+
*/
|
|
103367
|
+
/* TODO - DELETE
|
|
103368
|
+
export type PartisanCategory = {
|
|
103369
|
+
score: number; // An integer score [0–100]
|
|
103370
|
+
metrics: {
|
|
103371
|
+
partisanBias: 0.15, // TBD
|
|
103372
|
+
responsiveness: 2.0 // TBD
|
|
103373
|
+
};
|
|
103374
|
+
details: {
|
|
103375
|
+
// TODO - Need to flesh this out
|
|
103376
|
+
};
|
|
103377
|
+
datasets: T.Datasets;
|
|
103378
|
+
resources: {
|
|
103379
|
+
planScore?: string;
|
|
103380
|
+
};
|
|
102763
103381
|
};
|
|
102764
|
-
|
|
102765
|
-
|
|
102766
|
-
|
|
102767
|
-
|
|
102768
|
-
|
|
102769
|
-
|
|
102770
|
-
|
|
102771
|
-
|
|
103382
|
+
*/
|
|
103383
|
+
/* TODO - DELETE
|
|
103384
|
+
export type MinorityCategory = {
|
|
103385
|
+
score: null; // Explicitly NOT scored
|
|
103386
|
+
metrics: {
|
|
103387
|
+
nBlack37to50: number, // Integer >= 0; two-digit maximum
|
|
103388
|
+
nBlackMajority: number, // Ditto
|
|
103389
|
+
nHispanic37to50: number, // Ditto
|
|
103390
|
+
nHispanicMajority: number, // Ditto
|
|
103391
|
+
nPacific37to50: number, // Ditto
|
|
103392
|
+
nPacificMajority: number, // Ditto
|
|
103393
|
+
nAsian37to50: number, // Ditto
|
|
103394
|
+
nAsianMajority: number, // Ditto
|
|
103395
|
+
nNative37to50: number, // Ditto
|
|
103396
|
+
nNativeMajority: number, // Ditto
|
|
103397
|
+
nMinority37to50: number, // Ditto
|
|
103398
|
+
nMinorityMajority: number, // Ditto
|
|
103399
|
+
|
|
103400
|
+
averageDVoteShare: number // A fraction [0.0 – 1.0] to represent as a %
|
|
103401
|
+
};
|
|
103402
|
+
details: {
|
|
103403
|
+
vap: true, // true = using VAP data; false = CVAP data
|
|
103404
|
+
comboCategories: true // true = using combo fields; false = mutually exclusive
|
|
103405
|
+
};
|
|
103406
|
+
datasets: T.Datasets;
|
|
103407
|
+
resources: {
|
|
103408
|
+
// TODO - Add these ...
|
|
103409
|
+
};
|
|
102772
103410
|
};
|
|
103411
|
+
*/
|
|
103412
|
+
/* TODO - DELETE
|
|
103413
|
+
export type PlanAnalytics = {
|
|
103414
|
+
requirements: RequirementsCategory;
|
|
103415
|
+
compactness: CompactnessCategory;
|
|
103416
|
+
// TODO - Don't show these categories yet
|
|
103417
|
+
splitting: SplittingCategory;
|
|
103418
|
+
partisan: PartisanCategory;
|
|
103419
|
+
minority: MinorityCategory;
|
|
103420
|
+
}
|
|
103421
|
+
*/
|
|
103422
|
+
// EXAMPLE
|
|
103423
|
+
/* TODO - DELETE
|
|
103424
|
+
let sampleRequirements: RequirementsCategory = {
|
|
103425
|
+
score: T.TriState.Red,
|
|
103426
|
+
metrics: {
|
|
103427
|
+
complete: T.TriState.Green,
|
|
103428
|
+
contiguous: T.TriState.Red,
|
|
103429
|
+
freeOfHoles: T.TriState.Yellow,
|
|
103430
|
+
equalPopulation: T.TriState.Red
|
|
103431
|
+
},
|
|
103432
|
+
details: {
|
|
103433
|
+
unassignedFeatures: [],
|
|
103434
|
+
emptyDistricts: [],
|
|
103435
|
+
discontiguousDistricts: [2],
|
|
103436
|
+
embeddedDistricts: [],
|
|
103437
|
+
populationDeviation: 0.6748,
|
|
103438
|
+
deviationThreshold: 0.75 / 100
|
|
103439
|
+
},
|
|
103440
|
+
datasets: {
|
|
103441
|
+
census: "2010 Census Total Population"
|
|
103442
|
+
},
|
|
103443
|
+
resources: {
|
|
103444
|
+
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
103445
|
+
}
|
|
103446
|
+
}
|
|
103447
|
+
*/
|
|
103448
|
+
/* TODO - DELETE
|
|
103449
|
+
let sampleCompactness: CompactnessCategory = {
|
|
103450
|
+
score: 60,
|
|
103451
|
+
metrics: {
|
|
103452
|
+
reock: 0.3773,
|
|
103453
|
+
polsby: 0.3815
|
|
103454
|
+
},
|
|
103455
|
+
details: {},
|
|
103456
|
+
datasets: {
|
|
103457
|
+
shapes: "2010 VTD shapes"
|
|
103458
|
+
},
|
|
103459
|
+
resources: {}
|
|
103460
|
+
}
|
|
103461
|
+
*/
|
|
103462
|
+
/* TODO - DELETE
|
|
103463
|
+
let sampleSplitting: SplittingCategory = {
|
|
103464
|
+
score: 73,
|
|
103465
|
+
metrics: {
|
|
103466
|
+
sqEnt_DCreduced: 1.531,
|
|
103467
|
+
sqEnt_CDreduced: 1.760
|
|
103468
|
+
},
|
|
103469
|
+
details: {
|
|
103470
|
+
countiesSplitUnexpectedly: [
|
|
103471
|
+
"Bladen", "Buncombe", "Catawba", "Cumberland", "Durham", "Guilford", "Iredell", "Johnston", "Pitt", "Rowan", "Wilson"
|
|
103472
|
+
],
|
|
103473
|
+
unexpectedAffected: 0.3096,
|
|
103474
|
+
nSplitVTDs: 12,
|
|
103475
|
+
splitVTDs: ["VTD-01", "VTD-02", "VTD-03", "VTD-04", "VTD-05", "VTD-06", "VTD-07", "VTD-08", "VTD-09", "VTD-10", "VTD-11", "VTD-12"]
|
|
103476
|
+
},
|
|
103477
|
+
datasets: {},
|
|
103478
|
+
resources: {}
|
|
103479
|
+
}
|
|
103480
|
+
*/
|
|
103481
|
+
/* TODO - DELETE
|
|
103482
|
+
let samplePartisan: PartisanCategory = {
|
|
103483
|
+
score: 100,
|
|
103484
|
+
metrics: {
|
|
103485
|
+
partisanBias: 0.15,
|
|
103486
|
+
responsiveness: 2.0
|
|
103487
|
+
},
|
|
103488
|
+
details: {},
|
|
103489
|
+
datasets: {
|
|
103490
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
103491
|
+
},
|
|
103492
|
+
resources: {
|
|
103493
|
+
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
103494
|
+
}
|
|
103495
|
+
}
|
|
103496
|
+
*/
|
|
103497
|
+
/* TODO - DELETE
|
|
103498
|
+
let sampleMinority: MinorityCategory = {
|
|
103499
|
+
score: null,
|
|
103500
|
+
metrics: {
|
|
103501
|
+
nBlack37to50: 1,
|
|
103502
|
+
nBlackMajority: 12,
|
|
103503
|
+
nHispanic37to50: 0,
|
|
103504
|
+
nHispanicMajority: 0,
|
|
103505
|
+
nPacific37to50: 0,
|
|
103506
|
+
nPacificMajority: 0,
|
|
103507
|
+
nAsian37to50: 0,
|
|
103508
|
+
nAsianMajority: 0,
|
|
103509
|
+
nNative37to50: 0,
|
|
103510
|
+
nNativeMajority: 0,
|
|
103511
|
+
nMinority37to50: 0,
|
|
103512
|
+
nMinorityMajority: 0,
|
|
103513
|
+
|
|
103514
|
+
averageDVoteShare: 0.90
|
|
103515
|
+
},
|
|
103516
|
+
details: {
|
|
103517
|
+
vap: true,
|
|
103518
|
+
comboCategories: true
|
|
103519
|
+
},
|
|
103520
|
+
datasets: {
|
|
103521
|
+
vap: "2010 Voting Age Population"
|
|
103522
|
+
},
|
|
103523
|
+
resources: {}
|
|
103524
|
+
}
|
|
103525
|
+
*/
|
|
103526
|
+
/* TODO - DELETE
|
|
103527
|
+
export const samplePlanAnalytics: PlanAnalytics = {
|
|
103528
|
+
requirements: sampleRequirements,
|
|
103529
|
+
compactness: sampleCompactness,
|
|
103530
|
+
// TODO - Don't show these categories yet
|
|
103531
|
+
splitting: sampleSplitting,
|
|
103532
|
+
partisan: samplePartisan,
|
|
103533
|
+
minority: sampleMinority
|
|
103534
|
+
}
|
|
103535
|
+
*/
|
|
102773
103536
|
function prepareRequirementsChecklist(s, bLog = false) {
|
|
102774
103537
|
if (!(s.bPostProcessingDone)) {
|
|
102775
103538
|
doAnalyzePostProcessing(s);
|
|
@@ -102902,7 +103665,7 @@ function prepareDistrictStatistics(s, bLog = false) {
|
|
|
102902
103665
|
// None at this time
|
|
102903
103666
|
};
|
|
102904
103667
|
let dsDatasets = {
|
|
102905
|
-
shapes:
|
|
103668
|
+
shapes: S.SHAPES,
|
|
102906
103669
|
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
102907
103670
|
vap: U.deepCopy(s.config['descriptions']['VAP']),
|
|
102908
103671
|
election: U.deepCopy(s.config['descriptions']['ELECTION'])
|
|
@@ -102965,6 +103728,7 @@ const populationDeviationDefn = {
|
|
|
102965
103728
|
externalType: TestType.Percentage,
|
|
102966
103729
|
suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
|
|
102967
103730
|
};
|
|
103731
|
+
// TODO - SCORE: Comment these out ...
|
|
102968
103732
|
const reockDefn = {
|
|
102969
103733
|
ID: 5 /* Reock */,
|
|
102970
103734
|
name: "Reock",
|
|
@@ -103092,10 +103856,26 @@ function doAnalyzePostProcessing(s, bLog = false) {
|
|
|
103092
103856
|
else {
|
|
103093
103857
|
// Just populate the normalized population deviation score in the test
|
|
103094
103858
|
const scorecard = s._scorecard;
|
|
103095
|
-
let
|
|
103096
|
-
|
|
103859
|
+
let popDev = s.getTest(4 /* PopulationDeviation */);
|
|
103860
|
+
popDev['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
|
|
103097
103861
|
// TODO - DELETE
|
|
103098
103862
|
// test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
|
|
103863
|
+
// TODO - SCORE: Add datasets used to details by tab
|
|
103864
|
+
const datasets = {
|
|
103865
|
+
shapes: S.SHAPES,
|
|
103866
|
+
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
103867
|
+
vap: U.deepCopy(s.config['descriptions']['VAP']),
|
|
103868
|
+
election: U.deepCopy(s.config['descriptions']['ELECTION'])
|
|
103869
|
+
};
|
|
103870
|
+
scorecard.partisan.details['election'] = datasets.election;
|
|
103871
|
+
scorecard.minority.details['vap'] = datasets.vap;
|
|
103872
|
+
scorecard.traditionalPrinciples.details['shapes'] = datasets.shapes;
|
|
103873
|
+
scorecard.traditionalPrinciples.details['census'] = datasets.census;
|
|
103874
|
+
// TODO - SCORE: Add legacy splits details
|
|
103875
|
+
const simpleSplits = s.getTest(7 /* UnexpectedCountySplits */);
|
|
103876
|
+
scorecard.traditionalPrinciples.details['unexpectedAffected'] = simpleSplits['score'];
|
|
103877
|
+
scorecard.traditionalPrinciples.details['countiesSplitUnexpectedly'] = U.deepCopy(simpleSplits['details']['countiesSplitUnexpectedly']);
|
|
103878
|
+
// NOTE - Add split precincts in dra-client directly
|
|
103099
103879
|
}
|
|
103100
103880
|
// Derive secondary tests
|
|
103101
103881
|
analyze_1.doDeriveSecondaryTests(s, bLog);
|
|
@@ -103143,7 +103923,8 @@ function profilePlan(s, bLog = false) {
|
|
|
103143
103923
|
const summaryRow = s.districts.numberOfRows() - 1;
|
|
103144
103924
|
const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
|
|
103145
103925
|
const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
|
|
103146
|
-
const
|
|
103926
|
+
const statewideDemographics = getStatewideDemographics(s);
|
|
103927
|
+
const demographicsByDistrict = getDemographicsByDistrict(s);
|
|
103147
103928
|
const profile = {
|
|
103148
103929
|
state: state,
|
|
103149
103930
|
planName: planName,
|
|
@@ -103151,21 +103932,22 @@ function profilePlan(s, bLog = false) {
|
|
|
103151
103932
|
nCounties: nCounties,
|
|
103152
103933
|
legislativeDistricts: s.legislativeDistricts,
|
|
103153
103934
|
populationProfile: {
|
|
103154
|
-
|
|
103935
|
+
totalPopByDistrict: popByDistrict,
|
|
103155
103936
|
targetSize: targetSize
|
|
103156
103937
|
},
|
|
103157
103938
|
compactnessProfile: {
|
|
103158
|
-
|
|
103939
|
+
geometryByDistrict: geoPropsByDistrict
|
|
103159
103940
|
},
|
|
103160
103941
|
splittingProfile: {
|
|
103161
|
-
|
|
103942
|
+
countyPopByDistrict: splits
|
|
103162
103943
|
},
|
|
103163
103944
|
partisanProfile: {
|
|
103164
103945
|
statewideVf: statewideVf,
|
|
103165
|
-
|
|
103946
|
+
vfArray: vpiArray
|
|
103166
103947
|
},
|
|
103167
103948
|
demographicProfile: {
|
|
103168
|
-
|
|
103949
|
+
stateMfArray: statewideDemographics,
|
|
103950
|
+
mfArrayByDistrict: demographicsByDistrict
|
|
103169
103951
|
}
|
|
103170
103952
|
};
|
|
103171
103953
|
return profile;
|
|
@@ -103199,7 +103981,7 @@ function makeArrayOfGeoProps(s, bLog = false) {
|
|
|
103199
103981
|
}
|
|
103200
103982
|
return geometryByDistrict;
|
|
103201
103983
|
}
|
|
103202
|
-
function
|
|
103984
|
+
function getDemographicsByDistrict(s, bLog = false) {
|
|
103203
103985
|
let demographicsArray = [];
|
|
103204
103986
|
// Remove the unassigned & total dummy "districts"
|
|
103205
103987
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
@@ -103216,6 +103998,19 @@ function makeArrayOfDemographics(s, bLog = false) {
|
|
|
103216
103998
|
}
|
|
103217
103999
|
return demographicsArray;
|
|
103218
104000
|
}
|
|
104001
|
+
function getStatewideDemographics(s, bLog = false) {
|
|
104002
|
+
const summaryRow = s.districts.numberOfRows() - 1;
|
|
104003
|
+
const demographicsArray = [
|
|
104004
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.WhitePct][summaryRow]),
|
|
104005
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.MinorityPct][summaryRow]),
|
|
104006
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.BlackPct][summaryRow]),
|
|
104007
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.HispanicPct][summaryRow]),
|
|
104008
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.PacificPct][summaryRow]),
|
|
104009
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.AsianPct][summaryRow]),
|
|
104010
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.NativePct][summaryRow])
|
|
104011
|
+
];
|
|
104012
|
+
return demographicsArray;
|
|
104013
|
+
}
|
|
103219
104014
|
// SCORE A PLAN
|
|
103220
104015
|
function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
103221
104016
|
let scorer = new Score.Scorer();
|
|
@@ -103278,6 +104073,8 @@ exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
|
|
|
103278
104073
|
// County & district splitting weights
|
|
103279
104074
|
exports.COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
103280
104075
|
exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
104076
|
+
// TODO - 2020
|
|
104077
|
+
exports.SHAPES = "2010 VTD shapes";
|
|
103281
104078
|
|
|
103282
104079
|
|
|
103283
104080
|
/***/ }),
|