@dra2020/district-analytics 1.0.9 → 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.
@@ -118,11 +118,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
118
118
  Object.defineProperty(exports, "__esModule", { value: true });
119
119
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
120
120
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
121
- // TODO - DASHBOARD: Delete
122
- // import {
123
- // doConfigureScales, doAnalyzePostProcessing
124
- // doPrepareScorecard, doPrepareTestLog, Scorecard
125
- // } from './report'
126
121
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
127
122
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
128
123
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
@@ -153,7 +148,7 @@ class AnalyticsSession {
153
148
  // NOTE - Session settings are required:
154
149
  // - Analytics suites can be defaulted to all with [], but
155
150
  // - Dataset keys must be explicitly specified with 'dataset'
156
- // TODO - Remove this mechanism. Always run everything.
151
+ // TODO - DASHBOARD: Remove this mechanism. Always run everything.
157
152
  let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
158
153
  // If the config passed in has no suites = [], use the default suites
159
154
  if (U.isArrayEmpty(config['suites'])) {
@@ -333,10 +328,10 @@ class Districts {
333
328
  }
334
329
  // This is the workhorse computational routine!
335
330
  //
336
- // TODO - Optimize for getting multiple properties from the same feature?
337
- // 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?
338
333
  // In this case, special attention to getting county-splits right.
339
- // TODO - Optimize by async'ing this?
334
+ // TODO - OPTIMIZE by async'ing this?
340
335
  // TODO - Is there a way to do this programmatically off data? Does it matter?
341
336
  recalcStatistics(bLog = false) {
342
337
  // Compute these once per recalc cycle
@@ -345,7 +340,7 @@ class Districts {
345
340
  let planByDistrict = this._session.plan.byDistrictID();
346
341
  let plan = this._session.plan;
347
342
  let graph = this._session.graph;
348
- // TODO - SPLITTING
343
+ // NOTE - SPLITTING
349
344
  // Add an extra 0th virtual county bucket for county-district splitting analysis
350
345
  let nCountyBuckets = this._session.counties.nCounties + 1;
351
346
  // INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
@@ -371,7 +366,7 @@ class Districts {
371
366
  // INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
372
367
  let featurePop;
373
368
  let totalPop = 0;
374
- // TODO - SPLITTING
369
+ // NOTE - SPLITTING
375
370
  let countySplits = U.initArray(nCountyBuckets, 0);
376
371
  let demVotes = 0;
377
372
  let repVotes = 0;
@@ -402,31 +397,37 @@ class Districts {
402
397
  // Map from geoID to feature index
403
398
  let featureID = outerThis._session.features.featureID(geoID);
404
399
  let f = outerThis._session.features.featureByIndex(featureID);
405
- // ACCUMULATE VALUES
406
- // Total population of each feature
407
- // NOTE - This result is used more than once
408
- featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
409
- // Total district population
410
- totalPop += featurePop;
411
- // TODO - SPLITTING
412
- // Total population by counties w/in a district,
413
- // except the dummy unassigned district 0
414
- if (i > 0)
415
- countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
416
- // Democratic and Republican vote totals
417
- demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
418
- repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
419
- // Voting-age demographic breakdowns (or citizen voting-age)
420
- totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
421
- whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
422
- blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
423
- hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
424
- pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
425
- asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
426
- nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
427
- // 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
+ }
428
428
  }
429
- // else console.log("Skipping water-only feature in district statistics:", geoID);
429
+ else
430
+ console.log("Skipping water-only feature in district statistics:", geoID);
430
431
  });
431
432
  // COMPUTE DERIVED VALUES
432
433
  // Population deviation % and equal population (boolean) by district.
@@ -624,6 +625,8 @@ exports.Features = Features;
624
625
  // f is a direct GeoJSON feature
625
626
  // p is a geoID
626
627
  function _getFeatures(f, datasetKey, p) {
628
+ // Echo parameters for debugging
629
+ // console.log("f =", f, "k = ", datasetKey, "p =", p);
627
630
  // Shim to load sample data2.json from disk for command-line scaffolding
628
631
  if (f.properties && f.properties['datasets']) {
629
632
  return f.properties['datasets'][datasetKey][p];
@@ -710,7 +713,7 @@ class Plan {
710
713
  // return newPlan;
711
714
  // }
712
715
  invertPlan() {
713
- // TODO - UNASSIGNED
716
+ // NOTE - UNASSIGNED
714
717
  this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
715
718
  this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
716
719
  }
@@ -726,8 +729,8 @@ function invertPlan(plan, s) {
726
729
  let invertedPlan = {};
727
730
  // Add a dummy 'unassigned' district
728
731
  invertedPlan[S.NOT_ASSIGNED] = new Set();
729
- // TODO - UNASSIGNED
730
- // 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.
731
734
  // - In the DRA-calling context, there's an analytics session with a reference
732
735
  // to the features. Loop over all the features to find the unassigned ones,
733
736
  // and add them to the dummy unassigned district explicitly.
@@ -740,7 +743,7 @@ function invertPlan(plan, s) {
740
743
  // to the dummy unassigned district 0.
741
744
  if (!(U.keyExists(geoID, plan)))
742
745
  invertedPlan[S.NOT_ASSIGNED].add(geoID);
743
- // TODO - WATER-ONLY: NOT skipping water-only features here, because we're
746
+ // NOTE - NOT skipping WATER-ONLY features here, because we're
744
747
  // not skipping them below when they are explicitly assigned in plans. Should
745
748
  // we skip them in both places?
746
749
  }
@@ -767,7 +770,13 @@ class Graph {
767
770
  peerNeighbors(node) {
768
771
  // Get the neighboring geoIDs connected to a geoID
769
772
  // Ignore the lengths of the shared borders (the values), for now
770
- 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]);
771
780
  }
772
781
  }
773
782
  exports.Graph = Graph;
@@ -1510,7 +1519,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
1510
1519
  Object.defineProperty(exports, "__esModule", { value: true });
1511
1520
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1512
1521
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1513
- // TODO - TERRY: Confirm Cartesian calculations
1522
+ // TODO - POLY: Confirm Cartesian calculations
1514
1523
  function gfArea(poly) {
1515
1524
  let area = _polygonArea(poly);
1516
1525
  return area;
@@ -1545,7 +1554,7 @@ function _polygonSimpleArea(p) {
1545
1554
  // Generalizes the above for MultiPolygons -- cloned from polyArea() in 'poly'
1546
1555
  function _polygonArea(poly) {
1547
1556
  let polyOptions = { noLatitudeCorrection: true }; // NO-OP?
1548
- poly = Poly.polyNormalize(poly, polyOptions); // TODO - Discuss w/ Terry
1557
+ poly = Poly.polyNormalize(poly, polyOptions); // TODO - POLY: Discuss w/ Terry
1549
1558
  let a = 0;
1550
1559
  // A MultiPolygon is a set of polygons
1551
1560
  for (let i = 0; poly && i < poly.length; i++) {
@@ -1558,7 +1567,7 @@ function _polygonArea(poly) {
1558
1567
  }
1559
1568
  return a;
1560
1569
  }
1561
- // TODO - TERRY: Confirm Cartesian calculations
1570
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1562
1571
  // The perimeter calculation already just computes cartesian distance if you
1563
1572
  // pass in the noLatitudeCorrection flag. You would need to divide by
1564
1573
  // Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
@@ -1567,7 +1576,7 @@ function gfPerimeter(poly) {
1567
1576
  return perimeter;
1568
1577
  }
1569
1578
  exports.gfPerimeter = gfPerimeter;
1570
- // TODO - TERRY: Confirm Cartesian calculations
1579
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1571
1580
  // Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
1572
1581
  // NOTE: No conversion of degrees to radians!
1573
1582
  function _polygonPerimeter(poly) {
@@ -1591,7 +1600,7 @@ function _distance(x1, y1, x2, y2) {
1591
1600
  d = Math.sqrt((dLat * dLat) + (dLon * dLon));
1592
1601
  return d;
1593
1602
  }
1594
- // TODO - TERRY: Confirm Cartesian calculations
1603
+ // TODO - POLY: Confirm Cartesian calculations w/ Terry
1595
1604
  // As I mentioned, the polyCircle code was already just treating the coordinate
1596
1605
  // system as Cartesian. I then did polyFromCircle to convert it to a polygon that
1597
1606
  // then could be passed to polyArea in order to take into account the projection.
@@ -1625,6 +1634,7 @@ function __export(m) {
1625
1634
  }
1626
1635
  Object.defineProperty(exports, "__esModule", { value: true });
1627
1636
  __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
1637
+ __export(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1628
1638
  __export(__webpack_require__(/*! ./results */ "./src/results.ts"));
1629
1639
  __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
1630
1640
 
@@ -1800,7 +1810,7 @@ function doPreprocessData(s, bLog = false) {
1800
1810
  doPreprocessElection(s, bLog);
1801
1811
  s.bOneTimeProcessingDone = true;
1802
1812
  }
1803
- // TODO - UNASSIGNED: Made both the planByGeoID & DistrictID are right
1813
+ // NOTE - UNASSIGNED: Made both the planByGeoID & DistrictID are right
1804
1814
  // Invert the plan by district ID
1805
1815
  s.plan.invertPlan();
1806
1816
  // Create a map of geoIDs to feature IDs
@@ -1852,7 +1862,7 @@ function doPreprocessCensus(s, bLog = false) {
1852
1862
  let fipsCodes = U.getObjectKeys(totalByCounty);
1853
1863
  // Sort the results
1854
1864
  fipsCodes = fipsCodes.sort();
1855
- // TODO - SPLITTING
1865
+ // NOTE - SPLITTING
1856
1866
  // Add a dummy county, for county-district splitting analysis
1857
1867
  fipsCodes.unshift('000');
1858
1868
  // Create the ID-ordinal map
@@ -1883,7 +1893,7 @@ function doPreprocessCensus(s, bLog = false) {
1883
1893
  // Loop over the counties
1884
1894
  for (let county in fipsCodes) {
1885
1895
  let fipsCode = fipsCodes[county];
1886
- // TODO - SPLITTING
1896
+ // NOTE - SPLITTING
1887
1897
  // Skip the dummy county
1888
1898
  if (fipsCode == '000')
1889
1899
  continue;
@@ -1941,8 +1951,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
1941
1951
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1942
1952
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
1943
1953
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1944
- // TODO - DASHBOARD: Delete
1945
- // import { doAnalyzePostProcessing } from './report'
1946
1954
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
1947
1955
  const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
1948
1956
  // Example
@@ -1993,12 +2001,12 @@ let sampleSplitting = {
1993
2001
  ],
1994
2002
  unexpectedAffected: 0.3096,
1995
2003
  nSplitVTDs: 12,
1996
- 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"]
1997
2005
  },
1998
2006
  datasets: {},
1999
2007
  resources: {}
2000
2008
  };
2001
- // TODO - This category is still being fleshed out.
2009
+ // TODO - PARTISAN: This category is still being fleshed out.
2002
2010
  let samplePartisan = {
2003
2011
  score: 100,
2004
2012
  metrics: {
@@ -2013,7 +2021,7 @@ let samplePartisan = {
2013
2021
  planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2014
2022
  }
2015
2023
  };
2016
- // TODO - This category is still being fleshed out.
2024
+ // TODO - MINORITY: This category is still being fleshed out.
2017
2025
  let sampleMinority = {
2018
2026
  score: null,
2019
2027
  metrics: {
@@ -2131,9 +2139,9 @@ function preparePlanAnalytics(s, bLog = false) {
2131
2139
  // None at this time
2132
2140
  },
2133
2141
  datasets: {
2134
- shapes: "2010 VTD shapes"
2135
- // TODO - DATASETS
2136
- // shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2142
+ // NOTE - DATASETS
2143
+ shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2144
+ // shapes: "2010 VTD shapes"
2137
2145
  },
2138
2146
  resources: {
2139
2147
  // None at this time
@@ -2384,7 +2392,7 @@ const polsbyPopperDefn = {
2384
2392
  externalType: TestType.Number,
2385
2393
  suites: [2 /* Best */]
2386
2394
  };
2387
- // TODO - SPLITTING
2395
+ // NOTE - SPLITTING
2388
2396
  const unexpectedCountySplitsDefn = {
2389
2397
  ID: 7 /* UnexpectedCountySplits */,
2390
2398
  name: "Unexpected County Splits",
@@ -2392,7 +2400,7 @@ const unexpectedCountySplitsDefn = {
2392
2400
  externalType: TestType.Percentage,
2393
2401
  suites: [2 /* Best */]
2394
2402
  };
2395
- // TODO - SPLITTING
2403
+ // NOTE - SPLITTING
2396
2404
  const VTDSplitsDefn = {
2397
2405
  ID: 10 /* VTDSplits */,
2398
2406
  name: "VTD Splits",
@@ -2400,7 +2408,7 @@ const VTDSplitsDefn = {
2400
2408
  externalType: TestType.Number,
2401
2409
  suites: [2 /* Best */]
2402
2410
  };
2403
- // TODO - SPLITTING
2411
+ // NOTE - SPLITTING
2404
2412
  const countySplittingDefn = {
2405
2413
  ID: 8 /* CountySplitting */,
2406
2414
  name: "County Splitting",
@@ -2408,7 +2416,7 @@ const countySplittingDefn = {
2408
2416
  externalType: TestType.Number,
2409
2417
  suites: [2 /* Best */]
2410
2418
  };
2411
- // TODO - SPLITTING
2419
+ // NOTE - SPLITTING
2412
2420
  const districtSplittingDefn = {
2413
2421
  ID: 9 /* DistrictSplitting */,
2414
2422
  name: "District Splitting",
@@ -2432,7 +2440,7 @@ const testDefns = {
2432
2440
  [4 /* PopulationDeviation */]: populationDeviationDefn,
2433
2441
  [5 /* Reock */]: reockDefn,
2434
2442
  [6 /* PolsbyPopper */]: polsbyPopperDefn,
2435
- // TODO - SPLITTING
2443
+ // NOTE - SPLITTING
2436
2444
  [7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
2437
2445
  [10 /* VTDSplits */]: VTDSplitsDefn,
2438
2446
  [8 /* CountySplitting */]: countySplittingDefn,
@@ -2460,7 +2468,7 @@ function doConfigureScales(s) {
2460
2468
  s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
2461
2469
  const nDistricts = s.state.nDistricts;
2462
2470
  const nCounties = s.counties.nCounties;
2463
- // TODO - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
2471
+ // NOTE - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
2464
2472
  // number of county splits seems too stringent, empirically.
2465
2473
  const allowableCountySplitsMultiplier = 1.5;
2466
2474
  const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
@@ -2519,7 +2527,7 @@ exports.PRECISION = 4;
2519
2527
  exports.NORMALIZED_RANGE = 100;
2520
2528
  // The dummy district ID for features not assigned districts yet
2521
2529
  exports.NOT_ASSIGNED = 0;
2522
- // TODO - TERRY/DAVE: Discuss
2530
+ // TODO - DASHBOARD: Discuss w/ Dave
2523
2531
  // # of items to report as problematic (e.g., features, districts, etc.)
2524
2532
  exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
2525
2533
  // The virtual geoID for "neighbors" in other states
@@ -2584,7 +2592,7 @@ function isOutOfState(geoID) {
2584
2592
  return geoID == S.OUT_OF_STATE;
2585
2593
  }
2586
2594
  exports.isOutOfState = isOutOfState;
2587
- // TODO - UNASSIGNED
2595
+ // NOTE - UNASSIGNED
2588
2596
  // Get the districtID to which a geoID is assigned
2589
2597
  function getDistrict(plan, geoID) {
2590
2598
  // All geoIDs in a state *should be* assigned to a district (including the
@@ -2642,7 +2650,7 @@ function normalize(rawScore, testScale) {
2642
2650
  let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
2643
2651
  // Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
2644
2652
  let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
2645
- // TODO - SPLITTING
2653
+ // NOTE - SPLITTING
2646
2654
  // Invert the scaled value if necessary to make bigger = better
2647
2655
  if (testScale.bInvertScaled) {
2648
2656
  scaledValue = 1.0 - scaledValue;