@dra2020/district-analytics 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1408 -414
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +39 -12
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -89264,7 +89264,15 @@ module.exports = g;
|
|
|
89264
89264
|
//
|
|
89265
89265
|
// THE NODE PACKAGE API
|
|
89266
89266
|
//
|
|
89267
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
89268
|
+
if (mod && mod.__esModule) return mod;
|
|
89269
|
+
var result = {};
|
|
89270
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
89271
|
+
result["default"] = mod;
|
|
89272
|
+
return result;
|
|
89273
|
+
};
|
|
89267
89274
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89275
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
89268
89276
|
const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
|
|
89269
89277
|
class Scorer {
|
|
89270
89278
|
constructor() {
|
|
@@ -89272,6 +89280,9 @@ class Scorer {
|
|
|
89272
89280
|
score(profile, overridesJSON) {
|
|
89273
89281
|
return score_1.scorePlan(profile, overridesJSON);
|
|
89274
89282
|
}
|
|
89283
|
+
populationDeviationThreshold(bLegislative) {
|
|
89284
|
+
return C.popdevThreshold(bLegislative);
|
|
89285
|
+
}
|
|
89275
89286
|
}
|
|
89276
89287
|
exports.Scorer = Scorer;
|
|
89277
89288
|
|
|
@@ -89300,27 +89311,33 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
89300
89311
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89301
89312
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
89302
89313
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
89314
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
89303
89315
|
const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
|
|
89304
89316
|
function scoreCountySplitting(rawValue, nCounties, nDistricts) {
|
|
89305
89317
|
const _normalizer = new normalize_1.Normalizer(rawValue);
|
|
89306
|
-
const
|
|
89307
|
-
|
|
89308
|
-
_normalizer.
|
|
89318
|
+
const best = C.countySplittingRange()[C.BEG];
|
|
89319
|
+
const worst = countySplitThreshold(nCounties, nDistricts);
|
|
89320
|
+
_normalizer.clip(best, worst);
|
|
89321
|
+
_normalizer.unitize(best, worst);
|
|
89309
89322
|
_normalizer.invert();
|
|
89310
89323
|
_normalizer.rescale();
|
|
89311
89324
|
return _normalizer.normalizedNum;
|
|
89312
89325
|
}
|
|
89313
89326
|
exports.scoreCountySplitting = scoreCountySplitting;
|
|
89314
89327
|
function countySplitThreshold(nCounties, nDistricts) {
|
|
89315
|
-
const
|
|
89316
|
-
const
|
|
89328
|
+
const m = C.allowableSplitsMultiplier();
|
|
89329
|
+
const worst = C.countySplittingRange()[1];
|
|
89330
|
+
const nAllowableSplits = Math.min(m * (nDistricts - 1));
|
|
89331
|
+
const threshold = ((nAllowableSplits * worst) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
|
|
89317
89332
|
return threshold;
|
|
89318
89333
|
}
|
|
89319
89334
|
exports.countySplitThreshold = countySplitThreshold;
|
|
89320
89335
|
function scoreDistrictSplitting(rawValue) {
|
|
89321
89336
|
const _normalizer = new normalize_1.Normalizer(rawValue);
|
|
89322
|
-
|
|
89323
|
-
|
|
89337
|
+
const best = C.districtSplittingRange()[C.BEG];
|
|
89338
|
+
const worst = C.districtSplittingRange()[C.END];
|
|
89339
|
+
_normalizer.clip(best, worst);
|
|
89340
|
+
_normalizer.unitize(best, worst);
|
|
89324
89341
|
_normalizer.invert();
|
|
89325
89342
|
_normalizer.rescale();
|
|
89326
89343
|
return _normalizer.normalizedNum;
|
|
@@ -89415,7 +89432,7 @@ function reduceCSplits(CxD, districtTotals) {
|
|
|
89415
89432
|
for (let i = 1; i < nD; i++) {
|
|
89416
89433
|
let split_total = CxDreducedC[i][j];
|
|
89417
89434
|
if (split_total > 0) {
|
|
89418
|
-
if (areRoughlyEqual(split_total, districtTotals[i - 1])) {
|
|
89435
|
+
if (U.areRoughlyEqual(split_total, districtTotals[i - 1], S.EQUAL_TOLERANCE)) {
|
|
89419
89436
|
CxDreducedC[0][j] += split_total;
|
|
89420
89437
|
CxDreducedC[i][j] = 0;
|
|
89421
89438
|
}
|
|
@@ -89438,7 +89455,7 @@ function reduceDSplits(CxD, countyTotals) {
|
|
|
89438
89455
|
for (let j = 1; j < nC; j++) {
|
|
89439
89456
|
let split_total = CxDreducedD[i][j];
|
|
89440
89457
|
if (split_total > 0) {
|
|
89441
|
-
if (areRoughlyEqual(split_total, countyTotals[j - 1])) {
|
|
89458
|
+
if (U.areRoughlyEqual(split_total, countyTotals[j - 1], S.EQUAL_TOLERANCE)) {
|
|
89442
89459
|
CxDreducedD[i][0] += split_total;
|
|
89443
89460
|
CxDreducedD[i][j] = 0;
|
|
89444
89461
|
}
|
|
@@ -89502,13 +89519,6 @@ function calcDistrictFractions(CxD, districtTotals) {
|
|
|
89502
89519
|
return g;
|
|
89503
89520
|
}
|
|
89504
89521
|
exports.calcDistrictFractions = calcDistrictFractions;
|
|
89505
|
-
// Deal with decimal census "counts" due to disagg/re-agg
|
|
89506
|
-
function areRoughlyEqual(x, y) {
|
|
89507
|
-
let delta = Math.abs(x - y);
|
|
89508
|
-
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
89509
|
-
return result;
|
|
89510
|
-
}
|
|
89511
|
-
exports.areRoughlyEqual = areRoughlyEqual;
|
|
89512
89522
|
function splitScore(splits) {
|
|
89513
89523
|
let e;
|
|
89514
89524
|
if (splits.length > 0) {
|
|
@@ -89593,7 +89603,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
89593
89603
|
};
|
|
89594
89604
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89595
89605
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
89596
|
-
const
|
|
89606
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
89597
89607
|
const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
|
|
89598
89608
|
// Migrated from district-analytics:
|
|
89599
89609
|
// Measures of compactness compare district shapes to various ideally compact
|
|
@@ -89715,8 +89725,10 @@ function calcReock(area, diameter) {
|
|
|
89715
89725
|
exports.calcReock = calcReock;
|
|
89716
89726
|
function scoreReock(rawValue) {
|
|
89717
89727
|
const _normalizer = new normalize_1.Normalizer(rawValue);
|
|
89718
|
-
|
|
89719
|
-
|
|
89728
|
+
const worst = C.reockRange()[C.BEG];
|
|
89729
|
+
const best = C.reockRange()[C.END];
|
|
89730
|
+
_normalizer.clip(worst, best);
|
|
89731
|
+
_normalizer.unitize(worst, best);
|
|
89720
89732
|
_normalizer.rescale();
|
|
89721
89733
|
return _normalizer.normalizedNum;
|
|
89722
89734
|
}
|
|
@@ -89734,12 +89746,12 @@ function doPolsbyPopper(g, bLog = false) {
|
|
|
89734
89746
|
if (districtProps) {
|
|
89735
89747
|
let a = districtProps[0 /* Area */];
|
|
89736
89748
|
let p = districtProps[1 /* Perimeter */];
|
|
89737
|
-
let
|
|
89749
|
+
let polsby = calcPolsbyPopper(a, p);
|
|
89738
89750
|
// Save each district score
|
|
89739
|
-
scores.push(
|
|
89751
|
+
scores.push(polsby);
|
|
89740
89752
|
// Echo the results by district
|
|
89741
89753
|
if (bLog)
|
|
89742
|
-
console.log("Polsby-Popper for district", districtID, "=",
|
|
89754
|
+
console.log("Polsby-Popper for district", districtID, "=", polsby);
|
|
89743
89755
|
}
|
|
89744
89756
|
else {
|
|
89745
89757
|
console.log("No shape or no geopropertes in Polsby-Popper ...");
|
|
@@ -89762,14 +89774,233 @@ function calcPolsbyPopper(area, perimeter) {
|
|
|
89762
89774
|
exports.calcPolsbyPopper = calcPolsbyPopper;
|
|
89763
89775
|
function scorePolsbyPopper(rawValue) {
|
|
89764
89776
|
const _normalizer = new normalize_1.Normalizer(rawValue);
|
|
89765
|
-
|
|
89766
|
-
|
|
89777
|
+
const worst = C.polsbyRange()[C.BEG];
|
|
89778
|
+
const best = C.polsbyRange()[C.END];
|
|
89779
|
+
_normalizer.clip(worst, best);
|
|
89780
|
+
_normalizer.unitize(worst, best);
|
|
89767
89781
|
_normalizer.rescale();
|
|
89768
89782
|
return _normalizer.normalizedNum;
|
|
89769
89783
|
}
|
|
89770
89784
|
exports.scorePolsbyPopper = scorePolsbyPopper;
|
|
89771
89785
|
|
|
89772
89786
|
|
|
89787
|
+
/***/ }),
|
|
89788
|
+
|
|
89789
|
+
/***/ "./src/config.json":
|
|
89790
|
+
/*!*************************!*\
|
|
89791
|
+
!*** ./src/config.json ***!
|
|
89792
|
+
\*************************/
|
|
89793
|
+
/*! exports provided: partisan, minority, traditionalPrinciples, default */
|
|
89794
|
+
/***/ (function(module) {
|
|
89795
|
+
|
|
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.12,\"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]}}");
|
|
89797
|
+
|
|
89798
|
+
/***/ }),
|
|
89799
|
+
|
|
89800
|
+
/***/ "./src/config.ts":
|
|
89801
|
+
/*!***********************!*\
|
|
89802
|
+
!*** ./src/config.ts ***!
|
|
89803
|
+
\***********************/
|
|
89804
|
+
/*! no static exports found */
|
|
89805
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
89806
|
+
|
|
89807
|
+
"use strict";
|
|
89808
|
+
|
|
89809
|
+
//
|
|
89810
|
+
// THRESHOLDS, SCALES, AND WEIGHTS FOR SCORING MODEL
|
|
89811
|
+
//
|
|
89812
|
+
// * A layer of functions over config.json, so that they can be overridden dynamically (TODO)
|
|
89813
|
+
//
|
|
89814
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
89815
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
89816
|
+
};
|
|
89817
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89818
|
+
// Defaults
|
|
89819
|
+
const config_json_1 = __importDefault(__webpack_require__(/*! ./config.json */ "./src/config.json"));
|
|
89820
|
+
exports.BEG = 0;
|
|
89821
|
+
exports.END = 1;
|
|
89822
|
+
// PARTISAN
|
|
89823
|
+
function biasWeight(context, overridesJSON) {
|
|
89824
|
+
const bW = config_json_1.default.partisan.bias.weight[context];
|
|
89825
|
+
return bW;
|
|
89826
|
+
}
|
|
89827
|
+
exports.biasWeight = biasWeight;
|
|
89828
|
+
function biasRange(overridesJSON) {
|
|
89829
|
+
const range = config_json_1.default.partisan.bias.range;
|
|
89830
|
+
return range;
|
|
89831
|
+
}
|
|
89832
|
+
exports.biasRange = biasRange;
|
|
89833
|
+
function impactWeight(context, overridesJSON) {
|
|
89834
|
+
const iW = config_json_1.default.partisan.impact.weight[context];
|
|
89835
|
+
return iW;
|
|
89836
|
+
}
|
|
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;
|
|
89843
|
+
// The maximum # of unearned seats that scores positively
|
|
89844
|
+
function unearnedThreshold(overridesJSON) {
|
|
89845
|
+
const threshold = config_json_1.default.partisan.impact.threshold;
|
|
89846
|
+
return threshold;
|
|
89847
|
+
}
|
|
89848
|
+
exports.unearnedThreshold = unearnedThreshold;
|
|
89849
|
+
function competitivenessWeight(context, overridesJSON) {
|
|
89850
|
+
const cW = config_json_1.default.partisan.competitiveness.weight[context];
|
|
89851
|
+
return cW;
|
|
89852
|
+
}
|
|
89853
|
+
exports.competitivenessWeight = competitivenessWeight;
|
|
89854
|
+
function competitiveRange(overridesJSON) {
|
|
89855
|
+
const range = config_json_1.default.partisan.competitiveness.range;
|
|
89856
|
+
return range;
|
|
89857
|
+
}
|
|
89858
|
+
exports.competitiveRange = competitiveRange;
|
|
89859
|
+
function competitiveDistribution(overridesJSON) {
|
|
89860
|
+
const dist = config_json_1.default.partisan.competitiveness.distribution;
|
|
89861
|
+
return dist;
|
|
89862
|
+
}
|
|
89863
|
+
exports.competitiveDistribution = competitiveDistribution;
|
|
89864
|
+
function overallCompetitivenessRange(overridesJSON) {
|
|
89865
|
+
const range = config_json_1.default.partisan.competitiveness.overall.range;
|
|
89866
|
+
return range;
|
|
89867
|
+
}
|
|
89868
|
+
exports.overallCompetitivenessRange = overallCompetitivenessRange;
|
|
89869
|
+
function marginalCompetitivenessRange(overridesJSON) {
|
|
89870
|
+
const range = config_json_1.default.partisan.competitiveness.marginal.range;
|
|
89871
|
+
return range;
|
|
89872
|
+
}
|
|
89873
|
+
exports.marginalCompetitivenessRange = marginalCompetitivenessRange;
|
|
89874
|
+
function marginalCompetitivenessWeight(overridesJSON) {
|
|
89875
|
+
const mcW = config_json_1.default.partisan.competitiveness.marginal.weight;
|
|
89876
|
+
return mcW;
|
|
89877
|
+
}
|
|
89878
|
+
exports.marginalCompetitivenessWeight = marginalCompetitivenessWeight;
|
|
89879
|
+
function overallCompetitivenessWeight(overridesJSON) {
|
|
89880
|
+
const ocW = config_json_1.default.partisan.competitiveness.overall.weight;
|
|
89881
|
+
return ocW;
|
|
89882
|
+
}
|
|
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;
|
|
89910
|
+
// TRADITIONAL DISTRICTING PRINCIPLES
|
|
89911
|
+
function compactnessWeight(context, overridesJSON) {
|
|
89912
|
+
const cW = config_json_1.default.traditionalPrinciples.compactness.weight[context];
|
|
89913
|
+
return cW;
|
|
89914
|
+
}
|
|
89915
|
+
exports.compactnessWeight = compactnessWeight;
|
|
89916
|
+
function reockWeight(overridesJSON) {
|
|
89917
|
+
const rW = config_json_1.default.traditionalPrinciples.compactness.reock.weight;
|
|
89918
|
+
return rW;
|
|
89919
|
+
}
|
|
89920
|
+
exports.reockWeight = reockWeight;
|
|
89921
|
+
function reockRange(overridesJSON) {
|
|
89922
|
+
const range = config_json_1.default.traditionalPrinciples.compactness.reock.range;
|
|
89923
|
+
return range;
|
|
89924
|
+
}
|
|
89925
|
+
exports.reockRange = reockRange;
|
|
89926
|
+
function polsbyWeight(overridesJSON) {
|
|
89927
|
+
const ppW = config_json_1.default.traditionalPrinciples.compactness.polsby.weight;
|
|
89928
|
+
return ppW;
|
|
89929
|
+
}
|
|
89930
|
+
exports.polsbyWeight = polsbyWeight;
|
|
89931
|
+
function polsbyRange(overridesJSON) {
|
|
89932
|
+
const range = config_json_1.default.traditionalPrinciples.compactness.polsby.range;
|
|
89933
|
+
return range;
|
|
89934
|
+
}
|
|
89935
|
+
exports.polsbyRange = polsbyRange;
|
|
89936
|
+
function splittingWeight(context, overridesJSON) {
|
|
89937
|
+
const sW = config_json_1.default.traditionalPrinciples.splitting.weight[context];
|
|
89938
|
+
return sW;
|
|
89939
|
+
}
|
|
89940
|
+
exports.splittingWeight = splittingWeight;
|
|
89941
|
+
function countySplittingWeight(overridesJSON) {
|
|
89942
|
+
const csW = config_json_1.default.traditionalPrinciples.splitting.county.weight;
|
|
89943
|
+
return csW;
|
|
89944
|
+
}
|
|
89945
|
+
exports.countySplittingWeight = countySplittingWeight;
|
|
89946
|
+
function countySplittingRange(overridesJSON) {
|
|
89947
|
+
const range = config_json_1.default.traditionalPrinciples.splitting.county.range;
|
|
89948
|
+
return range;
|
|
89949
|
+
}
|
|
89950
|
+
exports.countySplittingRange = countySplittingRange;
|
|
89951
|
+
function allowableSplitsMultiplier(overridesJSON) {
|
|
89952
|
+
const m = config_json_1.default.traditionalPrinciples.splitting.county.allowableSplitsMultiplier;
|
|
89953
|
+
return m;
|
|
89954
|
+
}
|
|
89955
|
+
exports.allowableSplitsMultiplier = allowableSplitsMultiplier;
|
|
89956
|
+
function districtSplittingWeight(overridesJSON) {
|
|
89957
|
+
const dsW = config_json_1.default.traditionalPrinciples.splitting.district.weight;
|
|
89958
|
+
return dsW;
|
|
89959
|
+
}
|
|
89960
|
+
exports.districtSplittingWeight = districtSplittingWeight;
|
|
89961
|
+
function districtSplittingRange(overridesJSON) {
|
|
89962
|
+
const range = config_json_1.default.traditionalPrinciples.splitting.district.range;
|
|
89963
|
+
return range;
|
|
89964
|
+
}
|
|
89965
|
+
exports.districtSplittingRange = districtSplittingRange;
|
|
89966
|
+
function popdevWeight(context, overridesJSON) {
|
|
89967
|
+
const pdW = config_json_1.default.traditionalPrinciples.popdev.weight[context];
|
|
89968
|
+
return pdW;
|
|
89969
|
+
}
|
|
89970
|
+
exports.popdevWeight = popdevWeight;
|
|
89971
|
+
// NOTE - Raw ranges, not inverted (i.e., smaller is better)
|
|
89972
|
+
// NOTE - This could be optimized to not calc LD values for CD's (or do it once)
|
|
89973
|
+
function popdevRange(bLegislative, overridesJSON) {
|
|
89974
|
+
const cdRange = config_json_1.default.traditionalPrinciples.popdev.range[0 /* Congressional */];
|
|
89975
|
+
const worstCD = cdRange[exports.BEG];
|
|
89976
|
+
const bestCD = cdRange[exports.END];
|
|
89977
|
+
const ldRange = config_json_1.default.traditionalPrinciples.popdev.range[1 /* StateLegislative */];
|
|
89978
|
+
const iRange = bLegislative ? ldRange : cdRange;
|
|
89979
|
+
const worst = iRange[exports.BEG];
|
|
89980
|
+
const best = bLegislative ? (bestCD / worstCD) * iRange[exports.BEG] : iRange[exports.END];
|
|
89981
|
+
// Invert the range, so bigger is better.
|
|
89982
|
+
return [worst, best];
|
|
89983
|
+
}
|
|
89984
|
+
exports.popdevRange = popdevRange;
|
|
89985
|
+
// NOTE - Raw threshold, not inverted (i.e., smaller is better)
|
|
89986
|
+
function popdevThreshold(bLegislative, overridesJSON) {
|
|
89987
|
+
const threshold = popdevRange(bLegislative)[exports.BEG];
|
|
89988
|
+
return threshold;
|
|
89989
|
+
}
|
|
89990
|
+
exports.popdevThreshold = popdevThreshold;
|
|
89991
|
+
// OVERALL SCORE
|
|
89992
|
+
function partisanWeight(context, overridesJSON) {
|
|
89993
|
+
const pW = config_json_1.default.partisan.weight[context];
|
|
89994
|
+
return pW;
|
|
89995
|
+
}
|
|
89996
|
+
exports.partisanWeight = partisanWeight;
|
|
89997
|
+
function traditionalPrinciplesWeight(context, overridesJSON) {
|
|
89998
|
+
const tpW = config_json_1.default.traditionalPrinciples.weight[context];
|
|
89999
|
+
return tpW;
|
|
90000
|
+
}
|
|
90001
|
+
exports.traditionalPrinciplesWeight = traditionalPrinciplesWeight;
|
|
90002
|
+
|
|
90003
|
+
|
|
89773
90004
|
/***/ }),
|
|
89774
90005
|
|
|
89775
90006
|
/***/ "./src/equal.ts":
|
|
@@ -89793,7 +90024,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
89793
90024
|
};
|
|
89794
90025
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89795
90026
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
89796
|
-
const
|
|
90027
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
89797
90028
|
const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
|
|
89798
90029
|
// Migrated from district-analytics
|
|
89799
90030
|
// Expect a full dict of district IDs & values (which might be zero = empty)
|
|
@@ -89814,7 +90045,7 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
|
|
|
89814
90045
|
// Round the raw value to the desired level of precision
|
|
89815
90046
|
const popDev = U.trim((max - min) / targetSize);
|
|
89816
90047
|
const score = scorePopulationDeviation(popDev, bLegislative);
|
|
89817
|
-
const threshold = bLegislative
|
|
90048
|
+
const threshold = C.popdevThreshold(bLegislative);
|
|
89818
90049
|
const notes = {
|
|
89819
90050
|
'maxDeviation': max - min,
|
|
89820
90051
|
'threshold': threshold
|
|
@@ -89830,11 +90061,11 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
|
|
|
89830
90061
|
exports.doPopulationDeviation = doPopulationDeviation;
|
|
89831
90062
|
function scorePopulationDeviation(rawValue, bLegislative) {
|
|
89832
90063
|
const _normalizer = new normalize_1.Normalizer(rawValue);
|
|
89833
|
-
|
|
89834
|
-
const
|
|
90064
|
+
// Raw range in not inverted (i.e., smaller is better)
|
|
90065
|
+
const range = C.popdevRange(bLegislative);
|
|
89835
90066
|
_normalizer.invert();
|
|
89836
|
-
_normalizer.clip(
|
|
89837
|
-
_normalizer.unitize(
|
|
90067
|
+
_normalizer.clip(1.0 - range[C.BEG], 1.0 - range[C.END]);
|
|
90068
|
+
_normalizer.unitize(1.0 - range[C.BEG], 1.0 - range[C.END]);
|
|
89838
90069
|
_normalizer.rescale();
|
|
89839
90070
|
return _normalizer.normalizedNum;
|
|
89840
90071
|
}
|
|
@@ -89843,10 +90074,263 @@ exports.scorePopulationDeviation = scorePopulationDeviation;
|
|
|
89843
90074
|
|
|
89844
90075
|
/***/ }),
|
|
89845
90076
|
|
|
89846
|
-
/***/ "./src/
|
|
89847
|
-
|
|
89848
|
-
!*** ./src/
|
|
89849
|
-
|
|
90077
|
+
/***/ "./src/index.ts":
|
|
90078
|
+
/*!**********************!*\
|
|
90079
|
+
!*** ./src/index.ts ***!
|
|
90080
|
+
\**********************/
|
|
90081
|
+
/*! no static exports found */
|
|
90082
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
90083
|
+
|
|
90084
|
+
"use strict";
|
|
90085
|
+
|
|
90086
|
+
//
|
|
90087
|
+
// SCORE PACKAGE API
|
|
90088
|
+
//
|
|
90089
|
+
function __export(m) {
|
|
90090
|
+
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
90091
|
+
}
|
|
90092
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90093
|
+
__export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
|
|
90094
|
+
var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
|
|
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
|
+
let offset = 1; // Don't process 'White'
|
|
90134
|
+
let nDemos = 6 /* Native */ + 1 - offset; // Ditto
|
|
90135
|
+
let 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 level 1 (37–50%) and level 2 (> 50%) opportunity districts - ignore total minority
|
|
90157
|
+
// let l1: number = 0;
|
|
90158
|
+
// let l2: number = 0;
|
|
90159
|
+
// bucketsByDemo.slice(1).forEach(function (buckets: number[]): void
|
|
90160
|
+
// {
|
|
90161
|
+
// l1 += buckets[T.OpportunityBucket.Level1];
|
|
90162
|
+
// l2 += buckets[T.OpportunityBucket.Level2]
|
|
90163
|
+
// });
|
|
90164
|
+
// Sum the # of opportunity districts - ignore total minority
|
|
90165
|
+
const oD = U.sumArray(opptyByDemo.slice(1));
|
|
90166
|
+
// Sum the # of proportion districts - ignore total minority
|
|
90167
|
+
const pD = U.sumArray(districtsByDemo.slice(1));
|
|
90168
|
+
// Score opportunity
|
|
90169
|
+
const score = scoreMinority(oD, pD);
|
|
90170
|
+
let mS = {
|
|
90171
|
+
report: {
|
|
90172
|
+
averageDVf: averageDVf,
|
|
90173
|
+
bucketsByDemographic: bucketsByDemo
|
|
90174
|
+
},
|
|
90175
|
+
// nOpportunity1: l1,
|
|
90176
|
+
// nOpportunity2: l2,
|
|
90177
|
+
nProportional: pD,
|
|
90178
|
+
opportunityDistricts: oD,
|
|
90179
|
+
score: score
|
|
90180
|
+
};
|
|
90181
|
+
return mS;
|
|
90182
|
+
}
|
|
90183
|
+
exports.evalMinorityOpportunity = evalMinorityOpportunity;
|
|
90184
|
+
function scoreMinority(oD, pD) {
|
|
90185
|
+
const bonus = C.minorityBonus();
|
|
90186
|
+
const score = (pD > 0) ? Math.round((Math.min(oD, pD) / pD) * bonus) : 0;
|
|
90187
|
+
return score;
|
|
90188
|
+
}
|
|
90189
|
+
exports.scoreMinority = scoreMinority;
|
|
90190
|
+
function calcDistrictsByDemo(MfArray, N) {
|
|
90191
|
+
const districtsByDemo = MfArray.map(v => calcProportionalDistricts(v, N));
|
|
90192
|
+
return districtsByDemo;
|
|
90193
|
+
}
|
|
90194
|
+
exports.calcDistrictsByDemo = calcDistrictsByDemo;
|
|
90195
|
+
// NOTE - Shift minority proportions up, so 37% minority scores like 49% share,
|
|
90196
|
+
// but use the uncompressed seat probability distribution. This makes a 37%
|
|
90197
|
+
// district have a 40% chance of winning, a 42% district have an 84% chance,
|
|
90198
|
+
// and a 50% district have a 99% chance. Below 37% has no chance.
|
|
90199
|
+
//
|
|
90200
|
+
function estMinorityOpportunity(Mf) {
|
|
90201
|
+
// NOTE - Switch to compress the probability distribution
|
|
90202
|
+
const bCompress = false;
|
|
90203
|
+
const dist = bCompress ? C.minorityOpportunityDistribution() : [0.0, 1.0];
|
|
90204
|
+
const range = C.minorityOpportunityRange();
|
|
90205
|
+
const _normalizer = new normalize_1.Normalizer(Mf);
|
|
90206
|
+
const shift = C.minorityShift();
|
|
90207
|
+
_normalizer.wipNum += shift;
|
|
90208
|
+
_normalizer.clip(dist[C.BEG], dist[C.END]);
|
|
90209
|
+
_normalizer.unitize(dist[C.BEG], dist[C.END]);
|
|
90210
|
+
const oppty = (Mf < range[C.BEG]) ? 0.0 : partisan_1.estSeatProbability(_normalizer.wipNum, dist);
|
|
90211
|
+
return oppty;
|
|
90212
|
+
}
|
|
90213
|
+
exports.estMinorityOpportunity = estMinorityOpportunity;
|
|
90214
|
+
// HELPERS
|
|
90215
|
+
function exceedsMinimumThreshold(Mf) {
|
|
90216
|
+
const threshold = C.minorityOpportunityRange()[C.BEG];
|
|
90217
|
+
return Mf >= threshold;
|
|
90218
|
+
}
|
|
90219
|
+
function exceedsMaximumThreshold(Mf) {
|
|
90220
|
+
const threshold = C.minorityOpportunityRange()[C.END];
|
|
90221
|
+
return Mf > threshold;
|
|
90222
|
+
}
|
|
90223
|
+
function calcProportionalDistricts(proportion, nDistricts) {
|
|
90224
|
+
// TODO - Bump up to get a statewide proportion on the bubble to rate one district?
|
|
90225
|
+
const roundUp = 0.0;
|
|
90226
|
+
const fractional = proportion * nDistricts;
|
|
90227
|
+
const integral = Math.round(fractional + roundUp);
|
|
90228
|
+
return integral;
|
|
90229
|
+
}
|
|
90230
|
+
exports.calcProportionalDistricts = calcProportionalDistricts;
|
|
90231
|
+
|
|
90232
|
+
|
|
90233
|
+
/***/ }),
|
|
90234
|
+
|
|
90235
|
+
/***/ "./src/normalize.ts":
|
|
90236
|
+
/*!**************************!*\
|
|
90237
|
+
!*** ./src/normalize.ts ***!
|
|
90238
|
+
\**************************/
|
|
90239
|
+
/*! no static exports found */
|
|
90240
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
90241
|
+
|
|
90242
|
+
"use strict";
|
|
90243
|
+
|
|
90244
|
+
//
|
|
90245
|
+
// NORMALIZATION UTILITIES
|
|
90246
|
+
//
|
|
90247
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
90248
|
+
if (mod && mod.__esModule) return mod;
|
|
90249
|
+
var result = {};
|
|
90250
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
90251
|
+
result["default"] = mod;
|
|
90252
|
+
return result;
|
|
90253
|
+
};
|
|
90254
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90255
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
90256
|
+
class Normalizer {
|
|
90257
|
+
constructor(rawValue) {
|
|
90258
|
+
this.rawNum = rawValue;
|
|
90259
|
+
this.wipNum = this.rawNum;
|
|
90260
|
+
}
|
|
90261
|
+
// *Don't* transform the input.
|
|
90262
|
+
identity() {
|
|
90263
|
+
return this.wipNum;
|
|
90264
|
+
}
|
|
90265
|
+
positive() {
|
|
90266
|
+
this.wipNum = Math.abs(this.wipNum);
|
|
90267
|
+
return this.wipNum;
|
|
90268
|
+
}
|
|
90269
|
+
// Invert a value in the unit range [0.0–1.0] (so that bigger is better).
|
|
90270
|
+
invert() {
|
|
90271
|
+
// TODO - DAVID: Can we make this throw an Error and expect that in unit test?
|
|
90272
|
+
console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Inverting: " + S.OUT_OF_RANGE_MSG, this.wipNum);
|
|
90273
|
+
this.wipNum = 1.0 - this.wipNum;
|
|
90274
|
+
return this.wipNum;
|
|
90275
|
+
}
|
|
90276
|
+
// Constrain the value to be within a specified range.
|
|
90277
|
+
clip(begin_range, end_range) {
|
|
90278
|
+
// Handle the ends of the range being given either order
|
|
90279
|
+
const min_range = Math.min(begin_range, end_range);
|
|
90280
|
+
const max_range = Math.max(begin_range, end_range);
|
|
90281
|
+
this.wipNum = Math.max(Math.min(this.wipNum, max_range), min_range);
|
|
90282
|
+
return this.wipNum;
|
|
90283
|
+
}
|
|
90284
|
+
// Recast the value as the delta from a baseline value. NOOP if it is zero.
|
|
90285
|
+
// NOTE - Values can be + or -.
|
|
90286
|
+
rebase(base) {
|
|
90287
|
+
this.wipNum = this.wipNum - base;
|
|
90288
|
+
return this.wipNum;
|
|
90289
|
+
}
|
|
90290
|
+
// Re-scale a value into the [0.0 – 1.0] range, using a given range.
|
|
90291
|
+
// NOTE - This assumes that values have alrady been clipped into the range.
|
|
90292
|
+
unitize(begin_range, end_range) {
|
|
90293
|
+
const min_range = Math.min(begin_range, end_range);
|
|
90294
|
+
const max_range = Math.max(begin_range, end_range);
|
|
90295
|
+
// TODO - DAVID: Can we make this throw an Error and expect that in unit test?
|
|
90296
|
+
console.assert(((this.wipNum >= min_range) && (this.wipNum <= max_range)), "Unitizing: " + S.OUT_OF_RANGE_MSG, this.wipNum);
|
|
90297
|
+
const ranged = this.wipNum - min_range;
|
|
90298
|
+
this.wipNum = Math.abs(ranged / (end_range - begin_range));
|
|
90299
|
+
return this.wipNum;
|
|
90300
|
+
}
|
|
90301
|
+
// Decay a value in the unit range [0.0–1.0] by its distance from zero.
|
|
90302
|
+
// NOTE - If the range is already such that "bigger is better," then the closer
|
|
90303
|
+
// the value is to 1.0 (the best) the *less* it will decay.
|
|
90304
|
+
decay(fn = 0 /* Gravity */) {
|
|
90305
|
+
// TODO - DAVID: Can we make this throw an Error and expect that in unit test?
|
|
90306
|
+
console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Decaying: " + S.OUT_OF_RANGE_MSG, this.wipNum);
|
|
90307
|
+
switch (fn) {
|
|
90308
|
+
case 0 /* Gravity */: {
|
|
90309
|
+
this.wipNum = Math.pow(this.wipNum, S.DISTANCE_WEIGHT);
|
|
90310
|
+
return this.wipNum;
|
|
90311
|
+
}
|
|
90312
|
+
default: {
|
|
90313
|
+
throw new Error("Decay function not recognized.");
|
|
90314
|
+
}
|
|
90315
|
+
}
|
|
90316
|
+
}
|
|
90317
|
+
// Translate a value in the unit range to the user-friendly range [0 – 100].
|
|
90318
|
+
rescale() {
|
|
90319
|
+
// TODO - DAVID: Can we make this throw an Error and expect that in unit test?
|
|
90320
|
+
console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Rescaling: " + S.OUT_OF_RANGE_MSG, this.wipNum);
|
|
90321
|
+
this.normalizedNum = Math.round(this.wipNum * S.NORMALIZED_RANGE);
|
|
90322
|
+
return this.normalizedNum;
|
|
90323
|
+
}
|
|
90324
|
+
}
|
|
90325
|
+
exports.Normalizer = Normalizer;
|
|
90326
|
+
|
|
90327
|
+
|
|
90328
|
+
/***/ }),
|
|
90329
|
+
|
|
90330
|
+
/***/ "./src/partisan.ts":
|
|
90331
|
+
/*!*************************!*\
|
|
90332
|
+
!*** ./src/partisan.ts ***!
|
|
90333
|
+
\*************************/
|
|
89850
90334
|
/*! no static exports found */
|
|
89851
90335
|
/***/ (function(module, exports, __webpack_require__) {
|
|
89852
90336
|
|
|
@@ -89865,117 +90349,218 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
89865
90349
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89866
90350
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
89867
90351
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
90352
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
89868
90353
|
const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
|
|
89869
90354
|
// NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
|
|
89870
90355
|
// NOTE - I do not (cannot) assume that the values are sorted.
|
|
89871
90356
|
// SCORE BIAS & COMPETITIVENESS
|
|
89872
90357
|
/* SCORECARD FIELDS:
|
|
89873
90358
|
|
|
89874
|
-
* ^S# [
|
|
89875
|
-
* ^S% [
|
|
89876
|
-
*
|
|
89877
|
-
* S# [
|
|
89878
|
-
* S% [
|
|
89879
|
-
|
|
89880
|
-
*
|
|
89881
|
-
|
|
89882
|
-
*
|
|
89883
|
-
*
|
|
89884
|
-
|
|
89885
|
-
*
|
|
89886
|
-
*
|
|
89887
|
-
*
|
|
89888
|
-
*
|
|
89889
|
-
|
|
89890
|
-
*
|
|
90359
|
+
* ^S# [propS] = the Democratic seats closest to proportional
|
|
90360
|
+
* ^S% [propSf] = the corresponding Democratic seat share
|
|
90361
|
+
* S! [fptpS] = the estimated number of Democratic seats using first past the post
|
|
90362
|
+
* S# [estS] = the estimated Democratic seats, using seat probabilities
|
|
90363
|
+
* S% [estSf] = the estimated Democratic seat share fraction, calculated as S# / N
|
|
90364
|
+
|
|
90365
|
+
* Bs50 [s50V] = Seat bias as a fraction of N
|
|
90366
|
+
* Bv50 [v50S] = Votes bias as a fraction
|
|
90367
|
+
* decl [decl] = Declination
|
|
90368
|
+
* gSym [gSym] = Global symmetry
|
|
90369
|
+
|
|
90370
|
+
* EG [EG] = Efficiency gap as a fraction
|
|
90371
|
+
* sVg [BsGf] = Seats bias @ <V> (geometric)
|
|
90372
|
+
* prop [prop] = Disproportionality
|
|
90373
|
+
* MM [mMs] = Mean – median difference using statewide Vf
|
|
90374
|
+
* TO [tOf] = Turnout bias
|
|
90375
|
+
* MM' [mMd] = Mean – median difference using average district v
|
|
90376
|
+
* LO [LO] = Lopsided outcomes
|
|
90377
|
+
|
|
90378
|
+
* B% [bias]= the bias calculated as S% – ^S%
|
|
90379
|
+
* B$ [bias.score] = the bias score normalized [0–100]
|
|
90380
|
+
|
|
90381
|
+
* UE# [unearnedS] = the number of unearned seats (R = positive; D = negative)
|
|
90382
|
+
* I$ [impact.score] -- the unearned seats normalized [0–100] as impact
|
|
90383
|
+
|
|
90384
|
+
* R [bigR] = Overall responsiveness
|
|
90385
|
+
* r [littleR] = The point responsiveness at V%
|
|
90386
|
+
* MIR [MIR] = Minimal inverse responsiveness
|
|
90387
|
+
* rD [rD] = the estimated # of responsive districts (using probabilities)
|
|
90388
|
+
* rD% [rDf] = the estimated # of responsive districts, as a fraction of N
|
|
90389
|
+
|
|
90390
|
+
* C [c] = the number of districts that fall into the range [45–55%]
|
|
90391
|
+
* cD [cD] = the estimated # of competitive districts, using probabilities & a narrower [0.25–0.75] range
|
|
90392
|
+
* cD% [cDf] = the estimated # of competitive districts, as a fraction of N
|
|
90393
|
+
* beg/end [mRange] = the 1–N indices for the first and last district that separate the likely result and the proportional result
|
|
90394
|
+
* Md [mD] = the competitiveness of the marginal districts
|
|
90395
|
+
* Md% [mDf] = the probability that the marginal seats will flip, so S% => ^S%
|
|
90396
|
+
* C$ [competitiveness.score] = the competitiveness score normalized [0–100]
|
|
90397
|
+
|
|
90398
|
+
* <P$ [score] = the combined partisan score used to compare plans *across* states
|
|
90399
|
+
* >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
|
|
89891
90400
|
|
|
89892
90401
|
*/
|
|
89893
|
-
function scorePartisan(Vf, VfArray,
|
|
90402
|
+
function scorePartisan(Vf, VfArray, options) {
|
|
90403
|
+
const bAlternateMetrics = options.alternates;
|
|
90404
|
+
const bConstrained = options.constrained;
|
|
90405
|
+
const shift = options.shift;
|
|
89894
90406
|
const N = VfArray.length;
|
|
89895
|
-
const
|
|
89896
|
-
const
|
|
89897
|
-
const
|
|
89898
|
-
const
|
|
89899
|
-
const
|
|
89900
|
-
const
|
|
89901
|
-
const
|
|
89902
|
-
const
|
|
89903
|
-
const
|
|
89904
|
-
const
|
|
89905
|
-
|
|
89906
|
-
|
|
89907
|
-
const
|
|
89908
|
-
const
|
|
89909
|
-
const
|
|
89910
|
-
const
|
|
89911
|
-
|
|
89912
|
-
|
|
89913
|
-
|
|
89914
|
-
|
|
89915
|
-
|
|
89916
|
-
|
|
89917
|
-
|
|
90407
|
+
const propS = propSeats(N, Vf);
|
|
90408
|
+
const propSf = propSeatShare(propS, N);
|
|
90409
|
+
const fptpS = bAlternateMetrics ? estFPTPSeats(VfArray) : undefined;
|
|
90410
|
+
const range = bConstrained ? C.competitiveDistribution() : undefined;
|
|
90411
|
+
const estS = estSeats(VfArray, range);
|
|
90412
|
+
const estSf = estSeatShare(estS, N);
|
|
90413
|
+
const bias = estBias(estSf, propSf);
|
|
90414
|
+
const biasScore = scorebias(bias, Vf, estSf);
|
|
90415
|
+
const unearnedS = estUnearnedSeats(propS, estS);
|
|
90416
|
+
const impactScore = scoreImpact(unearnedS, Vf, N);
|
|
90417
|
+
// Calculate additional alternate metrics for reference
|
|
90418
|
+
// NOTE - Use the uncompressed seat probability function
|
|
90419
|
+
const inferredSVpoints = bAlternateMetrics ? inferSVpoints(Vf, VfArray, shift) : undefined;
|
|
90420
|
+
const TOf = bAlternateMetrics ? calcTurnoutBias(Vf, VfArray) : undefined;
|
|
90421
|
+
const Bs50 = bAlternateMetrics ? estPartisanBias(inferredSVpoints, N) : undefined;
|
|
90422
|
+
const Bs50f = (!(Bs50 === undefined)) ? U.trim(Bs50 / N) : undefined;
|
|
90423
|
+
const Bv50f = bAlternateMetrics ? estVotesBias(inferredSVpoints, N) : undefined;
|
|
90424
|
+
const decl = bAlternateMetrics ? calcDeclination(VfArray) : undefined;
|
|
90425
|
+
const gSym = bAlternateMetrics ? calcGlobalSymmetry(inferredSVpoints, Bs50f) : undefined;
|
|
90426
|
+
const EG = bAlternateMetrics ? calcEfficiencyGap(Vf, estSf) : undefined;
|
|
90427
|
+
const BsGf = bAlternateMetrics ? estGeometricSeatsBias(Vf, inferredSVpoints) : undefined;
|
|
90428
|
+
const prop = bAlternateMetrics ? calcDisproportionality(Vf, estSf) : undefined;
|
|
90429
|
+
const mMs = bAlternateMetrics ? estMeanMedianDifference(VfArray, Vf) : undefined;
|
|
90430
|
+
const mMd = bAlternateMetrics ? estMeanMedianDifference(VfArray) : undefined;
|
|
90431
|
+
const LO = bAlternateMetrics ? calcLopsidedOutcomes(VfArray) : undefined;
|
|
90432
|
+
// Calculate alternate responsiveness metrics for reference
|
|
90433
|
+
const bigR = bAlternateMetrics ? calcBigR(Vf, estSf) : undefined;
|
|
90434
|
+
const littleR = bAlternateMetrics ? estResponsiveness(Vf, inferredSVpoints) : undefined;
|
|
90435
|
+
const MIR = bAlternateMetrics ? calcMinimalInverseResponsiveness(Vf, littleR) : undefined;
|
|
90436
|
+
const rD = (!bConstrained || bAlternateMetrics) ? estResponsiveDistricts(VfArray) : undefined;
|
|
90437
|
+
const rDf = bAlternateMetrics ? estResponsiveDistrictsShare(rD, N) : undefined;
|
|
90438
|
+
const Cn = countCompetitiveDistricts(VfArray);
|
|
90439
|
+
const cD = bConstrained ? estCompetitiveDistricts(VfArray) : rD;
|
|
90440
|
+
const cDf = estCompetitiveDistrictsShare(cD, N);
|
|
90441
|
+
const Mrange = findMarginalDistricts(Vf, VfArray, N);
|
|
90442
|
+
const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
|
|
90443
|
+
const Mdf = estMarginalCompetitiveShare(Md, Mrange);
|
|
90444
|
+
const competitivenessScore = scoreCompetitiveness(Mdf, cDf);
|
|
90445
|
+
let biasScoring = {
|
|
90446
|
+
propS: propS,
|
|
90447
|
+
propSf: propSf,
|
|
90448
|
+
estS: estS,
|
|
90449
|
+
estSf: estSf,
|
|
90450
|
+
bias: bias,
|
|
90451
|
+
score: biasScore
|
|
89918
90452
|
};
|
|
89919
|
-
|
|
89920
|
-
|
|
89921
|
-
|
|
89922
|
-
|
|
89923
|
-
|
|
89924
|
-
|
|
89925
|
-
|
|
89926
|
-
|
|
89927
|
-
|
|
89928
|
-
|
|
89929
|
-
|
|
89930
|
-
|
|
89931
|
-
|
|
89932
|
-
|
|
89933
|
-
|
|
89934
|
-
|
|
89935
|
-
|
|
89936
|
-
|
|
89937
|
-
|
|
89938
|
-
|
|
90453
|
+
const impactScoring = {
|
|
90454
|
+
unearnedS: unearnedS,
|
|
90455
|
+
score: impactScore
|
|
90456
|
+
};
|
|
90457
|
+
let competitiveScoring = {
|
|
90458
|
+
c: Cn,
|
|
90459
|
+
cD: cD,
|
|
90460
|
+
cDf: cDf,
|
|
90461
|
+
mRange: Mrange,
|
|
90462
|
+
mD: Md,
|
|
90463
|
+
mDf: Mdf,
|
|
90464
|
+
score: competitivenessScore
|
|
90465
|
+
};
|
|
90466
|
+
if (bAlternateMetrics) {
|
|
90467
|
+
biasScoring.tOf = TOf;
|
|
90468
|
+
biasScoring.fptpS = fptpS;
|
|
90469
|
+
biasScoring.s50V = Bs50f;
|
|
90470
|
+
biasScoring.v50S = Bv50f;
|
|
90471
|
+
biasScoring.decl = decl;
|
|
90472
|
+
biasScoring.gSym = gSym;
|
|
90473
|
+
biasScoring.eG = EG;
|
|
90474
|
+
biasScoring.sVg = BsGf;
|
|
90475
|
+
biasScoring.prop = prop;
|
|
90476
|
+
biasScoring.mMs = mMs;
|
|
90477
|
+
biasScoring.mMd = mMd;
|
|
90478
|
+
biasScoring.lO = LO;
|
|
90479
|
+
competitiveScoring.bigR = bigR;
|
|
90480
|
+
competitiveScoring.littleR = littleR;
|
|
90481
|
+
competitiveScoring.mIR = MIR;
|
|
90482
|
+
competitiveScoring.rD = rD;
|
|
90483
|
+
competitiveScoring.rDf = rDf;
|
|
90484
|
+
}
|
|
90485
|
+
// Weight bias, impact, and competitiveness into partisan scores to compare
|
|
90486
|
+
// plans across states and within a state.
|
|
90487
|
+
const bS = biasScoring.score;
|
|
90488
|
+
const iS = impactScoring.score;
|
|
90489
|
+
const cS = competitiveScoring.score;
|
|
90490
|
+
const acrossStatesPartisanScore = weightPartisan(bS, iS, cS, 0 /* AcrossStates */);
|
|
90491
|
+
const withinStatesPartisanScore = weightPartisan(bS, iS, cS, 1 /* WithinAState */);
|
|
89939
90492
|
const s = {
|
|
89940
90493
|
bias: biasScoring,
|
|
89941
|
-
|
|
90494
|
+
impact: impactScoring,
|
|
90495
|
+
competitiveness: competitiveScoring,
|
|
90496
|
+
score: acrossStatesPartisanScore,
|
|
90497
|
+
score2: withinStatesPartisanScore
|
|
89942
90498
|
};
|
|
89943
90499
|
return s;
|
|
89944
90500
|
}
|
|
89945
90501
|
exports.scorePartisan = scorePartisan;
|
|
89946
|
-
function weightPartisan(
|
|
89947
|
-
|
|
90502
|
+
function weightPartisan(bS, iS, cS, context) {
|
|
90503
|
+
const bW = C.biasWeight(context);
|
|
90504
|
+
const iW = C.impactWeight(context);
|
|
90505
|
+
const cW = C.competitivenessWeight(context);
|
|
90506
|
+
const score = Math.round(((bS * bW) + (iS * iW) + (cS * cW)) / (bW + iW + cW));
|
|
90507
|
+
return score;
|
|
89948
90508
|
}
|
|
89949
90509
|
exports.weightPartisan = weightPartisan;
|
|
89950
|
-
function
|
|
89951
|
-
|
|
89952
|
-
|
|
89953
|
-
exports.printFairScorecardHeader = printFairScorecardHeader;
|
|
89954
|
-
function printFairScorecard(xx, name, N, Vf, s) {
|
|
89955
|
-
console.log('%s, %s, %i, %f, %i, %f, %i, %f, %f, %f, %i, %f, %f, %f, %f, %f, %f, %f, %i', xx, name, N, Vf, s.bias.BestS, s.bias.BestSf, s.bias.FptpS, s.bias.ProbableS, s.bias.ProbableSf, s.bias.Bias, s.bias.normalized, s.competitiveness.R, s.competitiveness.Rd, s.competitiveness.Rdf, s.competitiveness.Cd, s.competitiveness.Cdf, s.competitiveness.Mdf, s.competitiveness.normalized, s.score);
|
|
90510
|
+
function extraBonus(Vf) {
|
|
90511
|
+
const okExtra = (0.5 - Vf) * (C.winnerBonus() - 1.0);
|
|
90512
|
+
return U.trim(okExtra);
|
|
89956
90513
|
}
|
|
89957
|
-
exports.
|
|
89958
|
-
function
|
|
90514
|
+
exports.extraBonus = extraBonus;
|
|
90515
|
+
function scorebias(rawBias, Vf, Sf) {
|
|
89959
90516
|
if (isAntimajoritarian(Vf, Sf)) {
|
|
89960
90517
|
return 0;
|
|
89961
90518
|
}
|
|
89962
90519
|
else {
|
|
89963
|
-
|
|
89964
|
-
const
|
|
89965
|
-
const
|
|
89966
|
-
|
|
90520
|
+
// Adjust bias to incorporate an acceptable winner's bonus based on Vf
|
|
90521
|
+
const extra = extraBonus(Vf);
|
|
90522
|
+
const adjusted = adjustBias(Vf, rawBias, extra);
|
|
90523
|
+
// Then normalize
|
|
90524
|
+
const _normalizer = new normalize_1.Normalizer(adjusted);
|
|
90525
|
+
const worst = C.biasRange()[C.BEG];
|
|
90526
|
+
const best = C.biasRange()[C.END];
|
|
90527
|
+
_normalizer.positive();
|
|
89967
90528
|
_normalizer.clip(worst, best);
|
|
89968
90529
|
_normalizer.unitize(worst, best);
|
|
89969
90530
|
_normalizer.invert();
|
|
89970
|
-
// TODO - SCORE: Would also decaying raw values (e.g., by squaring them) be too
|
|
89971
|
-
// much with a small range (like 10% or one seat)?
|
|
89972
|
-
// _normalizer.decay();
|
|
89973
90531
|
_normalizer.rescale();
|
|
89974
90532
|
const score = _normalizer.normalizedNum;
|
|
89975
90533
|
return score;
|
|
89976
90534
|
}
|
|
89977
90535
|
}
|
|
89978
|
-
exports.
|
|
90536
|
+
exports.scorebias = scorebias;
|
|
90537
|
+
// Adjust bias to account for a winner's bonus
|
|
90538
|
+
function adjustBias(Vf, bias, extra) {
|
|
90539
|
+
if (Vf > 0.5)
|
|
90540
|
+
return Math.min(bias - extra, 0);
|
|
90541
|
+
else
|
|
90542
|
+
return Math.max(bias - extra, 0);
|
|
90543
|
+
}
|
|
90544
|
+
exports.adjustBias = adjustBias;
|
|
90545
|
+
// Normalize unearned seats
|
|
90546
|
+
function scoreImpact(rawUE, Vf, N) {
|
|
90547
|
+
// Adjust impact to incorporate an acceptable winner's bonus based on Vf
|
|
90548
|
+
const extra = extraBonus(Vf);
|
|
90549
|
+
const adjustedBias = adjustBias(Vf, rawUE / N, extra);
|
|
90550
|
+
const adjustedImpact = adjustedBias * N;
|
|
90551
|
+
// Then normalize
|
|
90552
|
+
const _normalizer = new normalize_1.Normalizer(adjustedImpact);
|
|
90553
|
+
const worst = C.unearnedThreshold();
|
|
90554
|
+
const best = 0.0;
|
|
90555
|
+
_normalizer.positive();
|
|
90556
|
+
_normalizer.clip(worst, best);
|
|
90557
|
+
_normalizer.unitize(worst, best);
|
|
90558
|
+
_normalizer.invert();
|
|
90559
|
+
_normalizer.rescale();
|
|
90560
|
+
const score = _normalizer.normalizedNum;
|
|
90561
|
+
return score;
|
|
90562
|
+
}
|
|
90563
|
+
exports.scoreImpact = scoreImpact;
|
|
89979
90564
|
function isAntimajoritarian(Vf, Sf) {
|
|
89980
90565
|
const bDem = ((Vf < 0.5) && (Sf > 0.5)) ? true : false;
|
|
89981
90566
|
const bRep = (((1 - Vf) < 0.5) && ((1 - Sf) > 0.5)) ? true : false;
|
|
@@ -89987,16 +90572,24 @@ function scoreCompetitiveness(rawMarginal, rawOverall) {
|
|
|
89987
90572
|
// But the practical max is more like 2/3's, so unitize that range to [0.0–1.0].
|
|
89988
90573
|
// Then scale the values to [0–100].
|
|
89989
90574
|
const _overall = new normalize_1.Normalizer(rawOverall);
|
|
89990
|
-
|
|
89991
|
-
|
|
90575
|
+
let worst = C.overallCompetitivenessRange()[C.BEG];
|
|
90576
|
+
let best = C.overallCompetitivenessRange()[C.END];
|
|
90577
|
+
_overall.clip(worst, best);
|
|
90578
|
+
_overall.unitize(worst, best);
|
|
89992
90579
|
_overall.rescale();
|
|
89993
|
-
const
|
|
90580
|
+
const ocS = _overall.normalizedNum;
|
|
89994
90581
|
// Normalize marginal competitiveness
|
|
89995
90582
|
const _marginal = new normalize_1.Normalizer(rawMarginal);
|
|
90583
|
+
worst = C.marginalCompetitivenessRange()[C.BEG];
|
|
90584
|
+
best = C.marginalCompetitivenessRange()[C.END];
|
|
90585
|
+
_marginal.clip(worst, best);
|
|
90586
|
+
_marginal.unitize(worst, best);
|
|
89996
90587
|
_marginal.rescale();
|
|
89997
|
-
const
|
|
90588
|
+
const mcS = _marginal.normalizedNum;
|
|
90589
|
+
const mcW = C.marginalCompetitivenessWeight();
|
|
90590
|
+
const ocW = C.overallCompetitivenessWeight();
|
|
89998
90591
|
// Then combine the results
|
|
89999
|
-
const score = Math.round(((
|
|
90592
|
+
const score = Math.round(((mcW * mcS) + (ocW * ocS)) / (mcW + ocW));
|
|
90000
90593
|
return score;
|
|
90001
90594
|
}
|
|
90002
90595
|
exports.scoreCompetitiveness = scoreCompetitiveness;
|
|
@@ -90006,30 +90599,56 @@ const { erf } = __webpack_require__(/*! mathjs */ "./node_modules/mathjs/main/es
|
|
|
90006
90599
|
// console.log("erf(-0.5) =", erf(-0.5)); // returns -0.5204998778130465
|
|
90007
90600
|
// console.log("erf(4) =", erf(4)); // returns 0.9999999845827421
|
|
90008
90601
|
// Estimate the probability of a seat win for district, given a Vf
|
|
90009
|
-
function estSeatProbability(Vf) {
|
|
90602
|
+
function estSeatProbability(Vf, range) {
|
|
90603
|
+
if (range) {
|
|
90604
|
+
// If a range is provided, it defines end points of a compressed probability
|
|
90605
|
+
// distribution. These *aren't* the points where races start or stop being
|
|
90606
|
+
// contested, just the end points of a distribution that yields the desired
|
|
90607
|
+
// probabilities in the typical competitive range [45-55%].
|
|
90608
|
+
const _normalizer = new normalize_1.Normalizer(Vf);
|
|
90609
|
+
const distBeg = range[C.BEG];
|
|
90610
|
+
const distEnd = range[C.END];
|
|
90611
|
+
_normalizer.clip(distBeg, distEnd);
|
|
90612
|
+
_normalizer.unitize(distBeg, distEnd);
|
|
90613
|
+
return seatProbabilityFn(_normalizer.wipNum);
|
|
90614
|
+
}
|
|
90615
|
+
else {
|
|
90616
|
+
// Otherwise, use the full probability distribution.
|
|
90617
|
+
return seatProbabilityFn(Vf);
|
|
90618
|
+
}
|
|
90619
|
+
}
|
|
90620
|
+
exports.estSeatProbability = estSeatProbability;
|
|
90621
|
+
function seatProbabilityFn(Vf) {
|
|
90010
90622
|
// Python: 0.5 * (1 + erf((vpi - 0.50) / (0.02 * sqrt(8))))
|
|
90011
90623
|
return U.trim(0.5 * (1.0 + erf((Vf - 0.50) / (0.02 * Math.sqrt(8)))));
|
|
90012
90624
|
}
|
|
90013
|
-
exports.estSeatProbability = estSeatProbability;
|
|
90014
90625
|
// Estimate the number of responsive districts [R(d)], given a set of Vf's
|
|
90015
90626
|
function estDistrictResponsiveness(Vf) {
|
|
90016
90627
|
// Python: 1 - 4 * (est_seat_probability(vpi) - 0.5)**2
|
|
90017
90628
|
return U.trim(1.0 - 4.0 * Math.pow((estSeatProbability(Vf) - 0.5), 2));
|
|
90018
90629
|
}
|
|
90019
90630
|
exports.estDistrictResponsiveness = estDistrictResponsiveness;
|
|
90020
|
-
function inferSVpoints(Vf, VfArray) {
|
|
90631
|
+
function inferSVpoints(Vf, VfArray, shift, range) {
|
|
90021
90632
|
const nDistricts = VfArray.length;
|
|
90022
90633
|
let SVpoints = [];
|
|
90023
90634
|
for (let shiftedVf of shiftRange()) {
|
|
90024
|
-
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
|
|
90025
|
-
const shiftedSf =
|
|
90635
|
+
const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf, shift);
|
|
90636
|
+
const shiftedSf = estSeats(shiftedVPI, range) / nDistricts;
|
|
90026
90637
|
SVpoints.push({ v: shiftedVf, s: shiftedSf });
|
|
90638
|
+
// TODO - Why can't I trim these? Why does that only break the Hypotheticals?!?
|
|
90639
|
+
// SVpoints.push({v: U.trim(Number(shiftedVf)), s: shiftedSf});
|
|
90027
90640
|
}
|
|
90028
90641
|
return SVpoints;
|
|
90029
90642
|
}
|
|
90030
90643
|
exports.inferSVpoints = inferSVpoints;
|
|
90031
|
-
|
|
90032
|
-
|
|
90644
|
+
function shiftDistricts(Vf, VfArray, shiftedVf, shift) {
|
|
90645
|
+
if (shift == 0 /* Proportional */)
|
|
90646
|
+
return shiftProportionally(Vf, VfArray, shiftedVf);
|
|
90647
|
+
else
|
|
90648
|
+
return shiftUniformly(Vf, VfArray, shiftedVf);
|
|
90649
|
+
}
|
|
90650
|
+
// Shift districts proportionally
|
|
90651
|
+
function shiftProportionally(Vf, VfArray, shiftedVf) {
|
|
90033
90652
|
let shiftedVfArray;
|
|
90034
90653
|
if (shiftedVf < Vf) {
|
|
90035
90654
|
// Shift down: D's to R's
|
|
@@ -90047,39 +90666,48 @@ function shiftDistricts(Vf, VfArray, shiftedVf) {
|
|
|
90047
90666
|
}
|
|
90048
90667
|
return shiftedVfArray;
|
|
90049
90668
|
}
|
|
90050
|
-
//
|
|
90669
|
+
// Shift districts uniformly
|
|
90670
|
+
function shiftUniformly(Vf, VfArray, shiftedVf) {
|
|
90671
|
+
const shift = shiftedVf - Vf;
|
|
90672
|
+
const shiftedVfArray = VfArray.map((v => v + shift));
|
|
90673
|
+
return shiftedVfArray;
|
|
90674
|
+
}
|
|
90675
|
+
// Generate a range of v's in 1/2% increments
|
|
90051
90676
|
function shiftRange() {
|
|
90052
|
-
const range = [];
|
|
90053
|
-
const
|
|
90054
|
-
const upper = 75 / 100;
|
|
90677
|
+
const range = [0.25, 0.75];
|
|
90678
|
+
const axisRange = [];
|
|
90055
90679
|
const step = (1 / 100) / 2;
|
|
90056
|
-
for (let v =
|
|
90057
|
-
|
|
90680
|
+
for (let v = range[0]; v <= range[1] + S.EPSILON; v += step) {
|
|
90681
|
+
axisRange.push(v);
|
|
90058
90682
|
}
|
|
90059
|
-
return
|
|
90683
|
+
return axisRange;
|
|
90060
90684
|
}
|
|
90061
|
-
// ESTIMATE BIAS
|
|
90685
|
+
// ESTIMATE BIAS
|
|
90686
|
+
//
|
|
90687
|
+
// NOTE: By convention, '+' = R bias; '-' = D bias.
|
|
90688
|
+
//
|
|
90062
90689
|
// ^S# - The # of Democratic seats closest to proportional @ statewide Vf
|
|
90063
|
-
|
|
90064
|
-
|
|
90690
|
+
// The "expected number of seats" from http://bit.ly/2Fcuf4q
|
|
90691
|
+
function propSeats(N, Vf) {
|
|
90692
|
+
return Math.round((N * Vf) - S.EPSILON);
|
|
90065
90693
|
}
|
|
90066
|
-
exports.
|
|
90694
|
+
exports.propSeats = propSeats;
|
|
90067
90695
|
// ^S% - The corresponding Democratic seat share
|
|
90068
|
-
function
|
|
90696
|
+
function propSeatShare(bestS, N) {
|
|
90069
90697
|
return U.trim(bestS / N);
|
|
90070
90698
|
}
|
|
90071
|
-
exports.
|
|
90072
|
-
// S# - The estimated # of Democratic seats
|
|
90073
|
-
function
|
|
90699
|
+
exports.propSeatShare = propSeatShare;
|
|
90700
|
+
// S# - The estimated # of Democratic seats, using seat probabilities
|
|
90701
|
+
function estSeats(VfArray, range) {
|
|
90074
90702
|
// Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
|
|
90075
|
-
return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v))));
|
|
90703
|
+
return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
|
|
90076
90704
|
}
|
|
90077
|
-
exports.
|
|
90078
|
-
// S% - The estimated Democratic seat share fraction
|
|
90079
|
-
function
|
|
90080
|
-
return U.trim(
|
|
90705
|
+
exports.estSeats = estSeats;
|
|
90706
|
+
// S% - The estimated Democratic seat share fraction
|
|
90707
|
+
function estSeatShare(estS, N) {
|
|
90708
|
+
return U.trim(estS / N);
|
|
90081
90709
|
}
|
|
90082
|
-
exports.
|
|
90710
|
+
exports.estSeatShare = estSeatShare;
|
|
90083
90711
|
// F# - The estimated number of Democratic seats using first past the post
|
|
90084
90712
|
function estFPTPSeats(VfArray) {
|
|
90085
90713
|
// Python: sum([1.0 for vpi in vpi_by_district if (vpi > 0.5)])
|
|
@@ -90093,27 +90721,33 @@ function estFPTPSeats(VfArray) {
|
|
|
90093
90721
|
}));
|
|
90094
90722
|
}
|
|
90095
90723
|
exports.estFPTPSeats = estFPTPSeats;
|
|
90096
|
-
// B% -
|
|
90097
|
-
function estBias(
|
|
90098
|
-
return U.trim(
|
|
90724
|
+
// B% - The bias calculated as ^S% — S%
|
|
90725
|
+
function estBias(estSf, propSf) {
|
|
90726
|
+
return U.trim(propSf - estSf);
|
|
90099
90727
|
}
|
|
90100
90728
|
exports.estBias = estBias;
|
|
90729
|
+
// UE# - The estimated # of unearned seats
|
|
90730
|
+
// UE_# from http://bit.ly/2Fcuf4q
|
|
90731
|
+
function estUnearnedSeats(proportional, probable) {
|
|
90732
|
+
return U.trim(proportional - probable);
|
|
90733
|
+
}
|
|
90734
|
+
exports.estUnearnedSeats = estUnearnedSeats;
|
|
90101
90735
|
// ESTIMATE RESPONSIVENESS ("COMPETITIVE")
|
|
90102
90736
|
// R# - Estimate responsiveness at the statewide vote share
|
|
90103
90737
|
function estResponsiveness(Vf, inferredSVpoints) {
|
|
90104
|
-
|
|
90105
|
-
//
|
|
90106
|
-
|
|
90107
|
-
|
|
90108
|
-
|
|
90109
|
-
|
|
90110
|
-
|
|
90111
|
-
|
|
90112
|
-
return
|
|
90738
|
+
let R = undefined;
|
|
90739
|
+
// NOTE - Seat values are already fractions [0.0–1.0] here.
|
|
90740
|
+
const lowerPt = findBracketingLowerVf(Vf, inferredSVpoints);
|
|
90741
|
+
const upperPt = findBracketingUpperVf(Vf, inferredSVpoints);
|
|
90742
|
+
if (!(U.areRoughlyEqual((upperPt.v - lowerPt.v), 0, S.EPSILON))) {
|
|
90743
|
+
R = ((upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v));
|
|
90744
|
+
R = U.trim(R);
|
|
90745
|
+
}
|
|
90746
|
+
return R;
|
|
90113
90747
|
}
|
|
90114
90748
|
exports.estResponsiveness = estResponsiveness;
|
|
90115
90749
|
// Find the S(V) point that brackets a Vf value on the lower end
|
|
90116
|
-
function
|
|
90750
|
+
function findBracketingLowerVf(Vf, inferredSVpoints) {
|
|
90117
90751
|
let lowerPt = inferredSVpoints[0];
|
|
90118
90752
|
let smallerPoints = [];
|
|
90119
90753
|
for (let pt of inferredSVpoints) {
|
|
@@ -90129,7 +90763,7 @@ function findLowerBracket(Vf, inferredSVpoints) {
|
|
|
90129
90763
|
return lowerPt;
|
|
90130
90764
|
}
|
|
90131
90765
|
// Find the S(V) point that brackets a Vf value on the upper end
|
|
90132
|
-
function
|
|
90766
|
+
function findBracketingUpperVf(Vf, inferredSVpoints) {
|
|
90133
90767
|
let upperPt = inferredSVpoints[-1];
|
|
90134
90768
|
for (let pt of inferredSVpoints) {
|
|
90135
90769
|
if (pt.v >= Vf) {
|
|
@@ -90140,19 +90774,56 @@ function findUpperBracket(Vf, inferredSVpoints) {
|
|
|
90140
90774
|
}
|
|
90141
90775
|
return upperPt;
|
|
90142
90776
|
}
|
|
90143
|
-
//
|
|
90777
|
+
// The corresponding functions via the Sf y-axis (vs. Vf x-axis)
|
|
90778
|
+
// Find the S(V) point that brackets a Sf value on the lower end
|
|
90779
|
+
function findBracketingLowerSf(Sf, inferredSVpoints) {
|
|
90780
|
+
let lowerPt = inferredSVpoints[0];
|
|
90781
|
+
let smallerPoints = [];
|
|
90782
|
+
for (let pt of inferredSVpoints) {
|
|
90783
|
+
if (pt.s <= Sf) {
|
|
90784
|
+
smallerPoints.push(pt);
|
|
90785
|
+
}
|
|
90786
|
+
else {
|
|
90787
|
+
break;
|
|
90788
|
+
}
|
|
90789
|
+
}
|
|
90790
|
+
// The last smaller point
|
|
90791
|
+
lowerPt = smallerPoints.slice(-1)[0];
|
|
90792
|
+
return lowerPt;
|
|
90793
|
+
}
|
|
90794
|
+
// Find the S(V) point that brackets a Sf value on the upper end
|
|
90795
|
+
function findBracketingUpperSf(Sf, inferredSVpoints) {
|
|
90796
|
+
let upperPt = inferredSVpoints[-1];
|
|
90797
|
+
for (let pt of inferredSVpoints) {
|
|
90798
|
+
if (pt.s >= Sf) {
|
|
90799
|
+
// The first bigger point
|
|
90800
|
+
upperPt = { v: pt.v, s: pt.s };
|
|
90801
|
+
break;
|
|
90802
|
+
}
|
|
90803
|
+
}
|
|
90804
|
+
return upperPt;
|
|
90805
|
+
}
|
|
90806
|
+
// rD - Estimate the number of responsive districts, given a set of Vf's
|
|
90144
90807
|
function estResponsiveDistricts(VfArray) {
|
|
90145
90808
|
// Python: sum([est_district_responsiveness(vpi) for vpi in vpi_by_district])
|
|
90146
90809
|
return U.trim(U.sumArray(VfArray.map(v => estDistrictResponsiveness(v))));
|
|
90147
90810
|
}
|
|
90148
90811
|
exports.estResponsiveDistricts = estResponsiveDistricts;
|
|
90149
|
-
//
|
|
90150
|
-
function estResponsiveDistrictsShare(
|
|
90151
|
-
return U.trim(
|
|
90812
|
+
// rD% - The estimated # of responsive districts, as a fraction of N
|
|
90813
|
+
function estResponsiveDistrictsShare(rD, N) {
|
|
90814
|
+
return U.trim(rD / N);
|
|
90152
90815
|
}
|
|
90153
90816
|
exports.estResponsiveDistrictsShare = estResponsiveDistrictsShare;
|
|
90154
90817
|
// ESTIMATE COMPETITIVENESS (ENHANCED)
|
|
90155
|
-
//
|
|
90818
|
+
// C - Count the # of competitive districts, defined as v in [45–55%]
|
|
90819
|
+
function countCompetitiveDistricts(VfArray) {
|
|
90820
|
+
return U.trim(U.sumArray(VfArray.map(v => isCompetitive(v))));
|
|
90821
|
+
}
|
|
90822
|
+
exports.countCompetitiveDistricts = countCompetitiveDistricts;
|
|
90823
|
+
function isCompetitive(v) {
|
|
90824
|
+
return ((v >= C.competitiveRange()[C.BEG]) && (v <= C.competitiveRange()[C.END])) ? 1 : 0;
|
|
90825
|
+
}
|
|
90826
|
+
// cD - The estimated # of competitive districts
|
|
90156
90827
|
function estCompetitiveDistricts(VfArray) {
|
|
90157
90828
|
return U.trim(U.sumArray(VfArray.map(v => estDistrictCompetitiveness(v))));
|
|
90158
90829
|
}
|
|
@@ -90160,21 +90831,48 @@ exports.estCompetitiveDistricts = estCompetitiveDistricts;
|
|
|
90160
90831
|
// Re-scale a Democratic vote share to the competitive range (e.g., 45–55%).
|
|
90161
90832
|
function estDistrictCompetitiveness(Vf) {
|
|
90162
90833
|
const _normalizer = new normalize_1.Normalizer(Vf);
|
|
90163
|
-
|
|
90164
|
-
|
|
90834
|
+
// The end points of a compressed probability distribution
|
|
90835
|
+
// NOTE - These aren't the points where races start or stop being contested,
|
|
90836
|
+
// just the end points of a distribution that yields the desired behavior
|
|
90837
|
+
// in the typical competitive range [45-55%].
|
|
90838
|
+
const distBeg = C.competitiveDistribution()[C.BEG];
|
|
90839
|
+
const distEnd = C.competitiveDistribution()[C.END];
|
|
90840
|
+
_normalizer.clip(distBeg, distEnd);
|
|
90841
|
+
_normalizer.unitize(distBeg, distEnd);
|
|
90165
90842
|
const dC = estDistrictResponsiveness(_normalizer.wipNum);
|
|
90166
90843
|
return dC;
|
|
90167
90844
|
}
|
|
90168
90845
|
exports.estDistrictCompetitiveness = estDistrictCompetitiveness;
|
|
90169
|
-
//
|
|
90170
|
-
function estCompetitiveDistrictsShare(
|
|
90171
|
-
return U.trim(
|
|
90846
|
+
// cD% - The estimated # of competitive districts, as a fraction of N
|
|
90847
|
+
function estCompetitiveDistrictsShare(cD, N) {
|
|
90848
|
+
return U.trim(cD / N);
|
|
90172
90849
|
}
|
|
90173
90850
|
exports.estCompetitiveDistrictsShare = estCompetitiveDistrictsShare;
|
|
90851
|
+
// Md - The estimated # of "marginal" districts in and around the likely FPTP
|
|
90852
|
+
// seats & the best seat split that are competitive.
|
|
90853
|
+
function estMarginalCompetitiveDistricts(Mrange, VfArray) {
|
|
90854
|
+
const minId = Mrange[C.BEG];
|
|
90855
|
+
const maxId = Mrange[C.END];
|
|
90856
|
+
// Sort the array values, and subset it to those districts
|
|
90857
|
+
// NOTE - I'm *not* keeping track of the district indexes right now
|
|
90858
|
+
let subsetVfArray = VfArray.sort().slice(minId - 1, maxId);
|
|
90859
|
+
// Est. competitive districts on that array
|
|
90860
|
+
const Md = U.sumArray(subsetVfArray.map(v => estDistrictCompetitiveness(v)));
|
|
90861
|
+
return U.trim(Md);
|
|
90862
|
+
}
|
|
90863
|
+
exports.estMarginalCompetitiveDistricts = estMarginalCompetitiveDistricts;
|
|
90174
90864
|
// Md% - The estimated competitiveness of the "marginal" districts in and around
|
|
90175
90865
|
// the likely FPTP seats & the best seat split as a fraction
|
|
90176
|
-
function estMarginalCompetitiveShare(
|
|
90177
|
-
const
|
|
90866
|
+
function estMarginalCompetitiveShare(Md, Mrange) {
|
|
90867
|
+
const minId = Mrange[C.BEG];
|
|
90868
|
+
const maxId = Mrange[C.END];
|
|
90869
|
+
// Est. competitive district share on that result
|
|
90870
|
+
const MdShare = U.trim(estCompetitiveDistrictsShare(Md, maxId - minId + 1));
|
|
90871
|
+
return U.trim(MdShare);
|
|
90872
|
+
}
|
|
90873
|
+
exports.estMarginalCompetitiveShare = estMarginalCompetitiveShare;
|
|
90874
|
+
function findMarginalDistricts(Vf, VfArray, N) {
|
|
90875
|
+
const bestS = propSeats(N, Vf);
|
|
90178
90876
|
const fptpS = estFPTPSeats(VfArray);
|
|
90179
90877
|
// Find the marginal districts IDs (indexed 1–N)
|
|
90180
90878
|
let minId;
|
|
@@ -90192,137 +90890,331 @@ function estMarginalCompetitiveShare(Vf, VfArray, N) {
|
|
|
90192
90890
|
minId = Math.max((N - bestS) - 1, 1);
|
|
90193
90891
|
maxId = Math.min((N - bestS) + 1, N);
|
|
90194
90892
|
}
|
|
90195
|
-
|
|
90196
|
-
// Sort the array values, and subset it to those districts
|
|
90197
|
-
// NOTE - I'm *not* keeping track of the district indexes right now
|
|
90198
|
-
let subsetVfArray = VfArray.sort().slice(minId - 1, maxId);
|
|
90199
|
-
// Est. competitive districts on that array
|
|
90200
|
-
const Md = U.sumArray(subsetVfArray.map(v => estDistrictCompetitiveness(v)));
|
|
90201
|
-
// Est. competitive district share on that result
|
|
90202
|
-
const MdShare = U.trim(estCompetitiveDistrictsShare(Md, deltaS));
|
|
90203
|
-
return U.trim(MdShare);
|
|
90893
|
+
return [minId, maxId];
|
|
90204
90894
|
}
|
|
90205
|
-
exports.
|
|
90206
|
-
|
|
90207
|
-
|
|
90208
|
-
|
|
90209
|
-
|
|
90210
|
-
|
|
90211
|
-
/*!**********************!*\
|
|
90212
|
-
!*** ./src/index.ts ***!
|
|
90213
|
-
\**********************/
|
|
90214
|
-
/*! no static exports found */
|
|
90215
|
-
/***/ (function(module, exports, __webpack_require__) {
|
|
90216
|
-
|
|
90217
|
-
"use strict";
|
|
90218
|
-
|
|
90219
|
-
//
|
|
90220
|
-
// SCORE PACKAGE API
|
|
90221
|
-
//
|
|
90222
|
-
function __export(m) {
|
|
90223
|
-
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
90895
|
+
exports.findMarginalDistricts = findMarginalDistricts;
|
|
90896
|
+
// ADVANCED/ALTERNATE METRICS FOR REFERENCE
|
|
90897
|
+
function calcTurnoutBias(statewideVf, VfArray) {
|
|
90898
|
+
const districtAvg = U.avgArray(VfArray);
|
|
90899
|
+
const turnoutBias = statewideVf - districtAvg;
|
|
90900
|
+
return U.trim(turnoutBias);
|
|
90224
90901
|
}
|
|
90225
|
-
|
|
90226
|
-
|
|
90227
|
-
|
|
90228
|
-
exports.sampleProfile = types_1.sampleProfile;
|
|
90229
|
-
exports.sampleScorecard = types_1.sampleScorecard;
|
|
90230
|
-
|
|
90231
|
-
|
|
90232
|
-
/***/ }),
|
|
90233
|
-
|
|
90234
|
-
/***/ "./src/normalize.ts":
|
|
90235
|
-
/*!**************************!*\
|
|
90236
|
-
!*** ./src/normalize.ts ***!
|
|
90237
|
-
\**************************/
|
|
90238
|
-
/*! no static exports found */
|
|
90239
|
-
/***/ (function(module, exports, __webpack_require__) {
|
|
90240
|
-
|
|
90241
|
-
"use strict";
|
|
90242
|
-
|
|
90243
|
-
//
|
|
90244
|
-
// NORMALIZATION UTILITIES
|
|
90902
|
+
exports.calcTurnoutBias = calcTurnoutBias;
|
|
90903
|
+
// PARTISAN BIAS - I'm using John Nagle's simple seat bias below, which is what
|
|
90904
|
+
// PlanScore is doing:
|
|
90245
90905
|
//
|
|
90246
|
-
|
|
90247
|
-
|
|
90248
|
-
|
|
90249
|
-
|
|
90250
|
-
|
|
90251
|
-
|
|
90252
|
-
|
|
90253
|
-
|
|
90254
|
-
|
|
90255
|
-
|
|
90256
|
-
|
|
90257
|
-
|
|
90258
|
-
|
|
90259
|
-
|
|
90260
|
-
|
|
90261
|
-
|
|
90262
|
-
|
|
90263
|
-
|
|
90264
|
-
|
|
90265
|
-
|
|
90266
|
-
|
|
90267
|
-
|
|
90268
|
-
|
|
90269
|
-
|
|
90270
|
-
|
|
90271
|
-
// TODO - DAVID: Can we make this throw an Error and expect that in unit test?
|
|
90272
|
-
console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), S.OUT_OF_RANGE_MSG);
|
|
90273
|
-
this.wipNum = 1.0 - this.wipNum;
|
|
90274
|
-
return this.wipNum;
|
|
90906
|
+
// "Partisan bias is the difference between each party’s seat share and 50 %
|
|
90907
|
+
// in a hypothetical, perfectly tied election.For example, if a party would
|
|
90908
|
+
// win 55 % of a plan’s districts if it received 50 % of the statewide vote,
|
|
90909
|
+
// then the plan would have a bias of 5 % in this party’s favor.To calculate
|
|
90910
|
+
// partisan bias, the observed vote share in each district is shifted by the
|
|
90911
|
+
// amount necessary to simulate a tied statewide election.Each party’s seat
|
|
90912
|
+
// share in this hypothetical election is then determined. The difference
|
|
90913
|
+
// between each party’s seat share and 50 % is partisan bias."
|
|
90914
|
+
//
|
|
90915
|
+
// This is *not* King's & others' geometric partisan bias metric per se.
|
|
90916
|
+
// That is below.
|
|
90917
|
+
function estPartisanBias(inferredSVpoints, nDistricts) {
|
|
90918
|
+
return estSeatBias(inferredSVpoints, nDistricts);
|
|
90919
|
+
}
|
|
90920
|
+
exports.estPartisanBias = estPartisanBias;
|
|
90921
|
+
// SEATS BIAS -- John Nagle's simple seat bias @ 50% (alpha), a fractional # of seats.
|
|
90922
|
+
function estSeatBias(inferredSVpoints, nDistricts) {
|
|
90923
|
+
const half = 0.5;
|
|
90924
|
+
const tolerance = 0.001;
|
|
90925
|
+
let dSeats = 0;
|
|
90926
|
+
for (let pt of inferredSVpoints) {
|
|
90927
|
+
if (U.areRoughlyEqual(pt.v, half, tolerance)) {
|
|
90928
|
+
dSeats = pt.s * nDistricts;
|
|
90929
|
+
break;
|
|
90930
|
+
}
|
|
90275
90931
|
}
|
|
90276
|
-
|
|
90277
|
-
|
|
90278
|
-
|
|
90279
|
-
|
|
90280
|
-
|
|
90281
|
-
|
|
90282
|
-
|
|
90932
|
+
const rSeats = nDistricts - dSeats;
|
|
90933
|
+
const Bs = (rSeats - dSeats) / 2.0;
|
|
90934
|
+
// NOTE - That is the same as (N/2) - S(0.5).
|
|
90935
|
+
// const BsAlt = (nDistricts / 2.0) - dSeats;
|
|
90936
|
+
return U.trim(Bs);
|
|
90937
|
+
}
|
|
90938
|
+
exports.estSeatBias = estSeatBias;
|
|
90939
|
+
// VOTES BIAS -- John Nagle's simple vote bias @ 50% (alpha2), a percentage.
|
|
90940
|
+
function estVotesBias(inferredSVpoints, nDistricts) {
|
|
90941
|
+
let extraVf = 0.0;
|
|
90942
|
+
// Interpolate the extra Vf required @ Sf = 0.5
|
|
90943
|
+
const lowerPt = findBracketingLowerSf(0.5, inferredSVpoints);
|
|
90944
|
+
const upperPt = findBracketingUpperSf(0.5, inferredSVpoints);
|
|
90945
|
+
if ((upperPt.s - lowerPt.s) != 0) {
|
|
90946
|
+
const ratio = (upperPt.v - lowerPt.v) / (upperPt.s - lowerPt.s);
|
|
90947
|
+
const deltaS = 0.5 - lowerPt.s;
|
|
90948
|
+
extraVf = lowerPt.v + (ratio * deltaS) - 0.5;
|
|
90949
|
+
}
|
|
90950
|
+
extraVf = U.trim(extraVf);
|
|
90951
|
+
return extraVf;
|
|
90952
|
+
}
|
|
90953
|
+
exports.estVotesBias = estVotesBias;
|
|
90954
|
+
// GEOMETRIC SEATS BIAS (@ V = statewide vote share)
|
|
90955
|
+
function estGeometricSeatsBias(Vf, inferredSVpoints) {
|
|
90956
|
+
const bgsSVpoints = inferGeometricSeatsBiasPoints(inferredSVpoints);
|
|
90957
|
+
// Interpolate the seat fraction @ Vf
|
|
90958
|
+
const lowerPt = findBracketingLowerVf(Vf, bgsSVpoints);
|
|
90959
|
+
const upperPt = findBracketingUpperVf(Vf, bgsSVpoints);
|
|
90960
|
+
const ratio = (upperPt.s - lowerPt.s) / (upperPt.v - lowerPt.v);
|
|
90961
|
+
const deltaV = Vf - lowerPt.v;
|
|
90962
|
+
const deltaS = ratio * deltaV;
|
|
90963
|
+
const BsGf = lowerPt.s + deltaS;
|
|
90964
|
+
return U.trim(BsGf);
|
|
90965
|
+
}
|
|
90966
|
+
exports.estGeometricSeatsBias = estGeometricSeatsBias;
|
|
90967
|
+
function inferGeometricSeatsBiasPoints(inferredSVpoints) {
|
|
90968
|
+
const nPoints = inferredSVpoints.length;
|
|
90969
|
+
const inverseSVpoints = invertSVPoints(inferredSVpoints);
|
|
90970
|
+
let bgsSVpoints = [];
|
|
90971
|
+
for (let i = 0; i < nPoints; i++) {
|
|
90972
|
+
const Vf = inferredSVpoints[i].v;
|
|
90973
|
+
const sD = inferredSVpoints[i].s;
|
|
90974
|
+
const sR = inverseSVpoints[i].s;
|
|
90975
|
+
const BsGf = 0.5 * (sR - sD);
|
|
90976
|
+
bgsSVpoints.push({ v: Vf, s: BsGf });
|
|
90977
|
+
}
|
|
90978
|
+
return bgsSVpoints;
|
|
90979
|
+
}
|
|
90980
|
+
exports.inferGeometricSeatsBiasPoints = inferGeometricSeatsBiasPoints;
|
|
90981
|
+
function invertSVPoints(inferredSVpoints) {
|
|
90982
|
+
let invertedSVpoints = [];
|
|
90983
|
+
for (let pt of inferredSVpoints) {
|
|
90984
|
+
const Vd = pt.v;
|
|
90985
|
+
const Sd = pt.s;
|
|
90986
|
+
const Vr = U.trim(1.0 - Vd);
|
|
90987
|
+
const Sr = 1.0 - Sd;
|
|
90988
|
+
invertedSVpoints.push({ v: Vr, s: Sr });
|
|
90989
|
+
}
|
|
90990
|
+
invertedSVpoints.sort(function (a, b) {
|
|
90991
|
+
return a.v - b.v;
|
|
90992
|
+
});
|
|
90993
|
+
return invertedSVpoints;
|
|
90994
|
+
}
|
|
90995
|
+
exports.invertSVPoints = invertSVPoints;
|
|
90996
|
+
// EFFICIENCY GAP -- note the formulation used. Also, to accommodate turnout bias,
|
|
90997
|
+
// we would need to have D & R votes, not just shares.
|
|
90998
|
+
function calcEfficiencyGap(Vf, Sf, shareType = 0 /* Democratic */) {
|
|
90999
|
+
let efficiencyGap;
|
|
91000
|
+
if (shareType == 1 /* Republican */) {
|
|
91001
|
+
// NOTE - This is the common formulation:
|
|
91002
|
+
//
|
|
91003
|
+
// EG = (Sf – 0.5) – (2 × (Vf – 0.5))
|
|
91004
|
+
//
|
|
91005
|
+
// in which it is implied that '-' = R bias; '+' = D bias.
|
|
91006
|
+
efficiencyGap = U.trim((Sf - 0.5) - (2.0 * (Vf - 0.5)));
|
|
90283
91007
|
}
|
|
90284
|
-
|
|
90285
|
-
|
|
90286
|
-
|
|
90287
|
-
|
|
90288
|
-
return this.wipNum;
|
|
91008
|
+
else {
|
|
91009
|
+
// NOTE - This is the alternate formulation in which '+' = R bias; '-' = D bias,
|
|
91010
|
+
// which is consistent with all our other metrics.
|
|
91011
|
+
efficiencyGap = U.trim((2.0 * (Vf - 0.5)) - (Sf - 0.5));
|
|
90289
91012
|
}
|
|
90290
|
-
|
|
90291
|
-
|
|
90292
|
-
|
|
90293
|
-
|
|
90294
|
-
|
|
90295
|
-
|
|
90296
|
-
|
|
90297
|
-
|
|
90298
|
-
|
|
90299
|
-
|
|
91013
|
+
return U.trim(efficiencyGap);
|
|
91014
|
+
}
|
|
91015
|
+
exports.calcEfficiencyGap = calcEfficiencyGap;
|
|
91016
|
+
// MEAN–MEDIAN DIFFERENCE
|
|
91017
|
+
//
|
|
91018
|
+
// From PlanScore.org: "The mean-median difference is a party’s median vote share
|
|
91019
|
+
// minus its mean vote share, across all of a plan’s districts. For example, if
|
|
91020
|
+
// a party has a median vote share of 45 % and a mean vote share of 50 %, then
|
|
91021
|
+
// the plan has a mean - median difference of 5 % against this party. When the
|
|
91022
|
+
// mean and the median diverge significantly, the district distribution is skewed
|
|
91023
|
+
// in favor of one party and against its opponent. Conversely, when the mean and
|
|
91024
|
+
// the median are close, the district distribution is more symmetric."
|
|
91025
|
+
//
|
|
91026
|
+
// From Princeton Gerrymandering Project: "The mean-median difference is calculated
|
|
91027
|
+
// by subtracting the average vote share of either party across all districts from
|
|
91028
|
+
// the median vote share of the same party across all districts. A negative mean -
|
|
91029
|
+
// median difference indicates that the examined party has an advantage; a positive
|
|
91030
|
+
// difference indicates that the examined party is disadvantaged."
|
|
91031
|
+
//
|
|
91032
|
+
// So:
|
|
91033
|
+
// * With D VPI, '+' = R bias; '-' = D bias <<< We're using this convention.
|
|
91034
|
+
// * With R VPI, '-' = R bias; '+' = D bias.
|
|
91035
|
+
function estMeanMedianDifference(VfArray, Vf) {
|
|
91036
|
+
const meanVf = Vf ? Vf : U.avgArray(VfArray);
|
|
91037
|
+
const medianVf = U.medianArray(VfArray);
|
|
91038
|
+
// NOTE - Switched order to get the signs correct
|
|
91039
|
+
const difference = meanVf - medianVf;
|
|
91040
|
+
// const difference: number = medianVf - meanVf;
|
|
91041
|
+
return U.trim(difference);
|
|
91042
|
+
}
|
|
91043
|
+
exports.estMeanMedianDifference = estMeanMedianDifference;
|
|
91044
|
+
// HELPERS FOR DECLINATION & LOPSIDED OUTCOMES
|
|
91045
|
+
// Key r(v) points, defined in Fig. 19:
|
|
91046
|
+
// * VfArray are Democratic seat shares (by convention).
|
|
91047
|
+
// * But the x-axis of r(v) graphs us Republican seat share.
|
|
91048
|
+
// * So, you have to invert the D/R axis for Vb; and
|
|
91049
|
+
// * Invert the D/R probabilities for Va.
|
|
91050
|
+
function keyRVpoints(VfArray) {
|
|
91051
|
+
const nDistricts = VfArray.length;
|
|
91052
|
+
const estS = estSeats(VfArray);
|
|
91053
|
+
const Sb = estSeatShare(estS, nDistricts);
|
|
91054
|
+
// TODO - Understand why the corresponding V to Sb is always @ 0.5.
|
|
91055
|
+
// John Nagle: "This is the dividing vote for party A vs party B wins defined
|
|
91056
|
+
// by Warrington.My modification just puts fractions of districts to the each side."
|
|
91057
|
+
const Rb = Sb / 2;
|
|
91058
|
+
const Ra = (1 + Sb) / 2;
|
|
91059
|
+
const Vb = 1.0 - (U.sumArray(VfArray.map(v => estSeatProbability(v) * v))) / estS;
|
|
91060
|
+
const Va = (U.sumArray(VfArray.map(v => estSeatProbability(1 - v) * (1 - v)))) / (nDistricts - estS);
|
|
91061
|
+
const keyPoints = {
|
|
91062
|
+
Sb: Sb,
|
|
91063
|
+
Ra: Ra,
|
|
91064
|
+
Rb: Rb,
|
|
91065
|
+
Va: Va,
|
|
91066
|
+
Vb: Vb
|
|
91067
|
+
};
|
|
91068
|
+
return keyPoints;
|
|
91069
|
+
}
|
|
91070
|
+
exports.keyRVpoints = keyRVpoints;
|
|
91071
|
+
function isASweep(Sf, nDistricts) {
|
|
91072
|
+
const oneDistrict = 1 / nDistricts;
|
|
91073
|
+
const bSweep = ((Sf > (1 - oneDistrict)) || (Sf < oneDistrict)) ? true : false;
|
|
91074
|
+
return bSweep;
|
|
91075
|
+
}
|
|
91076
|
+
exports.isASweep = isASweep;
|
|
91077
|
+
function radiansToDegrees(radians) {
|
|
91078
|
+
const degrees = radians * (180 / Math.PI);
|
|
91079
|
+
return degrees;
|
|
91080
|
+
}
|
|
91081
|
+
exports.radiansToDegrees = radiansToDegrees;
|
|
91082
|
+
// DECLINATION
|
|
91083
|
+
//
|
|
91084
|
+
// Declination is calculated using the key r(v) points, defined in Fig. 19.
|
|
91085
|
+
// Note that district vote shares are D shares, so party A = Rep & B = Dem.
|
|
91086
|
+
function calcDeclination(VfArray) {
|
|
91087
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91088
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91089
|
+
const bTooFewDistricts = (VfArray.length < 5) ? true : false;
|
|
91090
|
+
const bVaAt50 = (U.areRoughlyEqual((Va - 0.5), 0.0, S.EPSILON)) ? true : false;
|
|
91091
|
+
const bVbAt50 = (U.areRoughlyEqual((0.5 - Vb), 0.0, S.EPSILON)) ? true : false;
|
|
91092
|
+
let decl;
|
|
91093
|
+
if (bSweep || bTooFewDistricts || bVaAt50 || bVbAt50) {
|
|
91094
|
+
decl = undefined;
|
|
90300
91095
|
}
|
|
90301
|
-
|
|
90302
|
-
|
|
90303
|
-
|
|
90304
|
-
|
|
90305
|
-
|
|
90306
|
-
|
|
90307
|
-
|
|
90308
|
-
case 0 /* Gravity */: {
|
|
90309
|
-
this.wipNum = Math.pow(this.wipNum, S.DISTANCE_WEIGHT);
|
|
90310
|
-
return this.wipNum;
|
|
90311
|
-
}
|
|
90312
|
-
default: {
|
|
90313
|
-
throw new Error("Decay function not recognized.");
|
|
90314
|
-
}
|
|
90315
|
-
}
|
|
91096
|
+
else {
|
|
91097
|
+
const lTan = (Sb - Rb) / (0.5 - Vb);
|
|
91098
|
+
const rTan = (Ra - Sb) / (Va - 0.5);
|
|
91099
|
+
const lAngle = radiansToDegrees(Math.atan(lTan));
|
|
91100
|
+
const rAngle = radiansToDegrees(Math.atan(rTan));
|
|
91101
|
+
decl = rAngle - lAngle;
|
|
91102
|
+
decl = U.trim(decl);
|
|
90316
91103
|
}
|
|
90317
|
-
|
|
90318
|
-
|
|
90319
|
-
|
|
90320
|
-
|
|
90321
|
-
|
|
90322
|
-
|
|
91104
|
+
return decl;
|
|
91105
|
+
}
|
|
91106
|
+
exports.calcDeclination = calcDeclination;
|
|
91107
|
+
// LOPSIDED OUTCOMES
|
|
91108
|
+
//
|
|
91109
|
+
// This is a measure of packing bias is:
|
|
91110
|
+
//
|
|
91111
|
+
// LO = (1⁄2 - vB) - (vA – 1⁄2) Eq. 5.4.1 on P. 26
|
|
91112
|
+
//
|
|
91113
|
+
// "The ideal for this measure is that the excess vote share for districts
|
|
91114
|
+
// won by party A averaged over those districts equals the excess vote share
|
|
91115
|
+
// for districts won by party B averaged over those districts.
|
|
91116
|
+
// A positive value of LO indicates greater packing of party B voters and,
|
|
91117
|
+
// therefore, indicates a bias in favor of party A."
|
|
91118
|
+
function calcLopsidedOutcomes(VfArray) {
|
|
91119
|
+
const { Sb, Ra, Rb, Va, Vb } = keyRVpoints(VfArray);
|
|
91120
|
+
const bSweep = isASweep(Sb, VfArray.length);
|
|
91121
|
+
let LO;
|
|
91122
|
+
if (bSweep) {
|
|
91123
|
+
LO = undefined;
|
|
90323
91124
|
}
|
|
91125
|
+
else {
|
|
91126
|
+
LO = (0.5 - Vb) - (Va - 0.5);
|
|
91127
|
+
LO = U.trim(LO);
|
|
91128
|
+
}
|
|
91129
|
+
return LO;
|
|
91130
|
+
}
|
|
91131
|
+
exports.calcLopsidedOutcomes = calcLopsidedOutcomes;
|
|
91132
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91133
|
+
// GLOBAL SYMMETRY - Fig. 17 in Section 5.1
|
|
91134
|
+
function calcGlobalSymmetry(inferredSVpoints, S50V) {
|
|
91135
|
+
const invertedSVpoints = invertSVPoints(inferredSVpoints);
|
|
91136
|
+
let gSym = 0.0;
|
|
91137
|
+
for (let i in inferredSVpoints) {
|
|
91138
|
+
gSym += Math.abs(inferredSVpoints[i].s - invertedSVpoints[i].s) / 2;
|
|
91139
|
+
}
|
|
91140
|
+
const sign = (S50V < 0) ? -1 : 1;
|
|
91141
|
+
gSym *= sign;
|
|
91142
|
+
return U.trim(gSym);
|
|
91143
|
+
}
|
|
91144
|
+
exports.calcGlobalSymmetry = calcGlobalSymmetry;
|
|
91145
|
+
// RAW DISPROPORTIONALITY
|
|
91146
|
+
//
|
|
91147
|
+
// gamma = Sf – Vf : Eq.C.1.1 on P. 42
|
|
91148
|
+
function calcDisproportionality(Vf, Sf) {
|
|
91149
|
+
const gamma = Vf - Sf;
|
|
91150
|
+
// const gamma = Sf - Vf;
|
|
91151
|
+
return U.trim(gamma);
|
|
91152
|
+
}
|
|
91153
|
+
exports.calcDisproportionality = calcDisproportionality;
|
|
91154
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91155
|
+
// BIG 'R': Defined in Footnote 22 on P. 10
|
|
91156
|
+
function calcBigR(Vf, Sf) {
|
|
91157
|
+
let bigR = undefined;
|
|
91158
|
+
if (!(U.areRoughlyEqual(Vf, 0.5, S.EPSILON))) {
|
|
91159
|
+
bigR = (Sf - 0.5) / (Vf - 0.5);
|
|
91160
|
+
bigR = U.trim(bigR);
|
|
91161
|
+
}
|
|
91162
|
+
return bigR;
|
|
91163
|
+
}
|
|
91164
|
+
exports.calcBigR = calcBigR;
|
|
91165
|
+
// TODO - Add unit tests <<< Depends on S(V)
|
|
91166
|
+
// MINIMAL INVERSE RESPONSIVENESS
|
|
91167
|
+
//
|
|
91168
|
+
// zeta = (1 / r) - (1 / r_sub_max) : Eq. 5.2.1
|
|
91169
|
+
//
|
|
91170
|
+
// where r_sub_max = 10 or 20 for balanced and unbalanced states, respectively.
|
|
91171
|
+
function calcMinimalInverseResponsiveness(Vf, r) {
|
|
91172
|
+
let MIR = undefined;
|
|
91173
|
+
if (!(U.areRoughlyEqual(r, 0, S.EPSILON))) {
|
|
91174
|
+
const bBalanced = isBalanced(Vf);
|
|
91175
|
+
const ideal = bBalanced ? 0.1 : 0.2;
|
|
91176
|
+
MIR = (1 / r) - ideal;
|
|
91177
|
+
MIR = U.trim(MIR);
|
|
91178
|
+
}
|
|
91179
|
+
return MIR;
|
|
91180
|
+
}
|
|
91181
|
+
exports.calcMinimalInverseResponsiveness = calcMinimalInverseResponsiveness;
|
|
91182
|
+
function isBalanced(Vf) {
|
|
91183
|
+
const [lower, upper] = C.competitiveRange();
|
|
91184
|
+
const bBalanced = ((Vf > upper) || (Vf < lower)) ? false : true;
|
|
91185
|
+
return bBalanced;
|
|
90324
91186
|
}
|
|
90325
|
-
|
|
91187
|
+
// HELPERS
|
|
91188
|
+
function printPartisanScorecardHeader() {
|
|
91189
|
+
console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, <P$');
|
|
91190
|
+
}
|
|
91191
|
+
exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
|
|
91192
|
+
function printPartisanScorecardRow(xx, name, N, Vf, s) {
|
|
91193
|
+
console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i', xx, // 1
|
|
91194
|
+
name, // 2
|
|
91195
|
+
N, // 3
|
|
91196
|
+
Vf, // 4
|
|
91197
|
+
s.bias.propS, // 5
|
|
91198
|
+
s.bias.estS, // 6
|
|
91199
|
+
s.bias.bias, // 7
|
|
91200
|
+
s.bias.score, s.impact.unearnedS, // 9
|
|
91201
|
+
s.impact.score, s.competitiveness.c, // 11
|
|
91202
|
+
s.competitiveness.cD, s.competitiveness.mD, s.competitiveness.score, s.score // 15
|
|
91203
|
+
);
|
|
91204
|
+
}
|
|
91205
|
+
exports.printPartisanScorecardRow = printPartisanScorecardRow;
|
|
91206
|
+
// Generate partisan details (Table 1)
|
|
91207
|
+
function printPartisanDetailsHeader() {
|
|
91208
|
+
console.log('XX, <V>, S(<V>), S50V, V50S, Decl, B_G, EG, Beta, l-gamma, mM, TO, mM\', LO, R, r, Zeta');
|
|
91209
|
+
}
|
|
91210
|
+
exports.printPartisanDetailsHeader = printPartisanDetailsHeader;
|
|
91211
|
+
function printPartisanDetailsRow(xx, name, N, Vf, s) {
|
|
91212
|
+
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.s50V, s.bias.v50S, s.bias.decl, s.bias.gSym, s.bias.eG, s.bias.sVg, // Beta
|
|
91213
|
+
s.bias.prop, // Lower-gamma
|
|
91214
|
+
s.bias.mMs, s.bias.tOf, s.bias.mMd, s.bias.lO, s.competitiveness.bigR, s.competitiveness.littleR, s.competitiveness.mIR // Zeta
|
|
91215
|
+
);
|
|
91216
|
+
}
|
|
91217
|
+
exports.printPartisanDetailsRow = printPartisanDetailsRow;
|
|
90326
91218
|
|
|
90327
91219
|
|
|
90328
91220
|
/***/ }),
|
|
@@ -90348,73 +91240,129 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
90348
91240
|
};
|
|
90349
91241
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90350
91242
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
91243
|
+
const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
|
|
90351
91244
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
90352
91245
|
const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
|
|
90353
91246
|
const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
|
|
90354
|
-
const
|
|
90355
|
-
|
|
91247
|
+
const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
|
|
91248
|
+
const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
|
|
90356
91249
|
function scorePlan(p, overridesJSON) {
|
|
90357
|
-
//
|
|
91250
|
+
// TRADITIONAL DISTRICTING PRINCIPLES ("best") subcategories - compactness, splitting, population deviation
|
|
90358
91251
|
// Compactness
|
|
90359
|
-
const reockM = compact_1.doReock(p.compactnessProfile.
|
|
90360
|
-
const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.
|
|
90361
|
-
const compactnessScore =
|
|
91252
|
+
const reockM = compact_1.doReock(p.compactnessProfile.geometryByDistrict);
|
|
91253
|
+
const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.geometryByDistrict);
|
|
91254
|
+
const compactnessScore = weightCompactness(reockM.normalized, polsbyM.normalized);
|
|
90362
91255
|
const cS = {
|
|
90363
91256
|
score: compactnessScore,
|
|
90364
91257
|
reock: reockM,
|
|
90365
91258
|
polsby: polsbyM
|
|
90366
91259
|
};
|
|
90367
91260
|
// Splitting
|
|
90368
|
-
const CxD = p.splittingProfile.
|
|
91261
|
+
const CxD = p.splittingProfile.countyPopByDistrict;
|
|
90369
91262
|
const dT = cohesive_1.totalDistricts(CxD);
|
|
90370
91263
|
const cT = cohesive_1.totalCounties(CxD);
|
|
90371
91264
|
const countyM = cohesive_1.doCountySplittingReduced(CxD, dT, cT);
|
|
90372
91265
|
const districtM = cohesive_1.doDistrictSplittingReduced(CxD, dT, cT);
|
|
90373
|
-
const splittingScore =
|
|
91266
|
+
const splittingScore = weightSplitting(countyM.normalized, districtM.normalized);
|
|
90374
91267
|
const sS = {
|
|
90375
91268
|
score: splittingScore,
|
|
90376
91269
|
county: countyM,
|
|
90377
91270
|
district: districtM
|
|
90378
91271
|
};
|
|
90379
91272
|
// Population deviation
|
|
90380
|
-
const
|
|
90381
|
-
// Combine
|
|
90382
|
-
const
|
|
90383
|
-
// Populate the "best" scorecard
|
|
90384
|
-
const
|
|
90385
|
-
|
|
91273
|
+
const pdS = equal_1.doPopulationDeviation(p.populationProfile.totalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
|
|
91274
|
+
// Combine traditional principles into a score to compare plans w/in a state
|
|
91275
|
+
const tpScore = weightTradtionalPrinciples(cS.score, sS.score, pdS.normalized, 1 /* WithinAState */);
|
|
91276
|
+
// Populate the "best" traditional principles scorecard
|
|
91277
|
+
const tpS = {
|
|
91278
|
+
score: tpScore,
|
|
90386
91279
|
compactness: cS,
|
|
90387
91280
|
splitting: sS,
|
|
90388
|
-
populationDeviation:
|
|
91281
|
+
populationDeviation: pdS
|
|
91282
|
+
};
|
|
91283
|
+
// PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
|
|
91284
|
+
const options = {
|
|
91285
|
+
alternates: true,
|
|
91286
|
+
constrained: true,
|
|
91287
|
+
shift: 0 /* Proportional */
|
|
90389
91288
|
};
|
|
90390
|
-
|
|
90391
|
-
//
|
|
90392
|
-
|
|
90393
|
-
|
|
90394
|
-
|
|
90395
|
-
|
|
91289
|
+
const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.vfArray, options);
|
|
91290
|
+
// Combine the partisan/partisan & traditional principles/best scores into an overall
|
|
91291
|
+
// score for comparing plans w/in a state
|
|
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);
|
|
90396
91296
|
// Roll up an overall scorecard
|
|
90397
91297
|
const scorecard = {
|
|
90398
|
-
|
|
90399
|
-
|
|
90400
|
-
|
|
91298
|
+
partisan: pS,
|
|
91299
|
+
minority: mS,
|
|
91300
|
+
traditionalPrinciples: tpS,
|
|
91301
|
+
score: score
|
|
90401
91302
|
};
|
|
90402
91303
|
return scorecard;
|
|
90403
91304
|
}
|
|
90404
91305
|
exports.scorePlan = scorePlan;
|
|
91306
|
+
// HELPERS
|
|
91307
|
+
function weightCompactness(rS, ppS) {
|
|
91308
|
+
const rW = C.reockWeight();
|
|
91309
|
+
const ppW = C.polsbyWeight();
|
|
91310
|
+
const score = Math.round(((rS * rW) + (ppS * ppW)) / (rW + ppW));
|
|
91311
|
+
return score;
|
|
91312
|
+
}
|
|
91313
|
+
exports.weightCompactness = weightCompactness;
|
|
91314
|
+
function weightSplitting(csS, dsS) {
|
|
91315
|
+
const csW = C.countySplittingWeight();
|
|
91316
|
+
const dsW = C.districtSplittingWeight();
|
|
91317
|
+
const score = Math.round(((csS * csW) + (dsS * dsW)) / (csW + dsW));
|
|
91318
|
+
return score;
|
|
91319
|
+
}
|
|
91320
|
+
exports.weightSplitting = weightSplitting;
|
|
91321
|
+
function weightTradtionalPrinciples(cS, sS, pdS, context) {
|
|
91322
|
+
const cW = C.compactnessWeight(context);
|
|
91323
|
+
const sW = C.splittingWeight(context);
|
|
91324
|
+
const pdW = C.popdevWeight(context);
|
|
91325
|
+
const score = Math.round(((cS * cW) + (sS * sW) + (pdS * pdW)) / (cW + sW + pdW));
|
|
91326
|
+
return score;
|
|
91327
|
+
}
|
|
91328
|
+
exports.weightTradtionalPrinciples = weightTradtionalPrinciples;
|
|
91329
|
+
function weightOverall(pS, tpS, context) {
|
|
91330
|
+
const pW = C.partisanWeight(context);
|
|
91331
|
+
const tpW = C.traditionalPrinciplesWeight(context);
|
|
91332
|
+
const score = Math.round(((pS * pW) + (tpS * tpW)) / (pW + tpW));
|
|
91333
|
+
return score;
|
|
91334
|
+
}
|
|
91335
|
+
exports.weightOverall = weightOverall;
|
|
91336
|
+
function mixinMinorityBonus(score, minorityBonus) {
|
|
91337
|
+
const modifiedScore = Math.min(score + minorityBonus, S.NORMALIZED_RANGE);
|
|
91338
|
+
return modifiedScore;
|
|
91339
|
+
}
|
|
91340
|
+
exports.mixinMinorityBonus = mixinMinorityBonus;
|
|
91341
|
+
function printScorecardHeader() {
|
|
91342
|
+
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$, $$$');
|
|
91343
|
+
}
|
|
91344
|
+
exports.printScorecardHeader = printScorecardHeader;
|
|
91345
|
+
function printScorecardRow(xx, name, N, Vf, s) {
|
|
91346
|
+
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
|
|
91347
|
+
name, // 2
|
|
91348
|
+
N, // 3
|
|
91349
|
+
Vf, // 4
|
|
91350
|
+
s.partisan.bias.propS, // 5
|
|
91351
|
+
s.partisan.bias.estS, // 6
|
|
91352
|
+
s.partisan.bias.bias, // 7
|
|
91353
|
+
s.partisan.bias.score, s.partisan.impact.unearnedS, // 9
|
|
91354
|
+
s.partisan.impact.score, s.partisan.competitiveness.c, // 11
|
|
91355
|
+
s.partisan.competitiveness.cD, s.partisan.competitiveness.mD, s.partisan.competitiveness.score, s.partisan.score2, // 15
|
|
91356
|
+
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,
|
|
91357
|
+
// s.minority.nOpportunity1,
|
|
91358
|
+
// s.minority.nOpportunity2,
|
|
91359
|
+
s.minority.opportunityDistricts,
|
|
91360
|
+
// s.minority.nProportional,
|
|
91361
|
+
s.minority.score, s.score);
|
|
91362
|
+
}
|
|
91363
|
+
exports.printScorecardRow = printScorecardRow;
|
|
90405
91364
|
|
|
90406
91365
|
|
|
90407
|
-
/***/ }),
|
|
90408
|
-
|
|
90409
|
-
/***/ "./src/settings.json":
|
|
90410
|
-
/*!***************************!*\
|
|
90411
|
-
!*** ./src/settings.json ***!
|
|
90412
|
-
\***************************/
|
|
90413
|
-
/*! exports provided: fair, best, default */
|
|
90414
|
-
/***/ (function(module) {
|
|
90415
|
-
|
|
90416
|
-
module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\"weight\":80},\"competitive\":{\"worst\":0,\"best\":0.67,\"weight\":20,\"min\":0.25,\"max\":0.75,\"marginal\":75,\"overall\":25},\"weight\":80},\"best\":{\"compact\":{\"reock\":{\"worst\":0.25,\"best\":0.5,\"weight\":50},\"polsby\":{\"worst\":0.1,\"best\":0.5,\"weight\":50},\"weight\":50},\"cohesive\":{\"county\":{\"best\":1,\"worst\":1.71,\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"best\":1,\"worst\":1.5,\"weight\":50},\"weight\":50},\"equal\":{\"worst\":0.0075,\"best\":0.002,\"stateLeg\":{\"worst\":0.1},\"weight\":0},\"weight\":20}}");
|
|
90417
|
-
|
|
90418
91366
|
/***/ }),
|
|
90419
91367
|
|
|
90420
91368
|
/***/ "./src/settings.ts":
|
|
@@ -90429,16 +91377,13 @@ module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\
|
|
|
90429
91377
|
//
|
|
90430
91378
|
// GLOBAL CONSTANTS
|
|
90431
91379
|
//
|
|
90432
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
90433
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
90434
|
-
};
|
|
90435
91380
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90436
91381
|
// Normalized scores [0-100]
|
|
90437
91382
|
exports.NORMALIZED_RANGE = 100;
|
|
90438
91383
|
// Square deviations from the ideal
|
|
90439
91384
|
exports.DISTANCE_WEIGHT = 2;
|
|
90440
91385
|
// Out of range message
|
|
90441
|
-
exports.OUT_OF_RANGE_MSG = "
|
|
91386
|
+
exports.OUT_OF_RANGE_MSG = "%f out of range";
|
|
90442
91387
|
// A small delta to use when testing ranges of values
|
|
90443
91388
|
exports.EPSILON = 1 / Math.pow(10, 6);
|
|
90444
91389
|
// Keep 4 decimals for fractions [0.0–1.0], i.e., 2 for %'s [0–100]
|
|
@@ -90446,62 +91391,6 @@ exports.PRECISION = 4;
|
|
|
90446
91391
|
// "Roughly equal" = average census block size / 2
|
|
90447
91392
|
exports.AVERAGE_BLOCK_SIZE = 30;
|
|
90448
91393
|
exports.EQUAL_TOLERANCE = exports.AVERAGE_BLOCK_SIZE / 2;
|
|
90449
|
-
// SCORING PARAMETERS
|
|
90450
|
-
const settings_json_1 = __importDefault(__webpack_require__(/*! ./settings.json */ "./src/settings.json"));
|
|
90451
|
-
// TODO - DELETE
|
|
90452
|
-
function readOverrides() {
|
|
90453
|
-
return undefined;
|
|
90454
|
-
}
|
|
90455
|
-
const overrides = readOverrides();
|
|
90456
|
-
// SETTINGS FOR PARTISAN SCORING
|
|
90457
|
-
exports.MIN_CONTESTED = overrides ? overrides.fair.competitive.min : settings_json_1.default.fair.competitive.min;
|
|
90458
|
-
exports.MAX_CONTESTED = overrides ? overrides.fair.competitive.max : settings_json_1.default.fair.competitive.max;
|
|
90459
|
-
// SCALES FOR NORMALIZING RAW VALUES
|
|
90460
|
-
// Compactness scales
|
|
90461
|
-
exports.REOCK_WORST = overrides ? overrides.best.compact.reock.worst : settings_json_1.default.best.compact.reock.worst;
|
|
90462
|
-
exports.REOCK_BEST = overrides ? overrides.best.compact.reock.best : settings_json_1.default.best.compact.reock.best;
|
|
90463
|
-
exports.POLSBY_WORST = overrides ? overrides.best.compact.polsby.worst : settings_json_1.default.best.compact.polsby.worst;
|
|
90464
|
-
exports.POLSBY_BEST = overrides ? overrides.best.compact.polsby.best : settings_json_1.default.best.compact.polsby.best;
|
|
90465
|
-
// County-District splitting scales (not inverted)
|
|
90466
|
-
exports.COUNTY_BEST = overrides ? overrides.best.cohesive.county.best : settings_json_1.default.best.cohesive.county.best;
|
|
90467
|
-
exports.COUNTY_WORST = overrides ? overrides.best.cohesive.county.worst : settings_json_1.default.best.cohesive.county.worst;
|
|
90468
|
-
exports.ALLOWABLE_SPLITS_MULTIPLIER = overrides ? overrides.best.cohesive.county.allowableSplitsMultiplier : settings_json_1.default.best.cohesive.county.allowableSplitsMultiplier;
|
|
90469
|
-
exports.DISTRICT_BEST = overrides ? overrides.best.cohesive.district.best : settings_json_1.default.best.cohesive.district.best;
|
|
90470
|
-
exports.DISTRICT_WORST = overrides ? overrides.best.cohesive.district.worst : settings_json_1.default.best.cohesive.district.worst;
|
|
90471
|
-
// Population deviation thresholds & scales (inverted)
|
|
90472
|
-
exports.POPDEV_WORST = overrides ? overrides.best.equal.worst : settings_json_1.default.best.equal.worst;
|
|
90473
|
-
exports.POPDEV_BEST = overrides ? overrides.best.equal.best : settings_json_1.default.best.equal.best;
|
|
90474
|
-
exports.POPEQ_MIN = 1.0 - exports.POPDEV_WORST;
|
|
90475
|
-
exports.POPEQ_MAX = 1.0 - exports.POPDEV_BEST;
|
|
90476
|
-
exports.POPDEV_WORST_LD = overrides ? overrides.best.equal.stateLeg.worst : settings_json_1.default.best.equal.stateLeg.worst;
|
|
90477
|
-
exports.POPDEV_BEST_LD = ((exports.POPDEV_BEST / exports.POPDEV_WORST) * exports.POPDEV_WORST_LD);
|
|
90478
|
-
exports.POPEQ_MIN_LD = 1.0 - exports.POPDEV_WORST_LD;
|
|
90479
|
-
exports.POPEQ_MAX_LD = 1.0 - exports.POPDEV_BEST_LD;
|
|
90480
|
-
// Bias & competitiveness scales
|
|
90481
|
-
// TODO - SCORE:
|
|
90482
|
-
// * How wide a range do we want for bias? 10%? 20%?
|
|
90483
|
-
// * Do we want it to be state-specific? E.g., one seat?
|
|
90484
|
-
exports.BIAS_WORST = overrides ? overrides.fair.unbiased.worst : settings_json_1.default.fair.unbiased.worst;
|
|
90485
|
-
exports.BIAS_BEST = overrides ? overrides.fair.unbiased.best : settings_json_1.default.fair.unbiased.best;
|
|
90486
|
-
// The range for raw overall competitiveness values
|
|
90487
|
-
exports.COMPETITIVE_WORST = overrides ? overrides.fair.competitive.worst : settings_json_1.default.fair.competitive.worst;
|
|
90488
|
-
exports.COMPETITIVE_BEST = overrides ? overrides.fair.competitive.best : settings_json_1.default.fair.competitive.best;
|
|
90489
|
-
// WEIGHTS FOR COMBINING NORMALIZED VALUES
|
|
90490
|
-
exports.REOCK_WEIGHT = overrides ? overrides.best.compact.reock.weight : settings_json_1.default.best.compact.reock.weight;
|
|
90491
|
-
exports.POLSBY_WEIGHT = overrides ? overrides.best.compact.polsby.weight : settings_json_1.default.best.compact.polsby.weight;
|
|
90492
|
-
exports.COUNTY_WEIGHT = overrides ? overrides.best.cohesive.county.weight : settings_json_1.default.best.cohesive.county.weight;
|
|
90493
|
-
exports.DISTRICT_WEIGHT = overrides ? overrides.best.cohesive.district.weight : settings_json_1.default.best.cohesive.district.weight;
|
|
90494
|
-
exports.COMPACTNESS_WEIGHT = overrides ? overrides.best.compact.weight : settings_json_1.default.best.compact.weight;
|
|
90495
|
-
exports.SPLITTING_WEIGHT = overrides ? overrides.best.cohesive.weight : settings_json_1.default.best.cohesive.weight;
|
|
90496
|
-
exports.POPDEV_WEIGHT = overrides ? overrides.best.equal.weight : settings_json_1.default.best.equal.weight;
|
|
90497
|
-
// TODO - SCORE: What kind of relative weighting do we want between marginal and
|
|
90498
|
-
// overall competitiveness?
|
|
90499
|
-
exports.MARGINAL_WEIGHT = overrides ? overrides.fair.competitive.marginal : settings_json_1.default.fair.competitive.marginal;
|
|
90500
|
-
exports.OVERALL_WEIGHT = overrides ? overrides.fair.competitive.overall : settings_json_1.default.fair.competitive.overall;
|
|
90501
|
-
exports.BIAS_WEIGHT = overrides ? overrides.fair.unbiased.weight : settings_json_1.default.fair.unbiased.weight;
|
|
90502
|
-
exports.COMPETITIVE_WEIGHT = overrides ? overrides.fair.competitive.weight : settings_json_1.default.fair.competitive.weight;
|
|
90503
|
-
exports.FAIR_WEIGHT = overrides ? overrides.fair.weight : settings_json_1.default.fair.weight;
|
|
90504
|
-
exports.BEST_WEIGHT = overrides ? overrides.best.weight : settings_json_1.default.best.weight;
|
|
90505
91394
|
|
|
90506
91395
|
|
|
90507
91396
|
/***/ }),
|
|
@@ -90522,10 +91411,70 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
90522
91411
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
90523
91412
|
};
|
|
90524
91413
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
90525
|
-
const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/
|
|
91414
|
+
const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-profile.json */ "./testdata/samples/sample-profile.json"));
|
|
90526
91415
|
exports.sampleProfile = sample_profile_json_1.default;
|
|
90527
|
-
const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/
|
|
91416
|
+
const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-scorecard.json */ "./testdata/samples/sample-scorecard.json"));
|
|
90528
91417
|
exports.sampleScorecard = sample_scorecard_json_1.default;
|
|
91418
|
+
exports.Metrics = {
|
|
91419
|
+
// PARTISAN
|
|
91420
|
+
// Bias metrics
|
|
91421
|
+
Vf: { label: "Statewide D vote share", abbr: "V" },
|
|
91422
|
+
estS: { label: "Probable D seats", abbr: "S_V" },
|
|
91423
|
+
estSf: { label: "Probable D seat share", units: 1 /* Percentage */, abbr: "S%" },
|
|
91424
|
+
propS: { label: "D seats closest to proportional", abbr: "^S" },
|
|
91425
|
+
propSf: { label: "D seat share closest to proportional", units: 1 /* Percentage */, abbr: "^S%" },
|
|
91426
|
+
fptpS: { label: "Probable FPTP D seats", abbr: "S!" },
|
|
91427
|
+
bias: { label: "Effective bias", units: 1 /* Percentage */, abbr: "B%", description: "Calculated as S% – ^S%" },
|
|
91428
|
+
biasScore: { label: "Bias score", abbr: "B$", description: "Bias score normalized [0–100]" },
|
|
91429
|
+
// Additional bias metrics
|
|
91430
|
+
s50v: { label: "Seats bias", units: 1 /* Percentage */, abbr: "BS_50", symbol: "\u{03B1}" },
|
|
91431
|
+
v50s: { label: "Votes bias", units: 1 /* Percentage */, abbr: "BV_50", symbol: "\u{03B1}2" },
|
|
91432
|
+
decl: { label: "Declination", units: 2 /* Degrees */, abbr: "decl", symbol: "\u{03B4}" },
|
|
91433
|
+
gSym: { label: "Global symmetry", abbr: "GS", symbol: "\u{0393}" },
|
|
91434
|
+
EG: { label: "Efficiency gap", units: 1 /* Percentage */, abbr: "EG", symbol: "\u{03B3}2" },
|
|
91435
|
+
BsGf: { label: "Partisan bias", units: 1 /* Percentage */, abbr: "BS_V", symbol: "\u{03D0}" },
|
|
91436
|
+
prop: { label: "Disproprtionality", units: 1 /* Percentage */, abbr: "prop", symbol: "\u{03B3}" },
|
|
91437
|
+
MM: { label: "Mean–median", abbr: "MM" },
|
|
91438
|
+
LO: { label: "Lopsided outcomes", abbr: "LO" },
|
|
91439
|
+
// Impact metrics
|
|
91440
|
+
unearnedS: { label: "Unearned seats", abbr: "UE" },
|
|
91441
|
+
impactScore: { label: "Impact score", abbr: "I$", description: "Impact score normalized to [0–100]" },
|
|
91442
|
+
// Competitiveness/responsiveness metrics
|
|
91443
|
+
c: { label: "Simple competitive districts", abbr: "C", description: "Count of districts in the range [45–55%]" },
|
|
91444
|
+
cD: { label: "Probable competitive districts", abbr: "Cd", description: "Probable competitive districts, using probabilities" },
|
|
91445
|
+
cDf: { label: "Probable competitive districts share", units: 1 /* Percentage */, abbr: "Cdf" },
|
|
91446
|
+
mD: { label: "Competitive marginal districts", abbr: "Md", description: "Probable competitive marginal districts, using probabilities" },
|
|
91447
|
+
mDf: { label: "Competitive marginal districts share", units: 1 /* Percentage */, abbr: "Mdf" },
|
|
91448
|
+
competitivenessScore: { label: "Competitiveness score", abbr: "C$", description: "Competitiveness score normalized to [0–100]" },
|
|
91449
|
+
// Additional competitiveness/responsiveness metrics
|
|
91450
|
+
rD: { label: "Responsive districts", abbr: "Rd" },
|
|
91451
|
+
rDf: { label: "Responsive districts %", abbr: "Rd%" },
|
|
91452
|
+
bigR: { label: "Overall responsiveness", abbr: "R" /*, symbol: "\u{}" */ },
|
|
91453
|
+
littleR: { label: "Point responsiveness", abbr: "r", symbol: "\u{03C1}" },
|
|
91454
|
+
MIR: { label: "Minimal inverse responsiveness", abbr: "MIR", symbol: "\u{03B6}" },
|
|
91455
|
+
partisanScore: { label: "Partisan score", abbr: "P$" },
|
|
91456
|
+
// * <P$ [score] = the combined partisan score used to compare plans *across* states
|
|
91457
|
+
// * >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
|
|
91458
|
+
// MINORITY
|
|
91459
|
+
oD: { label: "Opportunity districts", abbr: "Od" },
|
|
91460
|
+
minorityBonus: { label: "Minority bonus", abbr: "M$" },
|
|
91461
|
+
// TRADITIONAL DISTRICTING PRINCIPLES
|
|
91462
|
+
reockRaw: { label: "Reock", abbr: "Rc" },
|
|
91463
|
+
reockNormalized: { label: "Reock", abbr: "Rc$", description: "Reock normalized to [0–100]" },
|
|
91464
|
+
polsbyRaw: { label: "Polsby-Popper", abbr: "Pc" },
|
|
91465
|
+
polsbyNormalized: { label: "Polsby-Popper", abbr: "Pc$", description: "Polsby-Popper normalized to [0–100]" },
|
|
91466
|
+
compactnessScore: { label: "Compactness score", abbr: "G$" },
|
|
91467
|
+
countySplittingRaw: { label: "County splitting", abbr: "Cs" },
|
|
91468
|
+
countySplittingNormalized: { label: "County splitting", abbr: "Cs$", description: "County splitting normalized to [0–100]" },
|
|
91469
|
+
districtSplittingRaw: { label: "District splitting", abbr: "Ds" },
|
|
91470
|
+
districtSplittingNormalized: { label: "District splitting", abbr: "Ds$", description: "District splitting normalized to [0–100]" },
|
|
91471
|
+
splittingScore: { label: "Splitting score", abbr: "S$" },
|
|
91472
|
+
populationDeviationRaw: { label: "Population deviation", abbr: "Eq" },
|
|
91473
|
+
populationDeviationNormalized: { label: "County splitting", abbr: "Eq$", description: "Population deviation normalized to [0–100]" },
|
|
91474
|
+
traditionalPrinciplesScore: { label: "Traditional districting principles score", abbr: "T$", description: "Traditional principles score normalized to [0–100]" },
|
|
91475
|
+
// OVERALL SCORE
|
|
91476
|
+
score: { label: "Overall score", abbr: "$$$" }
|
|
91477
|
+
};
|
|
90529
91478
|
|
|
90530
91479
|
|
|
90531
91480
|
/***/ }),
|
|
@@ -90568,11 +91517,31 @@ function maxArray(arr) {
|
|
|
90568
91517
|
return Math.max(...arr);
|
|
90569
91518
|
}
|
|
90570
91519
|
exports.maxArray = maxArray;
|
|
91520
|
+
// Modified from https://jsfiddle.net/Lucky500/3sy5au0c/
|
|
91521
|
+
function medianArray(arr) {
|
|
91522
|
+
if (arr.length === 0)
|
|
91523
|
+
return 0;
|
|
91524
|
+
arr.sort(function (a, b) {
|
|
91525
|
+
return a - b;
|
|
91526
|
+
});
|
|
91527
|
+
var half = Math.floor(arr.length / 2);
|
|
91528
|
+
if (arr.length % 2)
|
|
91529
|
+
return arr[half];
|
|
91530
|
+
return (arr[half - 1] + arr[half]) / 2.0;
|
|
91531
|
+
}
|
|
91532
|
+
exports.medianArray = medianArray;
|
|
90571
91533
|
function initArray(n, value) {
|
|
90572
91534
|
return Array.from(Array(n), () => value);
|
|
90573
91535
|
}
|
|
90574
91536
|
exports.initArray = initArray;
|
|
90575
91537
|
// MISCELLANEOUS
|
|
91538
|
+
// Deal with decimal census "counts" due to disagg/re-agg
|
|
91539
|
+
function areRoughlyEqual(x, y, tolerance) {
|
|
91540
|
+
let delta = Math.abs(x - y);
|
|
91541
|
+
let result = (delta < tolerance) ? true : false;
|
|
91542
|
+
return result;
|
|
91543
|
+
}
|
|
91544
|
+
exports.areRoughlyEqual = areRoughlyEqual;
|
|
90576
91545
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
90577
91546
|
function trim(fullFraction, digits = undefined) {
|
|
90578
91547
|
if (digits == 0) {
|
|
@@ -90606,25 +91575,25 @@ exports.deepCopy = deepCopy;
|
|
|
90606
91575
|
|
|
90607
91576
|
/***/ }),
|
|
90608
91577
|
|
|
90609
|
-
/***/ "./testdata/
|
|
90610
|
-
|
|
90611
|
-
!*** ./testdata/
|
|
90612
|
-
|
|
91578
|
+
/***/ "./testdata/samples/sample-profile.json":
|
|
91579
|
+
/*!**********************************************!*\
|
|
91580
|
+
!*** ./testdata/samples/sample-profile.json ***!
|
|
91581
|
+
\**********************************************/
|
|
90613
91582
|
/*! exports provided: state, planName, nDistricts, nCounties, legislativeDistricts, populationProfile, compactnessProfile, splittingProfile, partisanProfile, demographicProfile, default */
|
|
90614
91583
|
/***/ (function(module) {
|
|
90615
91584
|
|
|
90616
|
-
module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"
|
|
91585
|
+
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]]}}");
|
|
90617
91586
|
|
|
90618
91587
|
/***/ }),
|
|
90619
91588
|
|
|
90620
|
-
/***/ "./testdata/
|
|
90621
|
-
|
|
90622
|
-
!*** ./testdata/
|
|
90623
|
-
|
|
90624
|
-
/*! exports provided: score,
|
|
91589
|
+
/***/ "./testdata/samples/sample-scorecard.json":
|
|
91590
|
+
/*!************************************************!*\
|
|
91591
|
+
!*** ./testdata/samples/sample-scorecard.json ***!
|
|
91592
|
+
\************************************************/
|
|
91593
|
+
/*! exports provided: score, partisan, minority, traditionalPrinciples, default */
|
|
90625
91594
|
/***/ (function(module) {
|
|
90626
91595
|
|
|
90627
|
-
module.exports = JSON.parse("{\"score\":5,\"
|
|
91596
|
+
module.exports = JSON.parse("{\"score\":5,\"partisan\":{\"bias\":{\"propS\":7,\"propSf\":0.5385,\"estS\":4.1925,\"estSf\":0.3225,\"bias\":0.216,\"tOf\":-0.0023,\"fptpS\":3,\"s50V\":0.2172,\"v50S\":0.045,\"decl\":36.5164,\"gSym\":6.6602,\"eG\":0.2846,\"sVg\":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,\"c\":6,\"cD\":0.7266,\"cDf\":0.0559,\"mRange\":[5,11],\"mD\":0.7134,\"mDf\":0.1019,\"score\":10},\"score\":3,\"score2\":2},\"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},\"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}}}}");
|
|
90628
91597
|
|
|
90629
91598
|
/***/ })
|
|
90630
91599
|
|
|
@@ -100410,6 +101379,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
100410
101379
|
return result;
|
|
100411
101380
|
};
|
|
100412
101381
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
101382
|
+
const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js"));
|
|
100413
101383
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
100414
101384
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
100415
101385
|
const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
|
|
@@ -100603,9 +101573,13 @@ class AnalyticsSession {
|
|
|
100603
101573
|
return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
|
|
100604
101574
|
}
|
|
100605
101575
|
else {
|
|
101576
|
+
// NOTE - This assumes the plan has been profiled
|
|
101577
|
+
const scorer = new Score.Scorer();
|
|
101578
|
+
const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
|
|
101579
|
+
return popdev;
|
|
100606
101580
|
// TODO - SCORE: Temporary HACK. Query dra-score for threshold.
|
|
100607
101581
|
// NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
|
|
100608
|
-
return 1 - this.testScales[
|
|
101582
|
+
// return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
|
|
100609
101583
|
}
|
|
100610
101584
|
}
|
|
100611
101585
|
}
|
|
@@ -102815,7 +103789,9 @@ function doAnalyzePostProcessing(s, bLog = false) {
|
|
|
102815
103789
|
// Just populate the normalized population deviation score in the test
|
|
102816
103790
|
const scorecard = s._scorecard;
|
|
102817
103791
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
102818
|
-
test['normalizedScore'] = scorecard.
|
|
103792
|
+
test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
|
|
103793
|
+
// TODO - DELETE
|
|
103794
|
+
// test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
|
|
102819
103795
|
}
|
|
102820
103796
|
// Derive secondary tests
|
|
102821
103797
|
analyze_1.doDeriveSecondaryTests(s, bLog);
|
|
@@ -102861,9 +103837,10 @@ function profilePlan(s, bLog = false) {
|
|
|
102861
103837
|
const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
|
|
102862
103838
|
const splits = makeNakedCxD(s);
|
|
102863
103839
|
const summaryRow = s.districts.numberOfRows() - 1;
|
|
102864
|
-
const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
|
|
103840
|
+
const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
|
|
102865
103841
|
const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
|
|
102866
|
-
const
|
|
103842
|
+
const statewideDemographics = getStatewideDemographics(s);
|
|
103843
|
+
const demographicsByDistrict = getDemographicsByDistrict(s);
|
|
102867
103844
|
const profile = {
|
|
102868
103845
|
state: state,
|
|
102869
103846
|
planName: planName,
|
|
@@ -102871,21 +103848,22 @@ function profilePlan(s, bLog = false) {
|
|
|
102871
103848
|
nCounties: nCounties,
|
|
102872
103849
|
legislativeDistricts: s.legislativeDistricts,
|
|
102873
103850
|
populationProfile: {
|
|
102874
|
-
|
|
103851
|
+
totalPopByDistrict: popByDistrict,
|
|
102875
103852
|
targetSize: targetSize
|
|
102876
103853
|
},
|
|
102877
103854
|
compactnessProfile: {
|
|
102878
|
-
|
|
103855
|
+
geometryByDistrict: geoPropsByDistrict
|
|
102879
103856
|
},
|
|
102880
103857
|
splittingProfile: {
|
|
102881
|
-
|
|
103858
|
+
countyPopByDistrict: splits
|
|
102882
103859
|
},
|
|
102883
103860
|
partisanProfile: {
|
|
102884
103861
|
statewideVf: statewideVf,
|
|
102885
|
-
|
|
103862
|
+
vfArray: vpiArray
|
|
102886
103863
|
},
|
|
102887
103864
|
demographicProfile: {
|
|
102888
|
-
|
|
103865
|
+
stateMfArray: statewideDemographics,
|
|
103866
|
+
mfArrayByDistrict: demographicsByDistrict
|
|
102889
103867
|
}
|
|
102890
103868
|
};
|
|
102891
103869
|
return profile;
|
|
@@ -102919,7 +103897,7 @@ function makeArrayOfGeoProps(s, bLog = false) {
|
|
|
102919
103897
|
}
|
|
102920
103898
|
return geometryByDistrict;
|
|
102921
103899
|
}
|
|
102922
|
-
function
|
|
103900
|
+
function getDemographicsByDistrict(s, bLog = false) {
|
|
102923
103901
|
let demographicsArray = [];
|
|
102924
103902
|
// Remove the unassigned & total dummy "districts"
|
|
102925
103903
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
@@ -102936,6 +103914,19 @@ function makeArrayOfDemographics(s, bLog = false) {
|
|
|
102936
103914
|
}
|
|
102937
103915
|
return demographicsArray;
|
|
102938
103916
|
}
|
|
103917
|
+
function getStatewideDemographics(s, bLog = false) {
|
|
103918
|
+
const summaryRow = s.districts.numberOfRows() - 1;
|
|
103919
|
+
const demographicsArray = [
|
|
103920
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.WhitePct][summaryRow]),
|
|
103921
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.MinorityPct][summaryRow]),
|
|
103922
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.BlackPct][summaryRow]),
|
|
103923
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.HispanicPct][summaryRow]),
|
|
103924
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.PacificPct][summaryRow]),
|
|
103925
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.AsianPct][summaryRow]),
|
|
103926
|
+
U.deepCopy(s.districts.statistics[D.DistrictField.NativePct][summaryRow])
|
|
103927
|
+
];
|
|
103928
|
+
return demographicsArray;
|
|
103929
|
+
}
|
|
102939
103930
|
// SCORE A PLAN
|
|
102940
103931
|
function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
102941
103932
|
let scorer = new Score.Scorer();
|
|
@@ -102945,11 +103936,14 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
|
102945
103936
|
// calling sequence.
|
|
102946
103937
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
102947
103938
|
// TODO - SCORE: U.trim(popDev)???
|
|
103939
|
+
// const popDev = scorecard.best.populationDeviation.raw;
|
|
102948
103940
|
// Get the raw population deviation
|
|
102949
|
-
const popDev = scorecard.
|
|
103941
|
+
const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
|
|
102950
103942
|
// Populate the test entry
|
|
102951
103943
|
test['score'] = popDev;
|
|
102952
|
-
test['details'] = { 'maxDeviation': scorecard.
|
|
103944
|
+
test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
|
|
103945
|
+
// TODO - DELETE
|
|
103946
|
+
// test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
|
|
102953
103947
|
// Populate the N+1 summary "district" in district.statistics
|
|
102954
103948
|
let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
|
|
102955
103949
|
let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
|