@dra2020/district-analytics 3.1.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
@@ -86,10 +86,10 @@
86
86
  /************************************************************************/
87
87
  /******/ ({
88
88
 
89
- /***/ "../dra-score/dist/dra-score.bundle.js":
90
- /*!*********************************************!*\
91
- !*** ../dra-score/dist/dra-score.bundle.js ***!
92
- \*********************************************/
89
+ /***/ "./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js":
90
+ /*!******************************************************************!*\
91
+ !*** ./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js ***!
92
+ \******************************************************************/
93
93
  /*! no static exports found */
94
94
  /***/ (function(module, exports, __webpack_require__) {
95
95
 
@@ -89264,13 +89264,24 @@ module.exports = g;
89264
89264
  //
89265
89265
  // THE NODE PACKAGE API
89266
89266
  //
89267
+ var __importStar = (this && this.__importStar) || function (mod) {
89268
+ if (mod && mod.__esModule) return mod;
89269
+ var result = {};
89270
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
89271
+ result["default"] = mod;
89272
+ return result;
89273
+ };
89267
89274
  Object.defineProperty(exports, "__esModule", { value: true });
89275
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89268
89276
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
89269
89277
  class Scorer {
89270
89278
  constructor() {
89271
89279
  }
89272
- score(plan) {
89273
- return score_1.scorePlan(plan);
89280
+ score(profile, overridesJSON) {
89281
+ return score_1.scorePlan(profile, overridesJSON);
89282
+ }
89283
+ populationDeviationThreshold(bLegislative) {
89284
+ return C.popdevThreshold(bLegislative);
89274
89285
  }
89275
89286
  }
89276
89287
  exports.Scorer = Scorer;
@@ -89300,27 +89311,33 @@ var __importStar = (this && this.__importStar) || function (mod) {
89300
89311
  Object.defineProperty(exports, "__esModule", { value: true });
89301
89312
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89302
89313
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
89314
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89303
89315
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89304
89316
  function scoreCountySplitting(rawValue, nCounties, nDistricts) {
89305
89317
  const _normalizer = new normalize_1.Normalizer(rawValue);
89306
- const stateCountySplitThreshold = countySplitThreshold(nCounties, nDistricts);
89307
- _normalizer.clip(S.COUNTY_BEST, stateCountySplitThreshold);
89308
- _normalizer.unitize(S.COUNTY_BEST, stateCountySplitThreshold);
89318
+ const best = C.countySplittingRange()[C.BEG];
89319
+ const worst = countySplitThreshold(nCounties, nDistricts);
89320
+ _normalizer.clip(best, worst);
89321
+ _normalizer.unitize(best, worst);
89309
89322
  _normalizer.invert();
89310
89323
  _normalizer.rescale();
89311
89324
  return _normalizer.normalizedNum;
89312
89325
  }
89313
89326
  exports.scoreCountySplitting = scoreCountySplitting;
89314
89327
  function countySplitThreshold(nCounties, nDistricts) {
89315
- const nAllowableSplits = Math.min(S.ALLOWABLE_SPLITS_MULTIPLIER * (nDistricts - 1));
89316
- const threshold = ((nAllowableSplits * S.COUNTY_WORST) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
89328
+ const m = C.allowableSplitsMultiplier();
89329
+ const worst = C.countySplittingRange()[1];
89330
+ const nAllowableSplits = Math.min(m * (nDistricts - 1));
89331
+ const threshold = ((nAllowableSplits * worst) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
89317
89332
  return threshold;
89318
89333
  }
89319
89334
  exports.countySplitThreshold = countySplitThreshold;
89320
89335
  function scoreDistrictSplitting(rawValue) {
89321
89336
  const _normalizer = new normalize_1.Normalizer(rawValue);
89322
- _normalizer.clip(S.DISTRICT_BEST, S.DISTRICT_WORST);
89323
- _normalizer.unitize(S.DISTRICT_BEST, S.DISTRICT_WORST);
89337
+ const best = C.districtSplittingRange()[C.BEG];
89338
+ const worst = C.districtSplittingRange()[C.END];
89339
+ _normalizer.clip(best, worst);
89340
+ _normalizer.unitize(best, worst);
89324
89341
  _normalizer.invert();
89325
89342
  _normalizer.rescale();
89326
89343
  return _normalizer.normalizedNum;
@@ -89593,7 +89610,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
89593
89610
  };
89594
89611
  Object.defineProperty(exports, "__esModule", { value: true });
89595
89612
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89596
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
89613
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89597
89614
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89598
89615
  // Migrated from district-analytics:
89599
89616
  // Measures of compactness compare district shapes to various ideally compact
@@ -89715,8 +89732,10 @@ function calcReock(area, diameter) {
89715
89732
  exports.calcReock = calcReock;
89716
89733
  function scoreReock(rawValue) {
89717
89734
  const _normalizer = new normalize_1.Normalizer(rawValue);
89718
- _normalizer.clip(S.REOCK_WORST, S.REOCK_BEST);
89719
- _normalizer.unitize(S.REOCK_WORST, S.REOCK_BEST);
89735
+ const worst = C.reockRange()[C.BEG];
89736
+ const best = C.reockRange()[C.END];
89737
+ _normalizer.clip(worst, best);
89738
+ _normalizer.unitize(worst, best);
89720
89739
  _normalizer.rescale();
89721
89740
  return _normalizer.normalizedNum;
89722
89741
  }
@@ -89734,12 +89753,12 @@ function doPolsbyPopper(g, bLog = false) {
89734
89753
  if (districtProps) {
89735
89754
  let a = districtProps[0 /* Area */];
89736
89755
  let p = districtProps[1 /* Perimeter */];
89737
- let polsbyPopper = calcPolsbyPopper(a, p);
89756
+ let polsby = calcPolsbyPopper(a, p);
89738
89757
  // Save each district score
89739
- scores.push(polsbyPopper);
89758
+ scores.push(polsby);
89740
89759
  // Echo the results by district
89741
89760
  if (bLog)
89742
- console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
89761
+ console.log("Polsby-Popper for district", districtID, "=", polsby);
89743
89762
  }
89744
89763
  else {
89745
89764
  console.log("No shape or no geopropertes in Polsby-Popper ...");
@@ -89762,14 +89781,228 @@ function calcPolsbyPopper(area, perimeter) {
89762
89781
  exports.calcPolsbyPopper = calcPolsbyPopper;
89763
89782
  function scorePolsbyPopper(rawValue) {
89764
89783
  const _normalizer = new normalize_1.Normalizer(rawValue);
89765
- _normalizer.clip(S.POLSBY_WORST, S.POLSBY_BEST);
89766
- _normalizer.unitize(S.POLSBY_WORST, S.POLSBY_BEST);
89784
+ const worst = C.polsbyRange()[C.BEG];
89785
+ const best = C.polsbyRange()[C.END];
89786
+ _normalizer.clip(worst, best);
89787
+ _normalizer.unitize(worst, best);
89767
89788
  _normalizer.rescale();
89768
89789
  return _normalizer.normalizedNum;
89769
89790
  }
89770
89791
  exports.scorePolsbyPopper = scorePolsbyPopper;
89771
89792
 
89772
89793
 
89794
+ /***/ }),
89795
+
89796
+ /***/ "./src/config.json":
89797
+ /*!*************************!*\
89798
+ !*** ./src/config.json ***!
89799
+ \*************************/
89800
+ /*! exports provided: partisan, minority, traditionalPrinciples, default */
89801
+ /***/ (function(module) {
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]},\"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
+
89805
+ /***/ }),
89806
+
89807
+ /***/ "./src/config.ts":
89808
+ /*!***********************!*\
89809
+ !*** ./src/config.ts ***!
89810
+ \***********************/
89811
+ /*! no static exports found */
89812
+ /***/ (function(module, exports, __webpack_require__) {
89813
+
89814
+ "use strict";
89815
+
89816
+ //
89817
+ // THRESHOLDS, SCALES, AND WEIGHTS FOR SCORING MODEL
89818
+ //
89819
+ // * A layer of functions over config.json, so that they can be overridden dynamically (TODO)
89820
+ //
89821
+ var __importDefault = (this && this.__importDefault) || function (mod) {
89822
+ return (mod && mod.__esModule) ? mod : { "default": mod };
89823
+ };
89824
+ Object.defineProperty(exports, "__esModule", { value: true });
89825
+ // Defaults
89826
+ const config_json_1 = __importDefault(__webpack_require__(/*! ./config.json */ "./src/config.json"));
89827
+ exports.BEG = 0;
89828
+ exports.END = 1;
89829
+ // PARTISAN
89830
+ function biasWeight(context, overridesJSON) {
89831
+ const bW = config_json_1.default.partisan.bias.weight[context];
89832
+ return bW;
89833
+ }
89834
+ exports.biasWeight = biasWeight;
89835
+ function biasRange(overridesJSON) {
89836
+ const range = config_json_1.default.partisan.bias.range;
89837
+ return range;
89838
+ }
89839
+ exports.biasRange = biasRange;
89840
+ function impactWeight(context, overridesJSON) {
89841
+ const iW = config_json_1.default.partisan.impact.weight[context];
89842
+ return iW;
89843
+ }
89844
+ exports.impactWeight = impactWeight;
89845
+ // The maximum # of unearned seats that scores positively
89846
+ function unearnedThreshold(overridesJSON) {
89847
+ const threshold = config_json_1.default.partisan.impact.threshold;
89848
+ return threshold;
89849
+ }
89850
+ exports.unearnedThreshold = unearnedThreshold;
89851
+ function competitivenessWeight(context, overridesJSON) {
89852
+ const cW = config_json_1.default.partisan.competitiveness.weight[context];
89853
+ return cW;
89854
+ }
89855
+ exports.competitivenessWeight = competitivenessWeight;
89856
+ function competitiveRange(overridesJSON) {
89857
+ const range = config_json_1.default.partisan.competitiveness.range;
89858
+ return range;
89859
+ }
89860
+ exports.competitiveRange = competitiveRange;
89861
+ function competitiveDistribution(overridesJSON) {
89862
+ const dist = config_json_1.default.partisan.competitiveness.distribution;
89863
+ return dist;
89864
+ }
89865
+ exports.competitiveDistribution = competitiveDistribution;
89866
+ function overallCompetitivenessRange(overridesJSON) {
89867
+ const range = config_json_1.default.partisan.competitiveness.overall.range;
89868
+ return range;
89869
+ }
89870
+ exports.overallCompetitivenessRange = overallCompetitivenessRange;
89871
+ function marginalCompetitivenessRange(overridesJSON) {
89872
+ const range = config_json_1.default.partisan.competitiveness.marginal.range;
89873
+ return range;
89874
+ }
89875
+ exports.marginalCompetitivenessRange = marginalCompetitivenessRange;
89876
+ function marginalCompetitivenessWeight(overridesJSON) {
89877
+ const mcW = config_json_1.default.partisan.competitiveness.marginal.weight;
89878
+ return mcW;
89879
+ }
89880
+ exports.marginalCompetitivenessWeight = marginalCompetitivenessWeight;
89881
+ function overallCompetitivenessWeight(overridesJSON) {
89882
+ const ocW = config_json_1.default.partisan.competitiveness.overall.weight;
89883
+ return ocW;
89884
+ }
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;
89912
+ // TRADITIONAL DISTRICTING PRINCIPLES
89913
+ function compactnessWeight(context, overridesJSON) {
89914
+ const cW = config_json_1.default.traditionalPrinciples.compactness.weight[context];
89915
+ return cW;
89916
+ }
89917
+ exports.compactnessWeight = compactnessWeight;
89918
+ function reockWeight(overridesJSON) {
89919
+ const rW = config_json_1.default.traditionalPrinciples.compactness.reock.weight;
89920
+ return rW;
89921
+ }
89922
+ exports.reockWeight = reockWeight;
89923
+ function reockRange(overridesJSON) {
89924
+ const range = config_json_1.default.traditionalPrinciples.compactness.reock.range;
89925
+ return range;
89926
+ }
89927
+ exports.reockRange = reockRange;
89928
+ function polsbyWeight(overridesJSON) {
89929
+ const ppW = config_json_1.default.traditionalPrinciples.compactness.polsby.weight;
89930
+ return ppW;
89931
+ }
89932
+ exports.polsbyWeight = polsbyWeight;
89933
+ function polsbyRange(overridesJSON) {
89934
+ const range = config_json_1.default.traditionalPrinciples.compactness.polsby.range;
89935
+ return range;
89936
+ }
89937
+ exports.polsbyRange = polsbyRange;
89938
+ function splittingWeight(context, overridesJSON) {
89939
+ const sW = config_json_1.default.traditionalPrinciples.splitting.weight[context];
89940
+ return sW;
89941
+ }
89942
+ exports.splittingWeight = splittingWeight;
89943
+ function countySplittingWeight(overridesJSON) {
89944
+ const csW = config_json_1.default.traditionalPrinciples.splitting.county.weight;
89945
+ return csW;
89946
+ }
89947
+ exports.countySplittingWeight = countySplittingWeight;
89948
+ function countySplittingRange(overridesJSON) {
89949
+ const range = config_json_1.default.traditionalPrinciples.splitting.county.range;
89950
+ return range;
89951
+ }
89952
+ exports.countySplittingRange = countySplittingRange;
89953
+ function allowableSplitsMultiplier(overridesJSON) {
89954
+ const m = config_json_1.default.traditionalPrinciples.splitting.county.allowableSplitsMultiplier;
89955
+ return m;
89956
+ }
89957
+ exports.allowableSplitsMultiplier = allowableSplitsMultiplier;
89958
+ function districtSplittingWeight(overridesJSON) {
89959
+ const dsW = config_json_1.default.traditionalPrinciples.splitting.district.weight;
89960
+ return dsW;
89961
+ }
89962
+ exports.districtSplittingWeight = districtSplittingWeight;
89963
+ function districtSplittingRange(overridesJSON) {
89964
+ const range = config_json_1.default.traditionalPrinciples.splitting.district.range;
89965
+ return range;
89966
+ }
89967
+ exports.districtSplittingRange = districtSplittingRange;
89968
+ function popdevWeight(context, overridesJSON) {
89969
+ const pdW = config_json_1.default.traditionalPrinciples.popdev.weight[context];
89970
+ return pdW;
89971
+ }
89972
+ exports.popdevWeight = popdevWeight;
89973
+ // NOTE - Raw ranges, not inverted (i.e., smaller is better)
89974
+ // NOTE - This could be optimized to not calc LD values for CD's (or do it once)
89975
+ function popdevRange(bLegislative, overridesJSON) {
89976
+ const cdRange = config_json_1.default.traditionalPrinciples.popdev.range[0 /* Congressional */];
89977
+ const worstCD = cdRange[exports.BEG];
89978
+ const bestCD = cdRange[exports.END];
89979
+ const ldRange = config_json_1.default.traditionalPrinciples.popdev.range[1 /* StateLegislative */];
89980
+ const iRange = bLegislative ? ldRange : cdRange;
89981
+ const worst = iRange[exports.BEG];
89982
+ const best = bLegislative ? (bestCD / worstCD) * iRange[exports.BEG] : iRange[exports.END];
89983
+ // Invert the range, so bigger is better.
89984
+ return [worst, best];
89985
+ }
89986
+ exports.popdevRange = popdevRange;
89987
+ // NOTE - Raw threshold, not inverted (i.e., smaller is better)
89988
+ function popdevThreshold(bLegislative, overridesJSON) {
89989
+ const threshold = popdevRange(bLegislative)[exports.BEG];
89990
+ return threshold;
89991
+ }
89992
+ exports.popdevThreshold = popdevThreshold;
89993
+ // OVERALL SCORE
89994
+ function partisanWeight(context, overridesJSON) {
89995
+ const pW = config_json_1.default.partisan.weight[context];
89996
+ return pW;
89997
+ }
89998
+ exports.partisanWeight = partisanWeight;
89999
+ function traditionalPrinciplesWeight(context, overridesJSON) {
90000
+ const tpW = config_json_1.default.traditionalPrinciples.weight[context];
90001
+ return tpW;
90002
+ }
90003
+ exports.traditionalPrinciplesWeight = traditionalPrinciplesWeight;
90004
+
90005
+
89773
90006
  /***/ }),
89774
90007
 
89775
90008
  /***/ "./src/equal.ts":
@@ -89793,7 +90026,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
89793
90026
  };
89794
90027
  Object.defineProperty(exports, "__esModule", { value: true });
89795
90028
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89796
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90029
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89797
90030
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89798
90031
  // Migrated from district-analytics
89799
90032
  // Expect a full dict of district IDs & values (which might be zero = empty)
@@ -89814,7 +90047,7 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
89814
90047
  // Round the raw value to the desired level of precision
89815
90048
  const popDev = U.trim((max - min) / targetSize);
89816
90049
  const score = scorePopulationDeviation(popDev, bLegislative);
89817
- const threshold = bLegislative ? S.POPDEV_WORST_LD : S.POPDEV_WORST;
90050
+ const threshold = C.popdevThreshold(bLegislative);
89818
90051
  const notes = {
89819
90052
  'maxDeviation': max - min,
89820
90053
  'threshold': threshold
@@ -89830,11 +90063,11 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
89830
90063
  exports.doPopulationDeviation = doPopulationDeviation;
89831
90064
  function scorePopulationDeviation(rawValue, bLegislative) {
89832
90065
  const _normalizer = new normalize_1.Normalizer(rawValue);
89833
- const rangeMin = bLegislative ? S.POPEQ_MIN_LD : S.POPEQ_MIN;
89834
- const rangeMax = bLegislative ? S.POPEQ_MAX_LD : S.POPEQ_MAX;
90066
+ // Raw range in not inverted (i.e., smaller is better)
90067
+ const range = C.popdevRange(bLegislative);
89835
90068
  _normalizer.invert();
89836
- _normalizer.clip(rangeMin, rangeMax);
89837
- _normalizer.unitize(rangeMin, rangeMax);
90069
+ _normalizer.clip(1.0 - range[C.BEG], 1.0 - range[C.END]);
90070
+ _normalizer.unitize(1.0 - range[C.BEG], 1.0 - range[C.END]);
89838
90071
  _normalizer.rescale();
89839
90072
  return _normalizer.normalizedNum;
89840
90073
  }
@@ -89843,10 +90076,257 @@ exports.scorePopulationDeviation = scorePopulationDeviation;
89843
90076
 
89844
90077
  /***/ }),
89845
90078
 
89846
- /***/ "./src/fair.ts":
89847
- /*!*********************!*\
89848
- !*** ./src/fair.ts ***!
89849
- \*********************/
90079
+ /***/ "./src/index.ts":
90080
+ /*!**********************!*\
90081
+ !*** ./src/index.ts ***!
90082
+ \**********************/
90083
+ /*! no static exports found */
90084
+ /***/ (function(module, exports, __webpack_require__) {
90085
+
90086
+ "use strict";
90087
+
90088
+ //
90089
+ // SCORE PACKAGE API
90090
+ //
90091
+ function __export(m) {
90092
+ for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
90093
+ }
90094
+ Object.defineProperty(exports, "__esModule", { value: true });
90095
+ __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
90096
+ var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
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;
90227
+
90228
+
90229
+ /***/ }),
90230
+
90231
+ /***/ "./src/normalize.ts":
90232
+ /*!**************************!*\
90233
+ !*** ./src/normalize.ts ***!
90234
+ \**************************/
90235
+ /*! no static exports found */
90236
+ /***/ (function(module, exports, __webpack_require__) {
90237
+
90238
+ "use strict";
90239
+
90240
+ //
90241
+ // NORMALIZATION UTILITIES
90242
+ //
90243
+ var __importStar = (this && this.__importStar) || function (mod) {
90244
+ if (mod && mod.__esModule) return mod;
90245
+ var result = {};
90246
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
90247
+ result["default"] = mod;
90248
+ return result;
90249
+ };
90250
+ Object.defineProperty(exports, "__esModule", { value: true });
90251
+ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90252
+ class Normalizer {
90253
+ constructor(rawValue) {
90254
+ this.rawNum = rawValue;
90255
+ this.wipNum = this.rawNum;
90256
+ }
90257
+ // *Don't* transform the input.
90258
+ identity() {
90259
+ return this.wipNum;
90260
+ }
90261
+ positive() {
90262
+ this.wipNum = Math.abs(this.wipNum);
90263
+ return this.wipNum;
90264
+ }
90265
+ // Invert a value in the unit range [0.0–1.0] (so that bigger is better).
90266
+ invert() {
90267
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90268
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Inverting: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90269
+ this.wipNum = 1.0 - this.wipNum;
90270
+ return this.wipNum;
90271
+ }
90272
+ // Constrain the value to be within a specified range.
90273
+ clip(begin_range, end_range) {
90274
+ // Handle the ends of the range being given either order
90275
+ const min_range = Math.min(begin_range, end_range);
90276
+ const max_range = Math.max(begin_range, end_range);
90277
+ this.wipNum = Math.max(Math.min(this.wipNum, max_range), min_range);
90278
+ return this.wipNum;
90279
+ }
90280
+ // Recast the value as the delta from a baseline value. NOOP if it is zero.
90281
+ // NOTE - Values can be + or -.
90282
+ rebase(base) {
90283
+ this.wipNum = this.wipNum - base;
90284
+ return this.wipNum;
90285
+ }
90286
+ // Re-scale a value into the [0.0 – 1.0] range, using a given range.
90287
+ // NOTE - This assumes that values have alrady been clipped into the range.
90288
+ unitize(begin_range, end_range) {
90289
+ const min_range = Math.min(begin_range, end_range);
90290
+ const max_range = Math.max(begin_range, end_range);
90291
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90292
+ console.assert(((this.wipNum >= min_range) && (this.wipNum <= max_range)), "Unitizing: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90293
+ const ranged = this.wipNum - min_range;
90294
+ this.wipNum = Math.abs(ranged / (end_range - begin_range));
90295
+ return this.wipNum;
90296
+ }
90297
+ // Decay a value in the unit range [0.0–1.0] by its distance from zero.
90298
+ // NOTE - If the range is already such that "bigger is better," then the closer
90299
+ // the value is to 1.0 (the best) the *less* it will decay.
90300
+ decay(fn = 0 /* Gravity */) {
90301
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90302
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Decaying: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90303
+ switch (fn) {
90304
+ case 0 /* Gravity */: {
90305
+ this.wipNum = Math.pow(this.wipNum, S.DISTANCE_WEIGHT);
90306
+ return this.wipNum;
90307
+ }
90308
+ default: {
90309
+ throw new Error("Decay function not recognized.");
90310
+ }
90311
+ }
90312
+ }
90313
+ // Translate a value in the unit range to the user-friendly range [0 – 100].
90314
+ rescale() {
90315
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90316
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Rescaling: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90317
+ this.normalizedNum = Math.round(this.wipNum * S.NORMALIZED_RANGE);
90318
+ return this.normalizedNum;
90319
+ }
90320
+ }
90321
+ exports.Normalizer = Normalizer;
90322
+
90323
+
90324
+ /***/ }),
90325
+
90326
+ /***/ "./src/partisan.ts":
90327
+ /*!*************************!*\
90328
+ !*** ./src/partisan.ts ***!
90329
+ \*************************/
89850
90330
  /*! no static exports found */
89851
90331
  /***/ (function(module, exports, __webpack_require__) {
89852
90332
 
@@ -89865,117 +90345,159 @@ var __importStar = (this && this.__importStar) || function (mod) {
89865
90345
  Object.defineProperty(exports, "__esModule", { value: true });
89866
90346
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89867
90347
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90348
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89868
90349
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89869
90350
  // NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
89870
90351
  // NOTE - I do not (cannot) assume that the values are sorted.
89871
90352
  // SCORE BIAS & COMPETITIVENESS
89872
- /* SCORECARD FIELDS:
89873
-
89874
- * ^S# [BestS] = the Democratic seats closest to proportional
89875
- * ^S% [BestSf] = the corresponding Democratic seat share
89876
- * F# [FptpS] = the estimated number of Democratic seats using first past the post
89877
- * S# [ProbableS] = the estimated Democratic seats, using seat probabilities
89878
- * S% [ProbableSf] = the estimated Democratic seat share fraction, calculated as S# / N
89879
- * B% [Bias]= the bias calculated as S% – ^S%
89880
- * B$ [BiasScore] = the bias score normalized [0–100]
89881
-
89882
- * R# [R] = the responsiveness at V%
89883
- * Rd [Rd] = the estimated # of responsive districts (using probabilities)
89884
- * Rd% [Rdf] = the estimated # of responsive districts, as a fraction of N
89885
- * Cd [Cd] = the estimated # of competitive districts, using probabilities & a narrower [0.25–0.75] range
89886
- * Cd% [Cdf] = the estimated # of competitive districts, as a fraction of N
89887
- * Md% [Mdf] = the probability that the marginal seats will flip, so S% => ^S%
89888
- * C$ [CompetitiveScore] = the competitiveness score normalized [0–100]
89889
-
89890
- * $$$ = 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 [0–100]
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.250.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
89891
90380
 
89892
90381
  */
89893
- function scorePartisan(Vf, VfArray, all = false) {
90382
+ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
89894
90383
  const N = VfArray.length;
89895
- const BestS = bestSeats(N, Vf);
89896
- const BestSf = bestSeatShare(BestS, N);
89897
- const FptpS = all ? estFPTPSeats(VfArray) : undefined;
89898
- const ProbableS = estProbableSeats(VfArray);
89899
- const ProbableSf = estProbableSeatShare(ProbableS, N);
89900
- const Bias = estBias(ProbableSf, BestSf);
89901
- const normalizedBias = scoreBias(Bias, Vf, ProbableSf, N);
89902
- const inferredSVpoints = all ? inferSVpoints(Vf, VfArray) : undefined;
89903
- const R = all ? estResponsiveness(Vf, inferredSVpoints) : undefined;
89904
- const Rd = all ? estResponsiveDistricts(VfArray) : undefined;
89905
- const Rdf = all ? estResponsiveDistrictsShare(Rd, N) : undefined;
89906
- const Cd = estCompetitiveDistricts(VfArray);
89907
- const Cdf = estCompetitiveDistrictsShare(Cd, N);
89908
- const Mdf = estMarginalCompetitiveShare(Vf, VfArray, N);
89909
- const normalizedCompetitiveness = scoreCompetitiveness(Mdf, Cdf);
90384
+ const bestS = bestSeats(N, Vf);
90385
+ const bestSf = bestSeatShare(bestS, N);
90386
+ const fptpS = bAll ? estFPTPSeats(VfArray) : undefined;
90387
+ const range = bConstrained ? C.competitiveDistribution() : undefined;
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);
90394
+ const bInferSV = (bAll || (!bConstrained));
90395
+ const inferredSVpoints = bInferSV ? inferSVpoints(Vf, VfArray, range) : undefined;
90396
+ const R = bInferSV ? estResponsiveness(Vf, inferredSVpoints) : undefined;
90397
+ const rD = bInferSV ? estResponsiveDistricts(VfArray) : undefined;
90398
+ const rDf = bInferSV ? estResponsiveDistrictsShare(rD, N) : undefined;
90399
+ const Cn = countCompetitiveDistricts(VfArray);
90400
+ const cD = bConstrained ? estCompetitiveDistricts(VfArray) : rD;
90401
+ const cDf = estCompetitiveDistrictsShare(cD, N);
90402
+ const Mrange = findMarginalDistricts(Vf, VfArray, N);
90403
+ const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
90404
+ const Mdf = estMarginalCompetitiveShare(Md, Mrange);
90405
+ const competitivenessScore = scoreCompetitiveness(Mdf, cDf);
89910
90406
  const biasScoring = {
89911
- BestS: BestS,
89912
- BestSf: BestSf,
89913
- FptpS: FptpS,
89914
- ProbableS: ProbableS,
89915
- ProbableSf: ProbableSf,
89916
- Bias: Bias,
89917
- normalized: normalizedBias
90407
+ bestS: bestS,
90408
+ bestSf: bestSf,
90409
+ fptpS: fptpS,
90410
+ probableS: probableS,
90411
+ probableSf: probableSf,
90412
+ bias: bias,
90413
+ score: biasScore
89918
90414
  };
89919
- let competitiveScoring;
89920
- if (all) {
89921
- competitiveScoring = {
89922
- R: R,
89923
- Rd: Rd,
89924
- Rdf: Rdf,
89925
- Cd: Cd,
89926
- Cdf: Cdf,
89927
- Mdf: Mdf,
89928
- normalized: normalizedCompetitiveness
89929
- };
89930
- }
89931
- else {
89932
- competitiveScoring = {
89933
- Cd: Cd,
89934
- Cdf: Cdf,
89935
- Mdf: Mdf,
89936
- normalized: normalizedCompetitiveness
89937
- };
89938
- }
90415
+ const impactScoring = {
90416
+ unearnedS: unearnedS,
90417
+ score: impactScore
90418
+ };
90419
+ let competitiveScoring = {
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,
90429
+ score: competitivenessScore
90430
+ };
90431
+ if (bAll) {
90432
+ competitiveScoring.r = R;
90433
+ competitiveScoring.rD = rD;
90434
+ competitiveScoring.rDf = rDf;
90435
+ }
90436
+ // Weight bias, impact, and competitiveness into partisan scores to compare
90437
+ // plans across states and within a state.
90438
+ const bS = biasScoring.score;
90439
+ const iS = impactScoring.score;
90440
+ const cS = competitiveScoring.score;
90441
+ const acrossStatesPartisanScore = weightPartisan(bS, iS, cS, 0 /* AcrossStates */);
90442
+ const withinStatesPartisanScore = weightPartisan(bS, iS, cS, 1 /* WithinAState */);
89939
90443
  const s = {
89940
90444
  bias: biasScoring,
89941
- competitiveness: competitiveScoring
90445
+ impact: impactScoring,
90446
+ competitiveness: competitiveScoring,
90447
+ score: acrossStatesPartisanScore,
90448
+ score2: withinStatesPartisanScore
89942
90449
  };
89943
90450
  return s;
89944
90451
  }
89945
90452
  exports.scorePartisan = scorePartisan;
89946
- function weightPartisan(b, c) {
89947
- return Math.round(((S.BIAS_WEIGHT * b) + (S.COMPETITIVE_WEIGHT * c)) / (S.BIAS_WEIGHT + S.COMPETITIVE_WEIGHT));
90453
+ function weightPartisan(bS, iS, cS, context) {
90454
+ const bW = C.biasWeight(context);
90455
+ const iW = C.impactWeight(context);
90456
+ const cW = C.competitivenessWeight(context);
90457
+ const score = Math.round(((bS * bW) + (iS * iW) + (cS * cW)) / (bW + iW + cW));
90458
+ return score;
89948
90459
  }
89949
90460
  exports.weightPartisan = weightPartisan;
89950
- function printFairScorecardHeader() {
89951
- console.log('XX, Name, N, V%, ^S#, ^S%, F#, S#, S%, B%, B$, R#, Rd, Rd%, Cd, Cd%, Md%, C$, $$$');
90461
+ function printPartisanScorecardHeader() {
90462
+ 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$');
89952
90463
  }
89953
- exports.printFairScorecardHeader = printFairScorecardHeader;
89954
- function printFairScorecard(xx, name, N, Vf, s) {
89955
- console.log('%s, %s, %i, %f, %i, %f, %i, %f, %f, %f, %i, %f, %f, %f, %f, %f, %f, %f, %i', xx, name, N, Vf, s.bias.BestS, s.bias.BestSf, s.bias.FptpS, s.bias.ProbableS, s.bias.ProbableSf, s.bias.Bias, s.bias.normalized, s.competitiveness.R, s.competitiveness.Rd, s.competitiveness.Rdf, s.competitiveness.Cd, s.competitiveness.Cdf, s.competitiveness.Mdf, s.competitiveness.normalized, s.score);
90464
+ exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
90465
+ function printPartisanScorecardRow(xx, name, N, Vf, s) {
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);
89956
90467
  }
89957
- exports.printFairScorecard = printFairScorecard;
89958
- function scoreBias(rawBias, Vf, Sf, N) {
90468
+ exports.printPartisanScorecardRow = printPartisanScorecardRow;
90469
+ function scorebias(rawbias, Vf, Sf, N) {
89959
90470
  if (isAntimajoritarian(Vf, Sf)) {
89960
90471
  return 0;
89961
90472
  }
89962
90473
  else {
89963
- const _normalizer = new normalize_1.Normalizer(rawBias);
89964
- const oneSeatFraction = 1 / N;
89965
- const worst = Math.min(oneSeatFraction, S.BIAS_WORST);
89966
- const best = S.BIAS_BEST;
90474
+ const _normalizer = new normalize_1.Normalizer(rawbias);
90475
+ const worst = C.biasRange()[C.BEG];
90476
+ const best = C.biasRange()[C.END];
89967
90477
  _normalizer.clip(worst, best);
89968
90478
  _normalizer.unitize(worst, best);
89969
90479
  _normalizer.invert();
89970
- // TODO - SCORE: Would also decaying raw values (e.g., by squaring them) be too
89971
- // much with a small range (like 10% or one seat)?
89972
90480
  // _normalizer.decay();
89973
90481
  _normalizer.rescale();
89974
90482
  const score = _normalizer.normalizedNum;
89975
90483
  return score;
89976
90484
  }
89977
90485
  }
89978
- exports.scoreBias = scoreBias;
90486
+ exports.scorebias = scorebias;
90487
+ // Normalize unearned seats
90488
+ function scoreImpact(rawUE) {
90489
+ const _normalizer = new normalize_1.Normalizer(rawUE);
90490
+ const worst = C.unearnedThreshold();
90491
+ const best = 0.0;
90492
+ _normalizer.positive();
90493
+ _normalizer.clip(worst, best);
90494
+ _normalizer.unitize(worst, best);
90495
+ _normalizer.invert();
90496
+ _normalizer.rescale();
90497
+ const score = _normalizer.normalizedNum;
90498
+ return score;
90499
+ }
90500
+ exports.scoreImpact = scoreImpact;
89979
90501
  function isAntimajoritarian(Vf, Sf) {
89980
90502
  const bDem = ((Vf < 0.5) && (Sf > 0.5)) ? true : false;
89981
90503
  const bRep = (((1 - Vf) < 0.5) && ((1 - Sf) > 0.5)) ? true : false;
@@ -89987,16 +90509,24 @@ function scoreCompetitiveness(rawMarginal, rawOverall) {
89987
90509
  // But the practical max is more like 2/3's, so unitize that range to [0.0–1.0].
89988
90510
  // Then scale the values to [0–100].
89989
90511
  const _overall = new normalize_1.Normalizer(rawOverall);
89990
- _overall.clip(S.COMPETITIVE_WORST, S.COMPETITIVE_BEST);
89991
- _overall.unitize(S.COMPETITIVE_WORST, S.COMPETITIVE_BEST);
90512
+ let worst = C.overallCompetitivenessRange()[C.BEG];
90513
+ let best = C.overallCompetitivenessRange()[C.END];
90514
+ _overall.clip(worst, best);
90515
+ _overall.unitize(worst, best);
89992
90516
  _overall.rescale();
89993
- const overall = _overall.normalizedNum;
90517
+ const ocS = _overall.normalizedNum;
89994
90518
  // Normalize marginal competitiveness
89995
90519
  const _marginal = new normalize_1.Normalizer(rawMarginal);
90520
+ worst = C.marginalCompetitivenessRange()[C.BEG];
90521
+ best = C.marginalCompetitivenessRange()[C.END];
90522
+ _marginal.clip(worst, best);
90523
+ _marginal.unitize(worst, best);
89996
90524
  _marginal.rescale();
89997
- const marginal = _marginal.normalizedNum;
90525
+ const mcS = _marginal.normalizedNum;
90526
+ const mcW = C.marginalCompetitivenessWeight();
90527
+ const ocW = C.overallCompetitivenessWeight();
89998
90528
  // Then combine the results
89999
- const score = Math.round(((S.MARGINAL_WEIGHT * marginal) + (S.OVERALL_WEIGHT * overall)) / (S.MARGINAL_WEIGHT + S.OVERALL_WEIGHT));
90529
+ const score = Math.round(((mcW * mcS) + (ocW * ocS)) / (mcW + ocW));
90000
90530
  return score;
90001
90531
  }
90002
90532
  exports.scoreCompetitiveness = scoreCompetitiveness;
@@ -90006,23 +90536,40 @@ const { erf } = __webpack_require__(/*! mathjs */ "./node_modules/mathjs/main/es
90006
90536
  // console.log("erf(-0.5) =", erf(-0.5)); // returns -0.5204998778130465
90007
90537
  // console.log("erf(4) =", erf(4)); // returns 0.9999999845827421
90008
90538
  // Estimate the probability of a seat win for district, given a Vf
90009
- function estSeatProbability(Vf) {
90539
+ function estSeatProbability(Vf, range) {
90540
+ if (range) {
90541
+ const _normalizer = new normalize_1.Normalizer(Vf);
90542
+ // The end points of a compressed probability distribution
90543
+ // NOTE - These aren't the points where races start or stop being contested,
90544
+ // just the end points of a distribution that yields the desired behavior
90545
+ // in the typical competitive range [45-55%].
90546
+ const distBeg = range[C.BEG];
90547
+ const distEnd = range[C.END];
90548
+ _normalizer.clip(distBeg, distEnd);
90549
+ _normalizer.unitize(distBeg, distEnd);
90550
+ return seatProbabilityFn(_normalizer.wipNum);
90551
+ }
90552
+ else {
90553
+ return seatProbabilityFn(Vf);
90554
+ }
90555
+ }
90556
+ exports.estSeatProbability = estSeatProbability;
90557
+ function seatProbabilityFn(Vf) {
90010
90558
  // Python: 0.5 * (1 + erf((vpi - 0.50) / (0.02 * sqrt(8))))
90011
90559
  return U.trim(0.5 * (1.0 + erf((Vf - 0.50) / (0.02 * Math.sqrt(8)))));
90012
90560
  }
90013
- exports.estSeatProbability = estSeatProbability;
90014
90561
  // Estimate the number of responsive districts [R(d)], given a set of Vf's
90015
90562
  function estDistrictResponsiveness(Vf) {
90016
90563
  // Python: 1 - 4 * (est_seat_probability(vpi) - 0.5)**2
90017
90564
  return U.trim(1.0 - 4.0 * Math.pow((estSeatProbability(Vf) - 0.5), 2));
90018
90565
  }
90019
90566
  exports.estDistrictResponsiveness = estDistrictResponsiveness;
90020
- function inferSVpoints(Vf, VfArray) {
90567
+ function inferSVpoints(Vf, VfArray, range) {
90021
90568
  const nDistricts = VfArray.length;
90022
90569
  let SVpoints = [];
90023
90570
  for (let shiftedVf of shiftRange()) {
90024
90571
  const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
90025
- const shiftedSf = estProbableSeats(shiftedVPI) / nDistricts;
90572
+ const shiftedSf = estprobableSeats(shiftedVPI, range) / nDistricts;
90026
90573
  SVpoints.push({ v: shiftedVf, s: shiftedSf });
90027
90574
  }
90028
90575
  return SVpoints;
@@ -90060,8 +90607,9 @@ function shiftRange() {
90060
90607
  }
90061
90608
  // ESTIMATE BIAS ("FAIR")
90062
90609
  // ^S# - The # of Democratic seats closest to proportional @ statewide Vf
90610
+ // The "expected number of seats" from http://bit.ly/2Fcuf4q
90063
90611
  function bestSeats(N, Vf) {
90064
- return Math.round(N * Vf);
90612
+ return Math.round((N * Vf) - S.EPSILON);
90065
90613
  }
90066
90614
  exports.bestSeats = bestSeats;
90067
90615
  // ^S% - The corresponding Democratic seat share
@@ -90070,16 +90618,16 @@ function bestSeatShare(bestS, N) {
90070
90618
  }
90071
90619
  exports.bestSeatShare = bestSeatShare;
90072
90620
  // S# - The estimated # of Democratic seats @ statewide Vf, using seat probabilities
90073
- function estProbableSeats(VfArray) {
90621
+ function estprobableSeats(VfArray, range) {
90074
90622
  // Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
90075
- return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v))));
90623
+ return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
90076
90624
  }
90077
- exports.estProbableSeats = estProbableSeats;
90625
+ exports.estprobableSeats = estprobableSeats;
90078
90626
  // S% - The estimated Democratic seat share fraction @ statewide Vf
90079
- function estProbableSeatShare(probableS, N) {
90627
+ function estprobableSeatShare(probableS, N) {
90080
90628
  return U.trim(probableS / N);
90081
90629
  }
90082
- exports.estProbableSeatShare = estProbableSeatShare;
90630
+ exports.estprobableSeatShare = estprobableSeatShare;
90083
90631
  // F# - The estimated number of Democratic seats using first past the post
90084
90632
  function estFPTPSeats(VfArray) {
90085
90633
  // Python: sum([1.0 for vpi in vpi_by_district if (vpi > 0.5)])
@@ -90094,10 +90642,17 @@ function estFPTPSeats(VfArray) {
90094
90642
  }
90095
90643
  exports.estFPTPSeats = estFPTPSeats;
90096
90644
  // B% - Tthe bias calculated as S% – ^S%
90097
- function estBias(estSeatShare, bestSeatShare) {
90645
+ function estbias(estSeatShare, bestSeatShare) {
90098
90646
  return U.trim(Math.abs(estSeatShare - bestSeatShare));
90099
90647
  }
90100
- exports.estBias = estBias;
90648
+ exports.estbias = estbias;
90649
+ // UE# - The estimated # of unearned seats
90650
+ // UE_# from http://bit.ly/2Fcuf4q
90651
+ function estunearnedSeats(best, probable) {
90652
+ // NOTE - + values = unearned R seats; – values = unearned D seats
90653
+ return U.trim(best - probable);
90654
+ }
90655
+ exports.estunearnedSeats = estunearnedSeats;
90101
90656
  // ESTIMATE RESPONSIVENESS ("COMPETITIVE")
90102
90657
  // R# - Estimate responsiveness at the statewide vote share
90103
90658
  function estResponsiveness(Vf, inferredSVpoints) {
@@ -90140,19 +90695,27 @@ function findUpperBracket(Vf, inferredSVpoints) {
90140
90695
  }
90141
90696
  return upperPt;
90142
90697
  }
90143
- // 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
90144
90699
  function estResponsiveDistricts(VfArray) {
90145
90700
  // Python: sum([est_district_responsiveness(vpi) for vpi in vpi_by_district])
90146
90701
  return U.trim(U.sumArray(VfArray.map(v => estDistrictResponsiveness(v))));
90147
90702
  }
90148
90703
  exports.estResponsiveDistricts = estResponsiveDistricts;
90149
- // Rd% - The estimated # of responsive districts, as a fraction of N
90150
- function estResponsiveDistrictsShare(Rd, N) {
90151
- 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);
90152
90707
  }
90153
90708
  exports.estResponsiveDistrictsShare = estResponsiveDistrictsShare;
90154
90709
  // ESTIMATE COMPETITIVENESS (ENHANCED)
90155
- // Cd - The estimated # of competitive districts
90710
+ // C - Count the # of competitive districts, defined as v in [45–55%]
90711
+ function countCompetitiveDistricts(VfArray) {
90712
+ return U.trim(U.sumArray(VfArray.map(v => isCompetitive(v))));
90713
+ }
90714
+ exports.countCompetitiveDistricts = countCompetitiveDistricts;
90715
+ function isCompetitive(v) {
90716
+ return ((v >= C.competitiveRange()[C.BEG]) && (v <= C.competitiveRange()[C.END])) ? 1 : 0;
90717
+ }
90718
+ // cD - The estimated # of competitive districts
90156
90719
  function estCompetitiveDistricts(VfArray) {
90157
90720
  return U.trim(U.sumArray(VfArray.map(v => estDistrictCompetitiveness(v))));
90158
90721
  }
@@ -90160,20 +90723,47 @@ exports.estCompetitiveDistricts = estCompetitiveDistricts;
90160
90723
  // Re-scale a Democratic vote share to the competitive range (e.g., 45–55%).
90161
90724
  function estDistrictCompetitiveness(Vf) {
90162
90725
  const _normalizer = new normalize_1.Normalizer(Vf);
90163
- _normalizer.clip(S.MIN_CONTESTED, S.MAX_CONTESTED);
90164
- _normalizer.unitize(S.MIN_CONTESTED, S.MAX_CONTESTED);
90726
+ // The end points of a compressed probability distribution
90727
+ // NOTE - These aren't the points where races start or stop being contested,
90728
+ // just the end points of a distribution that yields the desired behavior
90729
+ // in the typical competitive range [45-55%].
90730
+ const distBeg = C.competitiveDistribution()[C.BEG];
90731
+ const distEnd = C.competitiveDistribution()[C.END];
90732
+ _normalizer.clip(distBeg, distEnd);
90733
+ _normalizer.unitize(distBeg, distEnd);
90165
90734
  const dC = estDistrictResponsiveness(_normalizer.wipNum);
90166
90735
  return dC;
90167
90736
  }
90168
90737
  exports.estDistrictCompetitiveness = estDistrictCompetitiveness;
90169
- // Cd% - The estimated # of competitive districts, as a fraction of N
90170
- function estCompetitiveDistrictsShare(Cd, N) {
90171
- 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);
90172
90741
  }
90173
90742
  exports.estCompetitiveDistrictsShare = estCompetitiveDistrictsShare;
90743
+ // Md - The estimated # of "marginal" districts in and around the likely FPTP
90744
+ // seats & the best seat split that are competitive.
90745
+ function estMarginalCompetitiveDistricts(Mrange, VfArray) {
90746
+ const minId = Mrange[C.BEG];
90747
+ const maxId = Mrange[C.END];
90748
+ // Sort the array values, and subset it to those districts
90749
+ // NOTE - I'm *not* keeping track of the district indexes right now
90750
+ let subsetVfArray = VfArray.sort().slice(minId - 1, maxId);
90751
+ // Est. competitive districts on that array
90752
+ const Md = U.sumArray(subsetVfArray.map(v => estDistrictCompetitiveness(v)));
90753
+ return U.trim(Md);
90754
+ }
90755
+ exports.estMarginalCompetitiveDistricts = estMarginalCompetitiveDistricts;
90174
90756
  // Md% - The estimated competitiveness of the "marginal" districts in and around
90175
90757
  // the likely FPTP seats & the best seat split as a fraction
90176
- function estMarginalCompetitiveShare(Vf, VfArray, N) {
90758
+ function estMarginalCompetitiveShare(Md, Mrange) {
90759
+ const minId = Mrange[C.BEG];
90760
+ const maxId = Mrange[C.END];
90761
+ // Est. competitive district share on that result
90762
+ const MdShare = U.trim(estCompetitiveDistrictsShare(Md, maxId - minId + 1));
90763
+ return U.trim(MdShare);
90764
+ }
90765
+ exports.estMarginalCompetitiveShare = estMarginalCompetitiveShare;
90766
+ function findMarginalDistricts(Vf, VfArray, N) {
90177
90767
  const bestS = bestSeats(N, Vf);
90178
90768
  const fptpS = estFPTPSeats(VfArray);
90179
90769
  // Find the marginal districts IDs (indexed 1–N)
@@ -90192,137 +90782,9 @@ function estMarginalCompetitiveShare(Vf, VfArray, N) {
90192
90782
  minId = Math.max((N - bestS) - 1, 1);
90193
90783
  maxId = Math.min((N - bestS) + 1, N);
90194
90784
  }
90195
- const deltaS = maxId - minId + 1;
90196
- // Sort the array values, and subset it to those districts
90197
- // NOTE - I'm *not* keeping track of the district indexes right now
90198
- let subsetVfArray = VfArray.sort().slice(minId - 1, maxId);
90199
- // Est. competitive districts on that array
90200
- const Md = U.sumArray(subsetVfArray.map(v => estDistrictCompetitiveness(v)));
90201
- // Est. competitive district share on that result
90202
- const MdShare = U.trim(estCompetitiveDistrictsShare(Md, deltaS));
90203
- return U.trim(MdShare);
90204
- }
90205
- exports.estMarginalCompetitiveShare = estMarginalCompetitiveShare;
90206
-
90207
-
90208
- /***/ }),
90209
-
90210
- /***/ "./src/index.ts":
90211
- /*!**********************!*\
90212
- !*** ./src/index.ts ***!
90213
- \**********************/
90214
- /*! no static exports found */
90215
- /***/ (function(module, exports, __webpack_require__) {
90216
-
90217
- "use strict";
90218
-
90219
- //
90220
- // SCORE PACKAGE API
90221
- //
90222
- function __export(m) {
90223
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
90224
- }
90225
- Object.defineProperty(exports, "__esModule", { value: true });
90226
- __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
90227
- var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
90228
- exports.sampleProfile = types_1.sampleProfile;
90229
- exports.sampleScorecard = types_1.sampleScorecard;
90230
-
90231
-
90232
- /***/ }),
90233
-
90234
- /***/ "./src/normalize.ts":
90235
- /*!**************************!*\
90236
- !*** ./src/normalize.ts ***!
90237
- \**************************/
90238
- /*! no static exports found */
90239
- /***/ (function(module, exports, __webpack_require__) {
90240
-
90241
- "use strict";
90242
-
90243
- //
90244
- // NORMALIZATION UTILITIES
90245
- //
90246
- var __importStar = (this && this.__importStar) || function (mod) {
90247
- if (mod && mod.__esModule) return mod;
90248
- var result = {};
90249
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
90250
- result["default"] = mod;
90251
- return result;
90252
- };
90253
- Object.defineProperty(exports, "__esModule", { value: true });
90254
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90255
- class Normalizer {
90256
- constructor(rawValue) {
90257
- this.rawNum = rawValue;
90258
- this.wipNum = this.rawNum;
90259
- }
90260
- // *Don't* transform the input.
90261
- identity() {
90262
- return this.wipNum;
90263
- }
90264
- // TODO - Do I need this? If so, add TEST cases.
90265
- positive() {
90266
- this.wipNum = Math.abs(this.wipNum);
90267
- return this.wipNum;
90268
- }
90269
- // Invert a value in the unit range [0.0–1.0] (so that bigger is better).
90270
- invert() {
90271
- // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90272
- console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), S.OUT_OF_RANGE_MSG);
90273
- this.wipNum = 1.0 - this.wipNum;
90274
- return this.wipNum;
90275
- }
90276
- // Constrain the value to be within a specified range.
90277
- clip(begin_range, end_range) {
90278
- // Handle the ends of the range being given either order
90279
- const min_range = Math.min(begin_range, end_range);
90280
- const max_range = Math.max(begin_range, end_range);
90281
- this.wipNum = Math.max(Math.min(this.wipNum, max_range), min_range);
90282
- return this.wipNum;
90283
- }
90284
- // Recast the value as the delta from a baseline value. NOOP if it is zero.
90285
- // NOTE - Values can be + or -.
90286
- rebase(base) {
90287
- this.wipNum = this.wipNum - base;
90288
- return this.wipNum;
90289
- }
90290
- // Re-scale a value into the [0.0 – 1.0] range, using a given range.
90291
- // NOTE - This assumes that values have alrady been clipped into the range.
90292
- unitize(begin_range, end_range) {
90293
- const min_range = Math.min(begin_range, end_range);
90294
- const max_range = Math.max(begin_range, end_range);
90295
- // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90296
- console.assert(((this.wipNum >= min_range) && (this.wipNum <= max_range)), S.OUT_OF_RANGE_MSG);
90297
- const ranged = this.wipNum - min_range;
90298
- this.wipNum = Math.abs(ranged / (end_range - begin_range));
90299
- return this.wipNum;
90300
- }
90301
- // Decay a value in the unit range [0.0–1.0] by its distance from zero.
90302
- // NOTE - If the range is already such that "bigger is better," then the closer
90303
- // the value is to 1.0 (the best) the *less* it will decay.
90304
- decay(fn = 0 /* Gravity */) {
90305
- // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90306
- console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), S.OUT_OF_RANGE_MSG);
90307
- switch (fn) {
90308
- case 0 /* Gravity */: {
90309
- this.wipNum = Math.pow(this.wipNum, S.DISTANCE_WEIGHT);
90310
- return this.wipNum;
90311
- }
90312
- default: {
90313
- throw new Error("Decay function not recognized.");
90314
- }
90315
- }
90316
- }
90317
- // Translate a value in the unit range to the user-friendly range [0 – 100].
90318
- rescale() {
90319
- // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90320
- console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), S.OUT_OF_RANGE_MSG);
90321
- this.normalizedNum = Math.round(this.wipNum * S.NORMALIZED_RANGE);
90322
- return this.normalizedNum;
90323
- }
90785
+ return [minId, maxId];
90324
90786
  }
90325
- exports.Normalizer = Normalizer;
90787
+ exports.findMarginalDistricts = findMarginalDistricts;
90326
90788
 
90327
90789
 
90328
90790
  /***/ }),
@@ -90347,74 +90809,111 @@ var __importStar = (this && this.__importStar) || function (mod) {
90347
90809
  return result;
90348
90810
  };
90349
90811
  Object.defineProperty(exports, "__esModule", { value: true });
90812
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
90350
90813
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90814
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
90351
90815
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
90352
90816
  const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
90353
90817
  const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
90354
- const fair_1 = __webpack_require__(/*! ./fair */ "./src/fair.ts");
90355
- // TODO - Score opportunity for minority representation
90356
- function scorePlan(p, overridesJSON = undefined) {
90357
- // BEST subcategories - compactness, splitting, population deviation
90818
+ const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
90819
+ const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
90820
+ function scorePlan(p, overridesJSON) {
90821
+ // TRADITIONAL DISTRICTING PRINCIPLES ("best") subcategories - compactness, splitting, population deviation
90358
90822
  // Compactness
90359
- const reockM = compact_1.doReock(p.compactnessProfile.GeometryByDistrict);
90360
- const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.GeometryByDistrict);
90361
- const compactnessScore = Math.round(((S.REOCK_WEIGHT * reockM.normalized) + (S.POLSBY_WEIGHT * polsbyM.normalized)) / (S.REOCK_WEIGHT + S.POLSBY_WEIGHT));
90823
+ const reockM = compact_1.doReock(p.compactnessProfile.geometryByDistrict);
90824
+ const polsbyM = compact_1.doPolsbyPopper(p.compactnessProfile.geometryByDistrict);
90825
+ const compactnessScore = weightCompactness(reockM.normalized, polsbyM.normalized);
90362
90826
  const cS = {
90363
90827
  score: compactnessScore,
90364
90828
  reock: reockM,
90365
90829
  polsby: polsbyM
90366
90830
  };
90367
90831
  // Splitting
90368
- const CxD = p.splittingProfile.CountyPopByDistrict;
90832
+ const CxD = p.splittingProfile.countyPopByDistrict;
90369
90833
  const dT = cohesive_1.totalDistricts(CxD);
90370
90834
  const cT = cohesive_1.totalCounties(CxD);
90371
90835
  const countyM = cohesive_1.doCountySplittingReduced(CxD, dT, cT);
90372
90836
  const districtM = cohesive_1.doDistrictSplittingReduced(CxD, dT, cT);
90373
- const splittingScore = Math.round(((S.COUNTY_WEIGHT * countyM.normalized) + (S.DISTANCE_WEIGHT * districtM.normalized)) / (S.COUNTY_WEIGHT + S.DISTANCE_WEIGHT));
90837
+ const splittingScore = weightSplitting(countyM.normalized, districtM.normalized);
90374
90838
  const sS = {
90375
90839
  score: splittingScore,
90376
90840
  county: countyM,
90377
90841
  district: districtM
90378
90842
  };
90379
90843
  // Population deviation
90380
- const eS = equal_1.doPopulationDeviation(p.populationProfile.TotalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
90381
- // Combine compactness, splitting, & population deviation into a "best" score
90382
- const bestScore = Math.round(((S.COMPACTNESS_WEIGHT * cS.score) + (S.SPLITTING_WEIGHT * sS.score) + (S.POPDEV_WEIGHT * eS.normalized)) / (S.COMPACTNESS_WEIGHT + S.SPLITTING_WEIGHT + S.POPDEV_WEIGHT));
90383
- // Populate the "best" scorecard
90384
- const bS = {
90385
- BestScore: bestScore,
90844
+ const pdS = equal_1.doPopulationDeviation(p.populationProfile.totalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
90845
+ // Combine traditional principles into a score to compare plans w/in a state
90846
+ const tpScore = weightTradtionalPrinciples(cS.score, sS.score, pdS.normalized, 1 /* WithinAState */);
90847
+ // Populate the "best" traditional principles scorecard
90848
+ const tpS = {
90849
+ score: tpScore,
90386
90850
  compactness: cS,
90387
90851
  splitting: sS,
90388
- populationDeviation: eS
90852
+ populationDeviation: pdS
90389
90853
  };
90390
- // FAIR subcategories - bias & competitiveness
90391
- // NOTE - This calculates a # of measures of bias & competitiveness.
90392
- const pS = fair_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.VfArray);
90393
- pS.score = fair_1.weightPartisan(pS.bias.normalized, pS.competitiveness.normalized);
90394
- // Combine "best" and "fair" scores into an overall score for the plan
90395
- const score = Math.round(((S.FAIR_WEIGHT * pS.score) + (S.BEST_WEIGHT * bS.BestScore)) / (S.FAIR_WEIGHT + S.BEST_WEIGHT));
90854
+ // PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
90855
+ const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.vfArray);
90856
+ // Combine the partisan/partisan & traditional principles/best scores into an overall
90857
+ // score for comparing plans w/in a state
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);
90396
90862
  // Roll up an overall scorecard
90397
90863
  const scorecard = {
90398
- score: score,
90399
- best: bS,
90400
- fair: pS
90864
+ partisan: pS,
90865
+ minority: mS,
90866
+ traditionalPrinciples: tpS,
90867
+ score: score
90401
90868
  };
90402
90869
  return scorecard;
90403
90870
  }
90404
90871
  exports.scorePlan = scorePlan;
90872
+ // HELPERS
90873
+ function weightCompactness(rS, ppS) {
90874
+ const rW = C.reockWeight();
90875
+ const ppW = C.polsbyWeight();
90876
+ const score = Math.round(((rS * rW) + (ppS * ppW)) / (rW + ppW));
90877
+ return score;
90878
+ }
90879
+ exports.weightCompactness = weightCompactness;
90880
+ function weightSplitting(csS, dsS) {
90881
+ const csW = C.countySplittingWeight();
90882
+ const dsW = C.districtSplittingWeight();
90883
+ const score = Math.round(((csS * csW) + (dsS * dsW)) / (csW + dsW));
90884
+ return score;
90885
+ }
90886
+ exports.weightSplitting = weightSplitting;
90887
+ function weightTradtionalPrinciples(cS, sS, pdS, context) {
90888
+ const cW = C.compactnessWeight(context);
90889
+ const sW = C.splittingWeight(context);
90890
+ const pdW = C.popdevWeight(context);
90891
+ const score = Math.round(((cS * cW) + (sS * sW) + (pdS * pdW)) / (cW + sW + pdW));
90892
+ return score;
90893
+ }
90894
+ exports.weightTradtionalPrinciples = weightTradtionalPrinciples;
90895
+ function weightOverall(pS, tpS, context) {
90896
+ const pW = C.partisanWeight(context);
90897
+ const tpW = C.traditionalPrinciplesWeight(context);
90898
+ const score = Math.round(((pS * pW) + (tpS * tpW)) / (pW + tpW));
90899
+ return score;
90900
+ }
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;
90907
+ function printScorecardHeader() {
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$, $$$');
90909
+ }
90910
+ exports.printScorecardHeader = printScorecardHeader;
90911
+ function printScorecardRow(xx, name, N, Vf, s) {
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);
90913
+ }
90914
+ exports.printScorecardRow = printScorecardRow;
90405
90915
 
90406
90916
 
90407
- /***/ }),
90408
-
90409
- /***/ "./src/scoring-defaults.json":
90410
- /*!***********************************!*\
90411
- !*** ./src/scoring-defaults.json ***!
90412
- \***********************************/
90413
- /*! exports provided: fair, best, default */
90414
- /***/ (function(module) {
90415
-
90416
- module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\"weight\":80},\"competitive\":{\"worst\":0,\"best\":0.67,\"weight\":20,\"min\":0.25,\"max\":0.75,\"marginal\":75,\"overall\":25},\"weight\":80},\"best\":{\"compact\":{\"reock\":{\"worst\":0.25,\"best\":0.5,\"weight\":50},\"polsby\":{\"worst\":0.1,\"best\":0.5,\"weight\":50},\"weight\":50},\"cohesive\":{\"county\":{\"best\":1,\"worst\":1.71,\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"best\":1,\"worst\":1.5,\"weight\":50},\"weight\":50},\"equal\":{\"worst\":0.0075,\"best\":0.002,\"stateLeg\":{\"worst\":0.1},\"weight\":0},\"weight\":20}}");
90417
-
90418
90917
  /***/ }),
90419
90918
 
90420
90919
  /***/ "./src/settings.ts":
@@ -90429,16 +90928,13 @@ module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\
90429
90928
  //
90430
90929
  // GLOBAL CONSTANTS
90431
90930
  //
90432
- var __importDefault = (this && this.__importDefault) || function (mod) {
90433
- return (mod && mod.__esModule) ? mod : { "default": mod };
90434
- };
90435
90931
  Object.defineProperty(exports, "__esModule", { value: true });
90436
90932
  // Normalized scores [0-100]
90437
90933
  exports.NORMALIZED_RANGE = 100;
90438
90934
  // Square deviations from the ideal
90439
90935
  exports.DISTANCE_WEIGHT = 2;
90440
90936
  // Out of range message
90441
- exports.OUT_OF_RANGE_MSG = "# out of range";
90937
+ exports.OUT_OF_RANGE_MSG = "%f out of range";
90442
90938
  // A small delta to use when testing ranges of values
90443
90939
  exports.EPSILON = 1 / Math.pow(10, 6);
90444
90940
  // Keep 4 decimals for fractions [0.0–1.0], i.e., 2 for %'s [0–100]
@@ -90446,68 +90942,6 @@ exports.PRECISION = 4;
90446
90942
  // "Roughly equal" = average census block size / 2
90447
90943
  exports.AVERAGE_BLOCK_SIZE = 30;
90448
90944
  exports.EQUAL_TOLERANCE = exports.AVERAGE_BLOCK_SIZE / 2;
90449
- // SCORING PARAMETERS
90450
- const scoring_defaults_json_1 = __importDefault(__webpack_require__(/*! ./scoring-defaults.json */ "./src/scoring-defaults.json"));
90451
- function readOverrides() {
90452
- // TODO - TERRY: Replumb this to:
90453
- // - Respond to an environment variable or config.json, and
90454
- // - Read settings.json at runtime from S3
90455
- // - Otherwise return false/null, so the conditionals below work
90456
- // - Note the utils/settings interdependency
90457
- // return U.readJSON('scoring-overrides.json');
90458
- return undefined;
90459
- }
90460
- exports.readOverrides = readOverrides;
90461
- let overrides = readOverrides();
90462
- // SETTINGS FOR PARTISAN SCORING
90463
- exports.MIN_CONTESTED = overrides ? overrides.fair.competitive.min : scoring_defaults_json_1.default.fair.competitive.min;
90464
- exports.MAX_CONTESTED = overrides ? overrides.fair.competitive.max : scoring_defaults_json_1.default.fair.competitive.max;
90465
- // SCALES FOR NORMALIZING RAW VALUES
90466
- // Compactness scales
90467
- exports.REOCK_WORST = overrides ? overrides.best.compact.reock.worst : scoring_defaults_json_1.default.best.compact.reock.worst;
90468
- exports.REOCK_BEST = overrides ? overrides.best.compact.reock.best : scoring_defaults_json_1.default.best.compact.reock.best;
90469
- exports.POLSBY_WORST = overrides ? overrides.best.compact.polsby.worst : scoring_defaults_json_1.default.best.compact.polsby.worst;
90470
- exports.POLSBY_BEST = overrides ? overrides.best.compact.polsby.best : scoring_defaults_json_1.default.best.compact.polsby.best;
90471
- // County-District splitting scales (not inverted)
90472
- exports.COUNTY_BEST = overrides ? overrides.best.cohesive.county.best : scoring_defaults_json_1.default.best.cohesive.county.best;
90473
- exports.COUNTY_WORST = overrides ? overrides.best.cohesive.county.worst : scoring_defaults_json_1.default.best.cohesive.county.worst;
90474
- exports.ALLOWABLE_SPLITS_MULTIPLIER = overrides ? overrides.best.cohesive.county.allowableSplitsMultiplier : scoring_defaults_json_1.default.best.cohesive.county.allowableSplitsMultiplier;
90475
- exports.DISTRICT_BEST = overrides ? overrides.best.cohesive.district.best : scoring_defaults_json_1.default.best.cohesive.district.best;
90476
- exports.DISTRICT_WORST = overrides ? overrides.best.cohesive.district.worst : scoring_defaults_json_1.default.best.cohesive.district.worst;
90477
- // Population deviation thresholds & scales (inverted)
90478
- exports.POPDEV_WORST = overrides ? overrides.best.equal.worst : scoring_defaults_json_1.default.best.equal.worst;
90479
- exports.POPDEV_BEST = overrides ? overrides.best.equal.best : scoring_defaults_json_1.default.best.equal.best;
90480
- exports.POPEQ_MIN = 1.0 - exports.POPDEV_WORST;
90481
- exports.POPEQ_MAX = 1.0 - exports.POPDEV_BEST;
90482
- exports.POPDEV_WORST_LD = overrides ? overrides.best.equal.stateLeg.worst : scoring_defaults_json_1.default.best.equal.stateLeg.worst;
90483
- exports.POPDEV_BEST_LD = ((exports.POPDEV_BEST / exports.POPDEV_WORST) * exports.POPDEV_WORST_LD);
90484
- exports.POPEQ_MIN_LD = 1.0 - exports.POPDEV_WORST_LD;
90485
- exports.POPEQ_MAX_LD = 1.0 - exports.POPDEV_BEST_LD;
90486
- // Bias & competitiveness scales
90487
- // TODO - SCORE:
90488
- // * How wide a range do we want for bias? 10%? 20%?
90489
- // * Do we want it to be state-specific? E.g., one seat?
90490
- exports.BIAS_WORST = overrides ? overrides.fair.unbiased.worst : scoring_defaults_json_1.default.fair.unbiased.worst;
90491
- exports.BIAS_BEST = overrides ? overrides.fair.unbiased.best : scoring_defaults_json_1.default.fair.unbiased.best;
90492
- // The range for raw overall competitiveness values
90493
- exports.COMPETITIVE_WORST = overrides ? overrides.fair.competitive.worst : scoring_defaults_json_1.default.fair.competitive.worst;
90494
- exports.COMPETITIVE_BEST = overrides ? overrides.fair.competitive.best : scoring_defaults_json_1.default.fair.competitive.best;
90495
- // WEIGHTS FOR COMBINING NORMALIZED VALUES
90496
- exports.REOCK_WEIGHT = overrides ? overrides.best.compact.reock.weight : scoring_defaults_json_1.default.best.compact.reock.weight;
90497
- exports.POLSBY_WEIGHT = overrides ? overrides.best.compact.polsby.weight : scoring_defaults_json_1.default.best.compact.polsby.weight;
90498
- exports.COUNTY_WEIGHT = overrides ? overrides.best.cohesive.county.weight : scoring_defaults_json_1.default.best.cohesive.county.weight;
90499
- exports.DISTRICT_WEIGHT = overrides ? overrides.best.cohesive.district.weight : scoring_defaults_json_1.default.best.cohesive.district.weight;
90500
- exports.COMPACTNESS_WEIGHT = overrides ? overrides.best.compact.weight : scoring_defaults_json_1.default.best.compact.weight;
90501
- exports.SPLITTING_WEIGHT = overrides ? overrides.best.cohesive.weight : scoring_defaults_json_1.default.best.cohesive.weight;
90502
- exports.POPDEV_WEIGHT = overrides ? overrides.best.equal.weight : scoring_defaults_json_1.default.best.equal.weight;
90503
- // TODO - SCORE: What kind of relative weighting do we want between marginal and
90504
- // overall competitiveness?
90505
- exports.MARGINAL_WEIGHT = overrides ? overrides.fair.competitive.marginal : scoring_defaults_json_1.default.fair.competitive.marginal;
90506
- exports.OVERALL_WEIGHT = overrides ? overrides.fair.competitive.overall : scoring_defaults_json_1.default.fair.competitive.overall;
90507
- exports.BIAS_WEIGHT = overrides ? overrides.fair.unbiased.weight : scoring_defaults_json_1.default.fair.unbiased.weight;
90508
- exports.COMPETITIVE_WEIGHT = overrides ? overrides.fair.competitive.weight : scoring_defaults_json_1.default.fair.competitive.weight;
90509
- exports.FAIR_WEIGHT = overrides ? overrides.fair.weight : scoring_defaults_json_1.default.fair.weight;
90510
- exports.BEST_WEIGHT = overrides ? overrides.best.weight : scoring_defaults_json_1.default.best.weight;
90511
90945
 
90512
90946
 
90513
90947
  /***/ }),
@@ -90528,9 +90962,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
90528
90962
  return (mod && mod.__esModule) ? mod : { "default": mod };
90529
90963
  };
90530
90964
  Object.defineProperty(exports, "__esModule", { value: true });
90531
- const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/profiles/sample-profile.json */ "./testdata/profiles/sample-profile.json"));
90965
+ const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-profile.json */ "./testdata/samples/sample-profile.json"));
90532
90966
  exports.sampleProfile = sample_profile_json_1.default;
90533
- const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/profiles/sample-scorecard.json */ "./testdata/profiles/sample-scorecard.json"));
90967
+ const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-scorecard.json */ "./testdata/samples/sample-scorecard.json"));
90534
90968
  exports.sampleScorecard = sample_scorecard_json_1.default;
90535
90969
 
90536
90970
 
@@ -90612,25 +91046,25 @@ exports.deepCopy = deepCopy;
90612
91046
 
90613
91047
  /***/ }),
90614
91048
 
90615
- /***/ "./testdata/profiles/sample-profile.json":
90616
- /*!***********************************************!*\
90617
- !*** ./testdata/profiles/sample-profile.json ***!
90618
- \***********************************************/
91049
+ /***/ "./testdata/samples/sample-profile.json":
91050
+ /*!**********************************************!*\
91051
+ !*** ./testdata/samples/sample-profile.json ***!
91052
+ \**********************************************/
90619
91053
  /*! exports provided: state, planName, nDistricts, nCounties, legislativeDistricts, populationProfile, compactnessProfile, splittingProfile, partisanProfile, demographicProfile, default */
90620
91054
  /***/ (function(module) {
90621
91055
 
90622
- 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]]}}");
90623
91057
 
90624
91058
  /***/ }),
90625
91059
 
90626
- /***/ "./testdata/profiles/sample-scorecard.json":
90627
- /*!*************************************************!*\
90628
- !*** ./testdata/profiles/sample-scorecard.json ***!
90629
- \*************************************************/
90630
- /*! exports provided: score, best, fair, default */
91060
+ /***/ "./testdata/samples/sample-scorecard.json":
91061
+ /*!************************************************!*\
91062
+ !*** ./testdata/samples/sample-scorecard.json ***!
91063
+ \************************************************/
91064
+ /*! exports provided: score, partisan, minority, traditionalPrinciples, default */
90631
91065
  /***/ (function(module) {
90632
91066
 
90633
- module.exports = JSON.parse("{\"score\":5,\"best\":{\"BestScore\":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}}},\"fair\":{\"bias\":{\"BestS\":7,\"BestSf\":0.5385,\"ProbableS\":4.1925,\"ProbableSf\":0.3225,\"Bias\":0.216,\"normalized\":0,\"notes\":{}},\"competitiveness\":{\"Cd\":0.7266,\"Cdf\":0.0559,\"Mdf\":0.1019,\"normalized\":10,\"notes\":{}},\"score\":2,\"notes\":{}}}");
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}}}}");
90634
91068
 
90635
91069
  /***/ })
90636
91070
 
@@ -100416,6 +100850,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
100416
100850
  return result;
100417
100851
  };
100418
100852
  Object.defineProperty(exports, "__esModule", { value: true });
100853
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js"));
100419
100854
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
100420
100855
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
100421
100856
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
@@ -100443,37 +100878,47 @@ class AnalyticsSession {
100443
100878
  this.districts = new D.Districts(this, SessionRequest['districtShapes']);
100444
100879
  // TODO - SCORE: Toggle
100445
100880
  if (this.useLegacy()) {
100881
+ console.log("Using legacy district-analytics.");
100446
100882
  // NOTE: I've pulled these out of the individual analytics to here. Eventually,
100447
100883
  // we could want them to passed into an analytics session as data, along with
100448
100884
  // everything else. For now, this keeps branching out of the main code.
100449
100885
  results_1.doConfigureScales(this);
100450
100886
  }
100887
+ else {
100888
+ console.log("Using dra-score analytics.");
100889
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
100890
+ results_1.doConfigureScales(this);
100891
+ }
100451
100892
  }
100452
100893
  processConfig(config) {
100453
100894
  // NOTE - Session settings are required:
100454
100895
  // - Analytics suites can be defaulted to all with [], but
100455
100896
  // - Dataset keys must be explicitly specified with 'dataset'
100897
+ // TODO - SCORE: Delete
100456
100898
  config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
100457
100899
  // Default the Census & redistricting cycle to 2010
100458
100900
  if (!(U.keyExists('cycle', config)))
100459
100901
  config['cycle'] = 2010;
100460
100902
  return config;
100461
100903
  }
100904
+ // TODO - SCORE: Toggle = Invert this logic
100462
100905
  useLegacy() {
100463
- return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
100906
+ // TODO - SCORE: Opt-out
100907
+ return (U.keyExists('useScore', this.config) && (this.config['useScore'] != null) && (!(this.config['useScore']))) ? true : false;
100908
+ // TODO - SCORE: Opt-in
100909
+ // return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
100464
100910
  }
100465
100911
  // Using the the data in the analytics session, calculate all the
100466
100912
  // analytics & validations, saving/updating the individual test results.
100467
- analyzePlan(bLog = false, overridesJSON = undefined) {
100913
+ analyzePlan(bLog = false, overridesJSON) {
100468
100914
  try {
100469
100915
  preprocess_1.doPreprocessData(this, bLog);
100470
100916
  analyze_1.doAnalyzeDistricts(this, bLog);
100471
- // TODO - SCORE
100472
100917
  analyze_1.doAnalyzePlan(this, bLog);
100918
+ // TODO - SCORE
100473
100919
  this._profile = score_1.profilePlan(this, bLog);
100474
- this._scorecard = score_1.scorePlan(this._profile, bLog);
100920
+ this._scorecard = score_1.scorePlan(this, this._profile, bLog, overridesJSON);
100475
100921
  results_1.doAnalyzePostProcessing(this, bLog);
100476
- //
100477
100922
  }
100478
100923
  catch (_a) {
100479
100924
  console.log("Exception caught by analyzePlan()");
@@ -100595,13 +101040,17 @@ class AnalyticsSession {
100595
101040
  // NOTE - Not sure why this has to be up here ...
100596
101041
  populationDeviationThreshold() {
100597
101042
  // TODO - SCORE: Toggle
100598
- // const bLegacy = this._bLegacy; DELETE
100599
101043
  if (this.useLegacy()) {
100600
101044
  return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
100601
101045
  }
100602
101046
  else {
100603
- const scorecard = this._scorecard;
100604
- return scorecard.best.populationDeviation.notes['threshold'];
101047
+ // NOTE - This assumes the plan has been profiled
101048
+ const scorer = new Score.Scorer();
101049
+ const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
101050
+ return popdev;
101051
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
101052
+ // NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
101053
+ // return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
100605
101054
  }
100606
101055
  }
100607
101056
  }
@@ -100778,7 +101227,6 @@ class Districts {
100778
101227
  let outerThis = this;
100779
101228
  // Default the pop dev % for the dummy Unassigned district to 0%.
100780
101229
  // Default the pop dev % for real (1–N) but empty districts to 100%.
100781
- // TODO - SCORE: Why did I mark this?
100782
101230
  let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
100783
101231
  // Get the geoIDs assigned to the district
100784
101232
  // Guard against empty districts
@@ -101228,12 +101676,44 @@ exports.doAnalyzeDistricts = doAnalyzeDistricts;
101228
101676
  // calls might make chunking for aync easier.
101229
101677
  function doAnalyzePlan(s, bLog = false) {
101230
101678
  // TODO - SCORE: Toggle
101231
- // const bLegacy = s._bLegacy;
101232
- // TODO - Remove this mechanism. Always run all tests
101233
- // Get the requested suites, and only execute those tests
101234
- let requestedSuites = s.config['suites'];
101235
- // Tests in the "Legal" suite, i.e., pass/ fail constraints
101236
- if (requestedSuites.includes(0 /* Legal */)) {
101679
+ if (s.useLegacy()) {
101680
+ // Disable most legacy analytics
101681
+ // Get the requested suites, and only execute those tests
101682
+ let requestedSuites = s.config['suites'];
101683
+ // Tests in the "Legal" suite, i.e., pass/ fail constraints
101684
+ if (requestedSuites.includes(0 /* Legal */)) {
101685
+ s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
101686
+ s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
101687
+ s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
101688
+ s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
101689
+ // NOTE - I can't check whether a population deviation is legal or not, until
101690
+ // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
101691
+ // the given type of district (CD vs. LD). The EqualPopulation test is derived
101692
+ // from PopulationDeviation, as part of scorecard or test log preparation.
101693
+ // Create an empty test entry here though ...
101694
+ s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
101695
+ }
101696
+ // Tests in the "Fair" suite
101697
+ if (requestedSuites.includes(1 /* Fair */)) {
101698
+ s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
101699
+ s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
101700
+ s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
101701
+ s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
101702
+ s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
101703
+ s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
101704
+ }
101705
+ // Tests in the "Best" suite, i.e., criteria for better/worse
101706
+ if (requestedSuites.includes(2 /* Best */)) {
101707
+ s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
101708
+ s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
101709
+ s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
101710
+ s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
101711
+ s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
101712
+ s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
101713
+ }
101714
+ }
101715
+ else {
101716
+ // TODO - SCORE: Except these. Continue to do these here vs. dra-score.
101237
101717
  s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
101238
101718
  s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
101239
101719
  s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
@@ -101244,24 +101724,8 @@ function doAnalyzePlan(s, bLog = false) {
101244
101724
  // from PopulationDeviation, as part of scorecard or test log preparation.
101245
101725
  // Create an empty test entry here though ...
101246
101726
  s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
101247
- }
101248
- // Tests in the "Fair" suite
101249
- if (requestedSuites.includes(1 /* Fair */)) {
101250
- s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
101251
- s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
101252
- s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
101253
- s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
101254
- s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
101255
- s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
101256
- }
101257
- // Tests in the "Best" suite, i.e., criteria for better/worse
101258
- if (requestedSuites.includes(2 /* Best */)) {
101259
- s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
101260
- s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
101261
101727
  s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
101262
101728
  s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
101263
- s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
101264
- s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
101265
101729
  }
101266
101730
  // Enable a Test Log and Scorecard to be generated
101267
101731
  s.bPlanAnalyzed = true;
@@ -101275,8 +101739,6 @@ exports.doAnalyzePlan = doAnalyzePlan;
101275
101739
  // NOTE - Should this be conditionalized on the test suites requested?
101276
101740
  // Those are encapsulated in reports.ts right now, so not doing that.
101277
101741
  function doDeriveSecondaryTests(s, bLog = false) {
101278
- // TODO - SCORE: Toggle
101279
- // const bLegacy = s._bLegacy;
101280
101742
  s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
101281
101743
  }
101282
101744
  exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
@@ -101896,18 +102358,11 @@ exports.doPopulationDeviation = doPopulationDeviation;
101896
102358
  // NOTE - This validity check is *derived* and depends on population deviation %
101897
102359
  // being computed (above) and normalized in test log & scorecard generation.
101898
102360
  function doHasEqualPopulations(s, bLog = false) {
101899
- // TODO - SCORE: Toggle
101900
- // const bLegacy = s._bLegacy;
101901
- const scorecard = s._scorecard;
101902
- const popDevNormalizedNEW = scorecard.best.populationDeviation.normalized;
101903
- const bScore = (popDevNormalizedNEW > 0) ? true : false;
101904
- // TODO - Get scales
101905
- // TODO - Flow through to RequirementsChecklist
101906
102361
  let test = s.getTest(3 /* EqualPopulation */);
101907
102362
  // Get the normalized population deviation %
101908
102363
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
101909
- let popDevPct = popDevTest['score'];
101910
- let popDevNormalized = popDevTest['normalizedScore'];
102364
+ const popDevPct = popDevTest['score'];
102365
+ const popDevNormalized = popDevTest['normalizedScore'];
101911
102366
  // Populate the test entry
101912
102367
  if (popDevNormalized > 0) {
101913
102368
  test['score'] = true;
@@ -101917,7 +102372,6 @@ function doHasEqualPopulations(s, bLog = false) {
101917
102372
  }
101918
102373
  test['details']['deviation'] = popDevPct;
101919
102374
  test['details']['thresholds'] = popDevTest['details']['scale'];
101920
- console.log("Equal population =", test['score'], bScore);
101921
102375
  // Populate the N+1 summary "district" in district.statistics
101922
102376
  let bEqualPop = s.districts.statistics[D.DistrictField.bEqualPop];
101923
102377
  let summaryRow = s.districts.numberOfRows() - 1;
@@ -102512,12 +102966,14 @@ function prepareRequirementsChecklist(s, bLog = false) {
102512
102966
  const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
102513
102967
  const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
102514
102968
  const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
102515
- const scorecard = s._scorecard;
102516
- const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
102517
- // TODO - SCORE: Toggle
102969
+ // TODO - SCORE: DELETE - This code is hooked correctly to use dra-score
102970
+ // const scorecard = s._scorecard as Score.Scorecard;
102971
+ // const populationDeviation = scorecard.best.populationDeviation.raw;
102518
102972
  const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
102973
+ // console.log("Population deviations =", populationDeviationDetail, populationDeviation);
102974
+ // const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
102519
102975
  const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
102520
- console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
102976
+ // console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
102521
102977
  const xx = s.state.xx;
102522
102978
  // TODO - JSON: Is there a better / easier way to work with the variable?
102523
102979
  const stateReqsDict = state_reqs_json_1.default;
@@ -102785,20 +103241,29 @@ exports.doConfigureScales = doConfigureScales;
102785
103241
  // Do this after analytics have been run and before preparing a test log or scorecard.
102786
103242
  function doAnalyzePostProcessing(s, bLog = false) {
102787
103243
  // TODO - SCORE: Toggle
102788
- // const bLegacy = s._bLegacy;
102789
- // Normalize the raw scores for all the numerics tests
102790
- let testResults = U.getNumericObjectKeys(testDefns);
102791
- for (let testID of testResults) {
102792
- if (testDefns[testID]['normalize']) {
102793
- let testResult = s.getTest(testID);
102794
- let rawScore = testResult['score'];
102795
- let normalizedScore;
102796
- normalizedScore = U.normalize(rawScore, s.testScales[testID]);
102797
- testResult['normalizedScore'] = normalizedScore;
102798
- // Add the scale used to normalize the raw score to the details
102799
- testResult['details']['scale'] = s.testScales[testID].scale;
103244
+ if (s.useLegacy()) {
103245
+ // Normalize the raw scores for all the numerics tests
103246
+ let testResults = U.getNumericObjectKeys(testDefns);
103247
+ for (let testID of testResults) {
103248
+ if (testDefns[testID]['normalize']) {
103249
+ let testResult = s.getTest(testID);
103250
+ let rawScore = testResult['score'];
103251
+ let normalizedScore;
103252
+ normalizedScore = U.normalize(rawScore, s.testScales[testID]);
103253
+ testResult['normalizedScore'] = normalizedScore;
103254
+ // Add the scale used to normalize the raw score to the details
103255
+ testResult['details']['scale'] = s.testScales[testID].scale;
103256
+ }
102800
103257
  }
102801
103258
  }
103259
+ else {
103260
+ // Just populate the normalized population deviation score in the test
103261
+ const scorecard = s._scorecard;
103262
+ let test = s.getTest(4 /* PopulationDeviation */);
103263
+ test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
103264
+ // TODO - DELETE
103265
+ // test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
103266
+ }
102802
103267
  // Derive secondary tests
102803
103268
  analyze_1.doDeriveSecondaryTests(s, bLog);
102804
103269
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
@@ -102829,7 +103294,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
102829
103294
  return result;
102830
103295
  };
102831
103296
  Object.defineProperty(exports, "__esModule", { value: true });
102832
- const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "../dra-score/dist/dra-score.bundle.js"));
103297
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js"));
102833
103298
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
102834
103299
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
102835
103300
  // PROFILE A PLAN
@@ -102843,9 +103308,10 @@ function profilePlan(s, bLog = false) {
102843
103308
  const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
102844
103309
  const splits = makeNakedCxD(s);
102845
103310
  const summaryRow = s.districts.numberOfRows() - 1;
102846
- const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
103311
+ const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
102847
103312
  const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
102848
- const demographicsByDistrict = makeArrayOfDemographics(s);
103313
+ const statewideDemographics = getStatewideDemographics(s);
103314
+ const demographicsByDistrict = getDemographicsByDistrict(s);
102849
103315
  const profile = {
102850
103316
  state: state,
102851
103317
  planName: planName,
@@ -102853,21 +103319,22 @@ function profilePlan(s, bLog = false) {
102853
103319
  nCounties: nCounties,
102854
103320
  legislativeDistricts: s.legislativeDistricts,
102855
103321
  populationProfile: {
102856
- TotalPopByDistrict: popByDistrict,
103322
+ totalPopByDistrict: popByDistrict,
102857
103323
  targetSize: targetSize
102858
103324
  },
102859
103325
  compactnessProfile: {
102860
- GeometryByDistrict: geoPropsByDistrict
103326
+ geometryByDistrict: geoPropsByDistrict
102861
103327
  },
102862
103328
  splittingProfile: {
102863
- CountyPopByDistrict: splits
103329
+ countyPopByDistrict: splits
102864
103330
  },
102865
103331
  partisanProfile: {
102866
103332
  statewideVf: statewideVf,
102867
- VfArray: vpiArray
103333
+ vfArray: vpiArray
102868
103334
  },
102869
103335
  demographicProfile: {
102870
- DemographicsByDistrict: demographicsByDistrict
103336
+ stateMfArray: statewideDemographics,
103337
+ mfArrayByDistrict: demographicsByDistrict
102871
103338
  }
102872
103339
  };
102873
103340
  return profile;
@@ -102901,7 +103368,7 @@ function makeArrayOfGeoProps(s, bLog = false) {
102901
103368
  }
102902
103369
  return geometryByDistrict;
102903
103370
  }
102904
- function makeArrayOfDemographics(s, bLog = false) {
103371
+ function getDemographicsByDistrict(s, bLog = false) {
102905
103372
  let demographicsArray = [];
102906
103373
  // Remove the unassigned & total dummy "districts"
102907
103374
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -102918,10 +103385,44 @@ function makeArrayOfDemographics(s, bLog = false) {
102918
103385
  }
102919
103386
  return demographicsArray;
102920
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
+ }
102921
103401
  // SCORE A PLAN
102922
- function scorePlan(p, bLog = false) {
103402
+ function scorePlan(s, p, bLog = false, overridesJSON) {
102923
103403
  let scorer = new Score.Scorer();
102924
- return scorer.score(p);
103404
+ const scorecard = scorer.score(p, overridesJSON);
103405
+ // TODO - SCORE: Toggle: Before returning, create a dummy population deviation
103406
+ // test, for doHasEqualPopulations() to use later. This is preserving the old
103407
+ // calling sequence.
103408
+ let test = s.getTest(4 /* PopulationDeviation */);
103409
+ // TODO - SCORE: U.trim(popDev)???
103410
+ // const popDev = scorecard.best.populationDeviation.raw;
103411
+ // Get the raw population deviation
103412
+ const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
103413
+ // Populate the test entry
103414
+ test['score'] = popDev;
103415
+ test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
103416
+ // TODO - DELETE
103417
+ // test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
103418
+ // Populate the N+1 summary "district" in district.statistics
103419
+ let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
103420
+ let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
103421
+ let summaryRow = s.districts.numberOfRows() - 1;
103422
+ totalPop[summaryRow] = p.populationProfile.targetSize;
103423
+ popDevPct[summaryRow] = popDev;
103424
+ //
103425
+ return scorecard;
102925
103426
  }
102926
103427
  exports.scorePlan = scorePlan;
102927
103428