@dra2020/district-analytics 3.2.0 → 3.3.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
@@ -89264,7 +89264,15 @@ module.exports = g;
89264
89264
  //
89265
89265
  // THE NODE PACKAGE API
89266
89266
  //
89267
+ var __importStar = (this && this.__importStar) || function (mod) {
89268
+ if (mod && mod.__esModule) return mod;
89269
+ var result = {};
89270
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
89271
+ result["default"] = mod;
89272
+ return result;
89273
+ };
89267
89274
  Object.defineProperty(exports, "__esModule", { value: true });
89275
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89268
89276
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
89269
89277
  class Scorer {
89270
89278
  constructor() {
@@ -89272,6 +89280,9 @@ class Scorer {
89272
89280
  score(profile, overridesJSON) {
89273
89281
  return score_1.scorePlan(profile, overridesJSON);
89274
89282
  }
89283
+ populationDeviationThreshold(bLegislative) {
89284
+ return C.popdevThreshold(bLegislative);
89285
+ }
89275
89286
  }
89276
89287
  exports.Scorer = Scorer;
89277
89288
 
@@ -89300,27 +89311,33 @@ var __importStar = (this && this.__importStar) || function (mod) {
89300
89311
  Object.defineProperty(exports, "__esModule", { value: true });
89301
89312
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89302
89313
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
89314
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89303
89315
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89304
89316
  function scoreCountySplitting(rawValue, nCounties, nDistricts) {
89305
89317
  const _normalizer = new normalize_1.Normalizer(rawValue);
89306
- const 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,202 @@ 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, 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]},\"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
+ // TRADITIONAL DISTRICTING PRINCIPLES
89887
+ function compactnessWeight(context, overridesJSON) {
89888
+ const cW = config_json_1.default.traditionalPrinciples.compactness.weight[context];
89889
+ return cW;
89890
+ }
89891
+ exports.compactnessWeight = compactnessWeight;
89892
+ function reockWeight(overridesJSON) {
89893
+ const rW = config_json_1.default.traditionalPrinciples.compactness.reock.weight;
89894
+ return rW;
89895
+ }
89896
+ exports.reockWeight = reockWeight;
89897
+ function reockRange(overridesJSON) {
89898
+ const range = config_json_1.default.traditionalPrinciples.compactness.reock.range;
89899
+ return range;
89900
+ }
89901
+ exports.reockRange = reockRange;
89902
+ function polsbyWeight(overridesJSON) {
89903
+ const ppW = config_json_1.default.traditionalPrinciples.compactness.polsby.weight;
89904
+ return ppW;
89905
+ }
89906
+ exports.polsbyWeight = polsbyWeight;
89907
+ function polsbyRange(overridesJSON) {
89908
+ const range = config_json_1.default.traditionalPrinciples.compactness.polsby.range;
89909
+ return range;
89910
+ }
89911
+ exports.polsbyRange = polsbyRange;
89912
+ function splittingWeight(context, overridesJSON) {
89913
+ const sW = config_json_1.default.traditionalPrinciples.splitting.weight[context];
89914
+ return sW;
89915
+ }
89916
+ exports.splittingWeight = splittingWeight;
89917
+ function countySplittingWeight(overridesJSON) {
89918
+ const csW = config_json_1.default.traditionalPrinciples.splitting.county.weight;
89919
+ return csW;
89920
+ }
89921
+ exports.countySplittingWeight = countySplittingWeight;
89922
+ function countySplittingRange(overridesJSON) {
89923
+ const range = config_json_1.default.traditionalPrinciples.splitting.county.range;
89924
+ return range;
89925
+ }
89926
+ exports.countySplittingRange = countySplittingRange;
89927
+ function allowableSplitsMultiplier(overridesJSON) {
89928
+ const m = config_json_1.default.traditionalPrinciples.splitting.county.allowableSplitsMultiplier;
89929
+ return m;
89930
+ }
89931
+ exports.allowableSplitsMultiplier = allowableSplitsMultiplier;
89932
+ function districtSplittingWeight(overridesJSON) {
89933
+ const dsW = config_json_1.default.traditionalPrinciples.splitting.district.weight;
89934
+ return dsW;
89935
+ }
89936
+ exports.districtSplittingWeight = districtSplittingWeight;
89937
+ function districtSplittingRange(overridesJSON) {
89938
+ const range = config_json_1.default.traditionalPrinciples.splitting.district.range;
89939
+ return range;
89940
+ }
89941
+ exports.districtSplittingRange = districtSplittingRange;
89942
+ function popdevWeight(context, overridesJSON) {
89943
+ const pdW = config_json_1.default.traditionalPrinciples.popdev.weight[context];
89944
+ return pdW;
89945
+ }
89946
+ exports.popdevWeight = popdevWeight;
89947
+ // NOTE - Raw ranges, not inverted (i.e., smaller is better)
89948
+ // NOTE - This could be optimized to not calc LD values for CD's (or do it once)
89949
+ function popdevRange(bLegislative, overridesJSON) {
89950
+ const cdRange = config_json_1.default.traditionalPrinciples.popdev.range[0 /* Congressional */];
89951
+ const worstCD = cdRange[exports.BEG];
89952
+ const bestCD = cdRange[exports.END];
89953
+ const ldRange = config_json_1.default.traditionalPrinciples.popdev.range[1 /* StateLegislative */];
89954
+ const iRange = bLegislative ? ldRange : cdRange;
89955
+ const worst = iRange[exports.BEG];
89956
+ const best = bLegislative ? (bestCD / worstCD) * iRange[exports.BEG] : iRange[exports.END];
89957
+ // Invert the range, so bigger is better.
89958
+ return [worst, best];
89959
+ }
89960
+ exports.popdevRange = popdevRange;
89961
+ // NOTE - Raw threshold, not inverted (i.e., smaller is better)
89962
+ function popdevThreshold(bLegislative, overridesJSON) {
89963
+ const threshold = popdevRange(bLegislative)[exports.BEG];
89964
+ return threshold;
89965
+ }
89966
+ exports.popdevThreshold = popdevThreshold;
89967
+ // OVERALL SCORE
89968
+ function partisanWeight(context, overridesJSON) {
89969
+ const pW = config_json_1.default.partisan.weight[context];
89970
+ return pW;
89971
+ }
89972
+ exports.partisanWeight = partisanWeight;
89973
+ function traditionalPrinciplesWeight(context, overridesJSON) {
89974
+ const tpW = config_json_1.default.traditionalPrinciples.weight[context];
89975
+ return tpW;
89976
+ }
89977
+ exports.traditionalPrinciplesWeight = traditionalPrinciplesWeight;
89978
+
89979
+
89773
89980
  /***/ }),
89774
89981
 
89775
89982
  /***/ "./src/equal.ts":
@@ -89793,7 +90000,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
89793
90000
  };
89794
90001
  Object.defineProperty(exports, "__esModule", { value: true });
89795
90002
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89796
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90003
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89797
90004
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89798
90005
  // Migrated from district-analytics
89799
90006
  // Expect a full dict of district IDs & values (which might be zero = empty)
@@ -89814,7 +90021,7 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
89814
90021
  // Round the raw value to the desired level of precision
89815
90022
  const popDev = U.trim((max - min) / targetSize);
89816
90023
  const score = scorePopulationDeviation(popDev, bLegislative);
89817
- const threshold = bLegislative ? S.POPDEV_WORST_LD : S.POPDEV_WORST;
90024
+ const threshold = C.popdevThreshold(bLegislative);
89818
90025
  const notes = {
89819
90026
  'maxDeviation': max - min,
89820
90027
  'threshold': threshold
@@ -89830,11 +90037,11 @@ function doPopulationDeviation(e, targetSize, bLegislative, bLog = false) {
89830
90037
  exports.doPopulationDeviation = doPopulationDeviation;
89831
90038
  function scorePopulationDeviation(rawValue, bLegislative) {
89832
90039
  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;
90040
+ // Raw range in not inverted (i.e., smaller is better)
90041
+ const range = C.popdevRange(bLegislative);
89835
90042
  _normalizer.invert();
89836
- _normalizer.clip(rangeMin, rangeMax);
89837
- _normalizer.unitize(rangeMin, rangeMax);
90043
+ _normalizer.clip(1.0 - range[C.BEG], 1.0 - range[C.END]);
90044
+ _normalizer.unitize(1.0 - range[C.BEG], 1.0 - range[C.END]);
89838
90045
  _normalizer.rescale();
89839
90046
  return _normalizer.normalizedNum;
89840
90047
  }
@@ -89843,10 +90050,129 @@ exports.scorePopulationDeviation = scorePopulationDeviation;
89843
90050
 
89844
90051
  /***/ }),
89845
90052
 
89846
- /***/ "./src/fair.ts":
89847
- /*!*********************!*\
89848
- !*** ./src/fair.ts ***!
89849
- \*********************/
90053
+ /***/ "./src/index.ts":
90054
+ /*!**********************!*\
90055
+ !*** ./src/index.ts ***!
90056
+ \**********************/
90057
+ /*! no static exports found */
90058
+ /***/ (function(module, exports, __webpack_require__) {
90059
+
90060
+ "use strict";
90061
+
90062
+ //
90063
+ // SCORE PACKAGE API
90064
+ //
90065
+ function __export(m) {
90066
+ for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
90067
+ }
90068
+ Object.defineProperty(exports, "__esModule", { value: true });
90069
+ __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
90070
+ var types_1 = __webpack_require__(/*! ./types */ "./src/types.ts");
90071
+ exports.sampleProfile = types_1.sampleProfile;
90072
+ exports.sampleScorecard = types_1.sampleScorecard;
90073
+
90074
+
90075
+ /***/ }),
90076
+
90077
+ /***/ "./src/normalize.ts":
90078
+ /*!**************************!*\
90079
+ !*** ./src/normalize.ts ***!
90080
+ \**************************/
90081
+ /*! no static exports found */
90082
+ /***/ (function(module, exports, __webpack_require__) {
90083
+
90084
+ "use strict";
90085
+
90086
+ //
90087
+ // NORMALIZATION UTILITIES
90088
+ //
90089
+ var __importStar = (this && this.__importStar) || function (mod) {
90090
+ if (mod && mod.__esModule) return mod;
90091
+ var result = {};
90092
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
90093
+ result["default"] = mod;
90094
+ return result;
90095
+ };
90096
+ Object.defineProperty(exports, "__esModule", { value: true });
90097
+ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90098
+ class Normalizer {
90099
+ constructor(rawValue) {
90100
+ this.rawNum = rawValue;
90101
+ this.wipNum = this.rawNum;
90102
+ }
90103
+ // *Don't* transform the input.
90104
+ identity() {
90105
+ return this.wipNum;
90106
+ }
90107
+ positive() {
90108
+ this.wipNum = Math.abs(this.wipNum);
90109
+ return this.wipNum;
90110
+ }
90111
+ // Invert a value in the unit range [0.0–1.0] (so that bigger is better).
90112
+ invert() {
90113
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90114
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Inverting: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90115
+ this.wipNum = 1.0 - this.wipNum;
90116
+ return this.wipNum;
90117
+ }
90118
+ // Constrain the value to be within a specified range.
90119
+ clip(begin_range, end_range) {
90120
+ // Handle the ends of the range being given either order
90121
+ const min_range = Math.min(begin_range, end_range);
90122
+ const max_range = Math.max(begin_range, end_range);
90123
+ this.wipNum = Math.max(Math.min(this.wipNum, max_range), min_range);
90124
+ return this.wipNum;
90125
+ }
90126
+ // Recast the value as the delta from a baseline value. NOOP if it is zero.
90127
+ // NOTE - Values can be + or -.
90128
+ rebase(base) {
90129
+ this.wipNum = this.wipNum - base;
90130
+ return this.wipNum;
90131
+ }
90132
+ // Re-scale a value into the [0.0 – 1.0] range, using a given range.
90133
+ // NOTE - This assumes that values have alrady been clipped into the range.
90134
+ unitize(begin_range, end_range) {
90135
+ const min_range = Math.min(begin_range, end_range);
90136
+ const max_range = Math.max(begin_range, end_range);
90137
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90138
+ console.assert(((this.wipNum >= min_range) && (this.wipNum <= max_range)), "Unitizing: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90139
+ const ranged = this.wipNum - min_range;
90140
+ this.wipNum = Math.abs(ranged / (end_range - begin_range));
90141
+ return this.wipNum;
90142
+ }
90143
+ // Decay a value in the unit range [0.0–1.0] by its distance from zero.
90144
+ // NOTE - If the range is already such that "bigger is better," then the closer
90145
+ // the value is to 1.0 (the best) the *less* it will decay.
90146
+ decay(fn = 0 /* Gravity */) {
90147
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90148
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Decaying: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90149
+ switch (fn) {
90150
+ case 0 /* Gravity */: {
90151
+ this.wipNum = Math.pow(this.wipNum, S.DISTANCE_WEIGHT);
90152
+ return this.wipNum;
90153
+ }
90154
+ default: {
90155
+ throw new Error("Decay function not recognized.");
90156
+ }
90157
+ }
90158
+ }
90159
+ // Translate a value in the unit range to the user-friendly range [0 – 100].
90160
+ rescale() {
90161
+ // TODO - DAVID: Can we make this throw an Error and expect that in unit test?
90162
+ console.assert(((this.wipNum >= 0.0) && (this.wipNum <= 1.0)), "Rescaling: " + S.OUT_OF_RANGE_MSG, this.wipNum);
90163
+ this.normalizedNum = Math.round(this.wipNum * S.NORMALIZED_RANGE);
90164
+ return this.normalizedNum;
90165
+ }
90166
+ }
90167
+ exports.Normalizer = Normalizer;
90168
+
90169
+
90170
+ /***/ }),
90171
+
90172
+ /***/ "./src/partisan.ts":
90173
+ /*!*************************!*\
90174
+ !*** ./src/partisan.ts ***!
90175
+ \*************************/
89850
90176
  /*! no static exports found */
89851
90177
  /***/ (function(module, exports, __webpack_require__) {
89852
90178
 
@@ -89865,48 +90191,61 @@ var __importStar = (this && this.__importStar) || function (mod) {
89865
90191
  Object.defineProperty(exports, "__esModule", { value: true });
89866
90192
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
89867
90193
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90194
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
89868
90195
  const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts");
89869
90196
  // NOTE - I'm passing T.VfArray's into everything. District indices = array indices.
89870
90197
  // NOTE - I do not (cannot) assume that the values are sorted.
89871
90198
  // SCORE BIAS & COMPETITIVENESS
89872
- /* SCORECARD FIELDS:
90199
+ /* SCORECARD FIELDS <<< TODO: Update
89873
90200
 
89874
90201
  * ^S# [BestS] = the Democratic seats closest to proportional
89875
90202
  * ^S% [BestSf] = the corresponding Democratic seat share
89876
- * F# [FptpS] = the estimated number of Democratic seats using first past the post
90203
+ * S! [FptpS] = the estimated number of Democratic seats using first past the post
89877
90204
  * S# [ProbableS] = the estimated Democratic seats, using seat probabilities
89878
90205
  * S% [ProbableSf] = the estimated Democratic seat share fraction, calculated as S# / N
90206
+ * UE# [UnearnedS] = the number of unearned seats (R = positive; D = negative)
89879
90207
  * B% [Bias]= the bias calculated as S% – ^S%
89880
90208
  * B$ [BiasScore] = the bias score normalized [0–100]
89881
90209
 
89882
90210
  * R# [R] = the responsiveness at V%
89883
90211
  * Rd [Rd] = the estimated # of responsive districts (using probabilities)
89884
90212
  * Rd% [Rdf] = the estimated # of responsive districts, as a fraction of N
90213
+
90214
+ * C [C] = the number of districts that fall into the range [45–55%]
89885
90215
  * Cd [Cd] = the estimated # of competitive districts, using probabilities & a narrower [0.25–0.75] range
89886
90216
  * Cd% [Cdf] = the estimated # of competitive districts, as a fraction of N
90217
+ * [beg, end] [Mrange] = the 1–N indices for the first and last district that separate the likely result and the proportional result
90218
+ * Md [Md] = the competitiveness of the marginal districts
89887
90219
  * Md% [Mdf] = the probability that the marginal seats will flip, so S% => ^S%
89888
90220
  * C$ [CompetitiveScore] = the competitiveness score normalized [0–100]
89889
90221
 
89890
- * $$$ = the combined partisan score normalize [0–100]
90222
+ * F$ = the combined partisan score normalize [0–100]
89891
90223
 
89892
90224
  */
89893
- function scorePartisan(Vf, VfArray, all = false) {
90225
+ function scorePartisan(Vf, VfArray, bAll = false, bConstrained = true) {
89894
90226
  const N = VfArray.length;
89895
90227
  const BestS = bestSeats(N, Vf);
89896
90228
  const BestSf = bestSeatShare(BestS, N);
89897
- const FptpS = all ? estFPTPSeats(VfArray) : undefined;
89898
- const ProbableS = estProbableSeats(VfArray);
90229
+ const FptpS = bAll ? estFPTPSeats(VfArray) : undefined;
90230
+ const range = bConstrained ? C.competitiveDistribution() : undefined;
90231
+ const ProbableS = estProbableSeats(VfArray, range);
89899
90232
  const ProbableSf = estProbableSeatShare(ProbableS, N);
89900
90233
  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);
90234
+ const biasScore = scoreBias(Bias, Vf, ProbableSf, N);
90235
+ const UnearnedS = estUnearnedSeats(BestS, ProbableS);
90236
+ const impactScore = scoreImpact(UnearnedS);
90237
+ const bInferSV = (bAll || (!bConstrained));
90238
+ const inferredSVpoints = bInferSV ? inferSVpoints(Vf, VfArray, range) : undefined;
90239
+ const R = bInferSV ? estResponsiveness(Vf, inferredSVpoints) : undefined;
90240
+ const Rd = bInferSV ? estResponsiveDistricts(VfArray) : undefined;
90241
+ const Rdf = bInferSV ? estResponsiveDistrictsShare(Rd, N) : undefined;
90242
+ const Cn = countCompetitiveDistricts(VfArray);
90243
+ const Cd = bConstrained ? estCompetitiveDistricts(VfArray) : Rd;
89907
90244
  const Cdf = estCompetitiveDistrictsShare(Cd, N);
89908
- const Mdf = estMarginalCompetitiveShare(Vf, VfArray, N);
89909
- const normalizedCompetitiveness = scoreCompetitiveness(Mdf, Cdf);
90245
+ const Mrange = findMarginalDistricts(Vf, VfArray, N);
90246
+ const Md = estMarginalCompetitiveDistricts(Mrange, VfArray);
90247
+ const Mdf = estMarginalCompetitiveShare(Md, Mrange);
90248
+ const competitivenessScore = scoreCompetitiveness(Mdf, Cdf);
89910
90249
  const biasScoring = {
89911
90250
  BestS: BestS,
89912
90251
  BestSf: BestSf,
@@ -89914,61 +90253,73 @@ function scorePartisan(Vf, VfArray, all = false) {
89914
90253
  ProbableS: ProbableS,
89915
90254
  ProbableSf: ProbableSf,
89916
90255
  Bias: Bias,
89917
- normalized: normalizedBias
90256
+ score: biasScore
89918
90257
  };
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
- }
90258
+ const impactScoring = {
90259
+ UnearnedS: UnearnedS,
90260
+ score: impactScore
90261
+ };
90262
+ let competitiveScoring = {
90263
+ R: R,
90264
+ Rd: Rd,
90265
+ Rdf: Rdf,
90266
+ C: Cn,
90267
+ Cd: Cd,
90268
+ Cdf: Cdf,
90269
+ Mrange: Mrange,
90270
+ Md: Md,
90271
+ Mdf: Mdf,
90272
+ score: competitivenessScore
90273
+ };
90274
+ if (bAll) {
90275
+ competitiveScoring.R = R;
90276
+ competitiveScoring.Rd = Rd;
90277
+ competitiveScoring.Rdf = Rdf;
90278
+ }
90279
+ // Weight bias, impact, and competitiveness into partisan scores to compare
90280
+ // plans across states and within a state.
90281
+ const bS = biasScoring.score;
90282
+ const iS = impactScoring.score;
90283
+ const cS = competitiveScoring.score;
90284
+ const acrossStatesPartisanScore = weightPartisan(bS, iS, cS, 0 /* AcrossStates */);
90285
+ const withinStatesPartisanScore = weightPartisan(bS, iS, cS, 1 /* WithinAState */);
89939
90286
  const s = {
89940
90287
  bias: biasScoring,
89941
- competitiveness: competitiveScoring
90288
+ impact: impactScoring,
90289
+ competitiveness: competitiveScoring,
90290
+ score: acrossStatesPartisanScore,
90291
+ score2: withinStatesPartisanScore
89942
90292
  };
89943
90293
  return s;
89944
90294
  }
89945
90295
  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));
90296
+ function weightPartisan(bS, iS, cS, context) {
90297
+ const bW = C.biasWeight(context);
90298
+ const iW = C.impactWeight(context);
90299
+ const cW = C.competitivenessWeight(context);
90300
+ const score = Math.round(((bS * bW) + (iS * iW) + (cS * cW)) / (bW + iW + cW));
90301
+ return score;
89948
90302
  }
89949
90303
  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$, $$$');
90304
+ function printPartisanScorecardHeader() {
90305
+ console.log('XX, Name, N, 1/N%, V%, ^S#, ^S%, S!, S#, S%, B%, B$, UE#, I$, R#, Rd, Rd%, C#, Cd, Cd%, beg, end, Md, Md%, C$, <P$, >P$');
89952
90306
  }
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);
90307
+ exports.printPartisanScorecardHeader = printPartisanScorecardHeader;
90308
+ function printPartisanScorecardRow(xx, name, N, Vf, s) {
90309
+ console.log('%s, %s, %i, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %i, %f, %f, %f, %i, %f, %f, %i, %i, %f, %f, %i, %i, %i', xx, name, N, U.trim(1 / N), Vf, s.bias.BestS, s.bias.BestSf, s.bias.FptpS, s.bias.ProbableS, s.bias.ProbableSf, s.bias.Bias, s.bias.score, s.impact.UnearnedS, s.impact.score, s.competitiveness.R, s.competitiveness.Rd, s.competitiveness.Rdf, s.competitiveness.C, s.competitiveness.Cd, s.competitiveness.Cdf, s.competitiveness.Mrange[C.BEG], s.competitiveness.Mrange[C.END], s.competitiveness.Md, s.competitiveness.Mdf, s.competitiveness.score, s.score, s.score2);
89956
90310
  }
89957
- exports.printFairScorecard = printFairScorecard;
90311
+ exports.printPartisanScorecardRow = printPartisanScorecardRow;
89958
90312
  function scoreBias(rawBias, Vf, Sf, N) {
89959
90313
  if (isAntimajoritarian(Vf, Sf)) {
89960
90314
  return 0;
89961
90315
  }
89962
90316
  else {
89963
90317
  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;
90318
+ const worst = C.biasRange()[C.BEG];
90319
+ const best = C.biasRange()[C.END];
89967
90320
  _normalizer.clip(worst, best);
89968
90321
  _normalizer.unitize(worst, best);
89969
90322
  _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
90323
  // _normalizer.decay();
89973
90324
  _normalizer.rescale();
89974
90325
  const score = _normalizer.normalizedNum;
@@ -89976,6 +90327,20 @@ function scoreBias(rawBias, Vf, Sf, N) {
89976
90327
  }
89977
90328
  }
89978
90329
  exports.scoreBias = scoreBias;
90330
+ // Normalize unearned seats
90331
+ function scoreImpact(rawUE) {
90332
+ const _normalizer = new normalize_1.Normalizer(rawUE);
90333
+ const worst = C.unearnedThreshold();
90334
+ const best = 0.0;
90335
+ _normalizer.positive();
90336
+ _normalizer.clip(worst, best);
90337
+ _normalizer.unitize(worst, best);
90338
+ _normalizer.invert();
90339
+ _normalizer.rescale();
90340
+ const score = _normalizer.normalizedNum;
90341
+ return score;
90342
+ }
90343
+ exports.scoreImpact = scoreImpact;
89979
90344
  function isAntimajoritarian(Vf, Sf) {
89980
90345
  const bDem = ((Vf < 0.5) && (Sf > 0.5)) ? true : false;
89981
90346
  const bRep = (((1 - Vf) < 0.5) && ((1 - Sf) > 0.5)) ? true : false;
@@ -89987,16 +90352,24 @@ function scoreCompetitiveness(rawMarginal, rawOverall) {
89987
90352
  // But the practical max is more like 2/3's, so unitize that range to [0.0–1.0].
89988
90353
  // Then scale the values to [0–100].
89989
90354
  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);
90355
+ let worst = C.overallCompetitivenessRange()[C.BEG];
90356
+ let best = C.overallCompetitivenessRange()[C.END];
90357
+ _overall.clip(worst, best);
90358
+ _overall.unitize(worst, best);
89992
90359
  _overall.rescale();
89993
- const overall = _overall.normalizedNum;
90360
+ const ocS = _overall.normalizedNum;
89994
90361
  // Normalize marginal competitiveness
89995
90362
  const _marginal = new normalize_1.Normalizer(rawMarginal);
90363
+ worst = C.marginalCompetitivenessRange()[C.BEG];
90364
+ best = C.marginalCompetitivenessRange()[C.END];
90365
+ _marginal.clip(worst, best);
90366
+ _marginal.unitize(worst, best);
89996
90367
  _marginal.rescale();
89997
- const marginal = _marginal.normalizedNum;
90368
+ const mcS = _marginal.normalizedNum;
90369
+ const mcW = C.marginalCompetitivenessWeight();
90370
+ const ocW = C.overallCompetitivenessWeight();
89998
90371
  // Then combine the results
89999
- const score = Math.round(((S.MARGINAL_WEIGHT * marginal) + (S.OVERALL_WEIGHT * overall)) / (S.MARGINAL_WEIGHT + S.OVERALL_WEIGHT));
90372
+ const score = Math.round(((mcW * mcS) + (ocW * ocS)) / (mcW + ocW));
90000
90373
  return score;
90001
90374
  }
90002
90375
  exports.scoreCompetitiveness = scoreCompetitiveness;
@@ -90006,23 +90379,40 @@ const { erf } = __webpack_require__(/*! mathjs */ "./node_modules/mathjs/main/es
90006
90379
  // console.log("erf(-0.5) =", erf(-0.5)); // returns -0.5204998778130465
90007
90380
  // console.log("erf(4) =", erf(4)); // returns 0.9999999845827421
90008
90381
  // Estimate the probability of a seat win for district, given a Vf
90009
- function estSeatProbability(Vf) {
90382
+ function estSeatProbability(Vf, range) {
90383
+ if (range) {
90384
+ const _normalizer = new normalize_1.Normalizer(Vf);
90385
+ // The end points of a compressed probability distribution
90386
+ // NOTE - These aren't the points where races start or stop being contested,
90387
+ // just the end points of a distribution that yields the desired behavior
90388
+ // in the typical competitive range [45-55%].
90389
+ const distBeg = range[C.BEG];
90390
+ const distEnd = range[C.END];
90391
+ _normalizer.clip(distBeg, distEnd);
90392
+ _normalizer.unitize(distBeg, distEnd);
90393
+ return seatProbabilityFn(_normalizer.wipNum);
90394
+ }
90395
+ else {
90396
+ return seatProbabilityFn(Vf);
90397
+ }
90398
+ }
90399
+ exports.estSeatProbability = estSeatProbability;
90400
+ function seatProbabilityFn(Vf) {
90010
90401
  // Python: 0.5 * (1 + erf((vpi - 0.50) / (0.02 * sqrt(8))))
90011
90402
  return U.trim(0.5 * (1.0 + erf((Vf - 0.50) / (0.02 * Math.sqrt(8)))));
90012
90403
  }
90013
- exports.estSeatProbability = estSeatProbability;
90014
90404
  // Estimate the number of responsive districts [R(d)], given a set of Vf's
90015
90405
  function estDistrictResponsiveness(Vf) {
90016
90406
  // Python: 1 - 4 * (est_seat_probability(vpi) - 0.5)**2
90017
90407
  return U.trim(1.0 - 4.0 * Math.pow((estSeatProbability(Vf) - 0.5), 2));
90018
90408
  }
90019
90409
  exports.estDistrictResponsiveness = estDistrictResponsiveness;
90020
- function inferSVpoints(Vf, VfArray) {
90410
+ function inferSVpoints(Vf, VfArray, range) {
90021
90411
  const nDistricts = VfArray.length;
90022
90412
  let SVpoints = [];
90023
90413
  for (let shiftedVf of shiftRange()) {
90024
90414
  const shiftedVPI = shiftDistricts(Vf, VfArray, shiftedVf);
90025
- const shiftedSf = estProbableSeats(shiftedVPI) / nDistricts;
90415
+ const shiftedSf = estProbableSeats(shiftedVPI, range) / nDistricts;
90026
90416
  SVpoints.push({ v: shiftedVf, s: shiftedSf });
90027
90417
  }
90028
90418
  return SVpoints;
@@ -90060,8 +90450,9 @@ function shiftRange() {
90060
90450
  }
90061
90451
  // ESTIMATE BIAS ("FAIR")
90062
90452
  // ^S# - The # of Democratic seats closest to proportional @ statewide Vf
90453
+ // The "expected number of seats" from http://bit.ly/2Fcuf4q
90063
90454
  function bestSeats(N, Vf) {
90064
- return Math.round(N * Vf);
90455
+ return Math.round((N * Vf) - S.EPSILON);
90065
90456
  }
90066
90457
  exports.bestSeats = bestSeats;
90067
90458
  // ^S% - The corresponding Democratic seat share
@@ -90070,9 +90461,9 @@ function bestSeatShare(bestS, N) {
90070
90461
  }
90071
90462
  exports.bestSeatShare = bestSeatShare;
90072
90463
  // S# - The estimated # of Democratic seats @ statewide Vf, using seat probabilities
90073
- function estProbableSeats(VfArray) {
90464
+ function estProbableSeats(VfArray, range) {
90074
90465
  // Python: sum([est_seat_probability(vpi) for vpi in vpi_by_district])
90075
- return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v))));
90466
+ return U.trim(U.sumArray(VfArray.map(v => estSeatProbability(v, range))));
90076
90467
  }
90077
90468
  exports.estProbableSeats = estProbableSeats;
90078
90469
  // S% - The estimated Democratic seat share fraction @ statewide Vf
@@ -90098,6 +90489,13 @@ function estBias(estSeatShare, bestSeatShare) {
90098
90489
  return U.trim(Math.abs(estSeatShare - bestSeatShare));
90099
90490
  }
90100
90491
  exports.estBias = estBias;
90492
+ // UE# - The estimated # of unearned seats
90493
+ // UE_# from http://bit.ly/2Fcuf4q
90494
+ function estUnearnedSeats(best, probable) {
90495
+ // NOTE - + values = unearned R seats; – values = unearned D seats
90496
+ return U.trim(best - probable);
90497
+ }
90498
+ exports.estUnearnedSeats = estUnearnedSeats;
90101
90499
  // ESTIMATE RESPONSIVENESS ("COMPETITIVE")
90102
90500
  // R# - Estimate responsiveness at the statewide vote share
90103
90501
  function estResponsiveness(Vf, inferredSVpoints) {
@@ -90152,6 +90550,14 @@ function estResponsiveDistrictsShare(Rd, N) {
90152
90550
  }
90153
90551
  exports.estResponsiveDistrictsShare = estResponsiveDistrictsShare;
90154
90552
  // ESTIMATE COMPETITIVENESS (ENHANCED)
90553
+ // C - Count the # of competitive districts, defined as v in [45–55%]
90554
+ function countCompetitiveDistricts(VfArray) {
90555
+ return U.trim(U.sumArray(VfArray.map(v => isCompetitive(v))));
90556
+ }
90557
+ exports.countCompetitiveDistricts = countCompetitiveDistricts;
90558
+ function isCompetitive(v) {
90559
+ return ((v >= C.competitiveRange()[C.BEG]) && (v <= C.competitiveRange()[C.END])) ? 1 : 0;
90560
+ }
90155
90561
  // Cd - The estimated # of competitive districts
90156
90562
  function estCompetitiveDistricts(VfArray) {
90157
90563
  return U.trim(U.sumArray(VfArray.map(v => estDistrictCompetitiveness(v))));
@@ -90160,8 +90566,14 @@ exports.estCompetitiveDistricts = estCompetitiveDistricts;
90160
90566
  // Re-scale a Democratic vote share to the competitive range (e.g., 45–55%).
90161
90567
  function estDistrictCompetitiveness(Vf) {
90162
90568
  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);
90569
+ // The end points of a compressed probability distribution
90570
+ // NOTE - These aren't the points where races start or stop being contested,
90571
+ // just the end points of a distribution that yields the desired behavior
90572
+ // in the typical competitive range [45-55%].
90573
+ const distBeg = C.competitiveDistribution()[C.BEG];
90574
+ const distEnd = C.competitiveDistribution()[C.END];
90575
+ _normalizer.clip(distBeg, distEnd);
90576
+ _normalizer.unitize(distBeg, distEnd);
90165
90577
  const dC = estDistrictResponsiveness(_normalizer.wipNum);
90166
90578
  return dC;
90167
90579
  }
@@ -90171,9 +90583,30 @@ function estCompetitiveDistrictsShare(Cd, N) {
90171
90583
  return U.trim(Cd / N);
90172
90584
  }
90173
90585
  exports.estCompetitiveDistrictsShare = estCompetitiveDistrictsShare;
90586
+ // Md - The estimated # of "marginal" districts in and around the likely FPTP
90587
+ // seats & the best seat split that are competitive.
90588
+ function estMarginalCompetitiveDistricts(Mrange, VfArray) {
90589
+ const minId = Mrange[C.BEG];
90590
+ const maxId = Mrange[C.END];
90591
+ // Sort the array values, and subset it to those districts
90592
+ // NOTE - I'm *not* keeping track of the district indexes right now
90593
+ let subsetVfArray = VfArray.sort().slice(minId - 1, maxId);
90594
+ // Est. competitive districts on that array
90595
+ const Md = U.sumArray(subsetVfArray.map(v => estDistrictCompetitiveness(v)));
90596
+ return U.trim(Md);
90597
+ }
90598
+ exports.estMarginalCompetitiveDistricts = estMarginalCompetitiveDistricts;
90174
90599
  // Md% - The estimated competitiveness of the "marginal" districts in and around
90175
90600
  // the likely FPTP seats & the best seat split as a fraction
90176
- function estMarginalCompetitiveShare(Vf, VfArray, N) {
90601
+ function estMarginalCompetitiveShare(Md, Mrange) {
90602
+ const minId = Mrange[C.BEG];
90603
+ const maxId = Mrange[C.END];
90604
+ // Est. competitive district share on that result
90605
+ const MdShare = U.trim(estCompetitiveDistrictsShare(Md, maxId - minId + 1));
90606
+ return U.trim(MdShare);
90607
+ }
90608
+ exports.estMarginalCompetitiveShare = estMarginalCompetitiveShare;
90609
+ function findMarginalDistricts(Vf, VfArray, N) {
90177
90610
  const bestS = bestSeats(N, Vf);
90178
90611
  const fptpS = estFPTPSeats(VfArray);
90179
90612
  // Find the marginal districts IDs (indexed 1–N)
@@ -90192,137 +90625,9 @@ function estMarginalCompetitiveShare(Vf, VfArray, N) {
90192
90625
  minId = Math.max((N - bestS) - 1, 1);
90193
90626
  maxId = Math.min((N - bestS) + 1, N);
90194
90627
  }
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);
90628
+ return [minId, maxId];
90204
90629
  }
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
- }
90324
- }
90325
- exports.Normalizer = Normalizer;
90630
+ exports.findMarginalDistricts = findMarginalDistricts;
90326
90631
 
90327
90632
 
90328
90633
  /***/ }),
@@ -90347,18 +90652,19 @@ var __importStar = (this && this.__importStar) || function (mod) {
90347
90652
  return result;
90348
90653
  };
90349
90654
  Object.defineProperty(exports, "__esModule", { value: true });
90350
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
90655
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
90656
+ const C = __importStar(__webpack_require__(/*! ./config */ "./src/config.ts"));
90351
90657
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
90352
90658
  const cohesive_1 = __webpack_require__(/*! ./cohesive */ "./src/cohesive.ts");
90353
90659
  const equal_1 = __webpack_require__(/*! ./equal */ "./src/equal.ts");
90354
- const fair_1 = __webpack_require__(/*! ./fair */ "./src/fair.ts");
90660
+ const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
90355
90661
  // TODO - Score opportunity for minority representation
90356
90662
  function scorePlan(p, overridesJSON) {
90357
- // BEST subcategories - compactness, splitting, population deviation
90663
+ // TRADITIONAL DISTRICTING PRINCIPLES ("best") subcategories - compactness, splitting, population deviation
90358
90664
  // Compactness
90359
90665
  const reockM = compact_1.doReock(p.compactnessProfile.GeometryByDistrict);
90360
90666
  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));
90667
+ const compactnessScore = weightCompactness(reockM.normalized, polsbyM.normalized);
90362
90668
  const cS = {
90363
90669
  score: compactnessScore,
90364
90670
  reock: reockM,
@@ -90370,51 +90676,77 @@ function scorePlan(p, overridesJSON) {
90370
90676
  const cT = cohesive_1.totalCounties(CxD);
90371
90677
  const countyM = cohesive_1.doCountySplittingReduced(CxD, dT, cT);
90372
90678
  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));
90679
+ const splittingScore = weightSplitting(countyM.normalized, districtM.normalized);
90374
90680
  const sS = {
90375
90681
  score: splittingScore,
90376
90682
  county: countyM,
90377
90683
  district: districtM
90378
90684
  };
90379
90685
  // 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,
90686
+ const pdS = equal_1.doPopulationDeviation(p.populationProfile.TotalPopByDistrict, p.populationProfile.targetSize, p.legislativeDistricts);
90687
+ // Combine traditional principles into a score to compare plans w/in a state
90688
+ const tpScore = weightTradtionalPrinciples(cS.score, sS.score, pdS.normalized, 1 /* WithinAState */);
90689
+ // Populate the "best" traditional principles scorecard
90690
+ const tpS = {
90691
+ score: tpScore,
90386
90692
  compactness: cS,
90387
90693
  splitting: sS,
90388
- populationDeviation: eS
90694
+ populationDeviation: pdS
90389
90695
  };
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));
90696
+ // PARTISAN ("fair") subcategories - bias, impact, & competitiveness (plus lots of supporting measures)
90697
+ const pS = partisan_1.scorePartisan(p.partisanProfile.statewideVf, p.partisanProfile.VfArray);
90698
+ // Combine the partisan/partisan & traditional principles/best scores into an overall
90699
+ // score for comparing plans w/in a state
90700
+ const score = weightBestFair(pS.score2, tpS.score, 1 /* WithinAState */);
90396
90701
  // Roll up an overall scorecard
90397
90702
  const scorecard = {
90398
- score: score,
90399
- best: bS,
90400
- fair: pS
90703
+ partisan: pS,
90704
+ traditionalPrinciples: tpS,
90705
+ score: score
90401
90706
  };
90402
90707
  return scorecard;
90403
90708
  }
90404
90709
  exports.scorePlan = scorePlan;
90710
+ // HELPERS
90711
+ function weightCompactness(rS, ppS) {
90712
+ const rW = C.reockWeight();
90713
+ const ppW = C.polsbyWeight();
90714
+ const score = Math.round(((rS * rW) + (ppS * ppW)) / (rW + ppW));
90715
+ return score;
90716
+ }
90717
+ exports.weightCompactness = weightCompactness;
90718
+ function weightSplitting(csS, dsS) {
90719
+ const csW = C.countySplittingWeight();
90720
+ const dsW = C.districtSplittingWeight();
90721
+ const score = Math.round(((csS * csW) + (dsS * dsW)) / (csW + dsW));
90722
+ return score;
90723
+ }
90724
+ exports.weightSplitting = weightSplitting;
90725
+ function weightTradtionalPrinciples(cS, sS, pdS, context) {
90726
+ const cW = C.compactnessWeight(context);
90727
+ const sW = C.splittingWeight(context);
90728
+ const pdW = C.popdevWeight(context);
90729
+ const score = Math.round(((cS * cW) + (sS * sW) + (pdS * pdW)) / (cW + sW + pdW));
90730
+ return score;
90731
+ }
90732
+ exports.weightTradtionalPrinciples = weightTradtionalPrinciples;
90733
+ function weightBestFair(pS, tpS, context) {
90734
+ const pW = C.partisanWeight(context);
90735
+ const tpW = C.traditionalPrinciplesWeight(context);
90736
+ const score = Math.round(((pS * pW) + (tpS * tpW)) / (pW + tpW));
90737
+ return score;
90738
+ }
90739
+ exports.weightBestFair = weightBestFair;
90740
+ function printScorecardHeader() {
90741
+ console.log('XX, Name, N, V%, ^S#, S#, B%, B$, UE#, I$, C#, Cd, Md, C$, <P$, >P$, Rc, Rc$, Pc, Pc$, G$, Cs, Cs$, Ds, Ds$, S$, Eq, Eq$, T$, $$$');
90742
+ }
90743
+ exports.printScorecardHeader = printScorecardHeader;
90744
+ function printScorecardRow(xx, name, N, Vf, s) {
90745
+ console.log('%s, %s, %i, %f, %i, %f, %f, %i, %f, %i, %i, %f, %f, %i, %i, %i, %f, %i, %f, %i, %i, %f, %i, %f, %i, %i, %f, %i, %i, %i', xx, name, N, U.trim(Vf), s.partisan.bias.BestS, s.partisan.bias.ProbableS, s.partisan.bias.Bias, s.partisan.bias.score, s.partisan.impact.UnearnedS, s.partisan.impact.score, s.partisan.competitiveness.C, s.partisan.competitiveness.Cd, s.partisan.competitiveness.Md, s.partisan.competitiveness.score, s.partisan.score, s.partisan.score2, s.traditionalPrinciples.compactness.reock.raw, s.traditionalPrinciples.compactness.reock.normalized, s.traditionalPrinciples.compactness.polsby.raw, s.traditionalPrinciples.compactness.polsby.normalized, s.traditionalPrinciples.compactness.score, s.traditionalPrinciples.splitting.county.raw, s.traditionalPrinciples.splitting.county.normalized, s.traditionalPrinciples.splitting.district.raw, s.traditionalPrinciples.splitting.district.normalized, s.traditionalPrinciples.splitting.score, s.traditionalPrinciples.populationDeviation.raw, s.traditionalPrinciples.populationDeviation.normalized, s.traditionalPrinciples.score, s.score);
90746
+ }
90747
+ exports.printScorecardRow = printScorecardRow;
90405
90748
 
90406
90749
 
90407
- /***/ }),
90408
-
90409
- /***/ "./src/settings.json":
90410
- /*!***************************!*\
90411
- !*** ./src/settings.json ***!
90412
- \***************************/
90413
- /*! exports provided: fair, best, default */
90414
- /***/ (function(module) {
90415
-
90416
- module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\"weight\":80},\"competitive\":{\"worst\":0,\"best\":0.67,\"weight\":20,\"min\":0.25,\"max\":0.75,\"marginal\":75,\"overall\":25},\"weight\":80},\"best\":{\"compact\":{\"reock\":{\"worst\":0.25,\"best\":0.5,\"weight\":50},\"polsby\":{\"worst\":0.1,\"best\":0.5,\"weight\":50},\"weight\":50},\"cohesive\":{\"county\":{\"best\":1,\"worst\":1.71,\"allowableSplitsMultiplier\":1.5,\"weight\":50},\"district\":{\"best\":1,\"worst\":1.5,\"weight\":50},\"weight\":50},\"equal\":{\"worst\":0.0075,\"best\":0.002,\"stateLeg\":{\"worst\":0.1},\"weight\":0},\"weight\":20}}");
90417
-
90418
90750
  /***/ }),
90419
90751
 
90420
90752
  /***/ "./src/settings.ts":
@@ -90429,16 +90761,13 @@ module.exports = JSON.parse("{\"fair\":{\"unbiased\":{\"worst\":0.1,\"best\":0,\
90429
90761
  //
90430
90762
  // GLOBAL CONSTANTS
90431
90763
  //
90432
- var __importDefault = (this && this.__importDefault) || function (mod) {
90433
- return (mod && mod.__esModule) ? mod : { "default": mod };
90434
- };
90435
90764
  Object.defineProperty(exports, "__esModule", { value: true });
90436
90765
  // Normalized scores [0-100]
90437
90766
  exports.NORMALIZED_RANGE = 100;
90438
90767
  // Square deviations from the ideal
90439
90768
  exports.DISTANCE_WEIGHT = 2;
90440
90769
  // Out of range message
90441
- exports.OUT_OF_RANGE_MSG = "# out of range";
90770
+ exports.OUT_OF_RANGE_MSG = "%f out of range";
90442
90771
  // A small delta to use when testing ranges of values
90443
90772
  exports.EPSILON = 1 / Math.pow(10, 6);
90444
90773
  // Keep 4 decimals for fractions [0.0–1.0], i.e., 2 for %'s [0–100]
@@ -90446,62 +90775,6 @@ exports.PRECISION = 4;
90446
90775
  // "Roughly equal" = average census block size / 2
90447
90776
  exports.AVERAGE_BLOCK_SIZE = 30;
90448
90777
  exports.EQUAL_TOLERANCE = exports.AVERAGE_BLOCK_SIZE / 2;
90449
- // SCORING PARAMETERS
90450
- const settings_json_1 = __importDefault(__webpack_require__(/*! ./settings.json */ "./src/settings.json"));
90451
- // TODO - DELETE
90452
- function readOverrides() {
90453
- return undefined;
90454
- }
90455
- const overrides = readOverrides();
90456
- // SETTINGS FOR PARTISAN SCORING
90457
- exports.MIN_CONTESTED = overrides ? overrides.fair.competitive.min : settings_json_1.default.fair.competitive.min;
90458
- exports.MAX_CONTESTED = overrides ? overrides.fair.competitive.max : settings_json_1.default.fair.competitive.max;
90459
- // SCALES FOR NORMALIZING RAW VALUES
90460
- // Compactness scales
90461
- exports.REOCK_WORST = overrides ? overrides.best.compact.reock.worst : settings_json_1.default.best.compact.reock.worst;
90462
- exports.REOCK_BEST = overrides ? overrides.best.compact.reock.best : settings_json_1.default.best.compact.reock.best;
90463
- exports.POLSBY_WORST = overrides ? overrides.best.compact.polsby.worst : settings_json_1.default.best.compact.polsby.worst;
90464
- exports.POLSBY_BEST = overrides ? overrides.best.compact.polsby.best : settings_json_1.default.best.compact.polsby.best;
90465
- // County-District splitting scales (not inverted)
90466
- exports.COUNTY_BEST = overrides ? overrides.best.cohesive.county.best : settings_json_1.default.best.cohesive.county.best;
90467
- exports.COUNTY_WORST = overrides ? overrides.best.cohesive.county.worst : settings_json_1.default.best.cohesive.county.worst;
90468
- exports.ALLOWABLE_SPLITS_MULTIPLIER = overrides ? overrides.best.cohesive.county.allowableSplitsMultiplier : settings_json_1.default.best.cohesive.county.allowableSplitsMultiplier;
90469
- exports.DISTRICT_BEST = overrides ? overrides.best.cohesive.district.best : settings_json_1.default.best.cohesive.district.best;
90470
- exports.DISTRICT_WORST = overrides ? overrides.best.cohesive.district.worst : settings_json_1.default.best.cohesive.district.worst;
90471
- // Population deviation thresholds & scales (inverted)
90472
- exports.POPDEV_WORST = overrides ? overrides.best.equal.worst : settings_json_1.default.best.equal.worst;
90473
- exports.POPDEV_BEST = overrides ? overrides.best.equal.best : settings_json_1.default.best.equal.best;
90474
- exports.POPEQ_MIN = 1.0 - exports.POPDEV_WORST;
90475
- exports.POPEQ_MAX = 1.0 - exports.POPDEV_BEST;
90476
- exports.POPDEV_WORST_LD = overrides ? overrides.best.equal.stateLeg.worst : settings_json_1.default.best.equal.stateLeg.worst;
90477
- exports.POPDEV_BEST_LD = ((exports.POPDEV_BEST / exports.POPDEV_WORST) * exports.POPDEV_WORST_LD);
90478
- exports.POPEQ_MIN_LD = 1.0 - exports.POPDEV_WORST_LD;
90479
- exports.POPEQ_MAX_LD = 1.0 - exports.POPDEV_BEST_LD;
90480
- // Bias & competitiveness scales
90481
- // TODO - SCORE:
90482
- // * How wide a range do we want for bias? 10%? 20%?
90483
- // * Do we want it to be state-specific? E.g., one seat?
90484
- exports.BIAS_WORST = overrides ? overrides.fair.unbiased.worst : settings_json_1.default.fair.unbiased.worst;
90485
- exports.BIAS_BEST = overrides ? overrides.fair.unbiased.best : settings_json_1.default.fair.unbiased.best;
90486
- // The range for raw overall competitiveness values
90487
- exports.COMPETITIVE_WORST = overrides ? overrides.fair.competitive.worst : settings_json_1.default.fair.competitive.worst;
90488
- exports.COMPETITIVE_BEST = overrides ? overrides.fair.competitive.best : settings_json_1.default.fair.competitive.best;
90489
- // WEIGHTS FOR COMBINING NORMALIZED VALUES
90490
- exports.REOCK_WEIGHT = overrides ? overrides.best.compact.reock.weight : settings_json_1.default.best.compact.reock.weight;
90491
- exports.POLSBY_WEIGHT = overrides ? overrides.best.compact.polsby.weight : settings_json_1.default.best.compact.polsby.weight;
90492
- exports.COUNTY_WEIGHT = overrides ? overrides.best.cohesive.county.weight : settings_json_1.default.best.cohesive.county.weight;
90493
- exports.DISTRICT_WEIGHT = overrides ? overrides.best.cohesive.district.weight : settings_json_1.default.best.cohesive.district.weight;
90494
- exports.COMPACTNESS_WEIGHT = overrides ? overrides.best.compact.weight : settings_json_1.default.best.compact.weight;
90495
- exports.SPLITTING_WEIGHT = overrides ? overrides.best.cohesive.weight : settings_json_1.default.best.cohesive.weight;
90496
- exports.POPDEV_WEIGHT = overrides ? overrides.best.equal.weight : settings_json_1.default.best.equal.weight;
90497
- // TODO - SCORE: What kind of relative weighting do we want between marginal and
90498
- // overall competitiveness?
90499
- exports.MARGINAL_WEIGHT = overrides ? overrides.fair.competitive.marginal : settings_json_1.default.fair.competitive.marginal;
90500
- exports.OVERALL_WEIGHT = overrides ? overrides.fair.competitive.overall : settings_json_1.default.fair.competitive.overall;
90501
- exports.BIAS_WEIGHT = overrides ? overrides.fair.unbiased.weight : settings_json_1.default.fair.unbiased.weight;
90502
- exports.COMPETITIVE_WEIGHT = overrides ? overrides.fair.competitive.weight : settings_json_1.default.fair.competitive.weight;
90503
- exports.FAIR_WEIGHT = overrides ? overrides.fair.weight : settings_json_1.default.fair.weight;
90504
- exports.BEST_WEIGHT = overrides ? overrides.best.weight : settings_json_1.default.best.weight;
90505
90778
 
90506
90779
 
90507
90780
  /***/ }),
@@ -90522,9 +90795,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
90522
90795
  return (mod && mod.__esModule) ? mod : { "default": mod };
90523
90796
  };
90524
90797
  Object.defineProperty(exports, "__esModule", { value: true });
90525
- const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/profiles/sample-profile.json */ "./testdata/profiles/sample-profile.json"));
90798
+ const sample_profile_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-profile.json */ "./testdata/samples/sample-profile.json"));
90526
90799
  exports.sampleProfile = sample_profile_json_1.default;
90527
- const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/profiles/sample-scorecard.json */ "./testdata/profiles/sample-scorecard.json"));
90800
+ const sample_scorecard_json_1 = __importDefault(__webpack_require__(/*! ../testdata/samples/sample-scorecard.json */ "./testdata/samples/sample-scorecard.json"));
90528
90801
  exports.sampleScorecard = sample_scorecard_json_1.default;
90529
90802
 
90530
90803
 
@@ -90606,10 +90879,10 @@ exports.deepCopy = deepCopy;
90606
90879
 
90607
90880
  /***/ }),
90608
90881
 
90609
- /***/ "./testdata/profiles/sample-profile.json":
90610
- /*!***********************************************!*\
90611
- !*** ./testdata/profiles/sample-profile.json ***!
90612
- \***********************************************/
90882
+ /***/ "./testdata/samples/sample-profile.json":
90883
+ /*!**********************************************!*\
90884
+ !*** ./testdata/samples/sample-profile.json ***!
90885
+ \**********************************************/
90613
90886
  /*! exports provided: state, planName, nDistricts, nCounties, legislativeDistricts, populationProfile, compactnessProfile, splittingProfile, partisanProfile, demographicProfile, default */
90614
90887
  /***/ (function(module) {
90615
90888
 
@@ -90617,14 +90890,14 @@ module.exports = JSON.parse("{\"state\":\"NC\",\"planName\":\"Sample profile\",\
90617
90890
 
90618
90891
  /***/ }),
90619
90892
 
90620
- /***/ "./testdata/profiles/sample-scorecard.json":
90621
- /*!*************************************************!*\
90622
- !*** ./testdata/profiles/sample-scorecard.json ***!
90623
- \*************************************************/
90624
- /*! exports provided: score, best, fair, default */
90893
+ /***/ "./testdata/samples/sample-scorecard.json":
90894
+ /*!************************************************!*\
90895
+ !*** ./testdata/samples/sample-scorecard.json ***!
90896
+ \************************************************/
90897
+ /*! exports provided: score, traditionalPrinciples, partisan, default */
90625
90898
  /***/ (function(module) {
90626
90899
 
90627
- 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\":{}}}");
90900
+ module.exports = JSON.parse("{\"score\":5,\"traditionalPrinciples\":{\"score\":18,\"compactness\":{\"score\":35,\"reock\":{\"raw\":0.3373,\"normalized\":35,\"notes\":{}},\"polsby\":{\"raw\":0.2418,\"normalized\":35,\"notes\":{}}},\"splitting\":{\"score\":0,\"county\":{\"raw\":1.1474,\"normalized\":0,\"notes\":{}},\"district\":{\"raw\":1.4839,\"normalized\":3,\"notes\":{}}},\"populationDeviation\":{\"raw\":0.016,\"normalized\":0,\"notes\":{\"maxDeviation\":11693}}},\"partisan\":{\"bias\":{\"BestS\":7,\"BestSf\":0.5385,\"ProbableS\":4.1925,\"ProbableSf\":0.3225,\"Bias\":0.216,\"score\":0},\"impact\":{\"UnearnedS\":2.8075,\"score\":0},\"competitiveness\":{\"C\":6,\"Cd\":0.7266,\"Cdf\":0.0559,\"Mrange\":[5,11],\"Md\":0.7134,\"Mdf\":0.1019,\"score\":10},\"score\":3,\"score2\":2}}");
90628
90901
 
90629
90902
  /***/ })
90630
90903
 
@@ -100410,6 +100683,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
100410
100683
  return result;
100411
100684
  };
100412
100685
  Object.defineProperty(exports, "__esModule", { value: true });
100686
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "./node_modules/@dra2020/dra-score/dist/dra-score.bundle.js"));
100413
100687
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
100414
100688
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
100415
100689
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
@@ -100603,9 +100877,13 @@ class AnalyticsSession {
100603
100877
  return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
100604
100878
  }
100605
100879
  else {
100880
+ // NOTE - This assumes the plan has been profiled
100881
+ const scorer = new Score.Scorer();
100882
+ const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
100883
+ return popdev;
100606
100884
  // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
100607
100885
  // NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
100608
- return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
100886
+ // return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
100609
100887
  }
100610
100888
  }
100611
100889
  }
@@ -102815,7 +103093,9 @@ function doAnalyzePostProcessing(s, bLog = false) {
102815
103093
  // Just populate the normalized population deviation score in the test
102816
103094
  const scorecard = s._scorecard;
102817
103095
  let test = s.getTest(4 /* PopulationDeviation */);
102818
- test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
103096
+ test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
103097
+ // TODO - DELETE
103098
+ // test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
102819
103099
  }
102820
103100
  // Derive secondary tests
102821
103101
  analyze_1.doDeriveSecondaryTests(s, bLog);
@@ -102861,7 +103141,7 @@ function profilePlan(s, bLog = false) {
102861
103141
  const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
102862
103142
  const splits = makeNakedCxD(s);
102863
103143
  const summaryRow = s.districts.numberOfRows() - 1;
102864
- const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
103144
+ const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
102865
103145
  const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
102866
103146
  const demographicsByDistrict = makeArrayOfDemographics(s);
102867
103147
  const profile = {
@@ -102945,11 +103225,14 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
102945
103225
  // calling sequence.
102946
103226
  let test = s.getTest(4 /* PopulationDeviation */);
102947
103227
  // TODO - SCORE: U.trim(popDev)???
103228
+ // const popDev = scorecard.best.populationDeviation.raw;
102948
103229
  // Get the raw population deviation
102949
- const popDev = scorecard.best.populationDeviation.raw;
103230
+ const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
102950
103231
  // Populate the test entry
102951
103232
  test['score'] = popDev;
102952
- test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
103233
+ test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
103234
+ // TODO - DELETE
103235
+ // test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
102953
103236
  // Populate the N+1 summary "district" in district.statistics
102954
103237
  let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
102955
103238
  let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];