@dra2020/district-analytics 1.0.11 → 2.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.
@@ -148,7 +148,7 @@ class AnalyticsSession {
148
148
  // NOTE - Session settings are required:
149
149
  // - Analytics suites can be defaulted to all with [], but
150
150
  // - Dataset keys must be explicitly specified with 'dataset'
151
- // TODO - Remove this mechanism. Always run everything.
151
+ // TODO - DASHBOARD: Remove this mechanism. Always run everything.
152
152
  let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
153
153
  // If the config passed in has no suites = [], use the default suites
154
154
  if (U.isArrayEmpty(config['suites'])) {
@@ -328,10 +328,10 @@ class Districts {
328
328
  }
329
329
  // This is the workhorse computational routine!
330
330
  //
331
- // TODO - Optimize for getting multiple properties from the same feature?
332
- // TODO - Optimize by only re-calc'ing districts that have changed?
331
+ // TODO - OPTIMIZE for getting multiple properties from the same feature?
332
+ // TODO - OPTIMIZE by only re-calc'ing districts that have changed?
333
333
  // In this case, special attention to getting county-splits right.
334
- // TODO - Optimize by async'ing this?
334
+ // TODO - OPTIMIZE by async'ing this?
335
335
  // TODO - Is there a way to do this programmatically off data? Does it matter?
336
336
  recalcStatistics(bLog = false) {
337
337
  // Compute these once per recalc cycle
@@ -340,7 +340,7 @@ class Districts {
340
340
  let planByDistrict = this._session.plan.byDistrictID();
341
341
  let plan = this._session.plan;
342
342
  let graph = this._session.graph;
343
- // TODO - SPLITTING
343
+ // NOTE - SPLITTING
344
344
  // Add an extra 0th virtual county bucket for county-district splitting analysis
345
345
  let nCountyBuckets = this._session.counties.nCounties + 1;
346
346
  // INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
@@ -366,7 +366,7 @@ class Districts {
366
366
  // INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
367
367
  let featurePop;
368
368
  let totalPop = 0;
369
- // TODO - SPLITTING
369
+ // NOTE - SPLITTING
370
370
  let countySplits = U.initArray(nCountyBuckets, 0);
371
371
  let demVotes = 0;
372
372
  let repVotes = 0;
@@ -397,31 +397,37 @@ class Districts {
397
397
  // Map from geoID to feature index
398
398
  let featureID = outerThis._session.features.featureID(geoID);
399
399
  let f = outerThis._session.features.featureByIndex(featureID);
400
- // ACCUMULATE VALUES
401
- // Total population of each feature
402
- // NOTE - This result is used more than once
403
- featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
404
- // Total district population
405
- totalPop += featurePop;
406
- // TODO - SPLITTING
407
- // Total population by counties w/in a district,
408
- // except the dummy unassigned district 0
409
- if (i > 0)
410
- countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
411
- // Democratic and Republican vote totals
412
- demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
413
- repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
414
- // Voting-age demographic breakdowns (or citizen voting-age)
415
- totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
416
- whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
417
- blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
418
- hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
419
- pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
420
- asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
421
- nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
422
- // 4 - MORE ...
400
+ if (f == undefined) {
401
+ console.log("Skipping undefined feature in district statistics: GEOID =", geoID, "Feature ID =", featureID);
402
+ }
403
+ else {
404
+ // ACCUMULATE VALUES
405
+ // Total population of each feature
406
+ // NOTE - This result is used more than once
407
+ featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
408
+ // Total district population
409
+ totalPop += featurePop;
410
+ // NOTE - SPLITTING
411
+ // Total population by counties w/in a district,
412
+ // except the dummy unassigned district 0
413
+ if (i > 0)
414
+ countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
415
+ // Democratic and Republican vote totals
416
+ demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
417
+ repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
418
+ // Voting-age demographic breakdowns (or citizen voting-age)
419
+ totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
420
+ whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
421
+ blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
422
+ hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
423
+ pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
424
+ asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
425
+ nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
426
+ // 4 - MORE ...
427
+ }
423
428
  }
424
- // else console.log("Skipping water-only feature in district statistics:", geoID);
429
+ else
430
+ console.log("Skipping water-only feature in district statistics:", geoID);
425
431
  });
426
432
  // COMPUTE DERIVED VALUES
427
433
  // Population deviation % and equal population (boolean) by district.
@@ -619,6 +625,8 @@ exports.Features = Features;
619
625
  // f is a direct GeoJSON feature
620
626
  // p is a geoID
621
627
  function _getFeatures(f, datasetKey, p) {
628
+ // Echo parameters for debugging
629
+ // console.log("f =", f, "k = ", datasetKey, "p =", p);
622
630
  // Shim to load sample data2.json from disk for command-line scaffolding
623
631
  if (f.properties && f.properties['datasets']) {
624
632
  return f.properties['datasets'][datasetKey][p];
@@ -705,7 +713,7 @@ class Plan {
705
713
  // return newPlan;
706
714
  // }
707
715
  invertPlan() {
708
- // TODO - UNASSIGNED
716
+ // NOTE - UNASSIGNED
709
717
  this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
710
718
  this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
711
719
  }
@@ -721,8 +729,8 @@ function invertPlan(plan, s) {
721
729
  let invertedPlan = {};
722
730
  // Add a dummy 'unassigned' district
723
731
  invertedPlan[S.NOT_ASSIGNED] = new Set();
724
- // TODO - UNASSIGNED
725
- // NOTE - The feature assignments coming from DRA do not include unassigned ones.
732
+ // NOTE - UNASSIGNED
733
+ // The feature assignments coming from DRA do not include unassigned ones.
726
734
  // - In the DRA-calling context, there's an analytics session with a reference
727
735
  // to the features. Loop over all the features to find the unassigned ones,
728
736
  // and add them to the dummy unassigned district explicitly.
@@ -735,7 +743,7 @@ function invertPlan(plan, s) {
735
743
  // to the dummy unassigned district 0.
736
744
  if (!(U.keyExists(geoID, plan)))
737
745
  invertedPlan[S.NOT_ASSIGNED].add(geoID);
738
- // TODO - WATER-ONLY: NOT skipping water-only features here, because we're
746
+ // NOTE - NOT skipping WATER-ONLY features here, because we're
739
747
  // not skipping them below when they are explicitly assigned in plans. Should
740
748
  // we skip them in both places?
741
749
  }
@@ -762,7 +770,13 @@ class Graph {
762
770
  peerNeighbors(node) {
763
771
  // Get the neighboring geoIDs connected to a geoID
764
772
  // Ignore the lengths of the shared borders (the values), for now
765
- return U.getObjectKeys(this._graph[node]);
773
+ // Protect against getting a GEOID that's not in the graph
774
+ if (U.keyExists(node, this._graph)) {
775
+ return U.getObjectKeys(this._graph[node]);
776
+ }
777
+ else
778
+ return [];
779
+ // return U.getObjectKeys(this._graph[node]);
766
780
  }
767
781
  }
768
782
  exports.Graph = Graph;
@@ -1505,7 +1519,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
1505
1519
  Object.defineProperty(exports, "__esModule", { value: true });
1506
1520
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1507
1521
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1508
- // TODO - TERRY: Confirm Cartesian calculations
1522
+ // TODO - POLY: Confirm Cartesian calculations
1509
1523
  function gfArea(poly) {
1510
1524
  let area = _polygonArea(poly);
1511
1525
  return area;
@@ -1540,7 +1554,7 @@ function _polygonSimpleArea(p) {
1540
1554
  // Generalizes the above for MultiPolygons -- cloned from polyArea() in 'poly'
1541
1555
  function _polygonArea(poly) {
1542
1556
  let polyOptions = { noLatitudeCorrection: true }; // NO-OP?
1543
- poly = Poly.polyNormalize(poly, polyOptions); // TODO - Discuss w/ Terry
1557
+ poly = Poly.polyNormalize(poly, polyOptions); // TODO - POLY: Discuss w/ Terry
1544
1558
  let a = 0;
1545
1559
  // A MultiPolygon is a set of polygons
1546
1560
  for (let i = 0; poly && i < poly.length; i++) {
@@ -1553,7 +1567,7 @@ function _polygonArea(poly) {
1553
1567
  }
1554
1568
  return a;
1555
1569
  }
1556
- // TODO - TERRY: Confirm Cartesian calculations
1570
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1557
1571
  // The perimeter calculation already just computes cartesian distance if you
1558
1572
  // pass in the noLatitudeCorrection flag. You would need to divide by
1559
1573
  // Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
@@ -1562,7 +1576,7 @@ function gfPerimeter(poly) {
1562
1576
  return perimeter;
1563
1577
  }
1564
1578
  exports.gfPerimeter = gfPerimeter;
1565
- // TODO - TERRY: Confirm Cartesian calculations
1579
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1566
1580
  // Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
1567
1581
  // NOTE: No conversion of degrees to radians!
1568
1582
  function _polygonPerimeter(poly) {
@@ -1586,7 +1600,7 @@ function _distance(x1, y1, x2, y2) {
1586
1600
  d = Math.sqrt((dLat * dLat) + (dLon * dLon));
1587
1601
  return d;
1588
1602
  }
1589
- // TODO - TERRY: Confirm Cartesian calculations
1603
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1590
1604
  // As I mentioned, the polyCircle code was already just treating the coordinate
1591
1605
  // system as Cartesian. I then did polyFromCircle to convert it to a polygon that
1592
1606
  // then could be passed to polyArea in order to take into account the projection.
@@ -1620,6 +1634,7 @@ function __export(m) {
1620
1634
  }
1621
1635
  Object.defineProperty(exports, "__esModule", { value: true });
1622
1636
  __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
1637
+ __export(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1623
1638
  __export(__webpack_require__(/*! ./results */ "./src/results.ts"));
1624
1639
  __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
1625
1640
 
@@ -1795,7 +1810,7 @@ function doPreprocessData(s, bLog = false) {
1795
1810
  doPreprocessElection(s, bLog);
1796
1811
  s.bOneTimeProcessingDone = true;
1797
1812
  }
1798
- // TODO - UNASSIGNED: Made both the planByGeoID & DistrictID are right
1813
+ // NOTE - UNASSIGNED: Made both the planByGeoID & DistrictID are right
1799
1814
  // Invert the plan by district ID
1800
1815
  s.plan.invertPlan();
1801
1816
  // Create a map of geoIDs to feature IDs
@@ -1847,7 +1862,7 @@ function doPreprocessCensus(s, bLog = false) {
1847
1862
  let fipsCodes = U.getObjectKeys(totalByCounty);
1848
1863
  // Sort the results
1849
1864
  fipsCodes = fipsCodes.sort();
1850
- // TODO - SPLITTING
1865
+ // NOTE - SPLITTING
1851
1866
  // Add a dummy county, for county-district splitting analysis
1852
1867
  fipsCodes.unshift('000');
1853
1868
  // Create the ID-ordinal map
@@ -1878,7 +1893,7 @@ function doPreprocessCensus(s, bLog = false) {
1878
1893
  // Loop over the counties
1879
1894
  for (let county in fipsCodes) {
1880
1895
  let fipsCode = fipsCodes[county];
1881
- // TODO - SPLITTING
1896
+ // NOTE - SPLITTING
1882
1897
  // Skip the dummy county
1883
1898
  if (fipsCode == '000')
1884
1899
  continue;
@@ -1936,8 +1951,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
1936
1951
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1937
1952
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
1938
1953
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1939
- // TODO - DASHBOARD: Delete
1940
- // import { doAnalyzePostProcessing } from './report'
1941
1954
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
1942
1955
  const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
1943
1956
  // Example
@@ -1988,12 +2001,12 @@ let sampleSplitting = {
1988
2001
  ],
1989
2002
  unexpectedAffected: 0.3096,
1990
2003
  nSplitVTDs: 12,
1991
- splitVTDs: []
2004
+ splitVTDs: ["VTD-01", "VTD-02", "VTD-03", "VTD-04", "VTD-05", "VTD-06", "VTD-07", "VTD-08", "VTD-09", "VTD-10", "VTD-11", "VTD-12"]
1992
2005
  },
1993
2006
  datasets: {},
1994
2007
  resources: {}
1995
2008
  };
1996
- // TODO - This category is still being fleshed out.
2009
+ // TODO - PARTISAN: This category is still being fleshed out.
1997
2010
  let samplePartisan = {
1998
2011
  score: 100,
1999
2012
  metrics: {
@@ -2008,7 +2021,7 @@ let samplePartisan = {
2008
2021
  planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2009
2022
  }
2010
2023
  };
2011
- // TODO - This category is still being fleshed out.
2024
+ // TODO - MINORITY: This category is still being fleshed out.
2012
2025
  let sampleMinority = {
2013
2026
  score: null,
2014
2027
  metrics: {
@@ -2126,9 +2139,9 @@ function preparePlanAnalytics(s, bLog = false) {
2126
2139
  // None at this time
2127
2140
  },
2128
2141
  datasets: {
2129
- shapes: "2010 VTD shapes"
2130
- // TODO - DATASETS
2131
- // shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2142
+ // NOTE - DATASETS
2143
+ shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2144
+ // shapes: "2010 VTD shapes"
2132
2145
  },
2133
2146
  resources: {
2134
2147
  // None at this time
@@ -2379,7 +2392,7 @@ const polsbyPopperDefn = {
2379
2392
  externalType: TestType.Number,
2380
2393
  suites: [2 /* Best */]
2381
2394
  };
2382
- // TODO - SPLITTING
2395
+ // NOTE - SPLITTING
2383
2396
  const unexpectedCountySplitsDefn = {
2384
2397
  ID: 7 /* UnexpectedCountySplits */,
2385
2398
  name: "Unexpected County Splits",
@@ -2387,7 +2400,7 @@ const unexpectedCountySplitsDefn = {
2387
2400
  externalType: TestType.Percentage,
2388
2401
  suites: [2 /* Best */]
2389
2402
  };
2390
- // TODO - SPLITTING
2403
+ // NOTE - SPLITTING
2391
2404
  const VTDSplitsDefn = {
2392
2405
  ID: 10 /* VTDSplits */,
2393
2406
  name: "VTD Splits",
@@ -2395,7 +2408,7 @@ const VTDSplitsDefn = {
2395
2408
  externalType: TestType.Number,
2396
2409
  suites: [2 /* Best */]
2397
2410
  };
2398
- // TODO - SPLITTING
2411
+ // NOTE - SPLITTING
2399
2412
  const countySplittingDefn = {
2400
2413
  ID: 8 /* CountySplitting */,
2401
2414
  name: "County Splitting",
@@ -2403,7 +2416,7 @@ const countySplittingDefn = {
2403
2416
  externalType: TestType.Number,
2404
2417
  suites: [2 /* Best */]
2405
2418
  };
2406
- // TODO - SPLITTING
2419
+ // NOTE - SPLITTING
2407
2420
  const districtSplittingDefn = {
2408
2421
  ID: 9 /* DistrictSplitting */,
2409
2422
  name: "District Splitting",
@@ -2427,7 +2440,7 @@ const testDefns = {
2427
2440
  [4 /* PopulationDeviation */]: populationDeviationDefn,
2428
2441
  [5 /* Reock */]: reockDefn,
2429
2442
  [6 /* PolsbyPopper */]: polsbyPopperDefn,
2430
- // TODO - SPLITTING
2443
+ // NOTE - SPLITTING
2431
2444
  [7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
2432
2445
  [10 /* VTDSplits */]: VTDSplitsDefn,
2433
2446
  [8 /* CountySplitting */]: countySplittingDefn,
@@ -2455,7 +2468,7 @@ function doConfigureScales(s) {
2455
2468
  s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
2456
2469
  const nDistricts = s.state.nDistricts;
2457
2470
  const nCounties = s.counties.nCounties;
2458
- // TODO - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
2471
+ // NOTE - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
2459
2472
  // number of county splits seems too stringent, empirically.
2460
2473
  const allowableCountySplitsMultiplier = 1.5;
2461
2474
  const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
@@ -2514,7 +2527,7 @@ exports.PRECISION = 4;
2514
2527
  exports.NORMALIZED_RANGE = 100;
2515
2528
  // The dummy district ID for features not assigned districts yet
2516
2529
  exports.NOT_ASSIGNED = 0;
2517
- // TODO - TERRY/DAVE: Discuss
2530
+ // TODO - DASHBOARD: Discuss w/ Dave
2518
2531
  // # of items to report as problematic (e.g., features, districts, etc.)
2519
2532
  exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
2520
2533
  // The virtual geoID for "neighbors" in other states
@@ -2579,7 +2592,7 @@ function isOutOfState(geoID) {
2579
2592
  return geoID == S.OUT_OF_STATE;
2580
2593
  }
2581
2594
  exports.isOutOfState = isOutOfState;
2582
- // TODO - UNASSIGNED
2595
+ // NOTE - UNASSIGNED
2583
2596
  // Get the districtID to which a geoID is assigned
2584
2597
  function getDistrict(plan, geoID) {
2585
2598
  // All geoIDs in a state *should be* assigned to a district (including the
@@ -2637,7 +2650,7 @@ function normalize(rawScore, testScale) {
2637
2650
  let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
2638
2651
  // Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
2639
2652
  let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
2640
- // TODO - SPLITTING
2653
+ // NOTE - SPLITTING
2641
2654
  // Invert the scaled value if necessary to make bigger = better
2642
2655
  if (testScale.bInvertScaled) {
2643
2656
  scaledValue = 1.0 - scaledValue;