@dra2020/district-analytics 3.3.0 → 4.2.0

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