@dra2020/district-analytics 2.0.4 → 2.0.7
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/README.md +4 -0
- package/dist/cli.js +45 -16
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +45 -16
- package/dist/district-analytics.js.map +1 -1
- package/package.json +1 -1
|
@@ -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();
|
|
@@ -383,6 +396,9 @@ class Districts {
|
|
|
383
396
|
// 3 - MORE ...
|
|
384
397
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
385
398
|
let outerThis = this;
|
|
399
|
+
// Default the pop dev % for the dummy Unassigned district to 0%.
|
|
400
|
+
// Default the pop dev % for real (1–N) but empty districts to 100%.
|
|
401
|
+
let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
|
|
386
402
|
// Get the geoIDs assigned to the district
|
|
387
403
|
// Guard against empty districts
|
|
388
404
|
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
@@ -429,11 +445,11 @@ class Districts {
|
|
|
429
445
|
});
|
|
430
446
|
// COMPUTE DERIVED VALUES
|
|
431
447
|
// Population deviation % and equal population (boolean) by district.
|
|
432
|
-
// Default the value for the dummy unassigned district to 0%.
|
|
433
|
-
let popDevPct = 0 / targetSize;
|
|
434
448
|
if (i > 0) {
|
|
435
|
-
|
|
436
|
-
|
|
449
|
+
if (totalPop > 0) {
|
|
450
|
+
popDevPct = (totalPop - targetSize) / targetSize;
|
|
451
|
+
bEqualPop = (popDevPct <= deviationThreshold);
|
|
452
|
+
}
|
|
437
453
|
}
|
|
438
454
|
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
439
455
|
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
@@ -522,7 +538,7 @@ class Districts {
|
|
|
522
538
|
}
|
|
523
539
|
}
|
|
524
540
|
else { // If a district is empty, zero these results (vs. null)
|
|
525
|
-
this.statistics[DistrictField.PopDevPct][i] =
|
|
541
|
+
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
526
542
|
this.statistics[DistrictField.DemVotes][i] = 0;
|
|
527
543
|
this.statistics[DistrictField.RepVotes][i] = 0;
|
|
528
544
|
this.statistics[DistrictField.TwoPartyVote][i] = 0;
|
|
@@ -569,6 +585,8 @@ class Districts {
|
|
|
569
585
|
this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
|
|
570
586
|
this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
|
|
571
587
|
}
|
|
588
|
+
console.log(`${nMissingDataset} features with missing datasets.`);
|
|
589
|
+
console.log(`${nMissingProperty} features with missing properties.`);
|
|
572
590
|
}
|
|
573
591
|
// NOTE - I did not roll these into district statistics, because creating the
|
|
574
592
|
// district shapes themselves is the big district-by-district activity, these
|
|
@@ -623,36 +641,46 @@ exports.Features = Features;
|
|
|
623
641
|
// f is a direct GeoJSON feature
|
|
624
642
|
// p is a geoID
|
|
625
643
|
function _getFeatures(f, datasetKey, p) {
|
|
626
|
-
// Echo parameters for debugging
|
|
627
|
-
// console.log("f =", f, "k = ", datasetKey, "p =", p);
|
|
628
644
|
// Shim to load sample data2.json from disk for command-line scaffolding
|
|
629
645
|
if (f.properties && f.properties['datasets']) {
|
|
646
|
+
if (!f.properties['datasets'][datasetKey]) {
|
|
647
|
+
// Feature is missing the dataset
|
|
648
|
+
nMissingDataset += 1;
|
|
649
|
+
console.log(`${nMissingDataset}: Data ${datasetKey} missing for feature ${f} Returning zero.`);
|
|
650
|
+
return 0;
|
|
651
|
+
}
|
|
630
652
|
return f.properties['datasets'][datasetKey][p];
|
|
631
653
|
}
|
|
632
654
|
// NOTE - The fGetW() code from dra-client below here ...
|
|
633
655
|
// Direct property?
|
|
634
|
-
if (f.properties && f.properties[p] !== undefined)
|
|
656
|
+
if (f.properties && f.properties[p] !== undefined) {
|
|
635
657
|
return f.properties[p];
|
|
658
|
+
}
|
|
636
659
|
// Joined property?
|
|
637
660
|
let a = _fGetJoined(f);
|
|
638
661
|
if (a) {
|
|
639
662
|
for (let i = 0; i < a.length; i++) {
|
|
640
663
|
let o = a[i];
|
|
641
664
|
if (!datasetKey) {
|
|
642
|
-
if (o[p] !== undefined)
|
|
665
|
+
if (o[p] !== undefined) {
|
|
643
666
|
return o[p];
|
|
667
|
+
}
|
|
644
668
|
}
|
|
645
669
|
else {
|
|
646
670
|
if (o['datasets'] && o['datasets'][datasetKey]) {
|
|
647
671
|
let v = (o['datasets'][datasetKey][p]);
|
|
648
|
-
if ((!(v == null)) && (!(v == undefined)))
|
|
672
|
+
if ((!(v == null)) && (!(v == undefined))) {
|
|
649
673
|
return o['datasets'][datasetKey][p];
|
|
674
|
+
}
|
|
650
675
|
}
|
|
651
676
|
}
|
|
652
677
|
}
|
|
653
678
|
}
|
|
654
|
-
|
|
655
|
-
|
|
679
|
+
// Feature is missing the property
|
|
680
|
+
nMissingProperty += 1;
|
|
681
|
+
console.log(`${nMissingProperty}: ${p} value undefined for ${f.properties['GEOID10']}. Returning zero.`);
|
|
682
|
+
return 0;
|
|
683
|
+
// return undefined;
|
|
656
684
|
}
|
|
657
685
|
function _fGetJoined(f) {
|
|
658
686
|
return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
|
|
@@ -697,6 +725,7 @@ class Plan {
|
|
|
697
725
|
}
|
|
698
726
|
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
699
727
|
// for contiguity. Just skip them in aggregating district statistics.
|
|
728
|
+
//
|
|
700
729
|
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
701
730
|
// let newPlan = {} as T.PlanByGeoID;
|
|
702
731
|
// for (let geoID in plan) {
|
|
@@ -1200,7 +1229,7 @@ exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
|
|
|
1200
1229
|
function doFindSplitVTDs(s, bLog = false) {
|
|
1201
1230
|
let test = s.getTest(10 /* VTDSplits */);
|
|
1202
1231
|
let splitVTDs = [];
|
|
1203
|
-
// TODO - SPLITTING: Flesh this out, using
|
|
1232
|
+
// TODO - SPLITTING: Flesh this out, using virtual VTD's ...
|
|
1204
1233
|
test['score'] = splitVTDs.length;
|
|
1205
1234
|
test['details']['splitVTDs'] = splitVTDs;
|
|
1206
1235
|
return test;
|
|
@@ -1860,9 +1889,9 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
1860
1889
|
// Sum total population by county
|
|
1861
1890
|
totalByCounty[countyFIPS] += value;
|
|
1862
1891
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1892
|
+
else {
|
|
1893
|
+
console.log("Skipping water-only feature in Census preprocessing:", geoID);
|
|
1894
|
+
}
|
|
1866
1895
|
}
|
|
1867
1896
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
1868
1897
|
// CREATE A FIPS CODE-ORDINAL MAP
|