@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.
@@ -116,6 +116,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
116
116
  return result;
117
117
  };
118
118
  Object.defineProperty(exports, "__esModule", { value: true });
119
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
119
120
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
120
121
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
121
122
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
@@ -143,37 +144,47 @@ class AnalyticsSession {
143
144
  this.districts = new D.Districts(this, SessionRequest['districtShapes']);
144
145
  // TODO - SCORE: Toggle
145
146
  if (this.useLegacy()) {
147
+ console.log("Using legacy district-analytics.");
146
148
  // NOTE: I've pulled these out of the individual analytics to here. Eventually,
147
149
  // we could want them to passed into an analytics session as data, along with
148
150
  // everything else. For now, this keeps branching out of the main code.
149
151
  results_1.doConfigureScales(this);
150
152
  }
153
+ else {
154
+ console.log("Using dra-score analytics.");
155
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
156
+ results_1.doConfigureScales(this);
157
+ }
151
158
  }
152
159
  processConfig(config) {
153
160
  // NOTE - Session settings are required:
154
161
  // - Analytics suites can be defaulted to all with [], but
155
162
  // - Dataset keys must be explicitly specified with 'dataset'
163
+ // TODO - SCORE: Delete
156
164
  config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
157
165
  // Default the Census & redistricting cycle to 2010
158
166
  if (!(U.keyExists('cycle', config)))
159
167
  config['cycle'] = 2010;
160
168
  return config;
161
169
  }
170
+ // TODO - SCORE: Toggle = Invert this logic
162
171
  useLegacy() {
163
- return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
172
+ // TODO - SCORE: Opt-out
173
+ return (U.keyExists('useScore', this.config) && (this.config['useScore'] != null) && (!(this.config['useScore']))) ? true : false;
174
+ // TODO - SCORE: Opt-in
175
+ // return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
164
176
  }
165
177
  // Using the the data in the analytics session, calculate all the
166
178
  // analytics & validations, saving/updating the individual test results.
167
- analyzePlan(bLog = false, overridesJSON = undefined) {
179
+ analyzePlan(bLog = false, overridesJSON) {
168
180
  try {
169
181
  preprocess_1.doPreprocessData(this, bLog);
170
182
  analyze_1.doAnalyzeDistricts(this, bLog);
171
- // TODO - SCORE
172
183
  analyze_1.doAnalyzePlan(this, bLog);
184
+ // TODO - SCORE
173
185
  this._profile = score_1.profilePlan(this, bLog);
174
- this._scorecard = score_1.scorePlan(this._profile, bLog);
186
+ this._scorecard = score_1.scorePlan(this, this._profile, bLog, overridesJSON);
175
187
  results_1.doAnalyzePostProcessing(this, bLog);
176
- //
177
188
  }
178
189
  catch (_a) {
179
190
  console.log("Exception caught by analyzePlan()");
@@ -295,13 +306,17 @@ class AnalyticsSession {
295
306
  // NOTE - Not sure why this has to be up here ...
296
307
  populationDeviationThreshold() {
297
308
  // TODO - SCORE: Toggle
298
- // const bLegacy = this._bLegacy; DELETE
299
309
  if (this.useLegacy()) {
300
310
  return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
301
311
  }
302
312
  else {
303
- const scorecard = this._scorecard;
304
- return scorecard.best.populationDeviation.notes['threshold'];
313
+ // NOTE - This assumes the plan has been profiled
314
+ const scorer = new Score.Scorer();
315
+ const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
316
+ return popdev;
317
+ // TODO - SCORE: Temporary HACK. Query dra-score for threshold.
318
+ // NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
319
+ // return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
305
320
  }
306
321
  }
307
322
  }
@@ -478,7 +493,6 @@ class Districts {
478
493
  let outerThis = this;
479
494
  // Default the pop dev % for the dummy Unassigned district to 0%.
480
495
  // Default the pop dev % for real (1–N) but empty districts to 100%.
481
- // TODO - SCORE: Why did I mark this?
482
496
  let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
483
497
  // Get the geoIDs assigned to the district
484
498
  // Guard against empty districts
@@ -928,12 +942,44 @@ exports.doAnalyzeDistricts = doAnalyzeDistricts;
928
942
  // calls might make chunking for aync easier.
929
943
  function doAnalyzePlan(s, bLog = false) {
930
944
  // TODO - SCORE: Toggle
931
- // const bLegacy = s._bLegacy;
932
- // TODO - Remove this mechanism. Always run all tests
933
- // Get the requested suites, and only execute those tests
934
- let requestedSuites = s.config['suites'];
935
- // Tests in the "Legal" suite, i.e., pass/ fail constraints
936
- if (requestedSuites.includes(0 /* Legal */)) {
945
+ if (s.useLegacy()) {
946
+ // Disable most legacy analytics
947
+ // Get the requested suites, and only execute those tests
948
+ let requestedSuites = s.config['suites'];
949
+ // Tests in the "Legal" suite, i.e., pass/ fail constraints
950
+ if (requestedSuites.includes(0 /* Legal */)) {
951
+ s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
952
+ s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
953
+ s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
954
+ s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
955
+ // NOTE - I can't check whether a population deviation is legal or not, until
956
+ // the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
957
+ // the given type of district (CD vs. LD). The EqualPopulation test is derived
958
+ // from PopulationDeviation, as part of scorecard or test log preparation.
959
+ // Create an empty test entry here though ...
960
+ s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
961
+ }
962
+ // Tests in the "Fair" suite
963
+ if (requestedSuites.includes(1 /* Fair */)) {
964
+ s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
965
+ s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
966
+ s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
967
+ s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
968
+ s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
969
+ s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
970
+ }
971
+ // Tests in the "Best" suite, i.e., criteria for better/worse
972
+ if (requestedSuites.includes(2 /* Best */)) {
973
+ s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
974
+ s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
975
+ s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
976
+ s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
977
+ s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
978
+ s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
979
+ }
980
+ }
981
+ else {
982
+ // TODO - SCORE: Except these. Continue to do these here vs. dra-score.
937
983
  s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
938
984
  s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
939
985
  s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
@@ -944,24 +990,8 @@ function doAnalyzePlan(s, bLog = false) {
944
990
  // from PopulationDeviation, as part of scorecard or test log preparation.
945
991
  // Create an empty test entry here though ...
946
992
  s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
947
- }
948
- // Tests in the "Fair" suite
949
- if (requestedSuites.includes(1 /* Fair */)) {
950
- s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
951
- s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
952
- s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
953
- s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
954
- s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
955
- s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
956
- }
957
- // Tests in the "Best" suite, i.e., criteria for better/worse
958
- if (requestedSuites.includes(2 /* Best */)) {
959
- s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
960
- s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
961
993
  s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
962
994
  s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
963
- s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
964
- s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
965
995
  }
966
996
  // Enable a Test Log and Scorecard to be generated
967
997
  s.bPlanAnalyzed = true;
@@ -975,8 +1005,6 @@ exports.doAnalyzePlan = doAnalyzePlan;
975
1005
  // NOTE - Should this be conditionalized on the test suites requested?
976
1006
  // Those are encapsulated in reports.ts right now, so not doing that.
977
1007
  function doDeriveSecondaryTests(s, bLog = false) {
978
- // TODO - SCORE: Toggle
979
- // const bLegacy = s._bLegacy;
980
1008
  s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
981
1009
  }
982
1010
  exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
@@ -1596,18 +1624,11 @@ exports.doPopulationDeviation = doPopulationDeviation;
1596
1624
  // NOTE - This validity check is *derived* and depends on population deviation %
1597
1625
  // being computed (above) and normalized in test log & scorecard generation.
1598
1626
  function doHasEqualPopulations(s, bLog = false) {
1599
- // TODO - SCORE: Toggle
1600
- // const bLegacy = s._bLegacy;
1601
- const scorecard = s._scorecard;
1602
- const popDevNormalizedNEW = scorecard.best.populationDeviation.normalized;
1603
- const bScore = (popDevNormalizedNEW > 0) ? true : false;
1604
- // TODO - Get scales
1605
- // TODO - Flow through to RequirementsChecklist
1606
1627
  let test = s.getTest(3 /* EqualPopulation */);
1607
1628
  // Get the normalized population deviation %
1608
1629
  let popDevTest = s.getTest(4 /* PopulationDeviation */);
1609
- let popDevPct = popDevTest['score'];
1610
- let popDevNormalized = popDevTest['normalizedScore'];
1630
+ const popDevPct = popDevTest['score'];
1631
+ const popDevNormalized = popDevTest['normalizedScore'];
1611
1632
  // Populate the test entry
1612
1633
  if (popDevNormalized > 0) {
1613
1634
  test['score'] = true;
@@ -1617,7 +1638,6 @@ function doHasEqualPopulations(s, bLog = false) {
1617
1638
  }
1618
1639
  test['details']['deviation'] = popDevPct;
1619
1640
  test['details']['thresholds'] = popDevTest['details']['scale'];
1620
- console.log("Equal population =", test['score'], bScore);
1621
1641
  // Populate the N+1 summary "district" in district.statistics
1622
1642
  let bEqualPop = s.districts.statistics[D.DistrictField.bEqualPop];
1623
1643
  let summaryRow = s.districts.numberOfRows() - 1;
@@ -2236,12 +2256,14 @@ function prepareRequirementsChecklist(s, bLog = false) {
2236
2256
  const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
2237
2257
  const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
2238
2258
  const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
2239
- const scorecard = s._scorecard;
2240
- const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
2241
- // TODO - SCORE: Toggle
2259
+ // TODO - SCORE: DELETE - This code is hooked correctly to use dra-score
2260
+ // const scorecard = s._scorecard as Score.Scorecard;
2261
+ // const populationDeviation = scorecard.best.populationDeviation.raw;
2242
2262
  const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
2263
+ // console.log("Population deviations =", populationDeviationDetail, populationDeviation);
2264
+ // const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
2243
2265
  const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
2244
- console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
2266
+ // console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
2245
2267
  const xx = s.state.xx;
2246
2268
  // TODO - JSON: Is there a better / easier way to work with the variable?
2247
2269
  const stateReqsDict = state_reqs_json_1.default;
@@ -2509,20 +2531,29 @@ exports.doConfigureScales = doConfigureScales;
2509
2531
  // Do this after analytics have been run and before preparing a test log or scorecard.
2510
2532
  function doAnalyzePostProcessing(s, bLog = false) {
2511
2533
  // TODO - SCORE: Toggle
2512
- // const bLegacy = s._bLegacy;
2513
- // Normalize the raw scores for all the numerics tests
2514
- let testResults = U.getNumericObjectKeys(testDefns);
2515
- for (let testID of testResults) {
2516
- if (testDefns[testID]['normalize']) {
2517
- let testResult = s.getTest(testID);
2518
- let rawScore = testResult['score'];
2519
- let normalizedScore;
2520
- normalizedScore = U.normalize(rawScore, s.testScales[testID]);
2521
- testResult['normalizedScore'] = normalizedScore;
2522
- // Add the scale used to normalize the raw score to the details
2523
- testResult['details']['scale'] = s.testScales[testID].scale;
2534
+ if (s.useLegacy()) {
2535
+ // Normalize the raw scores for all the numerics tests
2536
+ let testResults = U.getNumericObjectKeys(testDefns);
2537
+ for (let testID of testResults) {
2538
+ if (testDefns[testID]['normalize']) {
2539
+ let testResult = s.getTest(testID);
2540
+ let rawScore = testResult['score'];
2541
+ let normalizedScore;
2542
+ normalizedScore = U.normalize(rawScore, s.testScales[testID]);
2543
+ testResult['normalizedScore'] = normalizedScore;
2544
+ // Add the scale used to normalize the raw score to the details
2545
+ testResult['details']['scale'] = s.testScales[testID].scale;
2546
+ }
2524
2547
  }
2525
2548
  }
2549
+ else {
2550
+ // Just populate the normalized population deviation score in the test
2551
+ const scorecard = s._scorecard;
2552
+ let test = s.getTest(4 /* PopulationDeviation */);
2553
+ test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
2554
+ // TODO - DELETE
2555
+ // test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
2556
+ }
2526
2557
  // Derive secondary tests
2527
2558
  analyze_1.doDeriveSecondaryTests(s, bLog);
2528
2559
  // Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
@@ -2567,9 +2598,10 @@ function profilePlan(s, bLog = false) {
2567
2598
  const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
2568
2599
  const splits = makeNakedCxD(s);
2569
2600
  const summaryRow = s.districts.numberOfRows() - 1;
2570
- const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
2601
+ const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
2571
2602
  const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
2572
- const demographicsByDistrict = makeArrayOfDemographics(s);
2603
+ const statewideDemographics = getStatewideDemographics(s);
2604
+ const demographicsByDistrict = getDemographicsByDistrict(s);
2573
2605
  const profile = {
2574
2606
  state: state,
2575
2607
  planName: planName,
@@ -2577,21 +2609,22 @@ function profilePlan(s, bLog = false) {
2577
2609
  nCounties: nCounties,
2578
2610
  legislativeDistricts: s.legislativeDistricts,
2579
2611
  populationProfile: {
2580
- TotalPopByDistrict: popByDistrict,
2612
+ totalPopByDistrict: popByDistrict,
2581
2613
  targetSize: targetSize
2582
2614
  },
2583
2615
  compactnessProfile: {
2584
- GeometryByDistrict: geoPropsByDistrict
2616
+ geometryByDistrict: geoPropsByDistrict
2585
2617
  },
2586
2618
  splittingProfile: {
2587
- CountyPopByDistrict: splits
2619
+ countyPopByDistrict: splits
2588
2620
  },
2589
2621
  partisanProfile: {
2590
2622
  statewideVf: statewideVf,
2591
- VfArray: vpiArray
2623
+ vfArray: vpiArray
2592
2624
  },
2593
2625
  demographicProfile: {
2594
- DemographicsByDistrict: demographicsByDistrict
2626
+ stateMfArray: statewideDemographics,
2627
+ mfArrayByDistrict: demographicsByDistrict
2595
2628
  }
2596
2629
  };
2597
2630
  return profile;
@@ -2625,7 +2658,7 @@ function makeArrayOfGeoProps(s, bLog = false) {
2625
2658
  }
2626
2659
  return geometryByDistrict;
2627
2660
  }
2628
- function makeArrayOfDemographics(s, bLog = false) {
2661
+ function getDemographicsByDistrict(s, bLog = false) {
2629
2662
  let demographicsArray = [];
2630
2663
  // Remove the unassigned & total dummy "districts"
2631
2664
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -2642,10 +2675,44 @@ function makeArrayOfDemographics(s, bLog = false) {
2642
2675
  }
2643
2676
  return demographicsArray;
2644
2677
  }
2678
+ function getStatewideDemographics(s, bLog = false) {
2679
+ const summaryRow = s.districts.numberOfRows() - 1;
2680
+ const demographicsArray = [
2681
+ U.deepCopy(s.districts.statistics[D.DistrictField.WhitePct][summaryRow]),
2682
+ U.deepCopy(s.districts.statistics[D.DistrictField.MinorityPct][summaryRow]),
2683
+ U.deepCopy(s.districts.statistics[D.DistrictField.BlackPct][summaryRow]),
2684
+ U.deepCopy(s.districts.statistics[D.DistrictField.HispanicPct][summaryRow]),
2685
+ U.deepCopy(s.districts.statistics[D.DistrictField.PacificPct][summaryRow]),
2686
+ U.deepCopy(s.districts.statistics[D.DistrictField.AsianPct][summaryRow]),
2687
+ U.deepCopy(s.districts.statistics[D.DistrictField.NativePct][summaryRow])
2688
+ ];
2689
+ return demographicsArray;
2690
+ }
2645
2691
  // SCORE A PLAN
2646
- function scorePlan(p, bLog = false) {
2692
+ function scorePlan(s, p, bLog = false, overridesJSON) {
2647
2693
  let scorer = new Score.Scorer();
2648
- return scorer.score(p);
2694
+ const scorecard = scorer.score(p, overridesJSON);
2695
+ // TODO - SCORE: Toggle: Before returning, create a dummy population deviation
2696
+ // test, for doHasEqualPopulations() to use later. This is preserving the old
2697
+ // calling sequence.
2698
+ let test = s.getTest(4 /* PopulationDeviation */);
2699
+ // TODO - SCORE: U.trim(popDev)???
2700
+ // const popDev = scorecard.best.populationDeviation.raw;
2701
+ // Get the raw population deviation
2702
+ const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
2703
+ // Populate the test entry
2704
+ test['score'] = popDev;
2705
+ test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
2706
+ // TODO - DELETE
2707
+ // test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
2708
+ // Populate the N+1 summary "district" in district.statistics
2709
+ let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
2710
+ let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
2711
+ let summaryRow = s.districts.numberOfRows() - 1;
2712
+ totalPop[summaryRow] = p.populationProfile.targetSize;
2713
+ popDevPct[summaryRow] = popDev;
2714
+ //
2715
+ return scorecard;
2649
2716
  }
2650
2717
  exports.scorePlan = scorePlan;
2651
2718
 
@@ -2702,6 +2769,8 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
2702
2769
  Object.defineProperty(exports, "__esModule", { value: true });
2703
2770
  // TODO - SCORE
2704
2771
  var dra_score_1 = __webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score");
2772
+ // PartisanProfile,
2773
+ // PopulationProfile, ShapeProfile, SplittingProfile,
2705
2774
  exports.sampleProfile = dra_score_1.sampleProfile;
2706
2775
  exports.sampleScorecard = dra_score_1.sampleScorecard;
2707
2776
  // END