@dra2020/district-analytics 2.0.2 → 2.0.5

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.
@@ -139,6 +139,13 @@ class AnalyticsSession {
139
139
  this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
140
140
  this.plan = new D.Plan(this, SessionRequest['plan']);
141
141
  this.districts = new D.Districts(this, SessionRequest['districtShapes']);
142
+ // TODO - Confirming that the graph includes OUT_OF_STATE neighbors
143
+ // if (U.keyExists(S.OUT_OF_STATE, this.graph._graph)) {
144
+ // console.log("Contiguity graph includes out-of-state neighbors.");
145
+ // }
146
+ // else {
147
+ // console.log("Contiguity graph does NOT include out-of-state neighbors.");
148
+ // }
142
149
  // NOTE: I've pulled these out of the individual analytics to here. Eventually,
143
150
  // we could want them to passed into an analytics session as data, along with
144
151
  // everything else. For now, this keeps branching out of the main code.
@@ -248,6 +255,9 @@ const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"
248
255
  const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
249
256
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
250
257
  const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
258
+ // DEBUG COUNTERS
259
+ let nMissingDataset = 0;
260
+ let nMissingProperty = 0;
251
261
  // DISTRICT STATISTICS
252
262
  // Indexes for statistics fields in Districts
253
263
  // NOTE - Not a const, so the number can be determined dynamically
@@ -332,6 +342,9 @@ class Districts {
332
342
  // TODO - OPTIMIZE by async'ing this?
333
343
  // TODO - Is there a way to do this programmatically off data? Does it matter?
334
344
  recalcStatistics(bLog = false) {
345
+ // Initialize debug counters
346
+ nMissingDataset = 0;
347
+ nMissingProperty = 0;
335
348
  // Compute these once per recalc cycle
336
349
  let targetSize = this._session.state.totalPop / this._session.state.nDistricts;
337
350
  let deviationThreshold = this._session.populationDeviationThreshold();
@@ -429,8 +442,8 @@ class Districts {
429
442
  });
430
443
  // COMPUTE DERIVED VALUES
431
444
  // Population deviation % and equal population (boolean) by district.
432
- // Don't set the values for the dummy unassigned district.
433
- let popDevPct = null;
445
+ // Default the value for the dummy unassigned district to 0%.
446
+ let popDevPct = 0 / targetSize;
434
447
  if (i > 0) {
435
448
  popDevPct = (totalPop - targetSize) / targetSize;
436
449
  bEqualPop = (popDevPct <= deviationThreshold);
@@ -569,6 +582,8 @@ class Districts {
569
582
  this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
570
583
  this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
571
584
  }
585
+ console.log(`${nMissingDataset} features with missing datasets.`);
586
+ console.log(`${nMissingProperty} features with missing properties.`);
572
587
  }
573
588
  // NOTE - I did not roll these into district statistics, because creating the
574
589
  // district shapes themselves is the big district-by-district activity, these
@@ -623,36 +638,46 @@ exports.Features = Features;
623
638
  // f is a direct GeoJSON feature
624
639
  // p is a geoID
625
640
  function _getFeatures(f, datasetKey, p) {
626
- // Echo parameters for debugging
627
- // console.log("f =", f, "k = ", datasetKey, "p =", p);
628
641
  // Shim to load sample data2.json from disk for command-line scaffolding
629
642
  if (f.properties && f.properties['datasets']) {
643
+ if (!f.properties['datasets'][datasetKey]) {
644
+ // Feature is missing the dataset
645
+ nMissingDataset += 1;
646
+ console.log(`${nMissingDataset}: Data ${datasetKey} missing for feature ${f} Returning zero.`);
647
+ return 0;
648
+ }
630
649
  return f.properties['datasets'][datasetKey][p];
631
650
  }
632
651
  // NOTE - The fGetW() code from dra-client below here ...
633
652
  // Direct property?
634
- if (f.properties && f.properties[p] !== undefined)
653
+ if (f.properties && f.properties[p] !== undefined) {
635
654
  return f.properties[p];
655
+ }
636
656
  // Joined property?
637
657
  let a = _fGetJoined(f);
638
658
  if (a) {
639
659
  for (let i = 0; i < a.length; i++) {
640
660
  let o = a[i];
641
661
  if (!datasetKey) {
642
- if (o[p] !== undefined)
662
+ if (o[p] !== undefined) {
643
663
  return o[p];
664
+ }
644
665
  }
645
666
  else {
646
667
  if (o['datasets'] && o['datasets'][datasetKey]) {
647
668
  let v = (o['datasets'][datasetKey][p]);
648
- if ((!(v == null)) && (!(v == undefined)))
669
+ if ((!(v == null)) && (!(v == undefined))) {
649
670
  return o['datasets'][datasetKey][p];
671
+ }
650
672
  }
651
673
  }
652
674
  }
653
675
  }
654
- console.log(`${p} value undefined for ${f.properties['GEOID10']}!`);
655
- return undefined;
676
+ // Feature is missing the property
677
+ nMissingProperty += 1;
678
+ console.log(`${nMissingProperty}: ${p} value undefined for ${f.properties['GEOID10']}. Returning zero.`);
679
+ return 0;
680
+ // return undefined;
656
681
  }
657
682
  function _fGetJoined(f) {
658
683
  return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
@@ -697,6 +722,7 @@ class Plan {
697
722
  }
698
723
  // NOTE - DON'T remove water-only features from the plan, as they may be required
699
724
  // for contiguity. Just skip them in aggregating district statistics.
725
+ //
700
726
  // removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
701
727
  // let newPlan = {} as T.PlanByGeoID;
702
728
  // for (let geoID in plan) {
@@ -1200,7 +1226,7 @@ exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
1200
1226
  function doFindSplitVTDs(s, bLog = false) {
1201
1227
  let test = s.getTest(10 /* VTDSplits */);
1202
1228
  let splitVTDs = [];
1203
- // TODO - SPLITTING: Flesh this out, using Terry's virtual VTD's ...
1229
+ // TODO - SPLITTING: Flesh this out, using virtual VTD's ...
1204
1230
  test['score'] = splitVTDs.length;
1205
1231
  test['details']['splitVTDs'] = splitVTDs;
1206
1232
  return test;
@@ -1447,17 +1473,22 @@ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1447
1473
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1448
1474
  function doPopulationDeviation(s, bLog = false) {
1449
1475
  let test = s.getTest(4 /* PopulationDeviation */);
1450
- // Compute the min, max, and average district populations,
1451
- // excluding the dummy 'unassigned' 0 and N+1 summary "districts."
1476
+ let targetSize = s.state.totalPop / s.state.nDistricts;
1477
+ // Compute the min & max district populations
1478
+ // ... excluding the dummy the 'unassigned' 0 and N+1 summary "districts"
1452
1479
  let totPopByDistrict = s.districts.statistics[D.DistrictField.TotalPop];
1453
1480
  totPopByDistrict = totPopByDistrict.slice(1, -1);
1454
- let min = U.minArray(totPopByDistrict);
1455
- let max = U.maxArray(totPopByDistrict);
1456
- let total = U.sumArray(totPopByDistrict);
1457
- // Calculate the raw population deviation.
1458
- // The target size is the average population.
1459
- let avg = total / s.state.nDistricts;
1460
- let popDev = (max - min) / avg;
1481
+ // Remove empty districts
1482
+ totPopByDistrict = totPopByDistrict.filter(x => x > 0);
1483
+ let min = 0;
1484
+ let max = 0;
1485
+ // If there's more than 1 non-empty district, calculate a non-zero deviation
1486
+ if (totPopByDistrict.length > 1) {
1487
+ min = U.minArray(totPopByDistrict);
1488
+ max = U.maxArray(totPopByDistrict);
1489
+ }
1490
+ // Calculate the raw population deviation
1491
+ let popDev = (max - min) / targetSize;
1461
1492
  // Round the raw value to the desired level of precision
1462
1493
  popDev = U.trim(popDev);
1463
1494
  // Populate the test entry
@@ -1467,7 +1498,7 @@ function doPopulationDeviation(s, bLog = false) {
1467
1498
  let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
1468
1499
  let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
1469
1500
  let summaryRow = s.districts.numberOfRows() - 1;
1470
- totalPop[summaryRow] = avg; // aka "target size"
1501
+ totalPop[summaryRow] = targetSize;
1471
1502
  popDevPct[summaryRow] = popDev;
1472
1503
  return test;
1473
1504
  }
@@ -1855,9 +1886,9 @@ function doPreprocessCensus(s, bLog = false) {
1855
1886
  // Sum total population by county
1856
1887
  totalByCounty[countyFIPS] += value;
1857
1888
  }
1858
- // else {
1859
- // console.log("Skipping water-only feature in Census preprocessing:", geoID);
1860
- // }
1889
+ else {
1890
+ console.log("Skipping water-only feature in Census preprocessing:", geoID);
1891
+ }
1861
1892
  }
1862
1893
  // NOTE - The above could be replaced, if I got totals on county.geojson.
1863
1894
  // CREATE A FIPS CODE-ORDINAL MAP
@@ -2455,8 +2486,10 @@ const testDefns = {
2455
2486
  // Raw numeric analytics, such as population deviation, compactness, etc. are
2456
2487
  // normalized as part of creating a scorecard, so the code to normalize results
2457
2488
  // is encapsulated here.
2458
- // Configure scale parameters for normalizing each raw test result
2459
- // This needs to be separate from the scorecard configuration info above,
2489
+ // Configure scale parameters for normalizing each raw test result.
2490
+ // Scales consist of a minimum & a maximum *raw* value. If the values get
2491
+ // inverted (to make bigger better), these will switch.
2492
+ // This process needs to be separate from the test configuration info above,
2460
2493
  // because some scales need access to the analytics session object.
2461
2494
  function doConfigureScales(s) {
2462
2495
  // Scale defn for PopulationDeviation