@dra2020/district-analytics 3.3.0 → 4.0.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
@@ -89797,10 +89797,10 @@ exports.scorePolsbyPopper = scorePolsbyPopper;
89797
89797
  /*!*************************!*\
89798
89798
  !*** ./src/config.json ***!
89799
89799
  \*************************/
89800
- /*! exports provided: partisan, traditionalPrinciples, default */
89800
+ /*! exports provided: partisan, minority, traditionalPrinciples, default */
89801
89801
  /***/ (function(module) {
89802
89802
 
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]}}");
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]},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":0.12,\"bonus\":20},\"traditionalPrinciples\":{\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50},\"weight\":[0,50]},\"splitting\":{\"county\":{\"range\":[1,1.71],\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"range\":[1,1.5],\"weight\":50},\"weight\":[0,50]},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]],\"weight\":[0,0]},\"weight\":[0,20]}}");
89804
89804
 
89805
89805
  /***/ }),
89806
89806
 
@@ -89883,6 +89883,32 @@ function overallCompetitivenessWeight(overridesJSON) {
89883
89883
  return ocW;
89884
89884
  }
89885
89885
  exports.overallCompetitivenessWeight = overallCompetitivenessWeight;
89886
+ // MINORITY
89887
+ function minorityOpportunityRange(overridesJSON) {
89888
+ const range = config_json_1.default.minority.range;
89889
+ return range;
89890
+ }
89891
+ exports.minorityOpportunityRange = minorityOpportunityRange;
89892
+ function minorityOpportunityDistribution(overridesJSON) {
89893
+ const dist = config_json_1.default.minority.distribution;
89894
+ return dist;
89895
+ }
89896
+ exports.minorityOpportunityDistribution = minorityOpportunityDistribution;
89897
+ function minorityShift(overridesJSON) {
89898
+ const shift = config_json_1.default.minority.shift;
89899
+ return shift;
89900
+ }
89901
+ exports.minorityShift = minorityShift;
89902
+ function opportunityDistrictBonus(overridesJSON) {
89903
+ const bonus = config_json_1.default.minority.bonus;
89904
+ return bonus;
89905
+ }
89906
+ exports.opportunityDistrictBonus = opportunityDistrictBonus;
89907
+ function minorityBonus(overridesJSON) {
89908
+ const bonus = config_json_1.default.minority.bonus;
89909
+ return bonus;
89910
+ }
89911
+ exports.minorityBonus = minorityBonus;
89886
89912
  // TRADITIONAL DISTRICTING PRINCIPLES
89887
89913
  function compactnessWeight(context, overridesJSON) {
89888
89914
  const cW = config_json_1.default.traditionalPrinciples.compactness.weight[context];
@@ -90068,8 +90094,136 @@ function __export(m) {
90068
90094
  Object.defineProperty(exports, "__esModule", { value: true });
90069
90095
  __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
90070
90096
  var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
90071
- exports.sampleProfile = types_1.sampleProfile;
90072
90097
  exports.sampleScorecard = types_1.sampleScorecard;
90098
+ exports.sampleProfile = types_1.sampleProfile;
90099
+
90100
+
90101
+ /***/ }),
90102
+
90103
+ /***/ "./src/minority.ts":
90104
+ /*!*************************!*\
90105
+ !*** ./src/minority.ts ***!
90106
+ \*************************/
90107
+ /*! no static exports found */
90108
+ /***/ (function(module, exports, __webpack_require__) {
90109
+
90110
+ "use strict";
90111
+
90112
+ //
90113
+ // SCORING FOR MINORITY OPPORTUNITY
90114
+ //
90115
+ var __importStar = (this && this.__importStar) || function (mod) {
90116
+ if (mod && mod.__esModule) return mod;
90117
+ var result = {};
90118
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
90119
+ result["default"] = mod;
90120
+ return result;
90121
+ };
90122
+ Object.defineProperty(exports, "__esModule", { value: true });
90123
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
90124
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
90125
+ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
90126
+ const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
90127
+ function evalMinorityOpportunity(p, bLog = false) {
90128
+ // Calculate average Democratic win share
90129
+ const VfArray = p.partisanProfile.vfArray;
90130
+ const DWins = VfArray.filter(x => x > 0.5);
90131
+ const averageDVf = (DWins.length > 0) ? U.avgArray(DWins) : undefined;
90132
+ // Determine proportional minority districts by demographic (ignore 'White')
90133
+ const districtsByDemo = calcDistrictsByDemo(p.demographicProfile.stateMfArray.slice(1), p.nDistricts);
90134
+ // Initialize arrays for results
90135
+ let offset = 1; // Don't process 'White'
90136
+ let nDemos = 6 /* Native */ + 1 - offset; // Ditto
90137
+ let nBuckets = 2; // 37–50% and > 50%
90138
+ const demosByDistrict = p.demographicProfile.mfArrayByDistrict;
90139
+ let bucketsByDemo = new Array(nDemos);
90140
+ for (let j = 0; j < nDemos; j++) {
90141
+ bucketsByDemo[j] = [0, 0];
90142
+ }
90143
+ let opptyByDemo = U.initArray(nDemos, 0.0);
90144
+ // For each district
90145
+ for (let i = 0; i < p.nDistricts; i++) {
90146
+ // Find the opportunities for minority representation
90147
+ for (let j = 0; j < nDemos; j++) {
90148
+ const proportion = U.deepCopy(demosByDistrict[i][j + offset]);
90149
+ if (exceedsMinimumThreshold(proportion)) {
90150
+ // Bucket opportunity districts
90151
+ const bucket = (exceedsMaximumThreshold(proportion)) ? 1 : 0;
90152
+ bucketsByDemo[j][bucket] += 1;
90153
+ // Accumulate seat probabilities
90154
+ opptyByDemo[j] += estMinorityOpportunity(proportion);
90155
+ }
90156
+ }
90157
+ }
90158
+ // Sum the # of level 1 (37–50%) and level 2 (> 50%) opportunity districts - ignore total minority
90159
+ let l1 = 0;
90160
+ let l2 = 0;
90161
+ bucketsByDemo.slice(1).forEach(function (buckets) {
90162
+ l1 += buckets[0 /* Level1 */];
90163
+ l2 += buckets[1 /* Level2 */];
90164
+ });
90165
+ // Sum the # of opportunity districts - ignore total minority
90166
+ const oD = U.sumArray(opptyByDemo.slice(1));
90167
+ // Sum the # of proportion districts - ignore total minority
90168
+ const pD = U.sumArray(districtsByDemo.slice(1));
90169
+ // Score opportunity
90170
+ const score = scoreMinority(oD, pD);
90171
+ let mS = {
90172
+ report: {
90173
+ averageDVf: averageDVf,
90174
+ bucketsByDemographic: bucketsByDemo
90175
+ },
90176
+ nOpportunity1: l1,
90177
+ nOpportunity2: l2,
90178
+ nProportional: pD,
90179
+ opportunityDistricts: oD,
90180
+ score: score
90181
+ };
90182
+ return mS;
90183
+ }
90184
+ exports.evalMinorityOpportunity = evalMinorityOpportunity;
90185
+ function scoreMinority(oD, pD) {
90186
+ const bonus = C.minorityBonus();
90187
+ const score = (pD > 0) ? Math.round((Math.min(oD, pD) / pD) * bonus) : 0;
90188
+ return score;
90189
+ }
90190
+ exports.scoreMinority = scoreMinority;
90191
+ function calcDistrictsByDemo(MfArray, N) {
90192
+ const districtsByDemo = MfArray.map(v => calcProportionalDistricts(v, N));
90193
+ return districtsByDemo;
90194
+ }
90195
+ exports.calcDistrictsByDemo = calcDistrictsByDemo;
90196
+ function estMinorityOpportunity(Mf) {
90197
+ const _normalizer = new normalize_1.Normalizer(Mf);
90198
+ // NOTE - Shift proportions, so 37% minority scores like 49% partisan
90199
+ const shift = C.minorityShift();
90200
+ _normalizer.wipNum += shift;
90201
+ const range = C.minorityOpportunityDistribution();
90202
+ const distBeg = range[C.BEG];
90203
+ const distEnd = range[C.END];
90204
+ _normalizer.clip(distBeg, distEnd);
90205
+ _normalizer.unitize(distBeg, distEnd);
90206
+ const oppty = partisan_1.estSeatProbability(_normalizer.wipNum, range);
90207
+ return oppty;
90208
+ }
90209
+ exports.estMinorityOpportunity = estMinorityOpportunity;
90210
+ // HELPERS
90211
+ function exceedsMinimumThreshold(Mf) {
90212
+ const threshold = C.minorityOpportunityRange()[C.BEG];
90213
+ return Mf >= threshold;
90214
+ }
90215
+ function exceedsMaximumThreshold(Mf) {
90216
+ const threshold = C.minorityOpportunityRange()[C.END];
90217
+ return Mf > threshold;
90218
+ }
90219
+ function calcProportionalDistricts(proportion, nDistricts) {
90220
+ // TODO - Bump up to get a statewide proportion on the bubble to rate one district?
90221
+ const roundUp = 0.0;
90222
+ const fractional = proportion * nDistricts;
90223
+ const integral = Math.round(fractional + roundUp);
90224
+ return integral;
90225
+ }
90226
+ exports.calcProportionalDistricts = calcProportionalDistricts;
90073
90227
 
90074
90228
 
90075
90229
  /***/ }),
@@ -90196,85 +90350,88 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
90196
90350
  // NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
90197
90351
  // NOTE - I do not (cannot) assume that the values are sorted.
90198
90352
  // 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]
90353
+ /* SCORECARD FIELDS
90354
+
90355
+ * ^S# [bestS] = the Democratic seats closest to proportional
90356
+ * ^S% [bestSf] = the corresponding Democratic seat share
90357
+ * S! [fptpS] = the estimated number of Democratic seats using first past the post
90358
+ * S# [probableS] = the estimated Democratic seats, using seat probabilities
90359
+ * S% [probableSf] = the estimated Democratic seat share fraction, calculated as S# / N
90360
+ * B% [bias]= the bias calculated as S% ^S%
90361
+ * B$ [bias.score] = the bias score normalized [0100]
90362
+
90363
+ * UE# [unearnedS] = the number of unearned seats (R = positive; D = negative)
90364
+ * I$ [impact.score] -- the unearned seats normalized [0–100] as impact
90365
+
90366
+ * R# [r] = the responsiveness at V%
90367
+ * rD [rD] = the estimated # of responsive districts (using probabilities)
90368
+ * rD% [rDf] = the estimated # of responsive districts, as a fraction of N
90369
+
90370
+ * C [c] = the number of districts that fall into the range [45–55%]
90371
+ * cD [cD] = the estimated # of competitive districts, using probabilities & a narrower [0.25–0.75] range
90372
+ * cD% [cDf] = the estimated # of competitive districts, as a fraction of N
90373
+ * beg/end [mRange] = the 1–N indices for the first and last district that separate the likely result and the proportional result
90374
+ * Md [mD] = the competitiveness of the marginal districts
90375
+ * Md% [mDf] = the probability that the marginal seats will flip, so S% => ^S%
90376
+ * C$ [competitiveness.score] = the competitiveness score normalized [0–100]
90377
+
90378
+ * <P$ [score] = the combined partisan score used to compare plans *across* states
90379
+ * >P$ [score2] = the combined partisan score used to compare plans *within* a state, along with traditional districting principles
90223
90380
 
90224
90381
  */
90225
90382
  function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
90226
90383
  const N = VfArray.length;
90227
- const BestS = bestSeats(N, Vf);
90228
- const BestSf = bestSeatShare(BestS, N);
90229
- const FptpS = bAll ? estFPTPSeats(VfArray) : undefined;
90384
+ const bestS = bestSeats(N, Vf);
90385
+ const bestSf = bestSeatShare(bestS, N);
90386
+ const fptpS = bAll ? estFPTPSeats(VfArray) : undefined;
90230
90387
  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);
90388
+ const probableS = estprobableSeats(VfArray, range);
90389
+ const probableSf = estprobableSeatShare(probableS, N);
90390
+ const bias = estbias(probableSf, bestSf);
90391
+ const biasScore = scorebias(bias, Vf, probableSf, N);
90392
+ const unearnedS = estunearnedSeats(bestS, probableS);
90393
+ const impactScore = scoreImpact(unearnedS);
90237
90394
  const bInferSV = (bAll || (!bConstrained));
90238
90395
  const inferredSVpoints = bInferSV ? inferSVpoints(Vf, VfArray, range) : undefined;
90239
90396
  const R = bInferSV ? estResponsiveness(Vf, inferredSVpoints) : undefined;
90240
- const Rd = bInferSV ? estResponsiveDistricts(VfArray) : undefined;
90241
- const Rdf = bInferSV ? estResponsiveDistrictsShare(Rd, N) : undefined;
90397
+ const rD = bInferSV ? estResponsiveDistricts(VfArray) : undefined;
90398
+ const rDf = bInferSV ? estResponsiveDistrictsShare(rD, N) : undefined;
90242
90399
  const Cn = countCompetitiveDistricts(VfArray);
90243
- const Cd = bConstrained ? estCompetitiveDistricts(VfArray) : Rd;
90244
- const Cdf = estCompetitiveDistrictsShare(Cd, N);
90400
+ const cD = bConstrained ? estCompetitiveDistricts(VfArray) : rD;
90401
+ const cDf = estCompetitiveDistrictsShare(cD, N);
90245
90402
  const Mrange = findMarginalDistricts(Vf, VfArray, N);
90246
90403
  const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
90247
90404
  const Mdf = estMarginalCompetitiveShare(Md, Mrange);
90248
- const competitivenessScore = scoreCompetitiveness(Mdf, Cdf);
90405
+ const competitivenessScore = scoreCompetitiveness(Mdf, cDf);
90249
90406
  const biasScoring = {
90250
- BestS: BestS,
90251
- BestSf: BestSf,
90252
- FptpS: FptpS,
90253
- ProbableS: ProbableS,
90254
- ProbableSf: ProbableSf,
90255
- Bias: Bias,
90407
+ bestS: bestS,
90408
+ bestSf: bestSf,
90409
+ fptpS: fptpS,
90410
+ probableS: probableS,
90411
+ probableSf: probableSf,
90412
+ bias: bias,
90256
90413
  score: biasScore
90257
90414
  };
90258
90415
  const impactScoring = {
90259
- UnearnedS: UnearnedS,
90416
+ unearnedS: unearnedS,
90260
90417
  score: impactScore
90261
90418
  };
90262
90419
  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,
90420
+ r: R,
90421
+ rD: rD,
90422
+ rDf: rDf,
90423
+ c: Cn,
90424
+ cD: cD,
90425
+ cDf: cDf,
90426
+ mRange: Mrange,
90427
+ mD: Md,
90428
+ mDf: Mdf,
90272
90429
  score: competitivenessScore
90273
90430
  };
90274
90431
  if (bAll) {
90275
- competitiveScoring.R = R;
90276
- competitiveScoring.Rd = Rd;
90277
- competitiveScoring.Rdf = Rdf;
90432
+ competitiveScoring.r = R;
90433
+ competitiveScoring.rD = rD;
90434
+ competitiveScoring.rDf = rDf;
90278
90435
  }
90279
90436
  // Weight bias, impact, and competitiveness into partisan scores to compare
90280
90437
  // plans across states and within a state.
@@ -90306,15 +90463,15 @@ function printPartisanScorecardHeader() {
90306
90463
  }
90307
90464
  exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
90308
90465
  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);
90466
+ 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);
90310
90467
  }
90311
90468
  exports.printPartisanScorecardRow = printPartisanScorecardRow;
90312
- function scoreBias(rawBias, Vf, Sf, N) {
90469
+ function scorebias(rawbias, Vf, Sf, N) {
90313
90470
  if (isAntimajoritarian(Vf, Sf)) {
90314
90471
  return 0;
90315
90472
  }
90316
90473
  else {
90317
- const _normalizer = new normalize_1.Normalizer(rawBias);
90474
+ const _normalizer = new normalize_1.Normalizer(rawbias);
90318
90475
  const worst = C.biasRange()[C.BEG];
90319
90476
  const best = C.biasRange()[C.END];
90320
90477
  _normalizer.clip(worst, best);
@@ -90326,7 +90483,7 @@ function scoreBias(rawBias, Vf, Sf, N) {
90326
90483
  return score;
90327
90484
  }
90328
90485
  }
90329
- exports.scoreBias = scoreBias;
90486
+ exports.scorebias = scorebias;
90330
90487
  // Normalize unearned seats
90331
90488
  function scoreImpact(rawUE) {
90332
90489
  const _normalizer = new normalize_1.Normalizer(rawUE);
@@ -90412,7 +90569,7 @@ function inferSVpoints(Vf, VfArray, range) {
90412
90569
  let SVpoints = [];
90413
90570
  for (let shiftedVf of shiftRange()) {
90414
90571
  const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
90415
- const shiftedSf = estProbableSeats(shiftedVPI, range) / nDistricts;
90572
+ const shiftedSf = estprobableSeats(shiftedVPI, range) / nDistricts;
90416
90573
  SVpoints.push({ v: shiftedVf, s: shiftedSf });
90417
90574
  }
90418
90575
  return SVpoints;
@@ -90461,16 +90618,16 @@ function bestSeatShare(bestS, N) {
90461
90618
  }
90462
90619
  exports.bestSeatShare = bestSeatShare;
90463
90620
  // S# - The estimated # of Democratic seats @ statewide Vf, using seat probabilities
90464
- function estProbableSeats(VfArray, range) {
90621
+ function estprobableSeats(VfArray, range) {
90465
90622
  // Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
90466
90623
  return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
90467
90624
  }
90468
- exports.estProbableSeats = estProbableSeats;
90625
+ exports.estprobableSeats = estprobableSeats;
90469
90626
  // S% - The estimated Democratic seat share fraction @ statewide Vf
90470
- function estProbableSeatShare(probableS, N) {
90627
+ function estprobableSeatShare(probableS, N) {
90471
90628
  return U.trim(probableS / N);
90472
90629
  }
90473
- exports.estProbableSeatShare = estProbableSeatShare;
90630
+ exports.estprobableSeatShare = estprobableSeatShare;
90474
90631
  // F# - The estimated number of Democratic seats using first past the post
90475
90632
  function estFPTPSeats(VfArray) {
90476
90633
  // Python: sum([1.0 for vpi in vpi_by_district if (vpi > 0.5)])
@@ -90485,17 +90642,17 @@ function estFPTPSeats(VfArray) {
90485
90642
  }
90486
90643
  exports.estFPTPSeats = estFPTPSeats;
90487
90644
  // B% - Tthe bias calculated as S% – ^S%
90488
- function estBias(estSeatShare, bestSeatShare) {
90645
+ function estbias(estSeatShare, bestSeatShare) {
90489
90646
  return U.trim(Math.abs(estSeatShare - bestSeatShare));
90490
90647
  }
90491
- exports.estBias = estBias;
90648
+ exports.estbias = estbias;
90492
90649
  // UE# - The estimated # of unearned seats
90493
90650
  // UE_# from http://bit.ly/2Fcuf4q
90494
- function estUnearnedSeats(best, probable) {
90651
+ function estunearnedSeats(best, probable) {
90495
90652
  // NOTE - + values = unearned R seats; – values = unearned D seats
90496
90653
  return U.trim(best - probable);
90497
90654
  }
90498
- exports.estUnearnedSeats = estUnearnedSeats;
90655
+ exports.estunearnedSeats = estunearnedSeats;
90499
90656
  // ESTIMATE RESPONSIVENESS ("COMPETITIVE")
90500
90657
  // R# - Estimate responsiveness at the statewide vote share
90501
90658
  function estResponsiveness(Vf, inferredSVpoints) {
@@ -90538,15 +90695,15 @@ function findUpperBracket(Vf, inferredSVpoints) {
90538
90695
  }
90539
90696
  return upperPt;
90540
90697
  }
90541
- // Rd - Estimate the number of responsive districts, given a set of Vf's
90698
+ // rD - Estimate the number of responsive districts, given a set of Vf's
90542
90699
  function estResponsiveDistricts(VfArray) {
90543
90700
  // Python: sum([est_district_responsiveness(vpi) for vpi in vpi_by_district])
90544
90701
  return U.trim(U.sumArray(VfArray.map(v => estDistrictResponsiveness(v))));
90545
90702
  }
90546
90703
  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);
90704
+ // rD% - The estimated # of responsive districts, as a fraction of N
90705
+ function estResponsiveDistrictsShare(rD, N) {
90706
+ return U.trim(rD / N);
90550
90707
  }
90551
90708
  exports.estResponsiveDistrictsShare = estResponsiveDistrictsShare;
90552
90709
  // ESTIMATE COMPETITIVENESS (ENHANCED)
@@ -90558,7 +90715,7 @@ exports.countCompetitiveDistricts = countCompetitiveDistricts;
90558
90715
  function isCompetitive(v) {
90559
90716
  return ((v >= C.competitiveRange()[C.BEG]) && (v <= C.competitiveRange()[C.END])) ? 1 : 0;
90560
90717
  }
90561
- // Cd - The estimated # of competitive districts
90718
+ // cD - The estimated # of competitive districts
90562
90719
  function estCompetitiveDistricts(VfArray) {
90563
90720
  return U.trim(U.sumArray(VfArray.map(v => estDistrictCompetitiveness(v))));
90564
90721
  }
@@ -90578,9 +90735,9 @@ function estDistrictCompetitiveness(Vf) {
90578
90735
  return dC;
90579
90736
  }
90580
90737
  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);
90738
+ // cD% - The estimated # of competitive districts, as a fraction of N
90739
+ function estCompetitiveDistrictsShare(cD, N) {
90740
+ return U.trim(cD / N);
90584
90741
  }
90585
90742
  exports.estCompetitiveDistrictsShare = estCompetitiveDistrictsShare;
90586
90743
  // Md - The estimated # of "marginal" districts in and around the likely FPTP
@@ -90653,17 +90810,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
90653
90810
  };
90654
90811
  Object.defineProperty(exports, "__esModule", { value: true });
90655
90812
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
90813
+ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90656
90814
  const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
90657
90815
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
90658
90816
  const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
90659
90817
  const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
90660
90818
  const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
90661
- // TODO - Score opportunity for minority representation
90819
+ const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
90662
90820
  function scorePlan(p, overridesJSON) {
90663
90821
  // TRADITIONAL DISTRICTING PRINCIPLES ("best") subcategories - compactness, splitting, population deviation
90664
90822
  // Compactness
90665
- const reockM = compact_1.doReock(p.compactnessProfile.GeometryByDistrict);
90666
- const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.GeometryByDistrict);
90823
+ const reockM = compact_1.doReock(p.compactnessProfile.geometryByDistrict);
90824
+ const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.geometryByDistrict);
90667
90825
  const compactnessScore = weightCompactness(reockM.normalized, polsbyM.normalized);
90668
90826
  const cS = {
90669
90827
  score: compactnessScore,
@@ -90671,7 +90829,7 @@ function scorePlan(p, overridesJSON) {
90671
90829
  polsby: polsbyM
90672
90830
  };
90673
90831
  // Splitting
90674
- const CxD = p.splittingProfile.CountyPopByDistrict;
90832
+ const CxD = p.splittingProfile.countyPopByDistrict;
90675
90833
  const dT = cohesive_1.totalDistricts(CxD);
90676
90834
  const cT = cohesive_1.totalCounties(CxD);
90677
90835
  const countyM = cohesive_1.doCountySplittingReduced(CxD, dT, cT);
@@ -90683,7 +90841,7 @@ function scorePlan(p, overridesJSON) {
90683
90841
  district: districtM
90684
90842
  };
90685
90843
  // Population deviation
90686
- const pdS = equal_1.doPopulationDeviation(p.populationProfile.TotalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
90844
+ const pdS = equal_1.doPopulationDeviation(p.populationProfile.totalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
90687
90845
  // Combine traditional principles into a score to compare plans w/in a state
90688
90846
  const tpScore = weightTradtionalPrinciples(cS.score, sS.score, pdS.normalized, 1 /* WithinAState */);
90689
90847
  // Populate the "best" traditional principles scorecard
@@ -90694,13 +90852,17 @@ function scorePlan(p, overridesJSON) {
90694
90852
  populationDeviation: pdS
90695
90853
  };
90696
90854
  // PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
90697
- const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.VfArray);
90855
+ const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.vfArray);
90698
90856
  // Combine the partisan/partisan & traditional principles/best scores into an overall
90699
90857
  // score for comparing plans w/in a state
90700
- const score = weightBestFair(pS.score2, tpS.score, 1 /* WithinAState */);
90858
+ let score = weightOverall(pS.score2, tpS.score, 1 /* WithinAState */);
90859
+ const mS = minority_1.evalMinorityOpportunity(p);
90860
+ // Add minority bonus, keeping score it to the range [0–100]
90861
+ score = mixinMinorityBonus(score, mS.score);
90701
90862
  // Roll up an overall scorecard
90702
90863
  const scorecard = {
90703
90864
  partisan: pS,
90865
+ minority: mS,
90704
90866
  traditionalPrinciples: tpS,
90705
90867
  score: score
90706
90868
  };
@@ -90730,19 +90892,24 @@ function weightTradtionalPrinciples(cS, sS, pdS, context) {
90730
90892
  return score;
90731
90893
  }
90732
90894
  exports.weightTradtionalPrinciples = weightTradtionalPrinciples;
90733
- function weightBestFair(pS, tpS, context) {
90895
+ function weightOverall(pS, tpS, context) {
90734
90896
  const pW = C.partisanWeight(context);
90735
90897
  const tpW = C.traditionalPrinciplesWeight(context);
90736
90898
  const score = Math.round(((pS * pW) + (tpS * tpW)) / (pW + tpW));
90737
90899
  return score;
90738
90900
  }
90739
- exports.weightBestFair = weightBestFair;
90901
+ exports.weightOverall = weightOverall;
90902
+ function mixinMinorityBonus(score, minorityBonus) {
90903
+ const modifiedScore = Math.min(score + minorityBonus, S.NORMALIZED_RANGE);
90904
+ return modifiedScore;
90905
+ }
90906
+ exports.mixinMinorityBonus = mixinMinorityBonus;
90740
90907
  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$, $$$');
90908
+ 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$, O1, O2, Od, Pd, M$, $$$');
90742
90909
  }
90743
90910
  exports.printScorecardHeader = printScorecardHeader;
90744
90911
  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);
90912
+ 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, %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.minority.nOpportunity1, s.minority.nOpportunity2, s.minority.opportunityDistricts, s.minority.nProportional, s.minority.score, s.score);
90746
90913
  }
90747
90914
  exports.printScorecardRow = printScorecardRow;
90748
90915
 
@@ -90886,7 +91053,7 @@ exports.deepCopy = deepCopy;
90886
91053
  /*! exports provided: state, planName, nDistricts, nCounties, legislativeDistricts, populationProfile, compactnessProfile, splittingProfile, partisanProfile, demographicProfile, default */
90887
91054
  /***/ (function(module) {
90888
91055
 
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]]}}");
91056
+ 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
91057
 
90891
91058
  /***/ }),
90892
91059
 
@@ -90894,10 +91061,10 @@ module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"Sample profile\",\
90894
91061
  /*!************************************************!*\
90895
91062
  !*** ./testdata/samples/sample-scorecard.json ***!
90896
91063
  \************************************************/
90897
- /*! exports provided: score, traditionalPrinciples, partisan, default */
91064
+ /*! exports provided: score, partisan, minority, traditionalPrinciples, default */
90898
91065
  /***/ (function(module) {
90899
91066
 
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}}");
91067
+ module.exports = JSON.parse("{\"score\":5,\"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},\"minority\":{\"report\":{\"bucketsByDemographic\":[[10,14],[3,0],[1,9],[0,0],[0,0],[0,0]],\"averageDVf\":0.6737588682747838},\"nOpportunity1\":4,\"nOpportunity2\":9,\"nProportional\":18,\"opportunityDistricts\":12.9424,\"score\":14},\"traditionalPrinciples\":{\"score\":18,\"compactness\":{\"score\":35,\"reock\":{\"raw\":0.3373,\"normalized\":35,\"notes\":{}},\"polsby\":{\"raw\":0.2418,\"normalized\":35,\"notes\":{}}},\"splitting\":{\"score\":0,\"county\":{\"raw\":1.1474,\"normalized\":0,\"notes\":{}},\"district\":{\"raw\":1.4839,\"normalized\":3,\"notes\":{}}},\"populationDeviation\":{\"raw\":0.016,\"normalized\":0,\"notes\":{\"maxDeviation\":11693}}}}");
90901
91068
 
90902
91069
  /***/ })
90903
91070
 
@@ -103143,7 +103310,8 @@ function profilePlan(s, bLog = false) {
103143
103310
  const summaryRow = s.districts.numberOfRows() - 1;
103144
103311
  const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
103145
103312
  const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
103146
- const demographicsByDistrict = makeArrayOfDemographics(s);
103313
+ const statewideDemographics = getStatewideDemographics(s);
103314
+ const demographicsByDistrict = getDemographicsByDistrict(s);
103147
103315
  const profile = {
103148
103316
  state: state,
103149
103317
  planName: planName,
@@ -103151,21 +103319,22 @@ function profilePlan(s, bLog = false) {
103151
103319
  nCounties: nCounties,
103152
103320
  legislativeDistricts: s.legislativeDistricts,
103153
103321
  populationProfile: {
103154
- TotalPopByDistrict: popByDistrict,
103322
+ totalPopByDistrict: popByDistrict,
103155
103323
  targetSize: targetSize
103156
103324
  },
103157
103325
  compactnessProfile: {
103158
- GeometryByDistrict: geoPropsByDistrict
103326
+ geometryByDistrict: geoPropsByDistrict
103159
103327
  },
103160
103328
  splittingProfile: {
103161
- CountyPopByDistrict: splits
103329
+ countyPopByDistrict: splits
103162
103330
  },
103163
103331
  partisanProfile: {
103164
103332
  statewideVf: statewideVf,
103165
- VfArray: vpiArray
103333
+ vfArray: vpiArray
103166
103334
  },
103167
103335
  demographicProfile: {
103168
- DemographicsByDistrict: demographicsByDistrict
103336
+ stateMfArray: statewideDemographics,
103337
+ mfArrayByDistrict: demographicsByDistrict
103169
103338
  }
103170
103339
  };
103171
103340
  return profile;
@@ -103199,7 +103368,7 @@ function makeArrayOfGeoProps(s, bLog = false) {
103199
103368
  }
103200
103369
  return geometryByDistrict;
103201
103370
  }
103202
- function makeArrayOfDemographics(s, bLog = false) {
103371
+ function getDemographicsByDistrict(s, bLog = false) {
103203
103372
  let demographicsArray = [];
103204
103373
  // Remove the unassigned & total dummy "districts"
103205
103374
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -103216,6 +103385,19 @@ function makeArrayOfDemographics(s, bLog = false) {
103216
103385
  }
103217
103386
  return demographicsArray;
103218
103387
  }
103388
+ function getStatewideDemographics(s, bLog = false) {
103389
+ const summaryRow = s.districts.numberOfRows() - 1;
103390
+ const demographicsArray = [
103391
+ U.deepCopy(s.districts.statistics[D.DistrictField.WhitePct][summaryRow]),
103392
+ U.deepCopy(s.districts.statistics[D.DistrictField.MinorityPct][summaryRow]),
103393
+ U.deepCopy(s.districts.statistics[D.DistrictField.BlackPct][summaryRow]),
103394
+ U.deepCopy(s.districts.statistics[D.DistrictField.HispanicPct][summaryRow]),
103395
+ U.deepCopy(s.districts.statistics[D.DistrictField.PacificPct][summaryRow]),
103396
+ U.deepCopy(s.districts.statistics[D.DistrictField.AsianPct][summaryRow]),
103397
+ U.deepCopy(s.districts.statistics[D.DistrictField.NativePct][summaryRow])
103398
+ ];
103399
+ return demographicsArray;
103400
+ }
103219
103401
  // SCORE A PLAN
103220
103402
  function scorePlan(s, p, bLog = false, overridesJSON) {
103221
103403
  let scorer = new Score.Scorer();