@dra2020/district-analytics 1.0.10 → 2.0.1
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 +5 -5
- package/dist/cli.js +1686 -1354
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +1334 -1214
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/_api.d.ts +4 -4
- package/dist/src/_data.d.ts +5 -3
- package/dist/src/analyze.d.ts +1 -1
- package/dist/src/cohesive.d.ts +4 -2
- package/dist/src/equal.d.ts +2 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/minority.d.ts +1 -1
- package/dist/src/political.d.ts +5 -5
- package/dist/src/preprocess.d.ts +1 -1
- package/dist/src/report.d.ts +0 -15
- package/dist/src/results.d.ts +141 -0
- package/dist/src/settings.d.ts +3 -0
- package/dist/src/types.d.ts +27 -13
- package/dist/src/utils.d.ts +4 -3
- package/dist/src/valid.d.ts +3 -3
- package/dist/test/_cli.d.ts +2 -0
- package/package.json +6 -4
- package/dist/_api.d.ts +0 -27
- package/dist/_data.d.ts +0 -130
- package/dist/analyze.d.ts +0 -4
- package/dist/cohesive.d.ts +0 -4
- package/dist/compact.d.ts +0 -5
- package/dist/constants.d.ts +0 -6
- package/dist/equal.d.ts +0 -4
- package/dist/geofeature.d.ts +0 -3
- package/dist/index.d.ts +0 -2
- package/dist/minority.d.ts +0 -3
- package/dist/political.d.ts +0 -8
- package/dist/preprocess.d.ts +0 -2
- package/dist/report.d.ts +0 -15
- package/dist/settings.d.ts +0 -5
- package/dist/types.d.ts +0 -110
- package/dist/utils.d.ts +0 -28
- package/dist/valid.d.ts +0 -8
package/dist/cli.js
CHANGED
|
@@ -8952,12 +8952,20 @@ function sort(keys, values, left, right, compare) {
|
|
|
8952
8952
|
//
|
|
8953
8953
|
// THE NODE PACKAGE API
|
|
8954
8954
|
//
|
|
8955
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
8956
|
+
if (mod && mod.__esModule) return mod;
|
|
8957
|
+
var result = {};
|
|
8958
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
8959
|
+
result["default"] = mod;
|
|
8960
|
+
return result;
|
|
8961
|
+
};
|
|
8955
8962
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8956
8963
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
8957
8964
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
8958
|
-
const
|
|
8959
|
-
const
|
|
8960
|
-
const
|
|
8965
|
+
const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
8966
|
+
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
8967
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
8968
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
8961
8969
|
class AnalyticsSession {
|
|
8962
8970
|
constructor(SessionRequest) {
|
|
8963
8971
|
this.config = {};
|
|
@@ -8966,7 +8974,6 @@ class AnalyticsSession {
|
|
|
8966
8974
|
this.bPostProcessingDone = false;
|
|
8967
8975
|
this.testScales = {};
|
|
8968
8976
|
this.tests = {};
|
|
8969
|
-
this.scorecard = {};
|
|
8970
8977
|
this.title = SessionRequest['title'];
|
|
8971
8978
|
this.legislativeDistricts = SessionRequest['legislativeDistricts'];
|
|
8972
8979
|
this.config = this.processConfig(SessionRequest['config']);
|
|
@@ -8979,12 +8986,13 @@ class AnalyticsSession {
|
|
|
8979
8986
|
// NOTE: I've pulled these out of the individual analytics to here. Eventually,
|
|
8980
8987
|
// we could want them to passed into an analytics session as data, along with
|
|
8981
8988
|
// everything else. For now, this keeps branching out of the main code.
|
|
8982
|
-
|
|
8989
|
+
results_1.doConfigureScales(this);
|
|
8983
8990
|
}
|
|
8984
8991
|
processConfig(config) {
|
|
8985
8992
|
// NOTE - Session settings are required:
|
|
8986
8993
|
// - Analytics suites can be defaulted to all with [], but
|
|
8987
8994
|
// - Dataset keys must be explicitly specified with 'dataset'
|
|
8995
|
+
// TODO - DASHBOARD: Remove this mechanism. Always run everything.
|
|
8988
8996
|
let defaultSuites = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
|
|
8989
8997
|
// If the config passed in has no suites = [], use the default suites
|
|
8990
8998
|
if (U.isArrayEmpty(config['suites'])) {
|
|
@@ -8999,10 +9007,10 @@ class AnalyticsSession {
|
|
|
8999
9007
|
// analytics & validations, saving/updating the individual test results.
|
|
9000
9008
|
analyzePlan(bLog = false) {
|
|
9001
9009
|
try {
|
|
9002
|
-
preprocess_1.doPreprocessData(this);
|
|
9010
|
+
preprocess_1.doPreprocessData(this, bLog);
|
|
9003
9011
|
analyze_1.doAnalyzeDistricts(this, bLog);
|
|
9004
9012
|
analyze_1.doAnalyzePlan(this, bLog);
|
|
9005
|
-
|
|
9013
|
+
results_1.doAnalyzePostProcessing(this, bLog);
|
|
9006
9014
|
}
|
|
9007
9015
|
catch (_a) {
|
|
9008
9016
|
console.log("Exception caught by analyzePlan()");
|
|
@@ -9010,6 +9018,31 @@ class AnalyticsSession {
|
|
|
9010
9018
|
}
|
|
9011
9019
|
return true;
|
|
9012
9020
|
}
|
|
9021
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
9022
|
+
getPlanAnalytics(bLog = false) {
|
|
9023
|
+
return results_2.preparePlanAnalytics(this, bLog);
|
|
9024
|
+
}
|
|
9025
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
9026
|
+
getDistrictStatistics(bLog = false) {
|
|
9027
|
+
return results_2.prepareDistrictStatistics(this, bLog);
|
|
9028
|
+
}
|
|
9029
|
+
// NOTE - This assumes that analyzePlan() has been run!
|
|
9030
|
+
getDiscontiguousDistrictFeatures(bLog = false) {
|
|
9031
|
+
// Get the (possibly empty) list of discontiguous district IDs
|
|
9032
|
+
let contiguousTest = this.getTest(1 /* Contiguous */);
|
|
9033
|
+
let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
|
|
9034
|
+
// Convert them into a (possibly empty) list of features
|
|
9035
|
+
let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
|
|
9036
|
+
if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
|
|
9037
|
+
for (let id of discontiguousDistrictIDs) {
|
|
9038
|
+
let poly = this.districts.getDistrictShapeByID(id);
|
|
9039
|
+
if (poly)
|
|
9040
|
+
discontiguousDistrictFeatures.features.push(poly);
|
|
9041
|
+
}
|
|
9042
|
+
}
|
|
9043
|
+
return discontiguousDistrictFeatures;
|
|
9044
|
+
}
|
|
9045
|
+
// HELPERS USED INTERNALLY
|
|
9013
9046
|
// Get an individual test, so you can drive UI with the results.
|
|
9014
9047
|
getTest(testID) {
|
|
9015
9048
|
// Get the existing test entries
|
|
@@ -9024,18 +9057,9 @@ class AnalyticsSession {
|
|
|
9024
9057
|
// Return a pointer to the the test entry for this test
|
|
9025
9058
|
return this.tests[testID];
|
|
9026
9059
|
}
|
|
9027
|
-
//
|
|
9028
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
9029
|
-
prepareScorecard() {
|
|
9030
|
-
return report_1.doPrepareScorecard(this);
|
|
9031
|
-
}
|
|
9032
|
-
// Prepare test results for rendering
|
|
9033
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
9034
|
-
prepareTestLog() {
|
|
9035
|
-
return report_1.doPrepareTestLog(this);
|
|
9036
|
-
}
|
|
9060
|
+
// NOTE - Not sure why this has to be up here.
|
|
9037
9061
|
populationDeviationThreshold() {
|
|
9038
|
-
return 1 - this.testScales[4 /* PopulationDeviation */]['
|
|
9062
|
+
return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
|
|
9039
9063
|
}
|
|
9040
9064
|
}
|
|
9041
9065
|
exports.AnalyticsSession = AnalyticsSession;
|
|
@@ -9055,8 +9079,16 @@ exports.AnalyticsSession = AnalyticsSession;
|
|
|
9055
9079
|
//
|
|
9056
9080
|
// DATA ABSTRACTION LAYER
|
|
9057
9081
|
//
|
|
9082
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
9083
|
+
if (mod && mod.__esModule) return mod;
|
|
9084
|
+
var result = {};
|
|
9085
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
9086
|
+
result["default"] = mod;
|
|
9087
|
+
return result;
|
|
9088
|
+
};
|
|
9058
9089
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9059
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
9090
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
9091
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
9060
9092
|
const valid_1 = __webpack_require__(/*! ./valid */ "./src/valid.ts");
|
|
9061
9093
|
const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
|
|
9062
9094
|
const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
|
|
@@ -9095,23 +9127,6 @@ var DistrictField;
|
|
|
9095
9127
|
DistrictField[DistrictField["NativePct"] = 27] = "NativePct"; // Display
|
|
9096
9128
|
// 1 - MORE ...
|
|
9097
9129
|
})(DistrictField = exports.DistrictField || (exports.DistrictField = {}));
|
|
9098
|
-
// The fields to display in a District Statistics pane
|
|
9099
|
-
exports.DisplayFields = [
|
|
9100
|
-
DistrictField.TotalPop,
|
|
9101
|
-
DistrictField.PopDevPct,
|
|
9102
|
-
DistrictField.bEqualPop,
|
|
9103
|
-
DistrictField.bNotEmpty,
|
|
9104
|
-
DistrictField.bContiguous,
|
|
9105
|
-
DistrictField.bNotEmbedded,
|
|
9106
|
-
DistrictField.DemPct,
|
|
9107
|
-
DistrictField.WhitePct,
|
|
9108
|
-
DistrictField.MinorityPct,
|
|
9109
|
-
DistrictField.BlackPct,
|
|
9110
|
-
DistrictField.HispanicPct,
|
|
9111
|
-
DistrictField.PacificPct,
|
|
9112
|
-
DistrictField.AsianPct,
|
|
9113
|
-
DistrictField.NativePct
|
|
9114
|
-
];
|
|
9115
9130
|
class Districts {
|
|
9116
9131
|
constructor(s, ds) {
|
|
9117
9132
|
this._geoProperties = {};
|
|
@@ -9119,8 +9134,23 @@ class Districts {
|
|
|
9119
9134
|
this._shapes = ds;
|
|
9120
9135
|
this.statistics = this.initStatistics();
|
|
9121
9136
|
}
|
|
9122
|
-
|
|
9123
|
-
|
|
9137
|
+
getDistrictShapeByID(id) {
|
|
9138
|
+
// NOTE - Find the district shape by ID (not index)
|
|
9139
|
+
// return this._shapes.features[id];
|
|
9140
|
+
for (let f of this._shapes.features) {
|
|
9141
|
+
if (f.properties && (f.properties.id == id)) {
|
|
9142
|
+
return f;
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
return undefined;
|
|
9146
|
+
}
|
|
9147
|
+
getGeoProperties(i) {
|
|
9148
|
+
// Make sure the district shape exists & has geo properties
|
|
9149
|
+
if (i in this._geoProperties)
|
|
9150
|
+
return this._geoProperties[i];
|
|
9151
|
+
else
|
|
9152
|
+
return null;
|
|
9153
|
+
}
|
|
9124
9154
|
setGeoProperties(i, p) { this._geoProperties[i] = p; }
|
|
9125
9155
|
numberOfColumns() { return U.countEnumValues(DistrictField); }
|
|
9126
9156
|
// +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
|
|
@@ -9140,10 +9170,10 @@ class Districts {
|
|
|
9140
9170
|
}
|
|
9141
9171
|
// This is the workhorse computational routine!
|
|
9142
9172
|
//
|
|
9143
|
-
// TODO -
|
|
9144
|
-
// TODO -
|
|
9173
|
+
// TODO - OPTIMIZE for getting multiple properties from the same feature?
|
|
9174
|
+
// TODO - OPTIMIZE by only re-calc'ing districts that have changed?
|
|
9145
9175
|
// In this case, special attention to getting county-splits right.
|
|
9146
|
-
// TODO -
|
|
9176
|
+
// TODO - OPTIMIZE by async'ing this?
|
|
9147
9177
|
// TODO - Is there a way to do this programmatically off data? Does it matter?
|
|
9148
9178
|
recalcStatistics(bLog = false) {
|
|
9149
9179
|
// Compute these once per recalc cycle
|
|
@@ -9152,6 +9182,9 @@ class Districts {
|
|
|
9152
9182
|
let planByDistrict = this._session.plan.byDistrictID();
|
|
9153
9183
|
let plan = this._session.plan;
|
|
9154
9184
|
let graph = this._session.graph;
|
|
9185
|
+
// NOTE - SPLITTING
|
|
9186
|
+
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
9187
|
+
let nCountyBuckets = this._session.counties.nCounties + 1;
|
|
9155
9188
|
// INITIALIZE STATE VALUES THAT WILL BE ACCUMULATED
|
|
9156
9189
|
let stateTPVote = 0;
|
|
9157
9190
|
let stateDemVote = 0;
|
|
@@ -9167,7 +9200,7 @@ class Districts {
|
|
|
9167
9200
|
// NOTE - These plan-level booleans are set in their respective analytics:
|
|
9168
9201
|
// - Equal population (bEqualPop)
|
|
9169
9202
|
// - Complete (bNotEmpty)
|
|
9170
|
-
// -
|
|
9203
|
+
// - Contiguous (bContiguous)
|
|
9171
9204
|
// - Free of holes (bNotEmbedded)
|
|
9172
9205
|
// 2 - MORE ...
|
|
9173
9206
|
// Loop over the districts (including the dummy unassigned one)
|
|
@@ -9175,7 +9208,8 @@ class Districts {
|
|
|
9175
9208
|
// INITIALIZE DISTRICT VALUES THAT WILL BE ACCUMULATED (VS. DERIVED)
|
|
9176
9209
|
let featurePop;
|
|
9177
9210
|
let totalPop = 0;
|
|
9178
|
-
|
|
9211
|
+
// NOTE - SPLITTING
|
|
9212
|
+
let countySplits = U.initArray(nCountyBuckets, 0);
|
|
9179
9213
|
let demVotes = 0;
|
|
9180
9214
|
let repVotes = 0;
|
|
9181
9215
|
let totalVAP = 0;
|
|
@@ -9185,156 +9219,184 @@ class Districts {
|
|
|
9185
9219
|
let pacificPop = 0;
|
|
9186
9220
|
let asianPop = 0;
|
|
9187
9221
|
let nativePop = 0;
|
|
9222
|
+
// NOTE - Only report explicitly found validity issues
|
|
9223
|
+
let bNotEmpty = false;
|
|
9224
|
+
let bContiguous = true;
|
|
9225
|
+
let bNotEmbedded = true;
|
|
9226
|
+
let bEqualPop = true;
|
|
9188
9227
|
// 3 - MORE ...
|
|
9189
9228
|
// HACK - Because "this" gets ghosted inside the forEach loop below
|
|
9190
9229
|
let outerThis = this;
|
|
9191
|
-
// Get the geoIDs assigned to
|
|
9230
|
+
// Get the geoIDs assigned to the district
|
|
9231
|
+
// Guard against empty districts
|
|
9192
9232
|
let geoIDs = this._session.plan.geoIDsForDistrictID(i);
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
//
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
//
|
|
9235
|
-
//
|
|
9236
|
-
//
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
nativePct = nativePop / totalVAP;
|
|
9280
|
-
}
|
|
9281
|
-
// 5 - MORE ...
|
|
9282
|
-
// COMPUTE DISTRICT-LEVEL VALUES
|
|
9283
|
-
// Validations
|
|
9284
|
-
let bNotEmpty = (!U.isSetEmpty(geoIDs));
|
|
9285
|
-
let bContiguous = null;
|
|
9286
|
-
let bNotEmbedded = null;
|
|
9287
|
-
// Leave the values null for the dummy unassigned district,
|
|
9288
|
-
// and districts that are empty.
|
|
9289
|
-
if (i > 0) {
|
|
9290
|
-
if (bNotEmpty) {
|
|
9233
|
+
if (geoIDs && (geoIDs.size > 0)) {
|
|
9234
|
+
bNotEmpty = true;
|
|
9235
|
+
// ... loop over the geoIDs creating district-by-district statistics
|
|
9236
|
+
geoIDs.forEach(function (geoID) {
|
|
9237
|
+
// Skip water-only features
|
|
9238
|
+
if (!(U.isWaterOnly(geoID))) {
|
|
9239
|
+
// Map from geoID to feature index
|
|
9240
|
+
let featureID = outerThis._session.features.featureID(geoID);
|
|
9241
|
+
let f = outerThis._session.features.featureByIndex(featureID);
|
|
9242
|
+
if (f == undefined) {
|
|
9243
|
+
console.log("Skipping undefined feature in district statistics: GEOID =", geoID, "Feature ID =", featureID);
|
|
9244
|
+
}
|
|
9245
|
+
else {
|
|
9246
|
+
// ACCUMULATE VALUES
|
|
9247
|
+
// Total population of each feature
|
|
9248
|
+
// NOTE - This result is used more than once
|
|
9249
|
+
featurePop = outerThis._session.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
9250
|
+
// Total district population
|
|
9251
|
+
totalPop += featurePop;
|
|
9252
|
+
// NOTE - SPLITTING
|
|
9253
|
+
// Total population by counties w/in a district,
|
|
9254
|
+
// except the dummy unassigned district 0
|
|
9255
|
+
if (i > 0)
|
|
9256
|
+
countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
|
|
9257
|
+
// Democratic and Republican vote totals
|
|
9258
|
+
demVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
|
|
9259
|
+
repVotes += outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
|
|
9260
|
+
// Voting-age demographic breakdowns (or citizen voting-age)
|
|
9261
|
+
totalVAP += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
|
|
9262
|
+
whitePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
|
|
9263
|
+
blackPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */);
|
|
9264
|
+
hispanicPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */);
|
|
9265
|
+
pacificPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */);
|
|
9266
|
+
asianPop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */);
|
|
9267
|
+
nativePop += outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */);
|
|
9268
|
+
// 4 - MORE ...
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
else
|
|
9272
|
+
console.log("Skipping water-only feature in district statistics:", geoID);
|
|
9273
|
+
});
|
|
9274
|
+
// COMPUTE DERIVED VALUES
|
|
9275
|
+
// Population deviation % and equal population (boolean) by district.
|
|
9276
|
+
// Don't set the values for the dummy unassigned district.
|
|
9277
|
+
let popDevPct = null;
|
|
9278
|
+
if (i > 0) {
|
|
9279
|
+
popDevPct = (totalPop - targetSize) / targetSize;
|
|
9280
|
+
bEqualPop = (popDevPct <= deviationThreshold);
|
|
9281
|
+
}
|
|
9282
|
+
// Total two-party (not total total!) votes, Democratic and Republican vote
|
|
9283
|
+
// shares, and Democratic first-past-the-post win (= 1) or loss (= 0).
|
|
9284
|
+
let totVotes;
|
|
9285
|
+
let demPct = 0;
|
|
9286
|
+
let repPct = 0;
|
|
9287
|
+
let DemSeat = 0;
|
|
9288
|
+
totVotes = demVotes + repVotes;
|
|
9289
|
+
if (totVotes > 0) {
|
|
9290
|
+
demPct = demVotes / totVotes;
|
|
9291
|
+
repPct = repVotes / totVotes;
|
|
9292
|
+
DemSeat = political_1.fptpWin(demPct);
|
|
9293
|
+
}
|
|
9294
|
+
// Total minority VAP
|
|
9295
|
+
let minorityPop = totalVAP - whitePop;
|
|
9296
|
+
// Voting-age demographic proportions (or citizen voting-age)
|
|
9297
|
+
let whitePct = 0;
|
|
9298
|
+
let minorityPct = 0;
|
|
9299
|
+
let blackPct = 0;
|
|
9300
|
+
let hispanicPct = 0;
|
|
9301
|
+
let pacificPct = 0;
|
|
9302
|
+
let asianPct = 0;
|
|
9303
|
+
let nativePct = 0;
|
|
9304
|
+
if (totalVAP > 0) {
|
|
9305
|
+
whitePct = whitePop / totalVAP;
|
|
9306
|
+
minorityPct = minorityPop / totalVAP;
|
|
9307
|
+
blackPct = blackPop / totalVAP;
|
|
9308
|
+
hispanicPct = hispanicPop / totalVAP;
|
|
9309
|
+
pacificPct = pacificPop / totalVAP;
|
|
9310
|
+
asianPct = asianPop / totalVAP;
|
|
9311
|
+
nativePct = nativePop / totalVAP;
|
|
9312
|
+
}
|
|
9313
|
+
// 5 - MORE ...
|
|
9314
|
+
// COMPUTE DISTRICT-LEVEL VALUES
|
|
9315
|
+
// Validations
|
|
9316
|
+
// Leave the default values for the dummy unassigned district,
|
|
9317
|
+
// and districts that are empty.
|
|
9318
|
+
if ((i > 0) && bNotEmpty) {
|
|
9291
9319
|
bContiguous = valid_1.isConnected(geoIDs, graph);
|
|
9292
9320
|
bNotEmbedded = (!valid_1.isEmbedded(i, planByDistrict[i], plan, graph));
|
|
9293
9321
|
}
|
|
9322
|
+
// 6 - MORE ...
|
|
9323
|
+
{ // UPDATE THE DISTRICT STATISTICS
|
|
9324
|
+
// NOTE - These are set below for both non-empty & empty districts:
|
|
9325
|
+
// this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
9326
|
+
// this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
9327
|
+
// this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
9328
|
+
// this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
9329
|
+
// this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
9330
|
+
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
9331
|
+
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
9332
|
+
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
9333
|
+
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
9334
|
+
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
9335
|
+
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
9336
|
+
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
9337
|
+
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
9338
|
+
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
9339
|
+
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
9340
|
+
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
9341
|
+
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
9342
|
+
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
9343
|
+
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
9344
|
+
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
9345
|
+
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
9346
|
+
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
9347
|
+
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
9348
|
+
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
9349
|
+
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
9350
|
+
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
9351
|
+
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
9352
|
+
}
|
|
9353
|
+
// 7 - MORE ...
|
|
9354
|
+
{ // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
9355
|
+
stateTPVote += totVotes;
|
|
9356
|
+
stateDemVote += demVotes;
|
|
9357
|
+
stateRepVote += repVotes;
|
|
9358
|
+
stateVAPPop += totalVAP;
|
|
9359
|
+
stateWhitePop += whitePop;
|
|
9360
|
+
stateMinorityPop += minorityPop;
|
|
9361
|
+
stateBlackPop += blackPop;
|
|
9362
|
+
stateHispanicPop += hispanicPop;
|
|
9363
|
+
statePacificPop += pacificPop;
|
|
9364
|
+
stateAsianPop += asianPop;
|
|
9365
|
+
stateNativePop += nativePop;
|
|
9366
|
+
}
|
|
9367
|
+
}
|
|
9368
|
+
else { // If a district is empty, zero these results (vs. null)
|
|
9369
|
+
this.statistics[DistrictField.PopDevPct][i] = 0.0;
|
|
9370
|
+
this.statistics[DistrictField.DemVotes][i] = 0;
|
|
9371
|
+
this.statistics[DistrictField.RepVotes][i] = 0;
|
|
9372
|
+
this.statistics[DistrictField.TwoPartyVote][i] = 0;
|
|
9373
|
+
this.statistics[DistrictField.DemPct][i] = 0;
|
|
9374
|
+
this.statistics[DistrictField.RepPct][i] = 0;
|
|
9375
|
+
this.statistics[DistrictField.DemSeat][i] = 0;
|
|
9376
|
+
this.statistics[DistrictField.WhitePop][i] = 0;
|
|
9377
|
+
this.statistics[DistrictField.MinorityPop][i] = 0;
|
|
9378
|
+
this.statistics[DistrictField.BlackPop][i] = 0;
|
|
9379
|
+
this.statistics[DistrictField.HispanicPop][i] = 0;
|
|
9380
|
+
this.statistics[DistrictField.PacificPop][i] = 0;
|
|
9381
|
+
this.statistics[DistrictField.AsianPop][i] = 0;
|
|
9382
|
+
this.statistics[DistrictField.NativePop][i] = 0;
|
|
9383
|
+
this.statistics[DistrictField.TotalVAP][i] = 0;
|
|
9384
|
+
this.statistics[DistrictField.WhitePct][i] = 0;
|
|
9385
|
+
this.statistics[DistrictField.MinorityPct][i] = 0;
|
|
9386
|
+
this.statistics[DistrictField.BlackPct][i] = 0;
|
|
9387
|
+
this.statistics[DistrictField.HispanicPct][i] = 0;
|
|
9388
|
+
this.statistics[DistrictField.PacificPct][i] = 0;
|
|
9389
|
+
this.statistics[DistrictField.AsianPct][i] = 0;
|
|
9390
|
+
this.statistics[DistrictField.NativePct][i] = 0;
|
|
9391
|
+
}
|
|
9392
|
+
{ // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
|
|
9393
|
+
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
9394
|
+
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
9395
|
+
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
9396
|
+
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
9397
|
+
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
9398
|
+
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
9294
9399
|
}
|
|
9295
|
-
// 6 - MORE ...
|
|
9296
|
-
// UPDATE THE DISTRICT STATISTICS
|
|
9297
|
-
this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
|
|
9298
|
-
this.statistics[DistrictField.bContiguous][i] = bContiguous;
|
|
9299
|
-
this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
|
|
9300
|
-
this.statistics[DistrictField.TotalPop][i] = totalPop;
|
|
9301
|
-
this.statistics[DistrictField.PopDevPct][i] = popDevPct;
|
|
9302
|
-
this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
|
|
9303
|
-
this.statistics[DistrictField.CountySplits][i] = countySplits;
|
|
9304
|
-
this.statistics[DistrictField.DemVotes][i] = demVotes;
|
|
9305
|
-
this.statistics[DistrictField.RepVotes][i] = repVotes;
|
|
9306
|
-
this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
|
|
9307
|
-
this.statistics[DistrictField.DemPct][i] = demPct;
|
|
9308
|
-
this.statistics[DistrictField.RepPct][i] = repPct;
|
|
9309
|
-
this.statistics[DistrictField.DemSeat][i] = DemSeat;
|
|
9310
|
-
this.statistics[DistrictField.WhitePop][i] = whitePop;
|
|
9311
|
-
this.statistics[DistrictField.MinorityPop][i] = minorityPop;
|
|
9312
|
-
this.statistics[DistrictField.BlackPop][i] = blackPop;
|
|
9313
|
-
this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
|
|
9314
|
-
this.statistics[DistrictField.PacificPop][i] = pacificPop;
|
|
9315
|
-
this.statistics[DistrictField.AsianPop][i] = asianPop;
|
|
9316
|
-
this.statistics[DistrictField.NativePop][i] = nativePop;
|
|
9317
|
-
this.statistics[DistrictField.TotalVAP][i] = totalVAP;
|
|
9318
|
-
this.statistics[DistrictField.WhitePct][i] = whitePct;
|
|
9319
|
-
this.statistics[DistrictField.MinorityPct][i] = minorityPct;
|
|
9320
|
-
this.statistics[DistrictField.BlackPct][i] = blackPct;
|
|
9321
|
-
this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
|
|
9322
|
-
this.statistics[DistrictField.PacificPct][i] = pacificPct;
|
|
9323
|
-
this.statistics[DistrictField.AsianPct][i] = asianPct;
|
|
9324
|
-
this.statistics[DistrictField.NativePct][i] = nativePct;
|
|
9325
|
-
// 7 - MORE ...
|
|
9326
|
-
// ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
|
|
9327
|
-
stateTPVote += totVotes;
|
|
9328
|
-
stateDemVote += demVotes;
|
|
9329
|
-
stateRepVote += repVotes;
|
|
9330
|
-
stateVAPPop += totalVAP;
|
|
9331
|
-
stateWhitePop += whitePop;
|
|
9332
|
-
stateMinorityPop += minorityPop;
|
|
9333
|
-
stateBlackPop += blackPop;
|
|
9334
|
-
stateHispanicPop += hispanicPop;
|
|
9335
|
-
statePacificPop += pacificPop;
|
|
9336
|
-
stateAsianPop += asianPop;
|
|
9337
|
-
stateNativePop += nativePop;
|
|
9338
9400
|
}
|
|
9339
9401
|
// UPDATE STATE STATISTICS
|
|
9340
9402
|
let summaryRow = this.numberOfRows() - 1;
|
|
@@ -9368,16 +9430,6 @@ class Districts {
|
|
|
9368
9430
|
}
|
|
9369
9431
|
}
|
|
9370
9432
|
exports.Districts = Districts;
|
|
9371
|
-
exports.DatasetDescriptions = {
|
|
9372
|
-
D16F: "2016 ACS Total Population",
|
|
9373
|
-
D16T: "2016 ACS Voting Age Population",
|
|
9374
|
-
E16GPR: "2016 Presidential Election",
|
|
9375
|
-
D10F: "2010 Census Total Population",
|
|
9376
|
-
D10T: "2010 Voting Age Population",
|
|
9377
|
-
C16GCO: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
9378
|
-
// TODO - What other potential datasets?
|
|
9379
|
-
// MORE ...
|
|
9380
|
-
};
|
|
9381
9433
|
// Wrap data by feature, to abstract the specifics of the internal structure
|
|
9382
9434
|
class Features {
|
|
9383
9435
|
constructor(s, data, keys) {
|
|
@@ -9388,15 +9440,18 @@ class Features {
|
|
|
9388
9440
|
}
|
|
9389
9441
|
nFeatures() { return this._data.features.length; }
|
|
9390
9442
|
featureByIndex(i) { return this._data.features[i]; }
|
|
9391
|
-
|
|
9392
|
-
|
|
9443
|
+
geoIDForFeature(f) {
|
|
9444
|
+
// GEOIDs will be one of these properties
|
|
9445
|
+
let value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
|
|
9446
|
+
return value;
|
|
9447
|
+
}
|
|
9393
9448
|
fieldForFeature(f, dt, fk) {
|
|
9394
9449
|
let dk = this._keys[dt];
|
|
9395
9450
|
return _getFeatures(f, dk, fk);
|
|
9396
9451
|
}
|
|
9397
9452
|
resetDataset(d, k) {
|
|
9398
9453
|
this._keys[d] = k;
|
|
9399
|
-
// TODO - Does anything need to be recalc'd now when a dataset is changed?
|
|
9454
|
+
// TODO - RECALC: Does anything need to be recalc'd now when a dataset is changed?
|
|
9400
9455
|
}
|
|
9401
9456
|
mapGeoIDsToFeatureIDs() {
|
|
9402
9457
|
for (let i = 0; i < this._session.features.nFeatures(); i++) {
|
|
@@ -9412,6 +9467,8 @@ exports.Features = Features;
|
|
|
9412
9467
|
// f is a direct GeoJSON feature
|
|
9413
9468
|
// p is a geoID
|
|
9414
9469
|
function _getFeatures(f, datasetKey, p) {
|
|
9470
|
+
// Echo parameters for debugging
|
|
9471
|
+
// console.log("f =", f, "k = ", datasetKey, "p =", p);
|
|
9415
9472
|
// Shim to load sample data2.json from disk for command-line scaffolding
|
|
9416
9473
|
if (f.properties && f.properties['datasets']) {
|
|
9417
9474
|
return f.properties['datasets'][datasetKey][p];
|
|
@@ -9430,12 +9487,15 @@ function _getFeatures(f, datasetKey, p) {
|
|
|
9430
9487
|
return o[p];
|
|
9431
9488
|
}
|
|
9432
9489
|
else {
|
|
9433
|
-
if (o['datasets'] && o['datasets'][datasetKey])
|
|
9434
|
-
|
|
9490
|
+
if (o['datasets'] && o['datasets'][datasetKey]) {
|
|
9491
|
+
let v = (o['datasets'][datasetKey][p]);
|
|
9492
|
+
if ((!(v == null)) && (!(v == undefined)))
|
|
9435
9493
|
return o['datasets'][datasetKey][p];
|
|
9494
|
+
}
|
|
9436
9495
|
}
|
|
9437
9496
|
}
|
|
9438
9497
|
}
|
|
9498
|
+
console.log(`${p} value undefined for ${f.properties['GEOID10']}!`);
|
|
9439
9499
|
return undefined;
|
|
9440
9500
|
}
|
|
9441
9501
|
function _fGetJoined(f) {
|
|
@@ -9444,13 +9504,13 @@ function _fGetJoined(f) {
|
|
|
9444
9504
|
// Wrap data by county, to abstract the specifics of the internal structure
|
|
9445
9505
|
class Counties {
|
|
9446
9506
|
constructor(s, data) {
|
|
9507
|
+
this._countyNameLookup = {};
|
|
9447
9508
|
this.index = {};
|
|
9509
|
+
this.totalPopulation = [];
|
|
9448
9510
|
this._session = s;
|
|
9449
9511
|
this._data = data;
|
|
9450
9512
|
this.nCounties = this._data.features.length;
|
|
9451
|
-
this._countyNameLookup = {};
|
|
9452
9513
|
}
|
|
9453
|
-
// nCounties(): number { return this._data.features.length; }
|
|
9454
9514
|
countyByIndex(i) { return this._data.features[i]; }
|
|
9455
9515
|
propertyForCounty(f, pk) { return f.properties[pk]; }
|
|
9456
9516
|
mapFIPSToName(fips, name) { this._countyNameLookup[fips] = name; }
|
|
@@ -9479,8 +9539,24 @@ class Plan {
|
|
|
9479
9539
|
this._planByDistrictID = {};
|
|
9480
9540
|
this.districtIDs = []; // Set when the plan in inverted
|
|
9481
9541
|
}
|
|
9542
|
+
// NOTE - DON'T remove water-only features from the plan, as they may be required
|
|
9543
|
+
// for contiguity. Just skip them in aggregating district statistics.
|
|
9544
|
+
// removeWaterOnlyFeatures(plan: T.PlanByGeoID): T.PlanByGeoID {
|
|
9545
|
+
// let newPlan = {} as T.PlanByGeoID;
|
|
9546
|
+
// for (let geoID in plan) {
|
|
9547
|
+
// // Remove water-only features
|
|
9548
|
+
// if (!(U.isWaterOnly(geoID))) {
|
|
9549
|
+
// newPlan[geoID] = plan[geoID];
|
|
9550
|
+
// }
|
|
9551
|
+
// else {
|
|
9552
|
+
// console.log("Removing water-only feature", geoID);
|
|
9553
|
+
// }
|
|
9554
|
+
// }
|
|
9555
|
+
// return newPlan;
|
|
9556
|
+
// }
|
|
9482
9557
|
invertPlan() {
|
|
9483
|
-
|
|
9558
|
+
// NOTE - UNASSIGNED
|
|
9559
|
+
this._planByDistrictID = invertPlan(this._planByGeoID, this._session);
|
|
9484
9560
|
this.districtIDs = U.getNumericObjectKeys(this._planByDistrictID);
|
|
9485
9561
|
}
|
|
9486
9562
|
initializeDistrict(i) { this._planByDistrictID[i] = new Set(); }
|
|
@@ -9490,16 +9566,64 @@ class Plan {
|
|
|
9490
9566
|
geoIDsForDistrictID(i) { return this._planByDistrictID[i]; }
|
|
9491
9567
|
}
|
|
9492
9568
|
exports.Plan = Plan;
|
|
9569
|
+
// Invert a feature assignment structure to sets of ids by district
|
|
9570
|
+
function invertPlan(plan, s) {
|
|
9571
|
+
let invertedPlan = {};
|
|
9572
|
+
// Add a dummy 'unassigned' district
|
|
9573
|
+
invertedPlan[S.NOT_ASSIGNED] = new Set();
|
|
9574
|
+
// NOTE - UNASSIGNED
|
|
9575
|
+
// The feature assignments coming from DRA do not include unassigned ones.
|
|
9576
|
+
// - In the DRA-calling context, there's an analytics session with a reference
|
|
9577
|
+
// to the features. Loop over all the features to find the unassigned ones,
|
|
9578
|
+
// and add them to the dummy unassigned district explicitly.
|
|
9579
|
+
// - In the CLI-calling context, there's no session (yet) but the plan is complete.
|
|
9580
|
+
if (!(s == undefined)) {
|
|
9581
|
+
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
9582
|
+
let f = s.features.featureByIndex(i);
|
|
9583
|
+
let geoID = s.features.geoIDForFeature(f);
|
|
9584
|
+
// If the feature is NOT explicitly assigned to a district, add the geoID
|
|
9585
|
+
// to the dummy unassigned district 0.
|
|
9586
|
+
if (!(U.keyExists(geoID, plan)))
|
|
9587
|
+
invertedPlan[S.NOT_ASSIGNED].add(geoID);
|
|
9588
|
+
// NOTE - NOT skipping WATER-ONLY features here, because we're
|
|
9589
|
+
// not skipping them below when they are explicitly assigned in plans. Should
|
|
9590
|
+
// we skip them in both places?
|
|
9591
|
+
}
|
|
9592
|
+
}
|
|
9593
|
+
for (let geoID in plan) {
|
|
9594
|
+
let districtID = plan[geoID];
|
|
9595
|
+
// Make sure the set for the districtID exists
|
|
9596
|
+
if (!(U.objectContains(invertedPlan, districtID))) {
|
|
9597
|
+
invertedPlan[districtID] = new Set();
|
|
9598
|
+
}
|
|
9599
|
+
// Add the geoID to the districtID's set
|
|
9600
|
+
invertedPlan[districtID].add(geoID);
|
|
9601
|
+
if (U.isWaterOnly(geoID))
|
|
9602
|
+
console.log("Water-only feature still in plan!", geoID);
|
|
9603
|
+
}
|
|
9604
|
+
return invertedPlan;
|
|
9605
|
+
}
|
|
9606
|
+
exports.invertPlan = invertPlan;
|
|
9493
9607
|
class Graph {
|
|
9494
9608
|
constructor(s, graph) {
|
|
9495
9609
|
this._session = s;
|
|
9496
9610
|
this._graph = graph;
|
|
9497
9611
|
}
|
|
9498
|
-
// TODO - Rework this, when we support MIXED MAPS.
|
|
9499
9612
|
peerNeighbors(node) {
|
|
9500
9613
|
// Get the neighboring geoIDs connected to a geoID
|
|
9501
9614
|
// Ignore the lengths of the shared borders (the values), for now
|
|
9502
|
-
|
|
9615
|
+
// Protect against getting a GEOID that's not in the graph
|
|
9616
|
+
if (U.keyExists(node, this._graph)) {
|
|
9617
|
+
// NOTE - CONTIGUITY GRAPHS
|
|
9618
|
+
// Handle both unweighted & weighted neighbors
|
|
9619
|
+
let n = this._graph[node];
|
|
9620
|
+
let l = (n instanceof Array) ? n : U.getObjectKeys(n);
|
|
9621
|
+
return l;
|
|
9622
|
+
// return U.getObjectKeys(this._graph[node]);
|
|
9623
|
+
}
|
|
9624
|
+
else
|
|
9625
|
+
return [];
|
|
9626
|
+
// return U.getObjectKeys(this._graph[node]);
|
|
9503
9627
|
}
|
|
9504
9628
|
}
|
|
9505
9629
|
exports.Graph = Graph;
|
|
@@ -9532,19 +9656,20 @@ function doAnalyzeDistricts(s, bLog = false) {
|
|
|
9532
9656
|
s.districts.extractDistrictShapeProperties(bLog);
|
|
9533
9657
|
}
|
|
9534
9658
|
exports.doAnalyzeDistricts = doAnalyzeDistricts;
|
|
9535
|
-
// TODO - I could make this table-driven, but I'm thinking that the explicit
|
|
9536
|
-
// calls might make chunking for aync easier.
|
|
9537
9659
|
// Calculate the analytics & validations and cache the results
|
|
9538
9660
|
// NOTE - doAnalyzePlan() depends on doAnalyzeDistricts() having run first.
|
|
9661
|
+
// NOTE - I could make this table-driven, but I'm thinking that the explicit
|
|
9662
|
+
// calls might make chunking for aync easier.
|
|
9539
9663
|
function doAnalyzePlan(s, bLog = false) {
|
|
9664
|
+
// TODO - Remove this mechanism. Always run all tests
|
|
9540
9665
|
// Get the requested suites, and only execute those tests
|
|
9541
9666
|
let requestedSuites = s.config['suites'];
|
|
9542
9667
|
// Tests in the "Legal" suite, i.e., pass/ fail constraints
|
|
9543
9668
|
if (requestedSuites.includes(0 /* Legal */)) {
|
|
9544
|
-
s.tests[0 /* Complete */] = valid_1.doIsComplete(s);
|
|
9545
|
-
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s);
|
|
9546
|
-
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s);
|
|
9547
|
-
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s);
|
|
9669
|
+
s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
|
|
9670
|
+
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
|
|
9671
|
+
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
|
|
9672
|
+
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
|
|
9548
9673
|
// NOTE - I can't check whether a population deviation is legal or not, until
|
|
9549
9674
|
// the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
|
|
9550
9675
|
// the given type of district (CD vs. LD). The EqualPopulation test is derived
|
|
@@ -9554,19 +9679,21 @@ function doAnalyzePlan(s, bLog = false) {
|
|
|
9554
9679
|
}
|
|
9555
9680
|
// Tests in the "Fair" suite
|
|
9556
9681
|
if (requestedSuites.includes(1 /* Fair */)) {
|
|
9557
|
-
s.tests[
|
|
9558
|
-
s.tests[
|
|
9559
|
-
s.tests[
|
|
9560
|
-
s.tests[
|
|
9561
|
-
s.tests[
|
|
9562
|
-
s.tests[
|
|
9682
|
+
s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
|
|
9683
|
+
s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
|
|
9684
|
+
s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
|
|
9685
|
+
s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
|
|
9686
|
+
s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
|
|
9687
|
+
s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
|
|
9563
9688
|
}
|
|
9564
9689
|
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
9565
9690
|
if (requestedSuites.includes(2 /* Best */)) {
|
|
9566
9691
|
s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
|
|
9567
9692
|
s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
|
|
9568
|
-
s.tests[7 /*
|
|
9569
|
-
s.tests[
|
|
9693
|
+
s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
|
|
9694
|
+
s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
|
|
9695
|
+
s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
|
|
9696
|
+
s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
|
|
9570
9697
|
}
|
|
9571
9698
|
// Enable a Test Log and Scorecard to be generated
|
|
9572
9699
|
s.bPlanAnalyzed = true;
|
|
@@ -9579,8 +9706,8 @@ exports.doAnalyzePlan = doAnalyzePlan;
|
|
|
9579
9706
|
//
|
|
9580
9707
|
// NOTE - Should this be conditionalized on the test suites requested?
|
|
9581
9708
|
// Those are encapsulated in reports.ts right now, so not doing that.
|
|
9582
|
-
function doDeriveSecondaryTests(s) {
|
|
9583
|
-
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s);
|
|
9709
|
+
function doDeriveSecondaryTests(s, bLog = false) {
|
|
9710
|
+
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
|
|
9584
9711
|
}
|
|
9585
9712
|
exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
9586
9713
|
|
|
@@ -9597,19 +9724,237 @@ exports.doDeriveSecondaryTests = doDeriveSecondaryTests;
|
|
|
9597
9724
|
"use strict";
|
|
9598
9725
|
|
|
9599
9726
|
//
|
|
9600
|
-
//
|
|
9727
|
+
// SPLITTING of counties & districts
|
|
9601
9728
|
//
|
|
9729
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
9730
|
+
if (mod && mod.__esModule) return mod;
|
|
9731
|
+
var result = {};
|
|
9732
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
9733
|
+
result["default"] = mod;
|
|
9734
|
+
return result;
|
|
9735
|
+
};
|
|
9602
9736
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9603
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
9604
|
-
const
|
|
9605
|
-
|
|
9606
|
-
|
|
9737
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
9738
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
9739
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
9740
|
+
// CALCULATE ENHANCED SQRT ENTROPY METRIC
|
|
9741
|
+
function doCountySplitting(s, bLog = false) {
|
|
9742
|
+
let test = s.getTest(8 /* CountySplitting */);
|
|
9743
|
+
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
9744
|
+
let countyTotals = s.counties.totalPopulation;
|
|
9745
|
+
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
9746
|
+
let f = calcCountyFractions(CxD, countyTotals);
|
|
9747
|
+
let w = calcCountyWeights(countyTotals);
|
|
9748
|
+
let SqEnt_DC = countySplitting(f, w, bLog);
|
|
9749
|
+
let CxDreducedC = U.deepCopy(CxD);
|
|
9750
|
+
reduceCSplits(CxDreducedC, districtTotals);
|
|
9751
|
+
let fReduced = calcCountyFractions(CxDreducedC, countyTotals);
|
|
9752
|
+
let wReduced = calcCountyWeights(countyTotals);
|
|
9753
|
+
let SqEnt_DCreduced = countySplitting(fReduced, wReduced, bLog);
|
|
9754
|
+
test['score'] = SqEnt_DCreduced;
|
|
9755
|
+
test['details']['SqEnt_DC'] = SqEnt_DC;
|
|
9756
|
+
return test;
|
|
9757
|
+
}
|
|
9758
|
+
exports.doCountySplitting = doCountySplitting;
|
|
9759
|
+
function doDistrictSplitting(s, bLog = false) {
|
|
9760
|
+
let test = s.getTest(9 /* DistrictSplitting */);
|
|
9761
|
+
let CxD = s.districts.statistics[D.DistrictField.CountySplits].slice(0, -1);
|
|
9762
|
+
let countyTotals = s.counties.totalPopulation;
|
|
9763
|
+
let districtTotals = s.districts.statistics[D.DistrictField.TotalPop].slice(0, -1);
|
|
9764
|
+
let g = calcDistrictFractions(CxD, districtTotals);
|
|
9765
|
+
let x = calcDistrictWeights(districtTotals);
|
|
9766
|
+
let SqEnt_CD = districtSplitting(g, x, bLog);
|
|
9767
|
+
let CxDreducedD = U.deepCopy(CxD);
|
|
9768
|
+
reduceDSplits(CxDreducedD, countyTotals);
|
|
9769
|
+
let gReduced = calcDistrictFractions(CxDreducedD, districtTotals);
|
|
9770
|
+
let xReduced = calcDistrictWeights(districtTotals);
|
|
9771
|
+
let SqEnt_CDreduced = districtSplitting(gReduced, xReduced, bLog);
|
|
9772
|
+
test['score'] = SqEnt_CDreduced;
|
|
9773
|
+
test['details']['SqEnt_CD'] = SqEnt_CD;
|
|
9774
|
+
return test;
|
|
9775
|
+
}
|
|
9776
|
+
exports.doDistrictSplitting = doDistrictSplitting;
|
|
9777
|
+
// HELPERS
|
|
9778
|
+
// Loop over all the county-district combos, skipping the virtual district 0
|
|
9779
|
+
// and virtual county 0.
|
|
9780
|
+
//
|
|
9781
|
+
// NOTE - The county-district splits and the county & district totals may all,
|
|
9782
|
+
// in general, be fractional/decimal numbers as opposed to integers, due to
|
|
9783
|
+
// dissaggregation & re-aggregation. Hence, comparisons need to approximate
|
|
9784
|
+
// equality.
|
|
9785
|
+
// Consolidate districts (rows) consisting of just one county (column)
|
|
9786
|
+
// UP into the dummy district (0).
|
|
9787
|
+
function reduceCSplits(CxDreducedC, districtTotals) {
|
|
9788
|
+
let nD = CxDreducedC.length;
|
|
9789
|
+
let nC = CxDreducedC[0].length;
|
|
9790
|
+
for (let j = 1; j < nC; j++) {
|
|
9791
|
+
for (let i = 1; i < nD; i++) {
|
|
9792
|
+
let split_total = CxDreducedC[i][j];
|
|
9793
|
+
if (split_total > 0) {
|
|
9794
|
+
if (areRoughlyEqual(split_total, districtTotals[i])) {
|
|
9795
|
+
CxDreducedC[0][j] += split_total;
|
|
9796
|
+
CxDreducedC[i][j] = 0;
|
|
9797
|
+
}
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
}
|
|
9801
|
+
}
|
|
9802
|
+
// Consolidate whole counties (columns) in a district (row) LEFT into the
|
|
9803
|
+
// dummy county (0).
|
|
9804
|
+
function reduceDSplits(CxDreducedD, countyTotals) {
|
|
9805
|
+
let nD = CxDreducedD.length;
|
|
9806
|
+
let nC = CxDreducedD[0].length;
|
|
9807
|
+
for (let i = 1; i < nD; i++) {
|
|
9808
|
+
for (let j = 1; j < nC; j++) {
|
|
9809
|
+
let split_total = CxDreducedD[i][j];
|
|
9810
|
+
if (split_total > 0) {
|
|
9811
|
+
if (areRoughlyEqual(split_total, countyTotals[j])) {
|
|
9812
|
+
CxDreducedD[i][0] += split_total;
|
|
9813
|
+
CxDreducedD[i][j] = 0;
|
|
9814
|
+
}
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
function calcCountyWeights(countyTotals) {
|
|
9820
|
+
let nC = countyTotals.length;
|
|
9821
|
+
let cTotal = U.sumArray(countyTotals);
|
|
9822
|
+
let w = U.initArray(nC, 0.0);
|
|
9823
|
+
for (let j = 0; j < nC; j++) {
|
|
9824
|
+
w[j] = countyTotals[j] / cTotal;
|
|
9825
|
+
}
|
|
9826
|
+
return w;
|
|
9827
|
+
}
|
|
9828
|
+
function calcDistrictWeights(districtTotals) {
|
|
9829
|
+
let nD = districtTotals.length;
|
|
9830
|
+
let dTotal = U.sumArray(districtTotals);
|
|
9831
|
+
let x = U.initArray(nD, 0.0);
|
|
9832
|
+
for (let i = 0; i < nD; i++) {
|
|
9833
|
+
x[i] = districtTotals[i] / dTotal;
|
|
9834
|
+
}
|
|
9835
|
+
return x;
|
|
9836
|
+
}
|
|
9837
|
+
function calcCountyFractions(CxDreducedD, countyTotals) {
|
|
9838
|
+
let nD = CxDreducedD.length;
|
|
9839
|
+
let nC = CxDreducedD[0].length;
|
|
9840
|
+
let f = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
9841
|
+
for (let j = 0; j < nC; j++) {
|
|
9842
|
+
for (let i = 0; i < nD; i++) {
|
|
9843
|
+
if (countyTotals[j] > 0) {
|
|
9844
|
+
f[i][j] = CxDreducedD[i][j] / countyTotals[j];
|
|
9845
|
+
}
|
|
9846
|
+
else {
|
|
9847
|
+
f[i][j] = 0.0;
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
}
|
|
9851
|
+
return f;
|
|
9852
|
+
}
|
|
9853
|
+
function calcDistrictFractions(CxDreducedC, districtTotals) {
|
|
9854
|
+
let nD = CxDreducedC.length;
|
|
9855
|
+
let nC = CxDreducedC[0].length;
|
|
9856
|
+
let g = new Array(nD).fill(0.0).map(() => new Array(nC).fill(0.0));
|
|
9857
|
+
for (let j = 0; j < nC; j++) {
|
|
9858
|
+
for (let i = 0; i < nD; i++) {
|
|
9859
|
+
if (districtTotals[i] > 0) {
|
|
9860
|
+
g[i][j] = CxDreducedC[i][j] / districtTotals[i];
|
|
9861
|
+
}
|
|
9862
|
+
else {
|
|
9863
|
+
g[i][j] = 0.0;
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
}
|
|
9867
|
+
return g;
|
|
9868
|
+
}
|
|
9869
|
+
// Deal with decimal census "counts" due to disagg/re-agg
|
|
9870
|
+
function areRoughlyEqual(x, y) {
|
|
9871
|
+
let delta = Math.abs(x - y);
|
|
9872
|
+
let result = (delta < S.EQUAL_TOLERANCE) ? true : false;
|
|
9873
|
+
return result;
|
|
9874
|
+
}
|
|
9875
|
+
// For all districts in a county, sum the split score.
|
|
9876
|
+
function countySplitScore(j, f, numD, bLog = false) {
|
|
9877
|
+
let e = 0.0;
|
|
9878
|
+
for (let i = 0; i < numD; i++) {
|
|
9879
|
+
e += Math.sqrt(f[i][j]);
|
|
9880
|
+
}
|
|
9881
|
+
return e;
|
|
9882
|
+
}
|
|
9883
|
+
// For all counties, sum the weighted county splits.
|
|
9884
|
+
function countySplitting(f, w, bLog = false) {
|
|
9885
|
+
let numC = f[0].length;
|
|
9886
|
+
let numD = f.length;
|
|
9887
|
+
let e = 0.0;
|
|
9888
|
+
for (let j = 0; j < numC; j++) {
|
|
9889
|
+
let splitScore = countySplitScore(j, f, numD, bLog);
|
|
9890
|
+
e += w[j] * splitScore;
|
|
9891
|
+
if (bLog)
|
|
9892
|
+
console.log("County splitting =", j, w[j], splitScore, e);
|
|
9893
|
+
}
|
|
9894
|
+
return U.trim(e, 3);
|
|
9895
|
+
}
|
|
9896
|
+
// For all counties in a district, sum the split score.
|
|
9897
|
+
function districtSplitScore(i, g, numC, bLog = false) {
|
|
9898
|
+
let e = 0.0;
|
|
9899
|
+
for (let j = 0; j < numC; j++) {
|
|
9900
|
+
e += Math.sqrt(g[i][j]);
|
|
9901
|
+
}
|
|
9902
|
+
return e;
|
|
9903
|
+
}
|
|
9904
|
+
// For all districts, sum the weighted district splits.
|
|
9905
|
+
function districtSplitting(g, x, bLog = false) {
|
|
9906
|
+
let numC = g[0].length;
|
|
9907
|
+
let numD = g.length;
|
|
9908
|
+
let e = 0.0;
|
|
9909
|
+
for (let i = 0; i < numD; i++) {
|
|
9910
|
+
let splitScore = districtSplitScore(i, g, numC, bLog);
|
|
9911
|
+
e += x[i] * splitScore;
|
|
9912
|
+
if (bLog)
|
|
9913
|
+
console.log("District split score =", i, x[i], splitScore, e);
|
|
9914
|
+
}
|
|
9915
|
+
return U.trim(e, 3);
|
|
9916
|
+
}
|
|
9917
|
+
// ANALYZE SIMPLE COUNTY & VTD SPLITTING
|
|
9918
|
+
/*
|
|
9919
|
+
|
|
9920
|
+
Sample results for NC 2016 dongressional plan
|
|
9921
|
+
________________________________________________________________________________
|
|
9922
|
+
|
|
9923
|
+
State: NC
|
|
9924
|
+
Census: 2010
|
|
9925
|
+
Total population: 9,535,483
|
|
9926
|
+
Number of districts: 13
|
|
9927
|
+
Target district size: 733,499
|
|
9928
|
+
Number of counties: 100
|
|
9929
|
+
|
|
9930
|
+
Equal Population: 11.24% deviation
|
|
9931
|
+
Compactness: None
|
|
9932
|
+
Proportionality: 27.67% gap
|
|
9933
|
+
Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
|
|
9934
|
+
|
|
9935
|
+
These counties are split unexpectedly:
|
|
9936
|
+
|
|
9937
|
+
• Bladen
|
|
9938
|
+
• Buncombe
|
|
9939
|
+
• Catawba
|
|
9940
|
+
• Cumberland
|
|
9941
|
+
• Durham
|
|
9942
|
+
• Guilford
|
|
9943
|
+
• Iredell
|
|
9944
|
+
• Johnston
|
|
9945
|
+
• Pitt
|
|
9946
|
+
• Rowan
|
|
9947
|
+
• Wilson
|
|
9948
|
+
|
|
9949
|
+
*/
|
|
9950
|
+
function doFindCountiesSplitUnexpectedly(s, bLog = false) {
|
|
9951
|
+
let test = s.getTest(7 /* UnexpectedCountySplits */);
|
|
9607
9952
|
// THE THREE VALUES TO DETERMINE FOR A PLAN
|
|
9608
9953
|
let unexpectedSplits = 0;
|
|
9609
9954
|
let unexpectedAffected = 0;
|
|
9610
9955
|
let countiesSplitUnexpectedly = [];
|
|
9611
|
-
// FIRST, ANALYZE THE COUNTY
|
|
9612
|
-
//
|
|
9956
|
+
// FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
|
|
9957
|
+
// Get the county-district pivot ("splits")
|
|
9613
9958
|
let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
|
|
9614
9959
|
// countiesByDistrict = countiesByDistrict.slice(1, -1);
|
|
9615
9960
|
// Find the single-county districts, i.e., districts NOT split across counties.
|
|
@@ -9619,10 +9964,13 @@ function doCountySplits(s) {
|
|
|
9619
9964
|
// See if there's only one county partition
|
|
9620
9965
|
let nCountiesInDistrict = 0;
|
|
9621
9966
|
for (let c = 0; c < s.counties.nCounties; c++) {
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
if (
|
|
9625
|
-
|
|
9967
|
+
// Guard against empty district
|
|
9968
|
+
if (countiesByDistrict[d]) {
|
|
9969
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
9970
|
+
nCountiesInDistrict += 1;
|
|
9971
|
+
if (nCountiesInDistrict > 1) {
|
|
9972
|
+
break;
|
|
9973
|
+
}
|
|
9626
9974
|
}
|
|
9627
9975
|
}
|
|
9628
9976
|
}
|
|
@@ -9642,11 +9990,14 @@ function doCountySplits(s) {
|
|
|
9642
9990
|
let nCountyParts = 0;
|
|
9643
9991
|
let subtotal = 0;
|
|
9644
9992
|
for (let d = 1; d <= s.state.nDistricts; d++) {
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9993
|
+
// Guard against empty district
|
|
9994
|
+
if (countiesByDistrict[d]) {
|
|
9995
|
+
if (countiesByDistrict[d][c] > 0) {
|
|
9996
|
+
nPartitionsOverall += 1;
|
|
9997
|
+
nCountyParts += 1;
|
|
9998
|
+
if (!(U.arrayContains(singleCountyDistricts, d))) {
|
|
9999
|
+
subtotal += countiesByDistrict[d][c];
|
|
10000
|
+
}
|
|
9650
10001
|
}
|
|
9651
10002
|
}
|
|
9652
10003
|
}
|
|
@@ -9684,48 +10035,21 @@ function doCountySplits(s) {
|
|
|
9684
10035
|
countiesSplitUnexpectedly.push(s.counties.nameFromFIPS(fips));
|
|
9685
10036
|
}
|
|
9686
10037
|
countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
|
|
9687
|
-
|
|
9688
|
-
test['
|
|
9689
|
-
test['details'] =
|
|
9690
|
-
'unexpectedSplits': unexpectedSplits,
|
|
9691
|
-
'unexpectedAffected': unexpectedAffected,
|
|
9692
|
-
'countiesSplitUnexpectedly': countiesSplitUnexpectedly
|
|
9693
|
-
};
|
|
10038
|
+
test['score'] = U.trim(unexpectedAffected);
|
|
10039
|
+
test['details']['unexpectedSplits'] = unexpectedSplits;
|
|
10040
|
+
test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
|
|
9694
10041
|
return test;
|
|
9695
10042
|
}
|
|
9696
|
-
exports.
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
//
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
// - The map -- So it can count the features by summary level in the map,
|
|
9704
|
-
// as well as the number of BG’s that are split.
|
|
9705
|
-
//
|
|
9706
|
-
// TODO - Where would the state counts come from? Preprocessed and passed in, or
|
|
9707
|
-
// done in a one-time initialization call (which would require a full set of
|
|
9708
|
-
// block geo_id's for the state).
|
|
9709
|
-
//
|
|
9710
|
-
// However, if a map is not yet (fully) simplified, then determining the
|
|
9711
|
-
// complexity of a map also requires a preprocessed summary level hierarchy, so
|
|
9712
|
-
// you can get the child features (e.g., tracts) of a parent feature (e.g.,
|
|
9713
|
-
// a county).
|
|
9714
|
-
//
|
|
9715
|
-
// NOTE - I have script for producing this hierarchy which we could repurpose.
|
|
9716
|
-
//
|
|
9717
|
-
// TODO - For mixed map processing--specfically to find the neighbors of a feature
|
|
9718
|
-
// that are actually in the map (as opposed to just neighbors at the same
|
|
9719
|
-
// summary level in the static graph)--you need a special hierarchy that
|
|
9720
|
-
// distinguishes between the 'interior' and 'edge children of a feature.
|
|
9721
|
-
//
|
|
9722
|
-
// NOTE - The script noted above does this.
|
|
9723
|
-
function doPlanComplexity(s) {
|
|
9724
|
-
let test = s.getTest(8 /* Complexity */);
|
|
9725
|
-
console.log("TODO - Calculating plan complexity ...");
|
|
10043
|
+
exports.doFindCountiesSplitUnexpectedly = doFindCountiesSplitUnexpectedly;
|
|
10044
|
+
function doFindSplitVTDs(s, bLog = false) {
|
|
10045
|
+
let test = s.getTest(10 /* VTDSplits */);
|
|
10046
|
+
let splitVTDs = [];
|
|
10047
|
+
// TODO - SPLITTING: Flesh this out, using Terry's virtual VTD's ...
|
|
10048
|
+
test['score'] = splitVTDs.length;
|
|
10049
|
+
test['details']['splitVTDs'] = splitVTDs;
|
|
9726
10050
|
return test;
|
|
9727
10051
|
}
|
|
9728
|
-
exports.
|
|
10052
|
+
exports.doFindSplitVTDs = doFindSplitVTDs;
|
|
9729
10053
|
|
|
9730
10054
|
|
|
9731
10055
|
/***/ }),
|
|
@@ -9742,9 +10066,17 @@ exports.doPlanComplexity = doPlanComplexity;
|
|
|
9742
10066
|
//
|
|
9743
10067
|
// COMPACT
|
|
9744
10068
|
//
|
|
10069
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10070
|
+
if (mod && mod.__esModule) return mod;
|
|
10071
|
+
var result = {};
|
|
10072
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10073
|
+
result["default"] = mod;
|
|
10074
|
+
return result;
|
|
10075
|
+
};
|
|
9745
10076
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10077
|
+
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
|
|
9746
10078
|
const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
|
|
9747
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
10079
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
9748
10080
|
// Measures of compactness compare district shapes to various ideally compact
|
|
9749
10081
|
// benchmarks, such as circles. All else equal, more compact districts are better.
|
|
9750
10082
|
//
|
|
@@ -9832,16 +10164,19 @@ function doReock(s, bLog = false) {
|
|
|
9832
10164
|
let scores = [];
|
|
9833
10165
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
9834
10166
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
10167
|
+
// Guard against no shape and no properties
|
|
10168
|
+
if (districtProps) {
|
|
10169
|
+
let a = districtProps[0 /* Area */];
|
|
10170
|
+
let d = districtProps[1 /* Diameter */];
|
|
10171
|
+
let reock = (4 * a) / (Math.PI * Math.pow(d, 2));
|
|
10172
|
+
// Save each district score
|
|
10173
|
+
scores.push(reock);
|
|
10174
|
+
// Echo the results by district
|
|
10175
|
+
if (bLog)
|
|
10176
|
+
console.log("Reock for district", districtID, "=", reock);
|
|
10177
|
+
}
|
|
10178
|
+
}
|
|
10179
|
+
// Populate the test entry ... for the shapes that exist!
|
|
9845
10180
|
let averageReock = U.avgArray(scores);
|
|
9846
10181
|
test['score'] = U.trim(averageReock);
|
|
9847
10182
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -9857,16 +10192,19 @@ function doPolsbyPopper(s, bLog = false) {
|
|
|
9857
10192
|
let scores = [];
|
|
9858
10193
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
9859
10194
|
let districtProps = s.districts.getGeoProperties(districtID);
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
10195
|
+
// Guard against no shape and no properties
|
|
10196
|
+
if (districtProps) {
|
|
10197
|
+
let a = districtProps[0 /* Area */];
|
|
10198
|
+
let p = districtProps[2 /* Perimeter */];
|
|
10199
|
+
let polsbyPopper = (4 * Math.PI) * (a / Math.pow(p, 2));
|
|
10200
|
+
// Save each district score
|
|
10201
|
+
scores.push(polsbyPopper);
|
|
10202
|
+
// Echo the results by district
|
|
10203
|
+
if (bLog)
|
|
10204
|
+
console.log("Polsby-Popper for district", districtID, "=", polsbyPopper);
|
|
10205
|
+
}
|
|
10206
|
+
}
|
|
10207
|
+
// Populate the test entry ... for the shapes that exist!
|
|
9870
10208
|
let averagePolsbyPopper = U.avgArray(scores);
|
|
9871
10209
|
test['score'] = U.trim(averagePolsbyPopper);
|
|
9872
10210
|
test['details'] = {}; // TODO - Any details?
|
|
@@ -9875,20 +10213,25 @@ function doPolsbyPopper(s, bLog = false) {
|
|
|
9875
10213
|
exports.doPolsbyPopper = doPolsbyPopper;
|
|
9876
10214
|
// HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
|
|
9877
10215
|
function extractDistrictProperties(s, bLog = false) {
|
|
10216
|
+
// NOTE - I am assuming that district IDs are integers 1–N
|
|
9878
10217
|
for (let i = 1; i <= s.state.nDistricts; i++) {
|
|
9879
|
-
let
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
let
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
10218
|
+
let poly = s.districts.getDistrictShapeByID(i);
|
|
10219
|
+
// Guard against no shape for empty districts AND null shapes
|
|
10220
|
+
let polyOptions = { noLatitudeCorrection: true };
|
|
10221
|
+
let bNull = (!Poly.polyNormalize(poly, polyOptions));
|
|
10222
|
+
if (poly && (!bNull)) {
|
|
10223
|
+
// TODO - OPTIMIZE: Bundle these calls?
|
|
10224
|
+
let area = geofeature_1.gfArea(poly);
|
|
10225
|
+
let perimeter = geofeature_1.gfPerimeter(poly);
|
|
10226
|
+
let diameter = geofeature_1.gfDiameter(poly);
|
|
10227
|
+
let props = [0, 0, 0]; // TODO - TERRY?!?
|
|
10228
|
+
props[0 /* Area */] = area;
|
|
10229
|
+
props[1 /* Diameter */] = diameter;
|
|
10230
|
+
props[2 /* Perimeter */] = perimeter;
|
|
10231
|
+
s.districts.setGeoProperties(i, props);
|
|
10232
|
+
if (bLog)
|
|
10233
|
+
console.log("District", i, "A =", area, "P =", perimeter, "D =", diameter);
|
|
10234
|
+
}
|
|
9892
10235
|
}
|
|
9893
10236
|
}
|
|
9894
10237
|
exports.extractDistrictProperties = extractDistrictProperties;
|
|
@@ -9936,10 +10279,17 @@ exports.extractDistrictProperties = extractDistrictProperties;
|
|
|
9936
10279
|
//
|
|
9937
10280
|
// EQUAL POPULATION
|
|
9938
10281
|
//
|
|
10282
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10283
|
+
if (mod && mod.__esModule) return mod;
|
|
10284
|
+
var result = {};
|
|
10285
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10286
|
+
result["default"] = mod;
|
|
10287
|
+
return result;
|
|
10288
|
+
};
|
|
9939
10289
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9940
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
9941
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
9942
|
-
function doPopulationDeviation(s) {
|
|
10290
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
10291
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
10292
|
+
function doPopulationDeviation(s, bLog = false) {
|
|
9943
10293
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
9944
10294
|
// Compute the min, max, and average district populations,
|
|
9945
10295
|
// excluding the dummy 'unassigned' 0 and N+1 summary "districts."
|
|
@@ -9968,7 +10318,7 @@ function doPopulationDeviation(s) {
|
|
|
9968
10318
|
exports.doPopulationDeviation = doPopulationDeviation;
|
|
9969
10319
|
// NOTE - This validity check is *derived* and depends on population deviation %
|
|
9970
10320
|
// being computed (above) and normalized in test log & scorecard generation.
|
|
9971
|
-
function doHasEqualPopulations(s) {
|
|
10321
|
+
function doHasEqualPopulations(s, bLog = false) {
|
|
9972
10322
|
let test = s.getTest(3 /* EqualPopulation */);
|
|
9973
10323
|
// Get the normalized population deviation %
|
|
9974
10324
|
let popDevTest = s.getTest(4 /* PopulationDeviation */);
|
|
@@ -10006,14 +10356,19 @@ exports.doHasEqualPopulations = doHasEqualPopulations;
|
|
|
10006
10356
|
//
|
|
10007
10357
|
// GEO-FEATURES UTILITIES
|
|
10008
10358
|
//
|
|
10359
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10360
|
+
if (mod && mod.__esModule) return mod;
|
|
10361
|
+
var result = {};
|
|
10362
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10363
|
+
result["default"] = mod;
|
|
10364
|
+
return result;
|
|
10365
|
+
};
|
|
10009
10366
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10010
|
-
const Poly = __webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js");
|
|
10367
|
+
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
|
|
10011
10368
|
// CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
|
|
10012
|
-
// TODO -
|
|
10369
|
+
// TODO - POLY: Confirm Cartesian calculations
|
|
10013
10370
|
function gfArea(poly) {
|
|
10014
10371
|
let area = _polygonArea(poly);
|
|
10015
|
-
// let polyOptions = { noLatitudeCorrection: false } DELETE
|
|
10016
|
-
// let area: number = Poly.polyArea(poly, polyOptions);
|
|
10017
10372
|
return area;
|
|
10018
10373
|
}
|
|
10019
10374
|
exports.gfArea = gfArea;
|
|
@@ -10046,7 +10401,7 @@ function _polygonSimpleArea(p) {
|
|
|
10046
10401
|
// Generalizes the above for MultiPolygons -- cloned from polyArea() in 'poly'
|
|
10047
10402
|
function _polygonArea(poly) {
|
|
10048
10403
|
let polyOptions = { noLatitudeCorrection: true }; // NO-OP?
|
|
10049
|
-
poly = Poly.polyNormalize(poly, polyOptions); // TODO - Discuss w/ Terry
|
|
10404
|
+
poly = Poly.polyNormalize(poly, polyOptions); // TODO - POLY: Discuss w/ Terry
|
|
10050
10405
|
let a = 0;
|
|
10051
10406
|
// A MultiPolygon is a set of polygons
|
|
10052
10407
|
for (let i = 0; poly && i < poly.length; i++) {
|
|
@@ -10059,19 +10414,16 @@ function _polygonArea(poly) {
|
|
|
10059
10414
|
}
|
|
10060
10415
|
return a;
|
|
10061
10416
|
}
|
|
10062
|
-
// TODO -
|
|
10417
|
+
// TODO - POLY: Confirm Cartesian calculations w/ Terry
|
|
10063
10418
|
// The perimeter calculation already just computes cartesian distance if you
|
|
10064
10419
|
// pass in the noLatitudeCorrection flag. You would need to divide by
|
|
10065
10420
|
// Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
|
|
10066
10421
|
function gfPerimeter(poly) {
|
|
10067
10422
|
let perimeter = _polygonPerimeter(poly);
|
|
10068
|
-
// let polyOptions = { noLatitudeCorrection: true } // Cartesian distance
|
|
10069
|
-
// let perimeter: number = Poly.polyPerimeter(poly, polyOptions); DELETE
|
|
10070
10423
|
return perimeter;
|
|
10071
|
-
// return perimeter / Poly.EARTH_RADIUS; DELETE
|
|
10072
10424
|
}
|
|
10073
10425
|
exports.gfPerimeter = gfPerimeter;
|
|
10074
|
-
// TODO -
|
|
10426
|
+
// TODO - POLY: Confirm Cartesian calculations w/ Terry
|
|
10075
10427
|
// Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
|
|
10076
10428
|
// NOTE: No conversion of degrees to radians!
|
|
10077
10429
|
function _polygonPerimeter(poly) {
|
|
@@ -10083,10 +10435,8 @@ function _polygonPerimeter(poly) {
|
|
|
10083
10435
|
let p = poly[i][0];
|
|
10084
10436
|
for (let j = 0; j < p.length - 1; j++)
|
|
10085
10437
|
perimeter += _distance(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1]);
|
|
10086
|
-
// perimeter += haversine(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1], options); DELETE
|
|
10087
10438
|
if (p.length > 2 && (p[0][0] != p[p.length - 1][0] || p[0][1] != p[p.length - 1][1]))
|
|
10088
10439
|
perimeter += _distance(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1]);
|
|
10089
|
-
// perimeter += haversine(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1], options); DELETE
|
|
10090
10440
|
}
|
|
10091
10441
|
return perimeter;
|
|
10092
10442
|
}
|
|
@@ -10097,7 +10447,7 @@ function _distance(x1, y1, x2, y2) {
|
|
|
10097
10447
|
d = Math.sqrt((dLat * dLat) + (dLon * dLon));
|
|
10098
10448
|
return d;
|
|
10099
10449
|
}
|
|
10100
|
-
// TODO -
|
|
10450
|
+
// TODO - POLY: Confirm Cartesian calculations w/ Terry
|
|
10101
10451
|
// As I mentioned, the polyCircle code was already just treating the coordinate
|
|
10102
10452
|
// system as Cartesian. I then did polyFromCircle to convert it to a polygon that
|
|
10103
10453
|
// then could be passed to polyArea in order to take into account the projection.
|
|
@@ -10106,9 +10456,6 @@ function _distance(x1, y1, x2, y2) {
|
|
|
10106
10456
|
function gfDiameter(poly) {
|
|
10107
10457
|
let polyOptions = { noLatitudeCorrection: true }; // NO-OP
|
|
10108
10458
|
let circle = Poly.polyToCircle(poly, polyOptions);
|
|
10109
|
-
// let circleArea: number = Poly.polyArea(Poly.polyFromCircle(circle, undefined, polyOptions), polyOptions);
|
|
10110
|
-
// let circleRadius: number = Math.sqrt(circleArea / Math.PI);
|
|
10111
|
-
// let diameter: number = circleRadius * 2; DELETE
|
|
10112
10459
|
let diameter = circle.r * 2;
|
|
10113
10460
|
return diameter;
|
|
10114
10461
|
}
|
|
@@ -10130,61 +10477,17 @@ exports.gfDiameter = gfDiameter;
|
|
|
10130
10477
|
// PROTECTS MINORITIES
|
|
10131
10478
|
//
|
|
10132
10479
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
// The TOTAL, WHITE, BLACK, and HISPANIC counts from the Census, aggregated by
|
|
10138
|
-
// district. We *might* also ultimately need TOTAL18, WHITE18, BLACK18, and
|
|
10139
|
-
// HISPANIC18 counts by feature for these analytics.
|
|
10140
|
-
//
|
|
10141
|
-
// The minority population of a feature will probably be calculated as everyone
|
|
10142
|
-
// exceot non-Hispanic Whites:
|
|
10143
|
-
//
|
|
10144
|
-
// MINORITY = TOTAL - (WHITE - HISPANIC)
|
|
10145
|
-
//
|
|
10146
|
-
// That could be calculated as part of preprocessing the Census data, or it
|
|
10147
|
-
// could be computed on the fly. Since it's derived data and the formula might
|
|
10148
|
-
// change, it's probably best to compute it on the fly.
|
|
10149
|
-
//
|
|
10150
|
-
// In addition to the Census extract, these analytics need:
|
|
10151
|
-
// - The # of districts in the map, for determining minority proportionality
|
|
10152
|
-
// - Minorities as a % of the total population; possibly the voting age share
|
|
10153
|
-
//
|
|
10154
|
-
// TODO - Is the # of districts passed as a parameter or inferred from the # in
|
|
10155
|
-
// the map?
|
|
10156
|
-
// TODO - Is minority share preprocessed once and passed as a parameter or
|
|
10157
|
-
// computed in a initialization routine?
|
|
10158
|
-
function doMajorityMinorityDistricts(s) {
|
|
10159
|
-
let test = s.getTest(14 /* MajorityMinorityDistricts */);
|
|
10160
|
-
console.log("TODO - Calculating # of majority-minority districts ...");
|
|
10480
|
+
function doMajorityMinorityDistricts(s, bLog = false) {
|
|
10481
|
+
let test = s.getTest(16 /* MajorityMinorityDistricts */);
|
|
10482
|
+
if (bLog)
|
|
10483
|
+
console.log("TODO - Calculating # of majority-minority districts ...");
|
|
10161
10484
|
return test;
|
|
10162
10485
|
}
|
|
10163
10486
|
exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
10164
|
-
//
|
|
10165
|
-
//
|
|
10166
|
-
//
|
|
10167
|
-
//
|
|
10168
|
-
// IN ADDITION TO THE MAP (IDEALLY, GEO_IDS INDEXED BY DISTRICT_ID)
|
|
10169
|
-
//
|
|
10170
|
-
// Census data by geo_id - { total population | white | black | hispanic }
|
|
10171
|
-
//
|
|
10172
|
-
// The minority population of a feature will probably be calculated as the # of
|
|
10173
|
-
// non-White Hispanics:
|
|
10174
|
-
//
|
|
10175
|
-
// MINORITY = TOTAL - (WHITE - HISPANIC)
|
|
10176
|
-
//
|
|
10177
|
-
// That could be calculated as part of preprocessing the Census data, or it
|
|
10178
|
-
// could be computed on the fly.
|
|
10179
|
-
//
|
|
10180
|
-
// And probably:
|
|
10181
|
-
// 'districts' - The # of districts for determining minority proportionality.
|
|
10182
|
-
// 'minority_share' - Minorities as a % of the total population
|
|
10183
|
-
//
|
|
10184
|
-
// TODO - Is the # of districts passed as a parameter or inferred from the # in
|
|
10185
|
-
// the map?
|
|
10186
|
-
// TODO - Is minority share preprocessed once and passed as a parameter or
|
|
10187
|
-
// computed in a initialization routine?
|
|
10487
|
+
// Sources for majority-minority info:
|
|
10488
|
+
// - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
|
|
10489
|
+
// TODO - 2020: Update/revise this, when the update comes out in September:
|
|
10490
|
+
// - http://www.ncsl.org/Portals/1/Documents/Redistricting/Redistricting_2010.pdf
|
|
10188
10491
|
|
|
10189
10492
|
|
|
10190
10493
|
/***/ }),
|
|
@@ -10201,10 +10504,17 @@ exports.doMajorityMinorityDistricts = doMajorityMinorityDistricts;
|
|
|
10201
10504
|
//
|
|
10202
10505
|
// FAIR/PROPORTIONAL
|
|
10203
10506
|
//
|
|
10507
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10508
|
+
if (mod && mod.__esModule) return mod;
|
|
10509
|
+
var result = {};
|
|
10510
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10511
|
+
result["default"] = mod;
|
|
10512
|
+
return result;
|
|
10513
|
+
};
|
|
10204
10514
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10205
10515
|
const assert_1 = __webpack_require__(/*! assert */ "assert");
|
|
10206
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
10207
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
10516
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
10517
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
10208
10518
|
// Partisan analytics need the following data:
|
|
10209
10519
|
//
|
|
10210
10520
|
// An "election model" by geo_id, where each item has 4 pieces of data:
|
|
@@ -10224,36 +10534,41 @@ const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
|
10224
10534
|
// I'm labelling this general concept a "Voter Preference Index (VPI)," a
|
|
10225
10535
|
// conscious +1 letter play on Cook's "PVI" acronymn.
|
|
10226
10536
|
// MEASURING BIAS & RESPONSIVENESS (NAGLE'S METHOD)
|
|
10227
|
-
function doSeatsBias(s) {
|
|
10228
|
-
let test = s.getTest(
|
|
10229
|
-
|
|
10537
|
+
function doSeatsBias(s, bLog = false) {
|
|
10538
|
+
let test = s.getTest(11 /* SeatsBias */);
|
|
10539
|
+
if (bLog)
|
|
10540
|
+
console.log("TODO - Calculating seats bias ...");
|
|
10230
10541
|
return test;
|
|
10231
10542
|
}
|
|
10232
10543
|
exports.doSeatsBias = doSeatsBias;
|
|
10233
|
-
function doVotesBias(s) {
|
|
10234
|
-
let test = s.getTest(
|
|
10235
|
-
|
|
10544
|
+
function doVotesBias(s, bLog = false) {
|
|
10545
|
+
let test = s.getTest(12 /* VotesBias */);
|
|
10546
|
+
if (bLog)
|
|
10547
|
+
console.log("TODO - Calculating votes bias ...");
|
|
10236
10548
|
return test;
|
|
10237
10549
|
}
|
|
10238
10550
|
exports.doVotesBias = doVotesBias;
|
|
10239
|
-
function doResponsiveness(s) {
|
|
10240
|
-
let test = s.getTest(
|
|
10241
|
-
|
|
10551
|
+
function doResponsiveness(s, bLog = false) {
|
|
10552
|
+
let test = s.getTest(13 /* Responsiveness */);
|
|
10553
|
+
if (bLog)
|
|
10554
|
+
console.log("TODO - Calculating responsiveness ...");
|
|
10242
10555
|
return test;
|
|
10243
10556
|
}
|
|
10244
10557
|
exports.doResponsiveness = doResponsiveness;
|
|
10245
|
-
function doResponsiveDistricts(s) {
|
|
10246
|
-
let test = s.getTest(
|
|
10247
|
-
|
|
10558
|
+
function doResponsiveDistricts(s, bLog = false) {
|
|
10559
|
+
let test = s.getTest(14 /* ResponsiveDistricts */);
|
|
10560
|
+
if (bLog)
|
|
10561
|
+
console.log("TODO - Calculating # of responsive districts ...");
|
|
10248
10562
|
return test;
|
|
10249
10563
|
}
|
|
10250
10564
|
exports.doResponsiveDistricts = doResponsiveDistricts;
|
|
10251
10565
|
// OTHER MEASURES OF PARTISAN BIAS
|
|
10252
|
-
// TODO - This formula might need to be inverted for D vs. R +/-
|
|
10566
|
+
// TODO - PARTISAN: This formula might need to be inverted for D vs. R +/-
|
|
10253
10567
|
// TODO - Normalize the results.
|
|
10254
|
-
function doEfficiencyGap(s) {
|
|
10255
|
-
|
|
10256
|
-
|
|
10568
|
+
function doEfficiencyGap(s, bLog = false) {
|
|
10569
|
+
if (bLog)
|
|
10570
|
+
console.log("TODO - Calculating the efficiency gap ...");
|
|
10571
|
+
let test = s.getTest(15 /* EfficiencyGap */);
|
|
10257
10572
|
// Get partisan statistics by districts.
|
|
10258
10573
|
// Use Democratic votes, seats, and shares by convention.
|
|
10259
10574
|
let DVotes = s.districts.statistics[D.DistrictField.DemVotes];
|
|
@@ -10300,17 +10615,25 @@ exports.fptpWin = fptpWin;
|
|
|
10300
10615
|
//
|
|
10301
10616
|
// PREPROCESS DATA
|
|
10302
10617
|
//
|
|
10618
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10619
|
+
if (mod && mod.__esModule) return mod;
|
|
10620
|
+
var result = {};
|
|
10621
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10622
|
+
result["default"] = mod;
|
|
10623
|
+
return result;
|
|
10624
|
+
};
|
|
10303
10625
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10304
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
10626
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
10305
10627
|
// NOTE - Do preprocessing separately, so the constructor returns quickly.
|
|
10306
|
-
function doPreprocessData(s) {
|
|
10628
|
+
function doPreprocessData(s, bLog = false) {
|
|
10307
10629
|
// If necessary, do one-time preprocessing
|
|
10308
10630
|
if (!s.bOneTimeProcessingDone) {
|
|
10309
|
-
doPreprocessCountyFeatures(s);
|
|
10310
|
-
doPreprocessCensus(s);
|
|
10311
|
-
doPreprocessElection(s);
|
|
10631
|
+
doPreprocessCountyFeatures(s, bLog);
|
|
10632
|
+
doPreprocessCensus(s, bLog);
|
|
10633
|
+
doPreprocessElection(s, bLog);
|
|
10312
10634
|
s.bOneTimeProcessingDone = true;
|
|
10313
10635
|
}
|
|
10636
|
+
// NOTE - UNASSIGNED: Made both the planByGeoID & DistrictID are right
|
|
10314
10637
|
// Invert the plan by district ID
|
|
10315
10638
|
s.plan.invertPlan();
|
|
10316
10639
|
// Create a map of geoIDs to feature IDs
|
|
@@ -10318,7 +10641,7 @@ function doPreprocessData(s) {
|
|
|
10318
10641
|
}
|
|
10319
10642
|
exports.doPreprocessData = doPreprocessData;
|
|
10320
10643
|
// CREATE A FIPS CODE TO COUNTY NAME LOOKUP
|
|
10321
|
-
function doPreprocessCountyFeatures(s) {
|
|
10644
|
+
function doPreprocessCountyFeatures(s, bLog = false) {
|
|
10322
10645
|
for (let i = 0; i < s.counties.nCounties; i++) {
|
|
10323
10646
|
let county = s.counties.countyByIndex(i);
|
|
10324
10647
|
let fips = s.counties.propertyForCounty(county, 'COUNTYFP');
|
|
@@ -10327,29 +10650,58 @@ function doPreprocessCountyFeatures(s) {
|
|
|
10327
10650
|
}
|
|
10328
10651
|
}
|
|
10329
10652
|
// ANALYZE THE CENSUS BY COUNTY
|
|
10330
|
-
function doPreprocessCensus(s) {
|
|
10653
|
+
function doPreprocessCensus(s, bLog = false) {
|
|
10331
10654
|
// The county-splitting analytic needs the following info, using NC as an example:
|
|
10332
10655
|
// '_stateTotal' = The total state population, e.g., 9,535,483 for NC's 2010 Census
|
|
10333
10656
|
// 'totalByCounty' = The total population by county FIPS code
|
|
10657
|
+
// SUM TOTAL POPULATION BY COUNTY
|
|
10334
10658
|
let totalByCounty = {};
|
|
10335
10659
|
// NOTE - This works w/o GEOIDs, because you're looping over all features.
|
|
10336
10660
|
for (let i = 0; i < s.features.nFeatures(); i++) {
|
|
10337
10661
|
let f = s.features.featureByIndex(i);
|
|
10338
10662
|
let geoID = s.features.geoIDForFeature(f);
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10663
|
+
// Skip water-only features
|
|
10664
|
+
if (!(U.isWaterOnly(geoID))) {
|
|
10665
|
+
let value = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
10666
|
+
// Sum total population across the state
|
|
10667
|
+
s.state.totalPop += value;
|
|
10668
|
+
// Get the county FIPS code for the feature
|
|
10669
|
+
let county = U.parseGeoID(geoID)['county'];
|
|
10670
|
+
let countyFIPS = U.getFIPSFromCountyGeoID(county);
|
|
10671
|
+
// If a subtotal for the county doesn't exist, initialize one
|
|
10672
|
+
if (!(U.keyExists(countyFIPS, totalByCounty))) {
|
|
10673
|
+
totalByCounty[countyFIPS] = 0;
|
|
10674
|
+
}
|
|
10675
|
+
// Sum total population by county
|
|
10676
|
+
totalByCounty[countyFIPS] += value;
|
|
10677
|
+
}
|
|
10678
|
+
// else {
|
|
10679
|
+
// console.log("Skipping water-only feature in Census preprocessing:", geoID);
|
|
10680
|
+
// }
|
|
10351
10681
|
}
|
|
10352
10682
|
// NOTE - The above could be replaced, if I got totals on county.geojson.
|
|
10683
|
+
// CREATE A FIPS CODE-ORDINAL MAP
|
|
10684
|
+
// Get the county FIPS codes
|
|
10685
|
+
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
10686
|
+
// Sort the results
|
|
10687
|
+
fipsCodes = fipsCodes.sort();
|
|
10688
|
+
// NOTE - SPLITTING
|
|
10689
|
+
// Add a dummy county, for county-district splitting analysis
|
|
10690
|
+
fipsCodes.unshift('000');
|
|
10691
|
+
// Create the ID-ordinal map
|
|
10692
|
+
for (let i in fipsCodes) {
|
|
10693
|
+
s.counties.index[fipsCodes[i]] = Number(i);
|
|
10694
|
+
}
|
|
10695
|
+
// MAKE AN ARRAY OF TOTAL POPULATIONS BY COUNTY INDEX
|
|
10696
|
+
// Add an extra 0th virtual county bucket for county-district splitting analysis
|
|
10697
|
+
let nCountyBuckets = s.counties.nCounties + 1;
|
|
10698
|
+
let countyTotals = U.initArray(nCountyBuckets, 0);
|
|
10699
|
+
for (let fipsCode in totalByCounty) {
|
|
10700
|
+
let i = s.counties.indexFromFIPS(fipsCode);
|
|
10701
|
+
countyTotals[i] = totalByCounty[fipsCode];
|
|
10702
|
+
}
|
|
10703
|
+
s.counties.totalPopulation = countyTotals;
|
|
10704
|
+
// ANALYZE THE COUNTIES
|
|
10353
10705
|
// 'target_size': 733499, # calc as total / districts
|
|
10354
10706
|
let targetSize = Math.round(s.state.totalPop / s.state.nDistricts);
|
|
10355
10707
|
// Find counties that are bigger than the target district size.
|
|
@@ -10361,18 +10713,13 @@ function doPreprocessCensus(s) {
|
|
|
10361
10713
|
let tooBigName = [];
|
|
10362
10714
|
let expectedSplits = 0;
|
|
10363
10715
|
let expectedAffected = 0;
|
|
10364
|
-
// Create a FIPS code-ordinal map
|
|
10365
|
-
// Get the county FIPS codes
|
|
10366
|
-
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
10367
|
-
// Sort the results
|
|
10368
|
-
fipsCodes = fipsCodes.sort();
|
|
10369
|
-
// Create the ID-ordinal map
|
|
10370
|
-
for (let i in fipsCodes) {
|
|
10371
|
-
s.counties.index[fipsCodes[i]] = Number(i);
|
|
10372
|
-
}
|
|
10373
10716
|
// Loop over the counties
|
|
10374
10717
|
for (let county in fipsCodes) {
|
|
10375
10718
|
let fipsCode = fipsCodes[county];
|
|
10719
|
+
// NOTE - SPLITTING
|
|
10720
|
+
// Skip the dummy county
|
|
10721
|
+
if (fipsCode == '000')
|
|
10722
|
+
continue;
|
|
10376
10723
|
let countyAffected = 0;
|
|
10377
10724
|
// Find the number of required splits, assuming target district size.
|
|
10378
10725
|
let rawQuotient = totalByCounty[fipsCode] / (targetSize + 1);
|
|
@@ -10393,34 +10740,426 @@ function doPreprocessCensus(s) {
|
|
|
10393
10740
|
s.state.expectedAffected = expectedAffected;
|
|
10394
10741
|
}
|
|
10395
10742
|
// PREPROCESS ELECTION RESULTS
|
|
10396
|
-
function doPreprocessElection(s) {
|
|
10397
|
-
|
|
10743
|
+
function doPreprocessElection(s, bLog = false) {
|
|
10744
|
+
if (bLog)
|
|
10745
|
+
console.log("TODO - Preprocessing election data ...");
|
|
10398
10746
|
}
|
|
10399
10747
|
|
|
10400
10748
|
|
|
10401
10749
|
/***/ }),
|
|
10402
10750
|
|
|
10403
|
-
/***/ "./src/
|
|
10404
|
-
|
|
10405
|
-
!*** ./src/
|
|
10406
|
-
|
|
10751
|
+
/***/ "./src/results.ts":
|
|
10752
|
+
/*!************************!*\
|
|
10753
|
+
!*** ./src/results.ts ***!
|
|
10754
|
+
\************************/
|
|
10407
10755
|
/*! no static exports found */
|
|
10408
10756
|
/***/ (function(module, exports, __webpack_require__) {
|
|
10409
10757
|
|
|
10410
10758
|
"use strict";
|
|
10411
10759
|
|
|
10412
10760
|
//
|
|
10413
|
-
//
|
|
10414
|
-
// - A test log: a simple enumeration of all analytics & validations w/ raw results
|
|
10415
|
-
// - A scorecard: a structured subset of analytics & validations w/ normalized
|
|
10416
|
-
// results, cateories, and an overall score
|
|
10761
|
+
// TEMPLATES FOR UNFORMATTED ANALYTICS RESULTS
|
|
10417
10762
|
//
|
|
10763
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
10764
|
+
if (mod && mod.__esModule) return mod;
|
|
10765
|
+
var result = {};
|
|
10766
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
10767
|
+
result["default"] = mod;
|
|
10768
|
+
return result;
|
|
10769
|
+
};
|
|
10770
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10771
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10772
|
+
};
|
|
10418
10773
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10419
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
10420
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
10421
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
10774
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
10775
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
10776
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
10422
10777
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
10423
|
-
|
|
10778
|
+
const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
|
|
10779
|
+
// Example
|
|
10780
|
+
let sampleRequirements = {
|
|
10781
|
+
score: 2 /* Red */,
|
|
10782
|
+
metrics: {
|
|
10783
|
+
complete: 0 /* Green */,
|
|
10784
|
+
contiguous: 2 /* Red */,
|
|
10785
|
+
freeOfHoles: 1 /* Yellow */,
|
|
10786
|
+
equalPopulation: 2 /* Red */
|
|
10787
|
+
},
|
|
10788
|
+
details: {
|
|
10789
|
+
unassignedFeatures: [],
|
|
10790
|
+
emptyDistricts: [],
|
|
10791
|
+
discontiguousDistricts: [2],
|
|
10792
|
+
embeddedDistricts: [],
|
|
10793
|
+
populationDeviation: 0.6748,
|
|
10794
|
+
deviationThreshold: 0.75 / 100
|
|
10795
|
+
},
|
|
10796
|
+
datasets: {
|
|
10797
|
+
census: "2010 Census Total Population"
|
|
10798
|
+
},
|
|
10799
|
+
resources: {
|
|
10800
|
+
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
10801
|
+
}
|
|
10802
|
+
};
|
|
10803
|
+
let sampleCompactness = {
|
|
10804
|
+
score: 60,
|
|
10805
|
+
metrics: {
|
|
10806
|
+
reock: 0.3773,
|
|
10807
|
+
polsby: 0.3815
|
|
10808
|
+
},
|
|
10809
|
+
details: {},
|
|
10810
|
+
datasets: {
|
|
10811
|
+
shapes: "2010 VTD shapes"
|
|
10812
|
+
},
|
|
10813
|
+
resources: {}
|
|
10814
|
+
};
|
|
10815
|
+
let sampleSplitting = {
|
|
10816
|
+
score: 73,
|
|
10817
|
+
metrics: {
|
|
10818
|
+
sqEnt_DCreduced: 1.531,
|
|
10819
|
+
sqEnt_CDreduced: 1.760
|
|
10820
|
+
},
|
|
10821
|
+
details: {
|
|
10822
|
+
countiesSplitUnexpectedly: [
|
|
10823
|
+
"Bladen", "Buncombe", "Catawba", "Cumberland", "Durham", "Guilford", "Iredell", "Johnston", "Pitt", "Rowan", "Wilson"
|
|
10824
|
+
],
|
|
10825
|
+
unexpectedAffected: 0.3096,
|
|
10826
|
+
nSplitVTDs: 12,
|
|
10827
|
+
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"]
|
|
10828
|
+
},
|
|
10829
|
+
datasets: {},
|
|
10830
|
+
resources: {}
|
|
10831
|
+
};
|
|
10832
|
+
// TODO - PARTISAN: This category is still being fleshed out.
|
|
10833
|
+
let samplePartisan = {
|
|
10834
|
+
score: 100,
|
|
10835
|
+
metrics: {
|
|
10836
|
+
partisanBias: 0.15,
|
|
10837
|
+
responsiveness: 2.0
|
|
10838
|
+
},
|
|
10839
|
+
details: {},
|
|
10840
|
+
datasets: {
|
|
10841
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
10842
|
+
},
|
|
10843
|
+
resources: {
|
|
10844
|
+
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
10845
|
+
}
|
|
10846
|
+
};
|
|
10847
|
+
// TODO - MINORITY: This category is still being fleshed out.
|
|
10848
|
+
let sampleMinority = {
|
|
10849
|
+
score: null,
|
|
10850
|
+
metrics: {
|
|
10851
|
+
nBlack37to50: 1,
|
|
10852
|
+
nBlackMajority: 12,
|
|
10853
|
+
nHispanic37to50: 0,
|
|
10854
|
+
nHispanicMajority: 0,
|
|
10855
|
+
nPacific37to50: 0,
|
|
10856
|
+
nPacificMajority: 0,
|
|
10857
|
+
nAsian37to50: 0,
|
|
10858
|
+
nAsianMajority: 0,
|
|
10859
|
+
nNative37to50: 0,
|
|
10860
|
+
nNativeMajority: 0,
|
|
10861
|
+
nMinority37to50: 0,
|
|
10862
|
+
nMinorityMajority: 0,
|
|
10863
|
+
averageDVoteShare: 0.90
|
|
10864
|
+
},
|
|
10865
|
+
details: {
|
|
10866
|
+
vap: true,
|
|
10867
|
+
comboCategories: true
|
|
10868
|
+
},
|
|
10869
|
+
datasets: {
|
|
10870
|
+
vap: "2010 Voting Age Population"
|
|
10871
|
+
},
|
|
10872
|
+
resources: {}
|
|
10873
|
+
};
|
|
10874
|
+
exports.samplePlanAnalytics = {
|
|
10875
|
+
requirements: sampleRequirements,
|
|
10876
|
+
compactness: sampleCompactness,
|
|
10877
|
+
// TODO - Don't show these categories yet
|
|
10878
|
+
splitting: sampleSplitting,
|
|
10879
|
+
partisan: samplePartisan,
|
|
10880
|
+
minority: sampleMinority
|
|
10881
|
+
};
|
|
10882
|
+
function preparePlanAnalytics(s, bLog = false) {
|
|
10883
|
+
if (!(s.bPostProcessingDone)) {
|
|
10884
|
+
doAnalyzePostProcessing(s);
|
|
10885
|
+
}
|
|
10886
|
+
// REQUIREMENTS CATEGORY
|
|
10887
|
+
let paRequirements;
|
|
10888
|
+
{
|
|
10889
|
+
let completeTest = s.getTest(0 /* Complete */);
|
|
10890
|
+
let contiguousTest = s.getTest(1 /* Contiguous */);
|
|
10891
|
+
let freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
|
|
10892
|
+
let equalPopulationTest = s.getTest(3 /* EqualPopulation */);
|
|
10893
|
+
// Combine individual checks into an overall score
|
|
10894
|
+
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
10895
|
+
// requirements/validations, map booleans to tri-states here.
|
|
10896
|
+
let completeMetric = U.mapBooleanToTriState(completeTest['score']);
|
|
10897
|
+
let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
|
|
10898
|
+
let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
|
|
10899
|
+
let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
|
|
10900
|
+
let reqScore = 0 /* Green */;
|
|
10901
|
+
let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
|
|
10902
|
+
if (checks.includes(1 /* Yellow */))
|
|
10903
|
+
reqScore = 1 /* Yellow */;
|
|
10904
|
+
if (checks.includes(2 /* Red */))
|
|
10905
|
+
reqScore = 2 /* Red */;
|
|
10906
|
+
// Get values to support details entries
|
|
10907
|
+
let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
|
|
10908
|
+
let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
|
|
10909
|
+
let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
|
|
10910
|
+
let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
|
|
10911
|
+
let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
|
|
10912
|
+
let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
|
|
10913
|
+
let xx = s.state.xx;
|
|
10914
|
+
// TODO - JSON: Is there a better / easier way to work with the variable?
|
|
10915
|
+
let stateReqsDict = state_reqs_json_1.default;
|
|
10916
|
+
let reqLinkToStateReqs = stateReqsDict[xx];
|
|
10917
|
+
// Populate the category
|
|
10918
|
+
paRequirements = {
|
|
10919
|
+
score: reqScore,
|
|
10920
|
+
metrics: {
|
|
10921
|
+
complete: completeMetric,
|
|
10922
|
+
contiguous: contiguousMetric,
|
|
10923
|
+
freeOfHoles: freeOfHolesMetric,
|
|
10924
|
+
equalPopulation: equalPopulationMetric
|
|
10925
|
+
},
|
|
10926
|
+
details: {
|
|
10927
|
+
unassignedFeatures: unassignedFeaturesDetail,
|
|
10928
|
+
emptyDistricts: emptyDistrictsDetail,
|
|
10929
|
+
discontiguousDistricts: discontiguousDistrictsDetail,
|
|
10930
|
+
embeddedDistricts: embeddedDistrictsDetail,
|
|
10931
|
+
populationDeviation: populationDeviationDetail,
|
|
10932
|
+
deviationThreshold: deviationThresholdDetail
|
|
10933
|
+
},
|
|
10934
|
+
datasets: {
|
|
10935
|
+
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
10936
|
+
},
|
|
10937
|
+
resources: {
|
|
10938
|
+
stateReqs: reqLinkToStateReqs
|
|
10939
|
+
}
|
|
10940
|
+
};
|
|
10941
|
+
}
|
|
10942
|
+
// COMPACTNESS CATEGORY
|
|
10943
|
+
let paCompactness;
|
|
10944
|
+
{
|
|
10945
|
+
let reockWeight = 0.5;
|
|
10946
|
+
let polsbyWeight = 1.0 - reockWeight;
|
|
10947
|
+
let reockTest = s.getTest(5 /* Reock */);
|
|
10948
|
+
let polsbyTest = s.getTest(6 /* PolsbyPopper */);
|
|
10949
|
+
let normalizedReock = reockTest['normalizedScore'];
|
|
10950
|
+
let normalizedPolsby = reockTest['normalizedScore'];
|
|
10951
|
+
let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
|
|
10952
|
+
let reockMetric = U.deepCopy(reockTest['score']);
|
|
10953
|
+
let polsbyMetric = U.deepCopy(polsbyTest['score']);
|
|
10954
|
+
// Populate the category
|
|
10955
|
+
paCompactness = {
|
|
10956
|
+
score: compactnessScore,
|
|
10957
|
+
metrics: {
|
|
10958
|
+
reock: reockMetric,
|
|
10959
|
+
polsby: polsbyMetric
|
|
10960
|
+
},
|
|
10961
|
+
details: {
|
|
10962
|
+
// None at this time
|
|
10963
|
+
},
|
|
10964
|
+
datasets: {
|
|
10965
|
+
// NOTE - DATASETS
|
|
10966
|
+
shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
|
|
10967
|
+
// shapes: "2010 VTD shapes"
|
|
10968
|
+
},
|
|
10969
|
+
resources: {
|
|
10970
|
+
// None at this time
|
|
10971
|
+
}
|
|
10972
|
+
};
|
|
10973
|
+
}
|
|
10974
|
+
// SPLITTING CATEGORY
|
|
10975
|
+
let paSplitting;
|
|
10976
|
+
{
|
|
10977
|
+
let unexpectedCountySplittingTest = s.getTest(7 /* UnexpectedCountySplits */);
|
|
10978
|
+
let VTDSplitsTest = s.getTest(10 /* VTDSplits */);
|
|
10979
|
+
let countySplittingTest = s.getTest(8 /* CountySplitting */);
|
|
10980
|
+
let districtSplittingTest = s.getTest(9 /* DistrictSplitting */);
|
|
10981
|
+
let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
|
|
10982
|
+
let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
|
|
10983
|
+
let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
|
|
10984
|
+
let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
|
|
10985
|
+
let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
|
|
10986
|
+
let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
|
|
10987
|
+
let countySplittingNormalized = countySplittingTest['normalizedScore'];
|
|
10988
|
+
let districtSplittingNormalized = districtSplittingTest['normalizedScore'];
|
|
10989
|
+
let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
|
|
10990
|
+
+(S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
|
|
10991
|
+
paSplitting = {
|
|
10992
|
+
score: splittingScore,
|
|
10993
|
+
metrics: {
|
|
10994
|
+
sqEnt_DCreduced: SqEnt_DCreducedMetric,
|
|
10995
|
+
sqEnt_CDreduced: SqEnt_CDreducedMetric
|
|
10996
|
+
// NOTE - The un-reduced raw values
|
|
10997
|
+
// sqEnt_DC : SqEnt_DCMetric,
|
|
10998
|
+
// sqEnt_CD : SqEnt_CDMetric
|
|
10999
|
+
},
|
|
11000
|
+
details: {
|
|
11001
|
+
countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
|
|
11002
|
+
unexpectedAffected: unexpectedAffectedMetric,
|
|
11003
|
+
nSplitVTDs: nVTDSplitsMetric,
|
|
11004
|
+
splitVTDs: splitVTDsDetail
|
|
11005
|
+
},
|
|
11006
|
+
datasets: {
|
|
11007
|
+
// None at this time
|
|
11008
|
+
},
|
|
11009
|
+
resources: {
|
|
11010
|
+
// None at this time
|
|
11011
|
+
}
|
|
11012
|
+
};
|
|
11013
|
+
}
|
|
11014
|
+
// PARTISAN CATEGORY
|
|
11015
|
+
//
|
|
11016
|
+
// TODO - PARTISAN: This category is still being fleshed out. Just an example below.
|
|
11017
|
+
let paPartisan;
|
|
11018
|
+
{
|
|
11019
|
+
paPartisan = {
|
|
11020
|
+
score: 100,
|
|
11021
|
+
metrics: {
|
|
11022
|
+
partisanBias: 0.15,
|
|
11023
|
+
responsiveness: 2.0
|
|
11024
|
+
},
|
|
11025
|
+
details: {},
|
|
11026
|
+
datasets: {
|
|
11027
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
11028
|
+
},
|
|
11029
|
+
resources: {
|
|
11030
|
+
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
11031
|
+
}
|
|
11032
|
+
};
|
|
11033
|
+
}
|
|
11034
|
+
// MINORITY CATEGORY
|
|
11035
|
+
//
|
|
11036
|
+
// TODO - MINORITY: This category is still being fleshed out. Just an example below.
|
|
11037
|
+
let paMinority;
|
|
11038
|
+
{
|
|
11039
|
+
paMinority = {
|
|
11040
|
+
score: null,
|
|
11041
|
+
metrics: {
|
|
11042
|
+
nBlack37to50: 1,
|
|
11043
|
+
nBlackMajority: 12,
|
|
11044
|
+
nHispanic37to50: 0,
|
|
11045
|
+
nHispanicMajority: 0,
|
|
11046
|
+
nPacific37to50: 0,
|
|
11047
|
+
nPacificMajority: 0,
|
|
11048
|
+
nAsian37to50: 0,
|
|
11049
|
+
nAsianMajority: 0,
|
|
11050
|
+
nNative37to50: 0,
|
|
11051
|
+
nNativeMajority: 0,
|
|
11052
|
+
nMinority37to50: 0,
|
|
11053
|
+
nMinorityMajority: 0,
|
|
11054
|
+
averageDVoteShare: 0.90
|
|
11055
|
+
},
|
|
11056
|
+
details: {
|
|
11057
|
+
vap: true,
|
|
11058
|
+
comboCategories: true
|
|
11059
|
+
},
|
|
11060
|
+
datasets: {
|
|
11061
|
+
vap: "2010 Voting Age Population"
|
|
11062
|
+
},
|
|
11063
|
+
resources: {}
|
|
11064
|
+
};
|
|
11065
|
+
}
|
|
11066
|
+
// PLAN ANALYTICS
|
|
11067
|
+
let pa = {
|
|
11068
|
+
requirements: paRequirements,
|
|
11069
|
+
compactness: paCompactness,
|
|
11070
|
+
// TODO - Not implemented yet
|
|
11071
|
+
splitting: paSplitting,
|
|
11072
|
+
partisan: paPartisan,
|
|
11073
|
+
minority: paMinority
|
|
11074
|
+
};
|
|
11075
|
+
return pa;
|
|
11076
|
+
}
|
|
11077
|
+
exports.preparePlanAnalytics = preparePlanAnalytics;
|
|
11078
|
+
// Example
|
|
11079
|
+
exports.sampleDistrictStatistics = {
|
|
11080
|
+
table: [
|
|
11081
|
+
// District 0 is the dummy unassigned district
|
|
11082
|
+
[0, 0, 0, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
11083
|
+
[1, 653133, -0.0950, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.4177, 0.5823, 0.8631, 0.1369, 0.0734, 0.0360, 0.0009, 0.0235, 0.0064],
|
|
11084
|
+
[2, 620961, -0.1396, 0 /* Green */, 2 /* Red */, 2 /* Red */, 0 /* Green */, 0.8820, 0.1180, 0.3129, 0.6871, 0.6169, 0.0391, 0.0013, 0.0310, 0.0099],
|
|
11085
|
+
[3, 971777, 0.3465, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.7261, 0.2739, 0.5174, 0.4826, 0.1745, 0.1572, 0.0020, 0.1531, 0.0090],
|
|
11086
|
+
[4, 863420, 0.1964, 0 /* Green */, 2 /* Red */, 0 /* Green */, 0 /* Green */, 0.8957, 0.1043, 0.1734, 0.8266, 0.6489, 0.1348, 0.0020, 0.0496, 0.0127],
|
|
11087
|
+
[5, 805029, 0.1155, 0 /* Green */, 2 /* Red */, 0 /* Green */, 1 /* Yellow */, 0.5743, 0.4257, 0.6587, 0.3413, 0.2494, 0.0363, 0.0012, 0.0536, 0.0081],
|
|
11088
|
+
[6, 824741, 0.1428, 0 /* Green */, 2 /* Red */, 0 /* Green */, 2 /* Red */, 0.5341, 0.4659, 0.7045, 0.2955, 0.1619, 0.0526, 0.0018, 0.0782, 0.0090],
|
|
11089
|
+
[7, 549714, -0.2383, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0.5025, 0.4975, 0.6906, 0.3094, 0.2468, 0.0319, 0.0013, 0.0258, 0.0111],
|
|
11090
|
+
[8, 484777, -0.3283, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0 /* Green */, 0.4105, 0.5895, 0.8370, 0.1630, 0.1074, 0.0316, 0.0013, 0.0197, 0.0077],
|
|
11091
|
+
// District N+1 is the dummy state-summary district
|
|
11092
|
+
[9, 721694, 0.6748, 0 /* Green */, 2 /* Red */, 2 /* Red */, 2 /* Red */, 0.6293, 0.3707, 0.5722, 0.4278, 0.2925, 0.0729, 0.0015, 0.0618, 0.0093]
|
|
11093
|
+
],
|
|
11094
|
+
details: {},
|
|
11095
|
+
datasets: {
|
|
11096
|
+
shapes: "2010 VTD shapes",
|
|
11097
|
+
census: "2010 Census Total Population",
|
|
11098
|
+
vap: "2010 Voting Age Population",
|
|
11099
|
+
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
11100
|
+
},
|
|
11101
|
+
resources: {}
|
|
11102
|
+
};
|
|
11103
|
+
// Create a DistrictStatistics instance, deep copying the underlying values.
|
|
11104
|
+
function prepareDistrictStatistics(s, bLog = false) {
|
|
11105
|
+
if (!(s.bPostProcessingDone)) {
|
|
11106
|
+
doAnalyzePostProcessing(s);
|
|
11107
|
+
}
|
|
11108
|
+
let dsTable = [];
|
|
11109
|
+
for (let i = 0; i < s.districts.numberOfRows(); i++) {
|
|
11110
|
+
let rawRow = [
|
|
11111
|
+
i,
|
|
11112
|
+
s.districts.statistics[D.DistrictField.TotalPop][i],
|
|
11113
|
+
s.districts.statistics[D.DistrictField.PopDevPct][i],
|
|
11114
|
+
s.districts.statistics[D.DistrictField.bEqualPop][i],
|
|
11115
|
+
s.districts.statistics[D.DistrictField.bNotEmpty][i],
|
|
11116
|
+
s.districts.statistics[D.DistrictField.bContiguous][i],
|
|
11117
|
+
s.districts.statistics[D.DistrictField.bNotEmbedded][i],
|
|
11118
|
+
s.districts.statistics[D.DistrictField.DemPct][i],
|
|
11119
|
+
s.districts.statistics[D.DistrictField.RepPct][i],
|
|
11120
|
+
s.districts.statistics[D.DistrictField.WhitePct][i],
|
|
11121
|
+
s.districts.statistics[D.DistrictField.MinorityPct][i],
|
|
11122
|
+
s.districts.statistics[D.DistrictField.BlackPct][i],
|
|
11123
|
+
s.districts.statistics[D.DistrictField.HispanicPct][i],
|
|
11124
|
+
s.districts.statistics[D.DistrictField.PacificPct][i],
|
|
11125
|
+
s.districts.statistics[D.DistrictField.AsianPct][i],
|
|
11126
|
+
s.districts.statistics[D.DistrictField.NativePct][i]
|
|
11127
|
+
];
|
|
11128
|
+
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
11129
|
+
// requirements/validations, map booleans to tri-states here.
|
|
11130
|
+
rawRow[3 /* bEqualPop */] = U.mapBooleanToTriState(rawRow[3 /* bEqualPop */]);
|
|
11131
|
+
rawRow[4 /* bNotEmpty */] = U.mapBooleanToTriState(rawRow[4 /* bNotEmpty */]);
|
|
11132
|
+
rawRow[5 /* bContiguous */] = U.mapBooleanToTriState(rawRow[5 /* bContiguous */]);
|
|
11133
|
+
rawRow[6 /* bNotEmbedded */] = U.mapBooleanToTriState(rawRow[6 /* bNotEmbedded */]);
|
|
11134
|
+
let readyRow = U.deepCopy(rawRow);
|
|
11135
|
+
dsTable.push(readyRow);
|
|
11136
|
+
}
|
|
11137
|
+
let dsDetails = {
|
|
11138
|
+
// None at this time
|
|
11139
|
+
};
|
|
11140
|
+
let dsDatasets = {
|
|
11141
|
+
shapes: "2010 VTD shapes",
|
|
11142
|
+
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
11143
|
+
vap: U.deepCopy(s.config['descriptions']['VAP']),
|
|
11144
|
+
election: U.deepCopy(s.config['descriptions']['ELECTION'])
|
|
11145
|
+
};
|
|
11146
|
+
let dsResources = {
|
|
11147
|
+
// None at this time
|
|
11148
|
+
};
|
|
11149
|
+
let ds = {
|
|
11150
|
+
table: dsTable,
|
|
11151
|
+
details: dsDetails,
|
|
11152
|
+
datasets: dsDatasets,
|
|
11153
|
+
resources: dsResources
|
|
11154
|
+
};
|
|
11155
|
+
return ds;
|
|
11156
|
+
}
|
|
11157
|
+
exports.prepareDistrictStatistics = prepareDistrictStatistics;
|
|
11158
|
+
// META-DATA FOR TESTS/ANALYTICS
|
|
11159
|
+
//
|
|
11160
|
+
// NOTE - This structure is a vestige of having created a metadata-driven
|
|
11161
|
+
// scorecard w/in district-analytics at first. It works for creating the
|
|
11162
|
+
// unstyled results structures, so it isn't a high priority to rationalize.
|
|
10424
11163
|
var TestType;
|
|
10425
11164
|
(function (TestType) {
|
|
10426
11165
|
TestType[TestType["PassFail"] = 0] = "PassFail";
|
|
@@ -10432,7 +11171,6 @@ const completeDefn = {
|
|
|
10432
11171
|
name: "Complete",
|
|
10433
11172
|
normalize: false,
|
|
10434
11173
|
externalType: TestType.PassFail,
|
|
10435
|
-
detailsFn: doPrepareCompleteDetails,
|
|
10436
11174
|
suites: [0 /* Legal */]
|
|
10437
11175
|
};
|
|
10438
11176
|
const contiguousDefn = {
|
|
@@ -10440,7 +11178,6 @@ const contiguousDefn = {
|
|
|
10440
11178
|
name: "Contiguous",
|
|
10441
11179
|
normalize: false,
|
|
10442
11180
|
externalType: TestType.PassFail,
|
|
10443
|
-
detailsFn: doPrepareContiguousDetails,
|
|
10444
11181
|
suites: [0 /* Legal */]
|
|
10445
11182
|
};
|
|
10446
11183
|
const freeOfHolesDefn = {
|
|
@@ -10448,7 +11185,6 @@ const freeOfHolesDefn = {
|
|
|
10448
11185
|
name: "Free of Holes",
|
|
10449
11186
|
normalize: false,
|
|
10450
11187
|
externalType: TestType.PassFail,
|
|
10451
|
-
detailsFn: doPrepareFreeOfHolesDetails,
|
|
10452
11188
|
suites: [0 /* Legal */]
|
|
10453
11189
|
};
|
|
10454
11190
|
const equalPopulationDefn = {
|
|
@@ -10456,7 +11192,6 @@ const equalPopulationDefn = {
|
|
|
10456
11192
|
name: "Equal Population",
|
|
10457
11193
|
normalize: false,
|
|
10458
11194
|
externalType: TestType.PassFail,
|
|
10459
|
-
detailsFn: doPrepareEqualPopulationDetails,
|
|
10460
11195
|
suites: [0 /* Legal */]
|
|
10461
11196
|
};
|
|
10462
11197
|
const populationDeviationDefn = {
|
|
@@ -10464,7 +11199,6 @@ const populationDeviationDefn = {
|
|
|
10464
11199
|
name: "Population Deviation",
|
|
10465
11200
|
normalize: true,
|
|
10466
11201
|
externalType: TestType.Percentage,
|
|
10467
|
-
detailsFn: doPreparePopulationDeviationDetails,
|
|
10468
11202
|
suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
|
|
10469
11203
|
};
|
|
10470
11204
|
const reockDefn = {
|
|
@@ -10472,7 +11206,6 @@ const reockDefn = {
|
|
|
10472
11206
|
name: "Reock",
|
|
10473
11207
|
normalize: true,
|
|
10474
11208
|
externalType: TestType.Number,
|
|
10475
|
-
detailsFn: doPrepareReockDetails,
|
|
10476
11209
|
suites: [2 /* Best */]
|
|
10477
11210
|
};
|
|
10478
11211
|
const polsbyPopperDefn = {
|
|
@@ -10480,24 +11213,45 @@ const polsbyPopperDefn = {
|
|
|
10480
11213
|
name: "Polsby-Popper",
|
|
10481
11214
|
normalize: true,
|
|
10482
11215
|
externalType: TestType.Number,
|
|
10483
|
-
detailsFn: doPreparePolsbyPopperDetails,
|
|
10484
11216
|
suites: [2 /* Best */]
|
|
10485
11217
|
};
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
normalize:
|
|
11218
|
+
// NOTE - SPLITTING
|
|
11219
|
+
const unexpectedCountySplitsDefn = {
|
|
11220
|
+
ID: 7 /* UnexpectedCountySplits */,
|
|
11221
|
+
name: "Unexpected County Splits",
|
|
11222
|
+
normalize: false,
|
|
10491
11223
|
externalType: TestType.Percentage,
|
|
10492
|
-
|
|
11224
|
+
suites: [2 /* Best */]
|
|
11225
|
+
};
|
|
11226
|
+
// NOTE - SPLITTING
|
|
11227
|
+
const VTDSplitsDefn = {
|
|
11228
|
+
ID: 10 /* VTDSplits */,
|
|
11229
|
+
name: "VTD Splits",
|
|
11230
|
+
normalize: false,
|
|
11231
|
+
externalType: TestType.Number,
|
|
11232
|
+
suites: [2 /* Best */]
|
|
11233
|
+
};
|
|
11234
|
+
// NOTE - SPLITTING
|
|
11235
|
+
const countySplittingDefn = {
|
|
11236
|
+
ID: 8 /* CountySplitting */,
|
|
11237
|
+
name: "County Splitting",
|
|
11238
|
+
normalize: true,
|
|
11239
|
+
externalType: TestType.Number,
|
|
11240
|
+
suites: [2 /* Best */]
|
|
11241
|
+
};
|
|
11242
|
+
// NOTE - SPLITTING
|
|
11243
|
+
const districtSplittingDefn = {
|
|
11244
|
+
ID: 9 /* DistrictSplitting */,
|
|
11245
|
+
name: "District Splitting",
|
|
11246
|
+
normalize: true,
|
|
11247
|
+
externalType: TestType.Number,
|
|
10493
11248
|
suites: [2 /* Best */]
|
|
10494
11249
|
};
|
|
10495
11250
|
const efficiencyGapDefn = {
|
|
10496
|
-
ID:
|
|
11251
|
+
ID: 15 /* EfficiencyGap */,
|
|
10497
11252
|
name: "Efficiency Gap",
|
|
10498
11253
|
normalize: false,
|
|
10499
11254
|
externalType: TestType.Percentage,
|
|
10500
|
-
detailsFn: doPrepareEfficiencyGapDetails,
|
|
10501
11255
|
suites: [1 /* Fair */]
|
|
10502
11256
|
};
|
|
10503
11257
|
// All the tests that have been defined (can be reported on)
|
|
@@ -10509,72 +11263,13 @@ const testDefns = {
|
|
|
10509
11263
|
[4 /* PopulationDeviation */]: populationDeviationDefn,
|
|
10510
11264
|
[5 /* Reock */]: reockDefn,
|
|
10511
11265
|
[6 /* PolsbyPopper */]: polsbyPopperDefn,
|
|
10512
|
-
//
|
|
10513
|
-
[7 /*
|
|
10514
|
-
[
|
|
10515
|
-
/*
|
|
10516
|
-
|
|
10517
|
-
//
|
|
10518
|
-
|
|
10519
|
-
catName: "Map Validations",
|
|
10520
|
-
catTests: [
|
|
10521
|
-
{ testID: 0 /* Complete */ },
|
|
10522
|
-
{ testID: 1 /* Contiguous */ },
|
|
10523
|
-
{ testID: 2 /* FreeOfHoles */ },
|
|
10524
|
-
{ testID: 3 /* EqualPopulation */ }
|
|
10525
|
-
],
|
|
10526
|
-
catNumeric: false,
|
|
10527
|
-
catWeight: undefined,
|
|
10528
|
-
catPrepareFn: doPrepareValidSection
|
|
10529
|
-
};
|
|
10530
|
-
const fairCategory = {
|
|
10531
|
-
// TODO - Change this label
|
|
10532
|
-
catName: "Is the map fair?",
|
|
10533
|
-
catTests: [
|
|
10534
|
-
{
|
|
10535
|
-
testID: 13 /* EfficiencyGap */,
|
|
10536
|
-
testWeight: 100
|
|
10537
|
-
}
|
|
10538
|
-
],
|
|
10539
|
-
catNumeric: true,
|
|
10540
|
-
catWeight: undefined,
|
|
10541
|
-
catPrepareFn: doPrepareFairSection
|
|
10542
|
-
};
|
|
10543
|
-
// TODO - Decide on the relative weights of these tests!
|
|
10544
|
-
// NOTE: 'testWeights' are simply relative, i.e., each normalized score is
|
|
10545
|
-
// multiplied by the associated 'testWeight', and the sum of those is divided
|
|
10546
|
-
// by the total weight. Weights don't have to add to 100.
|
|
10547
|
-
const bestCategory = {
|
|
10548
|
-
catName: "Traditional Districting Principles",
|
|
10549
|
-
catTests: [
|
|
10550
|
-
{
|
|
10551
|
-
testID: 4 /* PopulationDeviation */,
|
|
10552
|
-
testWeight: 10
|
|
10553
|
-
},
|
|
10554
|
-
{
|
|
10555
|
-
testID: 5 /* Reock */,
|
|
10556
|
-
testWeight: 25
|
|
10557
|
-
},
|
|
10558
|
-
{
|
|
10559
|
-
testID: 6 /* PolsbyPopper */,
|
|
10560
|
-
testWeight: 25
|
|
10561
|
-
}
|
|
10562
|
-
// TODO - Re-enable, when the metric is implemented
|
|
10563
|
-
// {
|
|
10564
|
-
// testID: T.Test.CountySplits,
|
|
10565
|
-
// testWeight: 50
|
|
10566
|
-
// }
|
|
10567
|
-
],
|
|
10568
|
-
catNumeric: true,
|
|
10569
|
-
catWeight: undefined,
|
|
10570
|
-
catPrepareFn: doPrepareBestSection
|
|
10571
|
-
};
|
|
10572
|
-
// The overall scorecard definition
|
|
10573
|
-
const scorecardDefn = {
|
|
10574
|
-
[0 /* Legal */]: validCategory,
|
|
10575
|
-
// TODO - NIY
|
|
10576
|
-
// [T.Suite.Fair]: fairCategory,
|
|
10577
|
-
[2 /* Best */]: bestCategory
|
|
11266
|
+
// NOTE - SPLITTING
|
|
11267
|
+
[7 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
|
|
11268
|
+
[10 /* VTDSplits */]: VTDSplitsDefn,
|
|
11269
|
+
[8 /* CountySplitting */]: countySplittingDefn,
|
|
11270
|
+
[9 /* DistrictSplitting */]: districtSplittingDefn,
|
|
11271
|
+
// TODO - More tests ...
|
|
11272
|
+
[15 /* EfficiencyGap */]: efficiencyGapDefn
|
|
10578
11273
|
};
|
|
10579
11274
|
// NORMALIZE RAW ANALYTICS
|
|
10580
11275
|
// Raw numeric analytics, such as population deviation, compactness, etc. are
|
|
@@ -10589,19 +11284,29 @@ function doConfigureScales(s) {
|
|
|
10589
11284
|
const LDLimit = 10.00 / 100; // Deviation threshold for LD's
|
|
10590
11285
|
const CDGoodEnough = 0.20 / 100;
|
|
10591
11286
|
const LDGoodEnough = (CDGoodEnough / CDLimit) * LDLimit;
|
|
10592
|
-
const
|
|
11287
|
+
const popDevScale = (s.legislativeDistricts) ? [1.0 - LDLimit, 1.0 - LDGoodEnough] : [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
10593
11288
|
// const scale = [1.0 - CDLimit, 1.0 - CDGoodEnough];
|
|
10594
|
-
s.testScales[4 /* PopulationDeviation */] = {
|
|
10595
|
-
s.testScales[5 /* Reock */] = {
|
|
10596
|
-
s.testScales[6 /* PolsbyPopper */] = {
|
|
10597
|
-
const
|
|
10598
|
-
|
|
11289
|
+
s.testScales[4 /* PopulationDeviation */] = { scale: popDevScale, bInvertRaw: true };
|
|
11290
|
+
s.testScales[5 /* Reock */] = { scale: [0.25, 0.50] };
|
|
11291
|
+
s.testScales[6 /* PolsbyPopper */] = { scale: [0.10, 0.50] };
|
|
11292
|
+
const nDistricts = s.state.nDistricts;
|
|
11293
|
+
const nCounties = s.counties.nCounties;
|
|
11294
|
+
// NOTE - SPLITTING: Experiment w/ this multiplier. Only allowing the expected
|
|
11295
|
+
// number of county splits seems too stringent, empirically.
|
|
11296
|
+
const allowableCountySplitsMultiplier = 1.5;
|
|
11297
|
+
const nAllowableSplits = Math.min(allowableCountySplitsMultiplier * (nDistricts - 1));
|
|
11298
|
+
const countySplittingThreshold = ((nAllowableSplits * 1.71) + ((nCounties - nAllowableSplits) * 1.0)) / nCounties;
|
|
11299
|
+
const countySplittingScale = [1.0, countySplittingThreshold];
|
|
11300
|
+
s.testScales[8 /* CountySplitting */] = { scale: countySplittingScale, bInvertScaled: true };
|
|
11301
|
+
const districtSplittingThreshold = 1.5;
|
|
11302
|
+
const districtSplittingScale = [1.0, districtSplittingThreshold];
|
|
11303
|
+
s.testScales[9 /* DistrictSplitting */] = { scale: districtSplittingScale, bInvertScaled: true };
|
|
10599
11304
|
// TODO - More analytics ...
|
|
10600
11305
|
}
|
|
10601
11306
|
exports.doConfigureScales = doConfigureScales;
|
|
10602
11307
|
// Postprocess analytics - Normalize numeric results and derive secondary tests.
|
|
10603
11308
|
// Do this after analytics have been run and before preparing a test log or scorecard.
|
|
10604
|
-
function doAnalyzePostProcessing(s) {
|
|
11309
|
+
function doAnalyzePostProcessing(s, bLog = false) {
|
|
10605
11310
|
// Normalize the raw scores for all the numerics tests
|
|
10606
11311
|
let testResults = U.getNumericObjectKeys(testDefns);
|
|
10607
11312
|
for (let testID of testResults) {
|
|
@@ -10609,642 +11314,18 @@ function doAnalyzePostProcessing(s) {
|
|
|
10609
11314
|
let testResult = s.getTest(testID);
|
|
10610
11315
|
let rawScore = testResult['score'];
|
|
10611
11316
|
let normalizedScore;
|
|
10612
|
-
|
|
10613
|
-
normalizedScore = U.normalize(rawScore, testScale, testInvertp);
|
|
11317
|
+
normalizedScore = U.normalize(rawScore, s.testScales[testID]);
|
|
10614
11318
|
testResult['normalizedScore'] = normalizedScore;
|
|
10615
11319
|
// Add the scale used to normalize the raw score to the details
|
|
10616
|
-
testResult['details']['scale'] =
|
|
11320
|
+
testResult['details']['scale'] = s.testScales[testID].scale;
|
|
10617
11321
|
}
|
|
10618
11322
|
}
|
|
10619
11323
|
// Derive secondary tests
|
|
10620
|
-
analyze_1.doDeriveSecondaryTests(s);
|
|
11324
|
+
analyze_1.doDeriveSecondaryTests(s, bLog);
|
|
10621
11325
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
10622
11326
|
s.bPostProcessingDone = true;
|
|
10623
11327
|
}
|
|
10624
11328
|
exports.doAnalyzePostProcessing = doAnalyzePostProcessing;
|
|
10625
|
-
// Prepare a structured but unformatted scorecard, from the test results
|
|
10626
|
-
function doGenerateScorecard(s) {
|
|
10627
|
-
if (!(s.bPostProcessingDone)) {
|
|
10628
|
-
doAnalyzePostProcessing(s);
|
|
10629
|
-
}
|
|
10630
|
-
// Create a new scorecard
|
|
10631
|
-
let scorecard = {};
|
|
10632
|
-
// Filter the defined scorecard categories by the requested test suites
|
|
10633
|
-
let categories = U.getNumericObjectKeys(scorecardDefn);
|
|
10634
|
-
let suitesRequested = s.config['suites'];
|
|
10635
|
-
categories = categories.filter(x => suitesRequested.includes(x));
|
|
10636
|
-
// ... and initialize each one in the new scorecard
|
|
10637
|
-
for (let c of categories) {
|
|
10638
|
-
scorecard[c] = {};
|
|
10639
|
-
scorecard[c]['catName'] = scorecardDefn[c]['catName'];
|
|
10640
|
-
scorecard[c]['catTests'] = {};
|
|
10641
|
-
// scorecard[c]['catScore'] = undefined;
|
|
10642
|
-
}
|
|
10643
|
-
// For each scorecard category
|
|
10644
|
-
for (let c of categories) {
|
|
10645
|
-
// Grab the scorecard category definition
|
|
10646
|
-
let { catName, catTests, catNumeric } = scorecardDefn[c];
|
|
10647
|
-
let numericCategoryScore = 0;
|
|
10648
|
-
let totalWeight = 0;
|
|
10649
|
-
let booleanCategoryScore = true;
|
|
10650
|
-
// Process the results for each test result in the category
|
|
10651
|
-
for (let testDefn of catTests) {
|
|
10652
|
-
// Get the config info for the test
|
|
10653
|
-
let testID = testDefn['testID'];
|
|
10654
|
-
// ... and the actual test result
|
|
10655
|
-
let testResult = s.getTest(testID);
|
|
10656
|
-
// Create a new test entry for the scorecard
|
|
10657
|
-
let testReport = U.deepCopy(testResult);
|
|
10658
|
-
// Add the name
|
|
10659
|
-
testReport['name'] = testDefns[testID]['name'];
|
|
10660
|
-
if (catNumeric) {
|
|
10661
|
-
// Normalize raw numeric scores ... moved to FIRST PASS above
|
|
10662
|
-
// Accumulate a category score
|
|
10663
|
-
let normalizedScore = testReport['normalizedScore'];
|
|
10664
|
-
numericCategoryScore += normalizedScore * testDefn['testWeight'];
|
|
10665
|
-
totalWeight += testDefn['testWeight'];
|
|
10666
|
-
}
|
|
10667
|
-
else {
|
|
10668
|
-
// AND together pass/fail tests into a category score
|
|
10669
|
-
if (!testReport['score']) {
|
|
10670
|
-
booleanCategoryScore = false;
|
|
10671
|
-
}
|
|
10672
|
-
}
|
|
10673
|
-
scorecard[c]['catTests'][testID] = testReport;
|
|
10674
|
-
}
|
|
10675
|
-
// Set the category score
|
|
10676
|
-
if (catNumeric) {
|
|
10677
|
-
scorecard[c]['catScore'] = Math.round(numericCategoryScore / totalWeight);
|
|
10678
|
-
}
|
|
10679
|
-
else {
|
|
10680
|
-
scorecard[c]['catScore'] = booleanCategoryScore;
|
|
10681
|
-
}
|
|
10682
|
-
}
|
|
10683
|
-
// TODO - Compute an overall score from the category weights
|
|
10684
|
-
return scorecard;
|
|
10685
|
-
}
|
|
10686
|
-
// Prepare a formatted scorecard suitable for rendering
|
|
10687
|
-
function doPrepareScorecard(s) {
|
|
10688
|
-
// Initialize the output format
|
|
10689
|
-
let text = { data: [] };
|
|
10690
|
-
let blocks = text.data;
|
|
10691
|
-
// If the plan as already been analyzed, prepare a scorecard
|
|
10692
|
-
if (s.bPlanAnalyzed) {
|
|
10693
|
-
// Create and cache a new, unformatted scorecard
|
|
10694
|
-
s.scorecard = doGenerateScorecard(s);
|
|
10695
|
-
// Create a scorecard header
|
|
10696
|
-
blocks.push({ variant: 'h4', text: `Analysis - NC 2019 Special Edition` });
|
|
10697
|
-
blocks.push({ variant: 'body1', text: `In response to the recent court ruling in North Carolina and the court's requirement for transparency, we are pleased to provide the general public with early access to this base set of redistricting analytics. Stay tuned for more updates!` });
|
|
10698
|
-
blocks.push({ variant: 'body1', text: `For more details, see our blog post on Medium.` });
|
|
10699
|
-
// Prepare each scorecard category
|
|
10700
|
-
blocks.push({ variant: 'beginExpansion', text: `Overall Plan` });
|
|
10701
|
-
let categories = U.getNumericObjectKeys(s.scorecard);
|
|
10702
|
-
for (let c of categories) {
|
|
10703
|
-
let sectionPrepareFn = scorecardDefn[c]['catPrepareFn'];
|
|
10704
|
-
let sectionText = sectionPrepareFn(s, c);
|
|
10705
|
-
blocks.push(...sectionText);
|
|
10706
|
-
}
|
|
10707
|
-
// blocks.push({ variant: 'body1', text: `` });
|
|
10708
|
-
// blocks.push({ variant: 'body1', text: `There is much more analysis coming of county splitting, partisan fairness, and the opportunity for minority representation! For now, you can glean a lot from the district statistics below.` });
|
|
10709
|
-
blocks.push({ variant: 'endExpansion' });
|
|
10710
|
-
// Report district statistics
|
|
10711
|
-
blocks.push({ variant: 'beginExpansion', text: `Individual Districts` });
|
|
10712
|
-
let districtStatisticsText = doPrepareDistrictStatistics(s);
|
|
10713
|
-
blocks.push(...districtStatisticsText);
|
|
10714
|
-
blocks.push({ variant: 'endExpansion' });
|
|
10715
|
-
// Report what datasets were used
|
|
10716
|
-
let c = s.config['datasets']["CENSUS" /* CENSUS */];
|
|
10717
|
-
let v = s.config['datasets']["VAP" /* VAP */];
|
|
10718
|
-
let e = s.config['datasets']["ELECTION" /* ELECTION */];
|
|
10719
|
-
blocks.push({ variant: 'beginExpansion', text: `About the Data` });
|
|
10720
|
-
blocks.push({ variant: 'body1', text: `These are the 4 datasets used in this analysis:` });
|
|
10721
|
-
// TODO - Get the shape "dataset" from dra-client
|
|
10722
|
-
blocks.push({ variant: 'body1', text: `* Shapes: 2010 VTD shapes` });
|
|
10723
|
-
blocks.push({ variant: 'body1', text: `* Census: ${D.DatasetDescriptions[c]}` });
|
|
10724
|
-
blocks.push({ variant: 'body1', text: `* VAP: ${D.DatasetDescriptions[v]}` });
|
|
10725
|
-
blocks.push({ variant: 'body1', text: `* Elections: ${D.DatasetDescriptions[e]}` });
|
|
10726
|
-
blocks.push({ variant: 'endExpansion' });
|
|
10727
|
-
blocks.push({ variant: 'body1', text: `` });
|
|
10728
|
-
blocks.push({
|
|
10729
|
-
"variant": "link",
|
|
10730
|
-
"text": "Questions or comments?",
|
|
10731
|
-
"label": "analytics@davesredistricting.org",
|
|
10732
|
-
"link": "mailto:analytics@davesredistricting.org"
|
|
10733
|
-
});
|
|
10734
|
-
}
|
|
10735
|
-
// Otherwise, return a blank scorecard
|
|
10736
|
-
return text;
|
|
10737
|
-
}
|
|
10738
|
-
exports.doPrepareScorecard = doPrepareScorecard;
|
|
10739
|
-
function doPrepareDistrictStatistics(s) {
|
|
10740
|
-
let text = { data: [] };
|
|
10741
|
-
let blocks = text.data;
|
|
10742
|
-
blocks.push({ variant: 'beginTable' });
|
|
10743
|
-
blocks.push({ variant: 'row', cells: ['ID', 'Total', 'Δ%', 'OK?', '*', 'Dem', 'Rep', 'White', 'Minority', 'Black', 'Hispanic', 'Pacific', 'Asian', 'Native'] });
|
|
10744
|
-
for (let d = 0; d < s.districts.numberOfRows(); d++) {
|
|
10745
|
-
let tot = s.districts.statistics[D.DistrictField.TotalPop][d];
|
|
10746
|
-
if (tot == 0)
|
|
10747
|
-
blocks.push({ variant: 'row', cells: [String(d), '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-'] });
|
|
10748
|
-
else {
|
|
10749
|
-
tot = Math.round(tot);
|
|
10750
|
-
let dev = fractionToPercentage(s.districts.statistics[D.DistrictField.PopDevPct][d]);
|
|
10751
|
-
let bEq = s.districts.statistics[D.DistrictField.bEqualPop][d];
|
|
10752
|
-
let bC = s.districts.statistics[D.DistrictField.bContiguous][d]
|
|
10753
|
-
&& s.districts.statistics[D.DistrictField.bNotEmbedded][d];
|
|
10754
|
-
let dPct = fractionToPercentage(s.districts.statistics[D.DistrictField.DemPct][d]);
|
|
10755
|
-
let rPct = fractionToPercentage(s.districts.statistics[D.DistrictField.RepPct][d]);
|
|
10756
|
-
let wPct = fractionToPercentage(s.districts.statistics[D.DistrictField.WhitePct][d]);
|
|
10757
|
-
let mPct = fractionToPercentage(s.districts.statistics[D.DistrictField.MinorityPct][d]);
|
|
10758
|
-
let bPct = fractionToPercentage(s.districts.statistics[D.DistrictField.BlackPct][d]);
|
|
10759
|
-
let hPct = fractionToPercentage(s.districts.statistics[D.DistrictField.HispanicPct][d]);
|
|
10760
|
-
let pPct = fractionToPercentage(s.districts.statistics[D.DistrictField.PacificPct][d]);
|
|
10761
|
-
let aPct = fractionToPercentage(s.districts.statistics[D.DistrictField.AsianPct][d]);
|
|
10762
|
-
let nPct = fractionToPercentage(s.districts.statistics[D.DistrictField.NativePct][d]);
|
|
10763
|
-
let id;
|
|
10764
|
-
if (d == 0)
|
|
10765
|
-
id = "??";
|
|
10766
|
-
else if (d == (s.districts.numberOfRows() - 1))
|
|
10767
|
-
id = " ";
|
|
10768
|
-
else
|
|
10769
|
-
id = String(d);
|
|
10770
|
-
blocks.push({
|
|
10771
|
-
variant: 'row',
|
|
10772
|
-
cells: [
|
|
10773
|
-
`${id}`,
|
|
10774
|
-
`${formatInteger(tot)}`,
|
|
10775
|
-
`${formatPercentage(dev)}%`,
|
|
10776
|
-
`${pfBoolToString(bEq)}`,
|
|
10777
|
-
`${pfBoolToString(bC)}`,
|
|
10778
|
-
`${formatPercentage(dPct)}%`,
|
|
10779
|
-
`${formatPercentage(rPct)}%`,
|
|
10780
|
-
`${formatPercentage(wPct)}%`,
|
|
10781
|
-
`${formatPercentage(mPct)}%`,
|
|
10782
|
-
`${formatPercentage(bPct)}%`,
|
|
10783
|
-
`${formatPercentage(hPct)}%`,
|
|
10784
|
-
`${formatPercentage(pPct)}%`,
|
|
10785
|
-
`${formatPercentage(aPct)}%`,
|
|
10786
|
-
`${formatPercentage(nPct)}%`
|
|
10787
|
-
]
|
|
10788
|
-
});
|
|
10789
|
-
}
|
|
10790
|
-
}
|
|
10791
|
-
blocks.push({ variant: 'endTable' });
|
|
10792
|
-
return blocks;
|
|
10793
|
-
}
|
|
10794
|
-
// TEST LOG
|
|
10795
|
-
// Prepare formatted test results for rendering
|
|
10796
|
-
function doPrepareTestLog(s) {
|
|
10797
|
-
// Initialize the output format
|
|
10798
|
-
let text = { data: [] };
|
|
10799
|
-
let blocks = text.data;
|
|
10800
|
-
// If the plan as already been analyzed, prepare a test log
|
|
10801
|
-
if (s.bPlanAnalyzed) {
|
|
10802
|
-
if (!(s.bPostProcessingDone)) {
|
|
10803
|
-
doAnalyzePostProcessing(s);
|
|
10804
|
-
}
|
|
10805
|
-
// Create a test log header
|
|
10806
|
-
blocks.push({ variant: 'h4', text: `Test Log` });
|
|
10807
|
-
let testResults = U.getNumericObjectKeys(testDefns);
|
|
10808
|
-
let suitesRequested = new Set(s.config['suites']);
|
|
10809
|
-
for (let testID of testResults) {
|
|
10810
|
-
// Filter the defined tests by the requested test suites
|
|
10811
|
-
let inSuites = testDefns[testID]['suites'];
|
|
10812
|
-
if (!(U.isArrayEmpty(inSuites.filter(x => suitesRequested.has(x))))) {
|
|
10813
|
-
// Get the test result
|
|
10814
|
-
let testResult = s.getTest(testID);
|
|
10815
|
-
// Prepare the text for it, and append it to the output
|
|
10816
|
-
let testText = prepareTestEntry(testID, testResult);
|
|
10817
|
-
blocks.push(...testText);
|
|
10818
|
-
}
|
|
10819
|
-
}
|
|
10820
|
-
}
|
|
10821
|
-
// Otherwise, return a blank test log
|
|
10822
|
-
return text;
|
|
10823
|
-
}
|
|
10824
|
-
exports.doPrepareTestLog = doPrepareTestLog;
|
|
10825
|
-
function prepareTestEntry(testID, testResult) {
|
|
10826
|
-
let text = { data: [] };
|
|
10827
|
-
let blocks = text.data;
|
|
10828
|
-
let testName = testDefns[testID]['name'];
|
|
10829
|
-
let testNameTrailer = "";
|
|
10830
|
-
if (U.keyExists('trailer', testDefns[testID])) {
|
|
10831
|
-
testNameTrailer = testDefns[testID]['trailer'];
|
|
10832
|
-
}
|
|
10833
|
-
let testType = testDefns[testID]['externalType'];
|
|
10834
|
-
let bNormalize = testDefns[testID]['normalize'];
|
|
10835
|
-
let detailsFn = testDefns[testID]['detailsFn'];
|
|
10836
|
-
let detailsText = detailsFn(testResult);
|
|
10837
|
-
let score; // NOTE - Won't be undefined here
|
|
10838
|
-
let normalizedScore;
|
|
10839
|
-
let scoreText;
|
|
10840
|
-
// Get the score ...
|
|
10841
|
-
score = testResult['score'];
|
|
10842
|
-
// ... and format it for rendering
|
|
10843
|
-
switch (testType) {
|
|
10844
|
-
case TestType.PassFail: {
|
|
10845
|
-
scoreText = pfBoolToString(score);
|
|
10846
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${scoreText} ${testNameTrailer}` });
|
|
10847
|
-
break;
|
|
10848
|
-
}
|
|
10849
|
-
case TestType.Percentage: {
|
|
10850
|
-
score = fractionToPercentage(score);
|
|
10851
|
-
if (bNormalize) {
|
|
10852
|
-
normalizedScore = testResult['normalizedScore'];
|
|
10853
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatPercentage(score)}% ${testNameTrailer}` });
|
|
10854
|
-
}
|
|
10855
|
-
else {
|
|
10856
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatPercentage(score)}% ${testNameTrailer}` });
|
|
10857
|
-
}
|
|
10858
|
-
break;
|
|
10859
|
-
}
|
|
10860
|
-
case TestType.Number: {
|
|
10861
|
-
if (bNormalize) {
|
|
10862
|
-
normalizedScore = testResult['normalizedScore'];
|
|
10863
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${normalizedScore} / 100 : ${formatNumber(score)} ${testNameTrailer}` });
|
|
10864
|
-
}
|
|
10865
|
-
else {
|
|
10866
|
-
blocks.push({ variant: 'body1', text: `<b>${testName}</b>: ${formatNumber(score)} ${testNameTrailer}` });
|
|
10867
|
-
}
|
|
10868
|
-
break;
|
|
10869
|
-
}
|
|
10870
|
-
default: {
|
|
10871
|
-
// Unknown test type
|
|
10872
|
-
throw new RangeError();
|
|
10873
|
-
}
|
|
10874
|
-
}
|
|
10875
|
-
// Add the details text
|
|
10876
|
-
blocks.push(...detailsText);
|
|
10877
|
-
return blocks;
|
|
10878
|
-
}
|
|
10879
|
-
// FORMATTERS FOR TEST DETAILS
|
|
10880
|
-
function doPrepareCompleteDetails(testResult) {
|
|
10881
|
-
let text = { data: [] };
|
|
10882
|
-
let blocks = text.data;
|
|
10883
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
10884
|
-
let unassignedText = "";
|
|
10885
|
-
let emptyText = "";
|
|
10886
|
-
let missingText = "";
|
|
10887
|
-
if (U.keyExists('unassignedFeatures', testResult['details'])) {
|
|
10888
|
-
let unassignedFeatures = testResult['details']['unassignedFeatures'];
|
|
10889
|
-
let unassignedList = prepareListItems(unassignedFeatures);
|
|
10890
|
-
let unassignedTextTemplates = [
|
|
10891
|
-
`GEOID ${unassignedList} is not assigned to a district.`,
|
|
10892
|
-
`GEOIDs ${unassignedList} are not assigned to districts.`,
|
|
10893
|
-
`Several GEOIDs are not assigned to districts, including ${unassignedList}.`
|
|
10894
|
-
];
|
|
10895
|
-
unassignedText = prepareListText(unassignedFeatures, unassignedTextTemplates);
|
|
10896
|
-
}
|
|
10897
|
-
if (U.keyExists('emptyDistricts', testResult['details'])) {
|
|
10898
|
-
let emptyDistricts = testResult['details']['emptyDistricts'];
|
|
10899
|
-
let emptyList = prepareListItems(emptyDistricts);
|
|
10900
|
-
let emptyTextTemplates = [
|
|
10901
|
-
`District ${emptyList} is empty.`,
|
|
10902
|
-
`Districts ${emptyList} are empty.`,
|
|
10903
|
-
`Several districts are empty, including ${emptyList}.`
|
|
10904
|
-
];
|
|
10905
|
-
emptyText = prepareListText(emptyDistricts, emptyTextTemplates);
|
|
10906
|
-
}
|
|
10907
|
-
if (U.keyExists('missingDistricts', testResult['details'])) {
|
|
10908
|
-
missingText = `Not enough districts have been defined. `;
|
|
10909
|
-
}
|
|
10910
|
-
let detailsText = " " + unassignedText + emptyText + missingText;
|
|
10911
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
10912
|
-
}
|
|
10913
|
-
return blocks;
|
|
10914
|
-
}
|
|
10915
|
-
function doPrepareContiguousDetails(testResult) {
|
|
10916
|
-
let text = { data: [] };
|
|
10917
|
-
let blocks = text.data;
|
|
10918
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
10919
|
-
let discontiguousDistricts = testResult['details']['discontiguousDistricts'];
|
|
10920
|
-
let discontiguousList = prepareListItems(discontiguousDistricts);
|
|
10921
|
-
let discontiguousTextTemplates = [
|
|
10922
|
-
`District ${discontiguousList} is not contiguous.`,
|
|
10923
|
-
`Districts ${discontiguousList} are not contiguous.`,
|
|
10924
|
-
`Several districts are not contiguous, including ${discontiguousList}.`
|
|
10925
|
-
];
|
|
10926
|
-
let detailsText = prepareListText(discontiguousDistricts, discontiguousTextTemplates);
|
|
10927
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
10928
|
-
}
|
|
10929
|
-
return blocks;
|
|
10930
|
-
}
|
|
10931
|
-
function doPrepareFreeOfHolesDetails(testResult) {
|
|
10932
|
-
let text = { data: [] };
|
|
10933
|
-
let blocks = text.data;
|
|
10934
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
10935
|
-
let embeddedDistricts = testResult['details']['embeddedDistricts'];
|
|
10936
|
-
let embeddedList = prepareListItems(embeddedDistricts);
|
|
10937
|
-
let embeddedTextTemplates = [
|
|
10938
|
-
`District ${embeddedList} is fully embedded within another district.`,
|
|
10939
|
-
`Both districts ${embeddedList} are fully embedded within other districts.`,
|
|
10940
|
-
`Several districts are fully embedded within other districts, including ${embeddedList}.`
|
|
10941
|
-
];
|
|
10942
|
-
let detailsText = prepareListText(embeddedDistricts, embeddedTextTemplates);
|
|
10943
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
10944
|
-
}
|
|
10945
|
-
return blocks;
|
|
10946
|
-
}
|
|
10947
|
-
function doPrepareEqualPopulationDetails(testResult) {
|
|
10948
|
-
let text = { data: [] };
|
|
10949
|
-
let blocks = text.data;
|
|
10950
|
-
if (!U.isObjectEmpty(testResult['details'])) {
|
|
10951
|
-
let popDevPct = fractionToPercentage(testResult['details']['deviation']);
|
|
10952
|
-
let thresholdPct = fractionToPercentage(1.0 - testResult['details']['thresholds'][0]);
|
|
10953
|
-
let detailsText = '';
|
|
10954
|
-
if (!(testResult['score'])) {
|
|
10955
|
-
detailsText = `The ${formatPercentage(popDevPct)}% population deviation is greater than the ${formatPercentage(thresholdPct)}% threshold tolerated by courts.`;
|
|
10956
|
-
}
|
|
10957
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
10958
|
-
}
|
|
10959
|
-
return blocks;
|
|
10960
|
-
}
|
|
10961
|
-
function doPreparePopulationDeviationDetails(testResult) {
|
|
10962
|
-
let text = { data: [] };
|
|
10963
|
-
let blocks = text.data;
|
|
10964
|
-
let n = Math.round(testResult['details']['maxDeviation']);
|
|
10965
|
-
let term = "people";
|
|
10966
|
-
if (n == 1) {
|
|
10967
|
-
term = "person";
|
|
10968
|
-
}
|
|
10969
|
-
blocks.push({ variant: 'body1', text: `The maximum population deviation between districts is ${formatInteger(n)} ${term}.` });
|
|
10970
|
-
return blocks;
|
|
10971
|
-
}
|
|
10972
|
-
function doPrepareReockDetails(testResult) {
|
|
10973
|
-
let text = { data: [] };
|
|
10974
|
-
let blocks = text.data;
|
|
10975
|
-
// TODO - No details implemented yet
|
|
10976
|
-
return blocks;
|
|
10977
|
-
}
|
|
10978
|
-
function doPreparePolsbyPopperDetails(testResult) {
|
|
10979
|
-
let text = { data: [] };
|
|
10980
|
-
let blocks = text.data;
|
|
10981
|
-
// TODO - No details implemented yet
|
|
10982
|
-
return blocks;
|
|
10983
|
-
}
|
|
10984
|
-
function doPrepareEfficiencyGapDetails(testResult) {
|
|
10985
|
-
let text = { data: [] };
|
|
10986
|
-
let blocks = text.data;
|
|
10987
|
-
// TODO - Not yet implemented
|
|
10988
|
-
return blocks;
|
|
10989
|
-
}
|
|
10990
|
-
function doPrepareCountySplitDetails(testResult) {
|
|
10991
|
-
let text = { data: [] };
|
|
10992
|
-
let blocks = text.data;
|
|
10993
|
-
let unexpectedAffected = fractionToPercentage(testResult['details']['unexpectedAffected']);
|
|
10994
|
-
let affectedText = `affecting ${formatPercentage(unexpectedAffected)}% of the total population.`;
|
|
10995
|
-
let countiesSplitUnexpectedly = testResult['details']['countiesSplitUnexpectedly'];
|
|
10996
|
-
let nCountiesSplitUnexpectedly = countiesSplitUnexpectedly.length;
|
|
10997
|
-
let nUnexpectedSplits = testResult['details']['unexpectedSplits'];
|
|
10998
|
-
let splitList = prepareListItems(countiesSplitUnexpectedly);
|
|
10999
|
-
let splitTextTemplates = [
|
|
11000
|
-
`${splitList} county is split unexpectedly, `,
|
|
11001
|
-
`${splitList} counties are split unexpectedly, `,
|
|
11002
|
-
// `These ${formatInteger(nCountiesSplitUnexpectedly)} counties are split unexpectedly--${splitList}--`
|
|
11003
|
-
`${splitList} counties are split unexpectedly ${formatInteger(nUnexpectedSplits)} times, `
|
|
11004
|
-
];
|
|
11005
|
-
let detailsText = prepareListText(countiesSplitUnexpectedly, splitTextTemplates) + affectedText;
|
|
11006
|
-
blocks.push({ variant: 'body1', text: detailsText });
|
|
11007
|
-
return blocks;
|
|
11008
|
-
}
|
|
11009
|
-
// FORMATTERS FOR CATEGORIES
|
|
11010
|
-
// This function parses & formats the 'Valid' section of a scorecard.
|
|
11011
|
-
function doPrepareValidSection(s, c) {
|
|
11012
|
-
// Get the category meta data
|
|
11013
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
11014
|
-
let testReport;
|
|
11015
|
-
// Initialize the section text
|
|
11016
|
-
let text = { data: [] };
|
|
11017
|
-
let blocks = text.data;
|
|
11018
|
-
let testText;
|
|
11019
|
-
// Section header
|
|
11020
|
-
let stringScore = pfBoolToString(catScore);
|
|
11021
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName}` });
|
|
11022
|
-
// blocks.push({ variant: 'beginExpansion', text: `${catName} ${stringScore}` });
|
|
11023
|
-
// Complete
|
|
11024
|
-
testReport = catTests[0 /* Complete */];
|
|
11025
|
-
testText = prepareTestEntry(0 /* Complete */, testReport);
|
|
11026
|
-
blocks.push(...testText);
|
|
11027
|
-
// Contiguous
|
|
11028
|
-
testReport = catTests[1 /* Contiguous */];
|
|
11029
|
-
testText = prepareTestEntry(1 /* Contiguous */, testReport);
|
|
11030
|
-
blocks.push(...testText);
|
|
11031
|
-
// Free of holes (no embedded districts)
|
|
11032
|
-
testReport = catTests[2 /* FreeOfHoles */];
|
|
11033
|
-
testText = prepareTestEntry(2 /* FreeOfHoles */, testReport);
|
|
11034
|
-
blocks.push(...testText);
|
|
11035
|
-
// Equal population (w/in the appropriate legal threshold)
|
|
11036
|
-
testReport = catTests[3 /* EqualPopulation */];
|
|
11037
|
-
testText = prepareTestEntry(3 /* EqualPopulation */, testReport);
|
|
11038
|
-
blocks.push(...testText);
|
|
11039
|
-
blocks.push({ variant: 'endExpansion' });
|
|
11040
|
-
return blocks;
|
|
11041
|
-
}
|
|
11042
|
-
// TODO - NIY
|
|
11043
|
-
// This function parses & formats the 'Fair' section of a scorecard.
|
|
11044
|
-
function doPrepareFairSection(s, c) {
|
|
11045
|
-
// Get the category meta data
|
|
11046
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
11047
|
-
let testReport;
|
|
11048
|
-
// Initialize the section text
|
|
11049
|
-
let text = { data: [] };
|
|
11050
|
-
let blocks = text.data;
|
|
11051
|
-
let testText;
|
|
11052
|
-
// Section header
|
|
11053
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName} ${catScore} of 100` });
|
|
11054
|
-
// TODO - Flesh this out
|
|
11055
|
-
// There's only one test: Population Deviation.
|
|
11056
|
-
// testReport = catTests[T.Test.PopulationDeviation];
|
|
11057
|
-
// testText = prepareTestEntry(T.Test.PopulationDeviation, testReport);
|
|
11058
|
-
// blocks.push(...testText);
|
|
11059
|
-
blocks.push({ variant: 'endExpansion' });
|
|
11060
|
-
return blocks;
|
|
11061
|
-
}
|
|
11062
|
-
// This function parses & formats the 'Best' section of a scorecard.
|
|
11063
|
-
function doPrepareBestSection(s, c) {
|
|
11064
|
-
// Get the category meta data
|
|
11065
|
-
let { catName, catScore, catTests } = s.scorecard[c];
|
|
11066
|
-
let testReport;
|
|
11067
|
-
// Initialize the section text
|
|
11068
|
-
let text = { data: [] };
|
|
11069
|
-
let blocks = text.data;
|
|
11070
|
-
let testText;
|
|
11071
|
-
// Section header
|
|
11072
|
-
blocks.push({ variant: 'beginExpansion', text: `${catName} (${catScore} of 100)` });
|
|
11073
|
-
// Population deviation
|
|
11074
|
-
testReport = catTests[4 /* PopulationDeviation */];
|
|
11075
|
-
testText = prepareTestEntry(4 /* PopulationDeviation */, testReport);
|
|
11076
|
-
blocks.push(...testText);
|
|
11077
|
-
// Compactness
|
|
11078
|
-
testReport = catTests[5 /* Reock */];
|
|
11079
|
-
let normalizedReock = testReport['normalizedScore'];
|
|
11080
|
-
let reockTestText = prepareTestEntry(5 /* Reock */, testReport);
|
|
11081
|
-
testReport = catTests[6 /* PolsbyPopper */];
|
|
11082
|
-
let normalizedPolsbyPopper = testReport['normalizedScore'];
|
|
11083
|
-
let polsbyPopperTestText = prepareTestEntry(6 /* PolsbyPopper */, testReport);
|
|
11084
|
-
let compactnessScore = (normalizedReock + normalizedPolsbyPopper) / 2;
|
|
11085
|
-
blocks.push({ variant: 'body1', text: `Compactness: ${compactnessScore} / 100` });
|
|
11086
|
-
blocks.push(...reockTestText);
|
|
11087
|
-
blocks.push(...polsbyPopperTestText);
|
|
11088
|
-
// County splits
|
|
11089
|
-
// TODO - Re-enable this, when the county-splitting metric is implemented.
|
|
11090
|
-
// testReport = catTests[T.Test.CountySplits];
|
|
11091
|
-
// testText = prepareTestEntry(T.Test.CountySplits, testReport);
|
|
11092
|
-
// blocks.push(...testText);
|
|
11093
|
-
blocks.push({ variant: 'endExpansion' });
|
|
11094
|
-
return blocks;
|
|
11095
|
-
}
|
|
11096
|
-
// FORMATTING HELPERS
|
|
11097
|
-
// Convert a boolean representing Pass/Fail to a string
|
|
11098
|
-
function pfBoolToString(score) {
|
|
11099
|
-
if (score) {
|
|
11100
|
-
return "Yes";
|
|
11101
|
-
}
|
|
11102
|
-
else {
|
|
11103
|
-
return "No";
|
|
11104
|
-
}
|
|
11105
|
-
}
|
|
11106
|
-
function fractionToPercentage(f) {
|
|
11107
|
-
return f * 100;
|
|
11108
|
-
}
|
|
11109
|
-
function formatNumber(n) {
|
|
11110
|
-
let p = S.PRECISION;
|
|
11111
|
-
return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
|
|
11112
|
-
}
|
|
11113
|
-
function formatPercentage(n) {
|
|
11114
|
-
let p = (S.PRECISION / 2);
|
|
11115
|
-
return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
|
|
11116
|
-
}
|
|
11117
|
-
function formatInteger(i) {
|
|
11118
|
-
return new Intl.NumberFormat().format(i);
|
|
11119
|
-
}
|
|
11120
|
-
// Prepare the items in a list for rendering
|
|
11121
|
-
function prepareListItems(list) {
|
|
11122
|
-
let nItems = list.length;
|
|
11123
|
-
let listStr;
|
|
11124
|
-
switch (nItems) {
|
|
11125
|
-
case 1: {
|
|
11126
|
-
listStr = list[0];
|
|
11127
|
-
break;
|
|
11128
|
-
}
|
|
11129
|
-
case 2: {
|
|
11130
|
-
listStr = list[0] + " and " + list[1];
|
|
11131
|
-
break;
|
|
11132
|
-
}
|
|
11133
|
-
default: {
|
|
11134
|
-
let listWithCommas = list.join(', ');
|
|
11135
|
-
let lastCommaIndex = listWithCommas.length - ((list[list.length - 1].length) + 1);
|
|
11136
|
-
let beforeAnd = listWithCommas.substr(0, lastCommaIndex);
|
|
11137
|
-
let afterAnd = listWithCommas.substr(lastCommaIndex + 1);
|
|
11138
|
-
listStr = beforeAnd + " and " + afterAnd;
|
|
11139
|
-
break;
|
|
11140
|
-
}
|
|
11141
|
-
}
|
|
11142
|
-
return listStr;
|
|
11143
|
-
}
|
|
11144
|
-
// Pick the rendering text for the appropriate list length
|
|
11145
|
-
function prepareListText(list, listTemplates) {
|
|
11146
|
-
let nItems = list.length;
|
|
11147
|
-
switch (nItems) {
|
|
11148
|
-
case 1: {
|
|
11149
|
-
return listTemplates[0];
|
|
11150
|
-
break;
|
|
11151
|
-
}
|
|
11152
|
-
case 2: {
|
|
11153
|
-
return listTemplates[1];
|
|
11154
|
-
break;
|
|
11155
|
-
}
|
|
11156
|
-
default: {
|
|
11157
|
-
return listTemplates[2];
|
|
11158
|
-
break;
|
|
11159
|
-
}
|
|
11160
|
-
}
|
|
11161
|
-
}
|
|
11162
|
-
/* COMMAND-LINE TESTS
|
|
11163
|
-
|
|
11164
|
-
node main.js scorecard -v -x NC -n 13 -p ~/src/district-analytics/data/SAMPLE-BG-map.csv -d ~/src/district-analytics/data/SAMPLE-BG-data2.json -s ~/src/district-analytics/data/SAMPLE-BG-shapes.geojson -g ~/src/district-analytics/data/SAMPLE-BG-graph.json -c ~/src/district-analytics/data/SAMPLE-COUNTY.geojson
|
|
11165
|
-
node --inspect --inspect-brk main.js scorecard -v -x NC -n 13 -p ~/src/district-analytics/data/SAMPLE-BG-map.csv -d ~/src/district-analytics/data/SAMPLE-BG-data2.json -s ~/src/district-analytics/data/SAMPLE-BG-shapes.geojson -g ~/src/district-analytics/data/SAMPLE-BG-graph.json -c ~/src/district-analytics/data/SAMPLE-COUNTY.geojson
|
|
11166
|
-
|
|
11167
|
-
-or-
|
|
11168
|
-
|
|
11169
|
-
./main.js scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11170
|
-
./main.js --inspect --inspect-brk scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11171
|
-
|
|
11172
|
-
These calls work at the project directory, using samples in the data/ directory:
|
|
11173
|
-
|
|
11174
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11175
|
-
|
|
11176
|
-
TODO - Fix this
|
|
11177
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map-missing.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11178
|
-
./main.js scorecard -v -x NC-n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11179
|
-
|
|
11180
|
-
./main.js testlog -v -x NC -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11181
|
-
./main.js scorecard -v -x NC -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson
|
|
11182
|
-
|
|
11183
|
-
TODO - HERE
|
|
11184
|
-
|
|
11185
|
-
./main.js -v -x NC testlog -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson --empty
|
|
11186
|
-
./main.js -v -x NC scorecard -p ../data/SAMPLE-BG-map.csv -d ../data/SAMPLE-BG-data2.json -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -c ../data/SAMPLE-COUNTY.geojson --empty
|
|
11187
|
-
|
|
11188
|
-
TODO
|
|
11189
|
-
|
|
11190
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11191
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11192
|
-
|
|
11193
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11194
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11198
|
-
node main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
|
|
11202
|
-
|
|
11203
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11204
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11205
|
-
|
|
11206
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11207
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
*/
|
|
11212
|
-
/* TODO - DELETE
|
|
11213
|
-
|
|
11214
|
-
These calls work at the project directory, using samples in the data/ directory:
|
|
11215
|
-
|
|
11216
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11217
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11218
|
-
|
|
11219
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11220
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-missing.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11221
|
-
|
|
11222
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11223
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-unassigned.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11224
|
-
|
|
11225
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv --empty
|
|
11226
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv --empty
|
|
11227
|
-
|
|
11228
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11229
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-discontiguous.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11230
|
-
|
|
11231
|
-
./main.js testlog -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11232
|
-
./main.js scorecard -n 13 -p ../data/SAMPLE-BG-map-hole.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11236
|
-
node main.js scorecard -v -n 13 -p ../data/SAMPLE-BG-map.csv -c ../data/SAMPLE-BG-census.csv -s ../data/SAMPLE-BG-shapes.geojson -g ../data/SAMPLE-BG-graph.json -e ../data/SAMPLE-BG-election.csv -f ../data/SAMPLE-FIPS-county-name-map.csv
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
These calls test NC block-level data stored in my ~/data/redistricting-data/2010/compact/sample directory:
|
|
11240
|
-
|
|
11241
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11242
|
-
node --inspect --inspect-brk main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11243
|
-
|
|
11244
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-shapes.geojson -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11245
|
-
node main.js scorecard -v -n 13 -p ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-map.csv -c ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-census.csv -s ~/data/redistricting-data/2010/compact/sample/tl_2018_37_tabblock10.json -g ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-graph.json -e ~/data/redistricting-data/2010/compact/sample/SAMPLE-BLOCK-election.csv -f ~/data/redistricting-data/2010/compact/sample/SAMPLE-FIPS-county-name-map.csv
|
|
11246
|
-
|
|
11247
|
-
*/
|
|
11248
11329
|
|
|
11249
11330
|
|
|
11250
11331
|
/***/ }),
|
|
@@ -11269,11 +11350,17 @@ exports.PRECISION = 4;
|
|
|
11269
11350
|
exports.NORMALIZED_RANGE = 100;
|
|
11270
11351
|
// The dummy district ID for features not assigned districts yet
|
|
11271
11352
|
exports.NOT_ASSIGNED = 0;
|
|
11272
|
-
// TODO - Discuss w/ Dave
|
|
11353
|
+
// TODO - DASHBOARD: Discuss w/ Dave
|
|
11273
11354
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
11274
11355
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
11275
11356
|
// The virtual geoID for "neighbors" in other states
|
|
11276
11357
|
exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
11358
|
+
// "Roughly equal" = average census block size / 2
|
|
11359
|
+
const AVERAGE_BLOCK_SIZE = 30;
|
|
11360
|
+
exports.EQUAL_TOLERANCE = AVERAGE_BLOCK_SIZE / 2;
|
|
11361
|
+
// County & district splitting weights
|
|
11362
|
+
exports.COUNTY_SPLITTING_WEIGHT = 0.8;
|
|
11363
|
+
exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
|
|
11277
11364
|
|
|
11278
11365
|
|
|
11279
11366
|
/***/ }),
|
|
@@ -11290,8 +11377,15 @@ exports.OUT_OF_STATE = "OUT_OF_STATE";
|
|
|
11290
11377
|
//
|
|
11291
11378
|
// UTILITIES
|
|
11292
11379
|
//
|
|
11380
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
11381
|
+
if (mod && mod.__esModule) return mod;
|
|
11382
|
+
var result = {};
|
|
11383
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
11384
|
+
result["default"] = mod;
|
|
11385
|
+
return result;
|
|
11386
|
+
};
|
|
11293
11387
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11294
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
11388
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
11295
11389
|
// PLAN HELPERS
|
|
11296
11390
|
// Is a "neighbor" in state?
|
|
11297
11391
|
function isInState(geoID) {
|
|
@@ -11303,6 +11397,7 @@ function isOutOfState(geoID) {
|
|
|
11303
11397
|
return geoID == S.OUT_OF_STATE;
|
|
11304
11398
|
}
|
|
11305
11399
|
exports.isOutOfState = isOutOfState;
|
|
11400
|
+
// NOTE - UNASSIGNED
|
|
11306
11401
|
// Get the districtID to which a geoID is assigned
|
|
11307
11402
|
function getDistrict(plan, geoID) {
|
|
11308
11403
|
// All geoIDs in a state *should be* assigned to a district (including the
|
|
@@ -11316,24 +11411,6 @@ function getDistrict(plan, geoID) {
|
|
|
11316
11411
|
}
|
|
11317
11412
|
}
|
|
11318
11413
|
exports.getDistrict = getDistrict;
|
|
11319
|
-
// Invert a feature assignment structure to sets of ids by district
|
|
11320
|
-
// NOTE - This is here vs. _data.ts, so it can also be used in cli.ts
|
|
11321
|
-
function invertPlan(plan) {
|
|
11322
|
-
let invertedPlan = {};
|
|
11323
|
-
// Add a dummy 'unassigned' district
|
|
11324
|
-
invertedPlan[S.NOT_ASSIGNED] = new Set();
|
|
11325
|
-
for (let geoID in plan) {
|
|
11326
|
-
let districtID = plan[geoID];
|
|
11327
|
-
// Make sure the set for the districtID exists
|
|
11328
|
-
if (!(objectContains(invertedPlan, districtID))) {
|
|
11329
|
-
invertedPlan[districtID] = new Set();
|
|
11330
|
-
}
|
|
11331
|
-
// Add the geoID to the districtID's set
|
|
11332
|
-
invertedPlan[districtID].add(geoID);
|
|
11333
|
-
}
|
|
11334
|
-
return invertedPlan;
|
|
11335
|
-
}
|
|
11336
|
-
exports.invertPlan = invertPlan;
|
|
11337
11414
|
// WORKING WITH GEOIDS
|
|
11338
11415
|
function parseGeoID(geoID) {
|
|
11339
11416
|
let parts = {};
|
|
@@ -11356,27 +11433,46 @@ function getFIPSFromCountyGeoID(geoID) {
|
|
|
11356
11433
|
return geoID.substring(2, 5);
|
|
11357
11434
|
}
|
|
11358
11435
|
exports.getFIPSFromCountyGeoID = getFIPSFromCountyGeoID;
|
|
11436
|
+
function isWaterOnly(geoID) {
|
|
11437
|
+
let waterOnlySignature = 'ZZZZZZ';
|
|
11438
|
+
if (geoID.indexOf(waterOnlySignature) >= 0)
|
|
11439
|
+
return true;
|
|
11440
|
+
else
|
|
11441
|
+
return false;
|
|
11442
|
+
}
|
|
11443
|
+
exports.isWaterOnly = isWaterOnly;
|
|
11359
11444
|
// NORMALIZING RESULTS
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
if
|
|
11445
|
+
function normalize(rawScore, testScale) {
|
|
11446
|
+
let rangeMin = testScale.scale[0];
|
|
11447
|
+
let rangeMax = testScale.scale[1];
|
|
11448
|
+
// Invert the raw value if necessary to make bigger = better
|
|
11449
|
+
// TODO - This works for Population Deviation, because the max is 1.0.
|
|
11450
|
+
// Generalize this???
|
|
11451
|
+
if (testScale.bInvertRaw) {
|
|
11364
11452
|
rawScore = 1.0 - rawScore;
|
|
11365
11453
|
}
|
|
11366
11454
|
// Coerce the value to be w/in the given range
|
|
11367
|
-
let rangeMin = scale[0];
|
|
11368
|
-
let rangeMax = scale[1];
|
|
11369
11455
|
let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
|
|
11370
11456
|
// Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
|
|
11371
11457
|
let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
|
|
11458
|
+
// NOTE - SPLITTING
|
|
11459
|
+
// Invert the scaled value if necessary to make bigger = better
|
|
11460
|
+
if (testScale.bInvertScaled) {
|
|
11461
|
+
scaledValue = 1.0 - scaledValue;
|
|
11462
|
+
}
|
|
11372
11463
|
// Finally, make the range [0-100]
|
|
11373
11464
|
return Math.round(scaledValue * S.NORMALIZED_RANGE);
|
|
11374
11465
|
}
|
|
11375
11466
|
exports.normalize = normalize;
|
|
11376
11467
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
11377
|
-
function trim(fullFraction) {
|
|
11378
|
-
|
|
11379
|
-
|
|
11468
|
+
function trim(fullFraction, digits = undefined) {
|
|
11469
|
+
if (digits == 0) {
|
|
11470
|
+
return Math.round(fullFraction);
|
|
11471
|
+
}
|
|
11472
|
+
else {
|
|
11473
|
+
let shiftPlaces = Math.pow(10, (digits || S.PRECISION));
|
|
11474
|
+
return Math.round(fullFraction * shiftPlaces) / shiftPlaces;
|
|
11475
|
+
}
|
|
11380
11476
|
}
|
|
11381
11477
|
exports.trim = trim;
|
|
11382
11478
|
// ARRAY HELPERS
|
|
@@ -11405,13 +11501,14 @@ function andArray(arr) {
|
|
|
11405
11501
|
}
|
|
11406
11502
|
exports.andArray = andArray;
|
|
11407
11503
|
// WORKING WITH OBJECT KEYS/PROPERTIES
|
|
11408
|
-
// TODO -
|
|
11504
|
+
// TODO - TERRY, is this copesetic?
|
|
11505
|
+
// TODO - Handle integer keys?
|
|
11409
11506
|
// Does an object have a key/property?
|
|
11410
11507
|
function keyExists(k, o) {
|
|
11411
11508
|
return k in o;
|
|
11412
11509
|
}
|
|
11413
11510
|
exports.keyExists = keyExists;
|
|
11414
|
-
// TODO -
|
|
11511
|
+
// TODO - TERRY, can these three be combined into a generic isEmpty() check?
|
|
11415
11512
|
// Does an object (dict) have any keys/properties?
|
|
11416
11513
|
function isObjectEmpty(o) {
|
|
11417
11514
|
return Object.keys(o).length === 0;
|
|
@@ -11505,7 +11602,12 @@ function deepCopy(src) {
|
|
|
11505
11602
|
return src;
|
|
11506
11603
|
}
|
|
11507
11604
|
exports.deepCopy = deepCopy;
|
|
11508
|
-
// TODO -
|
|
11605
|
+
// TODO - TRI-STATES: Map booleans to tri-states.
|
|
11606
|
+
function mapBooleanToTriState(bool) {
|
|
11607
|
+
return (bool) ? 0 /* Green */ : 2 /* Red */;
|
|
11608
|
+
}
|
|
11609
|
+
exports.mapBooleanToTriState = mapBooleanToTriState;
|
|
11610
|
+
// TODO - TERRY: What is this the simple explanation of what this thing is doing?
|
|
11509
11611
|
var util_1 = __webpack_require__(/*! @dra2020/util */ "./node_modules/@dra2020/util/dist/util.js");
|
|
11510
11612
|
exports.depthof = util_1.depthof;
|
|
11511
11613
|
|
|
@@ -11524,40 +11626,54 @@ exports.depthof = util_1.depthof;
|
|
|
11524
11626
|
//
|
|
11525
11627
|
// MAP/PLAN VALIDATIONS
|
|
11526
11628
|
//
|
|
11629
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
11630
|
+
if (mod && mod.__esModule) return mod;
|
|
11631
|
+
var result = {};
|
|
11632
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
11633
|
+
result["default"] = mod;
|
|
11634
|
+
return result;
|
|
11635
|
+
};
|
|
11527
11636
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11528
|
-
const U = __webpack_require__(/*! ./utils */ "./src/utils.ts");
|
|
11529
|
-
const S = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
11530
|
-
const D = __webpack_require__(/*! ./_data */ "./src/_data.ts");
|
|
11637
|
+
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
11638
|
+
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
11639
|
+
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
11531
11640
|
//
|
|
11532
11641
|
// COMPLETE - Are all geo's assigned to a district, and do all districts have
|
|
11533
11642
|
// at least one geo assigned to them?
|
|
11534
11643
|
//
|
|
11535
|
-
function doIsComplete(s) {
|
|
11644
|
+
function doIsComplete(s, bLog = false) {
|
|
11536
11645
|
let test = s.getTest(0 /* Complete */);
|
|
11537
|
-
|
|
11538
|
-
let bNoneEmpty = true;
|
|
11539
|
-
let unassignedFeatures = [];
|
|
11540
|
-
let emptyDistricts = [];
|
|
11541
|
-
// Get the by district results, including the dummy unassigned district,
|
|
11646
|
+
// Get the by-district results, including the dummy unassigned district,
|
|
11542
11647
|
// but ignoring the N+1 summary district
|
|
11543
11648
|
let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
|
|
11544
11649
|
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
|
|
11545
11650
|
// Are all features assigned to districts?
|
|
11546
11651
|
// Check the dummy district that holds any unassigned features.
|
|
11547
|
-
|
|
11652
|
+
let unassignedFeatures = [];
|
|
11653
|
+
let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
|
|
11548
11654
|
if (!bAllAssigned) {
|
|
11549
11655
|
let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
|
|
11550
11656
|
unassignedFeatures = Array.from(unassignedDistrict);
|
|
11551
11657
|
unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
|
|
11552
11658
|
}
|
|
11553
11659
|
// Do all real districts have at least one feature assigned to them?
|
|
11554
|
-
|
|
11660
|
+
let emptyDistricts = [];
|
|
11661
|
+
let bNoneEmpty = true;
|
|
11662
|
+
bNotEmptyByDistrict = bNotEmptyByDistrict.slice(1);
|
|
11663
|
+
let districtID = 1;
|
|
11664
|
+
bNotEmptyByDistrict.forEach(function (bNotEmpty) {
|
|
11665
|
+
if (!bNotEmpty) {
|
|
11666
|
+
bNoneEmpty = false;
|
|
11667
|
+
emptyDistricts.push(districtID);
|
|
11668
|
+
}
|
|
11669
|
+
districtID += 1;
|
|
11670
|
+
});
|
|
11555
11671
|
// Case 1 - One or more districts are missing:
|
|
11556
11672
|
// The # of enumerated districts minus the dummy NOT_ASSIGNED one should
|
|
11557
11673
|
// equal the number apportioned districts. This guards against a district
|
|
11558
11674
|
// not being included in a map that is imported.
|
|
11559
11675
|
//
|
|
11560
|
-
//
|
|
11676
|
+
// NOTE - I'm no longer checking for this, but DRA should!
|
|
11561
11677
|
// Case 2 - Or a district is explicitly named but empty:
|
|
11562
11678
|
// Note, this can happen if a district is created, and then all features
|
|
11563
11679
|
// are removed from it (in DRA).
|
|
@@ -11584,7 +11700,7 @@ exports.doIsComplete = doIsComplete;
|
|
|
11584
11700
|
//
|
|
11585
11701
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
|
|
11586
11702
|
//
|
|
11587
|
-
function doIsContiguous(s) {
|
|
11703
|
+
function doIsContiguous(s, bLog = false) {
|
|
11588
11704
|
let test = s.getTest(1 /* Contiguous */);
|
|
11589
11705
|
// Get the contiguity of each district. Ignore dummy unassigned district
|
|
11590
11706
|
// and the N+1 summary district.
|
|
@@ -11614,8 +11730,7 @@ function doIsContiguous(s) {
|
|
|
11614
11730
|
exports.doIsContiguous = doIsContiguous;
|
|
11615
11731
|
// Are the features in a district fully connected?
|
|
11616
11732
|
function isConnected(districtGeos, graph) {
|
|
11617
|
-
//
|
|
11618
|
-
// TODO - Terry, why does this constructor need a <T> type specification?
|
|
11733
|
+
// TODO - TERRY, why does this constructor need a <T> type specification?
|
|
11619
11734
|
let visited = new Set();
|
|
11620
11735
|
let toProcess = [];
|
|
11621
11736
|
// Start processing with the first geoID in the district
|
|
@@ -11630,7 +11745,7 @@ function isConnected(districtGeos, graph) {
|
|
|
11630
11745
|
let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
|
|
11631
11746
|
// Add neighbors to visit, if they're in the same district Y haven't already been visited
|
|
11632
11747
|
let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
|
|
11633
|
-
// TODO -
|
|
11748
|
+
// TODO - TERRY, is this the quickest/best way to do this?
|
|
11634
11749
|
toProcess.push(...neighborsToVisit);
|
|
11635
11750
|
}
|
|
11636
11751
|
// Stop when you've visited all the geoIDs in the district
|
|
@@ -11647,10 +11762,10 @@ exports.isConnected = isConnected;
|
|
|
11647
11762
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
|
|
11648
11763
|
// Buncombe County (37021), is a donut hole w/in District 3.
|
|
11649
11764
|
//
|
|
11650
|
-
// TODO -
|
|
11765
|
+
// TODO - OPTIMIZE: This to take advantage of district boundary info, if/when
|
|
11651
11766
|
// we cache one to optimize compactness.
|
|
11652
11767
|
//
|
|
11653
|
-
function doIsFreeOfHoles(s) {
|
|
11768
|
+
function doIsFreeOfHoles(s, bLog = false) {
|
|
11654
11769
|
let test = s.getTest(2 /* FreeOfHoles */);
|
|
11655
11770
|
// Initialize values
|
|
11656
11771
|
let bFreeOfHoles = true;
|
|
@@ -11681,7 +11796,7 @@ function doIsFreeOfHoles(s) {
|
|
|
11681
11796
|
exports.doIsFreeOfHoles = doIsFreeOfHoles;
|
|
11682
11797
|
// Test whether one district is embedded w/in any other.
|
|
11683
11798
|
function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
11684
|
-
//
|
|
11799
|
+
// NOTE - "features" here = "geoIDs." These aren't "features" proper, just
|
|
11685
11800
|
// identifier strings.
|
|
11686
11801
|
let features = geoIDs;
|
|
11687
11802
|
let planByGeo = plan.byGeoID();
|
|
@@ -11689,7 +11804,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
11689
11804
|
let bEmbedded = true;
|
|
11690
11805
|
// Keep track of the neighoring districts
|
|
11691
11806
|
let neighboringDistricts = new Set();
|
|
11692
|
-
// TODO - Use just the boundary features, when available
|
|
11807
|
+
// TODO - OPTIMIZE: Use just the boundary features, when available
|
|
11693
11808
|
// Get the features for the real district
|
|
11694
11809
|
let featuresToCheck = Array.from(features);
|
|
11695
11810
|
// If the district has features, check whether it is embedded
|
|
@@ -11715,7 +11830,7 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
11715
11830
|
break;
|
|
11716
11831
|
}
|
|
11717
11832
|
else {
|
|
11718
|
-
// TODO - Since we're checking *all* features in a district right
|
|
11833
|
+
// TODO - OPTIMIZE: Since we're checking *all* features in a district right
|
|
11719
11834
|
// now, not just boundary features and neighbors in other districts,
|
|
11720
11835
|
// prune out the current district. If/when we optimize compactness
|
|
11721
11836
|
// to cache district boundaries (as before in my Python implementation),
|
|
@@ -11741,16 +11856,19 @@ function isEmbedded(districtID, geoIDs, plan, graph) {
|
|
|
11741
11856
|
return bEmbedded;
|
|
11742
11857
|
}
|
|
11743
11858
|
exports.isEmbedded = isEmbedded;
|
|
11744
|
-
// TODO - MIXED MAPS
|
|
11745
|
-
// - When we generalize to mixed maps, the determination of "neighbors in
|
|
11746
|
-
// the map" -- which is the core function in determining connectedness -- becomes
|
|
11747
|
-
// *much* more complicated and dynamic.
|
|
11748
|
-
// - I can write up (if not implement) the logic for this. It is a bit tricky
|
|
11749
|
-
// and requires special preprocessing of the summary level hierarchy (which I
|
|
11750
|
-
// also have a script for that we can repurpose) to distinguish between 'interior'
|
|
11751
|
-
// and 'edge' children.
|
|
11752
11859
|
|
|
11753
11860
|
|
|
11861
|
+
/***/ }),
|
|
11862
|
+
|
|
11863
|
+
/***/ "./static/state-reqs.json":
|
|
11864
|
+
/*!********************************!*\
|
|
11865
|
+
!*** ./static/state-reqs.json ***!
|
|
11866
|
+
\********************************/
|
|
11867
|
+
/*! exports provided: AL, AK, AZ, AR, CA, CO, CT, DE, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, default */
|
|
11868
|
+
/***/ (function(module) {
|
|
11869
|
+
|
|
11870
|
+
module.exports = JSON.parse("{\"AL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_01.pdf\",\"AK\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_02.pdf\",\"AZ\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_03.pdf\",\"AR\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_04.pdf\",\"CA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_05.pdf\",\"CO\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_06.pdf\",\"CT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_07.pdf\",\"DE\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_08.pdf\",\"FL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_09.pdf\",\"GA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_10.pdf\",\"HI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_11.pdf\",\"ID\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_12.pdf\",\"IL\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_13.pdf\",\"IN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_14.pdf\",\"IA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_15.pdf\",\"KS\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_16.pdf\",\"KY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_17.pdf\",\"LA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_18.pdf\",\"ME\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_19.pdf\",\"MD\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf\",\"MA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_21.pdf\",\"MI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_22.pdf\",\"MN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_23.pdf\",\"MS\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_24.pdf\",\"MO\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_25.pdf\",\"MT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_26.pdf\",\"NE\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_27.pdf\",\"NV\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_28.pdf\",\"NH\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_29.pdf\",\"NJ\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_30.pdf\",\"NM\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_31.pdf\",\"NY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_32.pdf\",\"NC\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_33.pdf\",\"ND\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_34.pdf\",\"OH\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_35.pdf\",\"OK\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_36.pdf\",\"OR\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_37.pdf\",\"PA\":\"https://www.brennancenter.org/sites/default/files/publications/2018_05_50States_FINALsinglepages_38.pdf\",\"RI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_39.pdf\",\"SC\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_40.pdf\",\"SD\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_41.pdf\",\"TN\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_42.pdf\",\"TX\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_43.pdf\",\"UT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_44.pdf\",\"VT\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_45.pdf\",\"VA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_46.pdf\",\"WA\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_47.pdf\",\"WV\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_48.pdf\",\"WI\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_49.pdf\",\"WY\":\"https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_50.pdf\"}");
|
|
11871
|
+
|
|
11754
11872
|
/***/ }),
|
|
11755
11873
|
|
|
11756
11874
|
/***/ "./test/_cli.ts":
|
|
@@ -11761,35 +11879,105 @@ exports.isEmbedded = isEmbedded;
|
|
|
11761
11879
|
/***/ (function(module, exports, __webpack_require__) {
|
|
11762
11880
|
|
|
11763
11881
|
"use strict";
|
|
11764
|
-
|
|
11882
|
+
|
|
11765
11883
|
//
|
|
11766
11884
|
// A SIMPLE COMMAND-LINE INTERFACE FOR EXERCISING THE ANALYTICS & VALIDATIONS
|
|
11767
11885
|
//
|
|
11886
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11887
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11888
|
+
};
|
|
11889
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
11890
|
+
if (mod && mod.__esModule) return mod;
|
|
11891
|
+
var result = {};
|
|
11892
|
+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
11893
|
+
result["default"] = mod;
|
|
11894
|
+
return result;
|
|
11895
|
+
};
|
|
11768
11896
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11897
|
+
/*
|
|
11898
|
+
|
|
11899
|
+
CONFIGURATIONS
|
|
11900
|
+
|
|
11901
|
+
These configurations implement various CLI tests (using the commands in parens)
|
|
11902
|
+
w/in VS Code:
|
|
11903
|
+
|
|
11904
|
+
* api-analyze (analyze) - Analyzes a plan and logs the results.
|
|
11905
|
+
|
|
11906
|
+
* unit-valid (valid) - Exercises the validations.
|
|
11907
|
+
* unit-equal (equal) - Exercises the population deviation analytic.
|
|
11908
|
+
* unit-compact (compact) - Exercises the compactness analytics.
|
|
11909
|
+
* unit-cohesive (cohesive) - Exercises the splitting analytics.
|
|
11910
|
+
* unit-political (political) - Exercises the partisan analytics.
|
|
11911
|
+
* unit-minority (minority) - Exercises the minority analytics.
|
|
11912
|
+
|
|
11913
|
+
* invalid-unassigned (valid) - Tests a plan w/ unassigned features.
|
|
11914
|
+
* invalid-missing (valid) - Tests a plan w/ a missing district.
|
|
11915
|
+
* invalid-empty (valid) - Tests a plan w/ an empty district.
|
|
11916
|
+
* invalid-discontiguous (valid) - Tests a plan w/ a discontiguous district.
|
|
11917
|
+
* invalid-hole (valid) - Tests a plan w/ an embedded district.
|
|
11918
|
+
|
|
11919
|
+
|
|
11920
|
+
COMMANDS
|
|
11921
|
+
|
|
11922
|
+
Tests can also be exercised from the commmand line from the project directory.
|
|
11923
|
+
This is a sample. Use "node --inspect --inspect-brk main.js ..." to attach a debugger.
|
|
11924
|
+
|
|
11925
|
+
1 - Plain vanilla run:
|
|
11926
|
+
./main.js analyze -x NC -n 13 -p data/SAMPLE-BG-map.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
|
|
11927
|
+
|
|
11928
|
+
2 - Missing district:
|
|
11929
|
+
./main.js analyze -x NC -n 13 -p data/SAMPLE-BG-map-missing.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
|
|
11930
|
+
|
|
11931
|
+
3 - Empty district:
|
|
11932
|
+
./main.js valid -x NC -n 13 -p data/SAMPLE-BG-map.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson --empty
|
|
11933
|
+
|
|
11934
|
+
4 - Unassigned features:
|
|
11935
|
+
./main.js analyze -x NC -n 13 -p data/SAMPLE-BG-map-unassigned.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
|
|
11936
|
+
|
|
11937
|
+
5 - Discontiguous districts:
|
|
11938
|
+
./main.js analyze -n 13 -p data/SAMPLE-BG-map-discontiguous.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
|
|
11939
|
+
|
|
11940
|
+
6 - Embedded district:
|
|
11941
|
+
./main.js analyze -n 13 -p data/SAMPLE-BG-map-hole.csv -d data/SAMPLE-BG-data2.json -s data/SAMPLE-BG-shapes.geojson -g data/SAMPLE-BG-graph.json -c data/SAMPLE-COUNTY.geojson
|
|
11942
|
+
|
|
11943
|
+
7 - Test using NC block-level data <<< TODO - Need BLOCK-data2.json & COUNTY.geojson to run this again.
|
|
11944
|
+
|
|
11945
|
+
cd ~/Documents/Professional/Projects/Redistricting/DRA2020/Analytics/compact/classic/ts_sample
|
|
11946
|
+
./main.js analyze -v -n 13 -p SAMPLE-BLOCK-map.csv -d SAMPLE-BLOCK-data2.json -s SAMPLE-BLOCK-shapes.geojson -g SAMPLE-BLOCK-graph.json -c SAMPLE-COUNTY.geojson
|
|
11947
|
+
|
|
11948
|
+
*/
|
|
11949
|
+
const yargs_1 = __importDefault(__webpack_require__(/*! yargs */ "yargs"));
|
|
11950
|
+
// TODO - Fix this import, so I don't have to do the 'union' workaround below.
|
|
11951
|
+
const PC = __importStar(__webpack_require__(/*! polygon-clipping */ "./node_modules/polygon-clipping/dist/polygon-clipping.esm.js"));
|
|
11952
|
+
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "./node_modules/@dra2020/poly/dist/poly.js"));
|
|
11953
|
+
const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
|
|
11954
|
+
const path = __importStar(__webpack_require__(/*! path */ "path"));
|
|
11955
|
+
const sync_1 = __importDefault(__webpack_require__(/*! csv-parse/lib/sync */ "./node_modules/csv-parse/lib/sync.js"));
|
|
11775
11956
|
const _api_1 = __webpack_require__(/*! ../src/_api */ "./src/_api.ts");
|
|
11776
11957
|
const preprocess_1 = __webpack_require__(/*! ../src/preprocess */ "./src/preprocess.ts");
|
|
11777
11958
|
const analyze_1 = __webpack_require__(/*! ../src/analyze */ "./src/analyze.ts");
|
|
11959
|
+
const results_1 = __webpack_require__(/*! ../src/results */ "./src/results.ts");
|
|
11778
11960
|
const valid_1 = __webpack_require__(/*! ../src/valid */ "./src/valid.ts");
|
|
11779
11961
|
const equal_1 = __webpack_require__(/*! ../src/equal */ "./src/equal.ts");
|
|
11780
11962
|
const compact_1 = __webpack_require__(/*! ../src/compact */ "./src/compact.ts");
|
|
11781
11963
|
const cohesive_1 = __webpack_require__(/*! ../src/cohesive */ "./src/cohesive.ts");
|
|
11782
11964
|
const political_1 = __webpack_require__(/*! ../src/political */ "./src/political.ts");
|
|
11783
11965
|
const minority_1 = __webpack_require__(/*! ../src/minority */ "./src/minority.ts");
|
|
11784
|
-
const U = __webpack_require__(/*! ../src/utils */ "./src/utils.ts");
|
|
11785
|
-
const S = __webpack_require__(/*! ../src/settings */ "./src/settings.ts");
|
|
11966
|
+
const U = __importStar(__webpack_require__(/*! ../src/utils */ "./src/utils.ts"));
|
|
11967
|
+
const S = __importStar(__webpack_require__(/*! ../src/settings */ "./src/settings.ts"));
|
|
11968
|
+
const D = __importStar(__webpack_require__(/*! ../src/_data */ "./src/_data.ts"));
|
|
11786
11969
|
// Simulate DRA unioning district shapes in the background
|
|
11787
11970
|
function addToPoly(poly, polys) {
|
|
11788
|
-
|
|
11971
|
+
// TODO - POLY: Fix 'poly' import, so I don't have to do this workaround.
|
|
11972
|
+
// return PC.union(poly, ...polys);
|
|
11973
|
+
let union = PC.union;
|
|
11974
|
+
if (union === undefined)
|
|
11975
|
+
union = PC.default.union;
|
|
11976
|
+
return union(poly, ...polys);
|
|
11789
11977
|
}
|
|
11790
11978
|
console.log("Starting command @ ", new Date());
|
|
11791
11979
|
// COMMAND LINE
|
|
11792
|
-
let argv =
|
|
11980
|
+
let argv = yargs_1.default
|
|
11793
11981
|
.usage('Usage: $0 command [options]')
|
|
11794
11982
|
.example('$0 equal -f foo.geojson', 'Calculate population deviation')
|
|
11795
11983
|
.demandCommand(1, 'You must specify a command to execute.')
|
|
@@ -11869,34 +12057,37 @@ let xx = argv.state;
|
|
|
11869
12057
|
let bLegislativeDistricts = argv.legislative;
|
|
11870
12058
|
let nDistricts = argv.n;
|
|
11871
12059
|
let planByGeoID = readPlanCSV(argv.plan);
|
|
11872
|
-
let graph =
|
|
11873
|
-
let data =
|
|
11874
|
-
let counties =
|
|
11875
|
-
// Don't load other datasets, if just calculating compactness
|
|
11876
|
-
if (command != 'compact') {
|
|
11877
|
-
graph = readJSON(argv.graph);
|
|
11878
|
-
data = readJSON(argv.data);
|
|
11879
|
-
counties = readJSON(argv.counties);
|
|
11880
|
-
}
|
|
12060
|
+
let graph = readJSON(argv.graph);
|
|
12061
|
+
let data = readJSON(argv.data);
|
|
12062
|
+
let counties = readJSON(argv.counties);
|
|
11881
12063
|
let shapes = readJSON(argv.shapes);
|
|
11882
12064
|
let bEmpty = argv.empty;
|
|
11883
12065
|
let suites = argv.suites;
|
|
11884
12066
|
// Some default dataset keys
|
|
11885
12067
|
let datasetKeys = {
|
|
12068
|
+
SHAPES: "2010_VD",
|
|
11886
12069
|
CENSUS: "D16F",
|
|
11887
12070
|
VAP: "D16T",
|
|
11888
12071
|
ELECTION: "E16GPR"
|
|
11889
12072
|
};
|
|
12073
|
+
let datasetDescriptions = {
|
|
12074
|
+
SHAPES: "2010 Voting Districts",
|
|
12075
|
+
CENSUS: "2016 ACS Total Population",
|
|
12076
|
+
VAP: "2016 ACS Voting Age Population",
|
|
12077
|
+
ELECTION: "2016 Presidential Election",
|
|
12078
|
+
};
|
|
11890
12079
|
// Session settings are required:
|
|
11891
12080
|
// - Analytics suites can be defaulted to all with [], but
|
|
11892
|
-
// -
|
|
12081
|
+
// - Datasets must be explicitly specified
|
|
11893
12082
|
let sessionSettings = {
|
|
11894
12083
|
'suites': suites,
|
|
11895
|
-
'datasets': datasetKeys
|
|
12084
|
+
'datasets': datasetKeys,
|
|
12085
|
+
'descriptions': datasetDescriptions
|
|
11896
12086
|
};
|
|
11897
12087
|
let bLog = argv.verbose;
|
|
11898
12088
|
// Invert the plan, so you can create the district shapes for the SessionRequest.
|
|
11899
|
-
|
|
12089
|
+
// NOTE - The plan here is complete (all features assigned).
|
|
12090
|
+
let planByDistrictID = D.invertPlan(planByGeoID);
|
|
11900
12091
|
// SIMULATE THE HOST
|
|
11901
12092
|
// 1 - Create district shapes & extract properties (area, diameter, perimeter)
|
|
11902
12093
|
const polyOptions = {
|
|
@@ -11957,30 +12148,33 @@ districtShapes['type'] = "FeatureCollection";
|
|
|
11957
12148
|
districtShapes['features'] = [];
|
|
11958
12149
|
for (let districtID = 1; districtID <= nDistricts; districtID++) {
|
|
11959
12150
|
let districtFeatures = [];
|
|
11960
|
-
//
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
bPolygon =
|
|
11972
|
-
poly
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
12151
|
+
// If the district is not empty, collect its Features
|
|
12152
|
+
// TODO - Should we generate degenerate shape, if a district is empty?
|
|
12153
|
+
if (planByDistrictID[districtID]) {
|
|
12154
|
+
planByDistrictID[districtID].forEach(function (geoID) {
|
|
12155
|
+
districtFeatures.push(shapesByGeoID[geoID]);
|
|
12156
|
+
});
|
|
12157
|
+
// Union them together
|
|
12158
|
+
let poly1Coordinates = Poly.polyNormalize(districtFeatures[0], polyOptions);
|
|
12159
|
+
let polyNCoordinates = districtFeatures.slice(1).map((i) => Poly.polyNormalize(i, polyOptions));
|
|
12160
|
+
let poly = addToPoly(poly1Coordinates, polyNCoordinates);
|
|
12161
|
+
// Convert the result to a Feature
|
|
12162
|
+
let bPolygon = U.depthof(poly) == 4;
|
|
12163
|
+
if (!bPolygon && poly.length == 1) {
|
|
12164
|
+
bPolygon = true;
|
|
12165
|
+
poly = poly[0];
|
|
12166
|
+
}
|
|
12167
|
+
eliminateAnomalousHoles(poly);
|
|
12168
|
+
let f = {
|
|
12169
|
+
type: 'Feature',
|
|
12170
|
+
properties: { districtID: `${districtID}` },
|
|
12171
|
+
geometry: {
|
|
12172
|
+
type: bPolygon ? 'Polygon' : 'MultiPolygon',
|
|
12173
|
+
coordinates: poly
|
|
12174
|
+
}
|
|
12175
|
+
};
|
|
12176
|
+
districtShapes.features.push(f);
|
|
12177
|
+
} // End non-empty districts
|
|
11984
12178
|
}
|
|
11985
12179
|
// CONSTRUCT A SESSION REQUEST
|
|
11986
12180
|
let SessionRequest = {};
|
|
@@ -11998,21 +12192,7 @@ SessionRequest['config'] = sessionSettings;
|
|
|
11998
12192
|
let s = new _api_1.AnalyticsSession(SessionRequest);
|
|
11999
12193
|
// DISPATCH TO REQUESTED COMMAND W/ NEEDED SCAFFOLDING
|
|
12000
12194
|
let t;
|
|
12001
|
-
let text;
|
|
12002
|
-
function echoTestResult(test, t) {
|
|
12003
|
-
console.log("");
|
|
12004
|
-
console.log("Test:", test);
|
|
12005
|
-
console.log("Score:", t['score']);
|
|
12006
|
-
let keys = U.getObjectKeys(t['details']);
|
|
12007
|
-
if (keys.length > 0) {
|
|
12008
|
-
console.log("Details:");
|
|
12009
|
-
for (let i in keys) {
|
|
12010
|
-
let key = keys[i];
|
|
12011
|
-
console.log("-", key, "=", t['details'][key]);
|
|
12012
|
-
}
|
|
12013
|
-
}
|
|
12014
|
-
console.log("___");
|
|
12015
|
-
}
|
|
12195
|
+
// let text: any;
|
|
12016
12196
|
switch (command) {
|
|
12017
12197
|
case 'valid': {
|
|
12018
12198
|
preprocess_1.doPreprocessData(s);
|
|
@@ -12024,16 +12204,17 @@ switch (command) {
|
|
|
12024
12204
|
s.plan.initializeDistrict(randomDistrict);
|
|
12025
12205
|
}
|
|
12026
12206
|
analyze_1.doAnalyzeDistricts(s);
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
echoTestResult("
|
|
12035
|
-
|
|
12036
|
-
echoTestResult("
|
|
12207
|
+
let t1 = valid_1.doIsComplete(s);
|
|
12208
|
+
let t2 = valid_1.doIsContiguous(s);
|
|
12209
|
+
let t3 = valid_1.doIsFreeOfHoles(s);
|
|
12210
|
+
let t4 = equal_1.doPopulationDeviation(s);
|
|
12211
|
+
let t5 = equal_1.doHasEqualPopulations(s);
|
|
12212
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12213
|
+
echoTestResult("Complete:", t1);
|
|
12214
|
+
echoTestResult("Contiguous:", t2);
|
|
12215
|
+
echoTestResult("Free of holes:", t3);
|
|
12216
|
+
echoTestResult("Population deviation (%):", t4);
|
|
12217
|
+
echoTestResult("Equal populations:", t5);
|
|
12037
12218
|
break;
|
|
12038
12219
|
}
|
|
12039
12220
|
case 'equal': {
|
|
@@ -12041,98 +12222,250 @@ switch (command) {
|
|
|
12041
12222
|
analyze_1.doAnalyzeDistricts(s);
|
|
12042
12223
|
t = equal_1.doPopulationDeviation(s);
|
|
12043
12224
|
echoTestResult("Population deviation (%):", t);
|
|
12225
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12044
12226
|
break;
|
|
12045
12227
|
}
|
|
12046
12228
|
case 'compact': {
|
|
12047
12229
|
preprocess_1.doPreprocessData(s);
|
|
12048
12230
|
analyze_1.doAnalyzeDistricts(s);
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
echoTestResult("
|
|
12231
|
+
let t1 = compact_1.doReock(s, bLog);
|
|
12232
|
+
let t2 = compact_1.doPolsbyPopper(s, bLog);
|
|
12233
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12234
|
+
echoTestResult("Reock:", t1);
|
|
12235
|
+
echoTestResult("Polsby-Popper:", t2);
|
|
12053
12236
|
break;
|
|
12054
12237
|
}
|
|
12055
12238
|
case 'cohesive': {
|
|
12056
12239
|
preprocess_1.doPreprocessData(s);
|
|
12057
12240
|
analyze_1.doAnalyzeDistricts(s);
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
cohesive_1.
|
|
12241
|
+
// NOTE - SPLITTING
|
|
12242
|
+
let t1 = cohesive_1.doFindCountiesSplitUnexpectedly(s);
|
|
12243
|
+
let t2 = cohesive_1.doFindSplitVTDs(s);
|
|
12244
|
+
let t3 = cohesive_1.doCountySplitting(s);
|
|
12245
|
+
let t4 = cohesive_1.doDistrictSplitting(s);
|
|
12246
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12247
|
+
echoTestResult("Counties split unexpectedly:", t1);
|
|
12248
|
+
echoTestResult("Split VTDs:", t2);
|
|
12249
|
+
echoTestResult("County splitting:", t3);
|
|
12250
|
+
echoTestResult("District splitting:", t4);
|
|
12061
12251
|
break;
|
|
12062
12252
|
}
|
|
12063
12253
|
case 'political': {
|
|
12064
12254
|
preprocess_1.doPreprocessData(s);
|
|
12065
12255
|
analyze_1.doAnalyzeDistricts(s);
|
|
12066
|
-
political_1.doSeatsBias(s);
|
|
12067
|
-
political_1.doVotesBias(s);
|
|
12068
|
-
political_1.doResponsiveness(s);
|
|
12069
|
-
political_1.doResponsiveDistricts(s);
|
|
12070
|
-
|
|
12071
|
-
|
|
12256
|
+
let t1 = political_1.doSeatsBias(s);
|
|
12257
|
+
let t2 = political_1.doVotesBias(s);
|
|
12258
|
+
let t3 = political_1.doResponsiveness(s);
|
|
12259
|
+
let t4 = political_1.doResponsiveDistricts(s);
|
|
12260
|
+
let t5 = political_1.doEfficiencyGap(s);
|
|
12261
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12262
|
+
// echoTestResult("Seats Bias:", t1); TODO
|
|
12263
|
+
// echoTestResult("Votes Bias:", t2); TODO
|
|
12264
|
+
// echoTestResult("Responsiveness:", t3); TODO
|
|
12265
|
+
// echoTestResult("Responsive Districts:", t4); TODO
|
|
12266
|
+
echoTestResult("Efficiency gap (%):", t5);
|
|
12072
12267
|
break;
|
|
12073
12268
|
}
|
|
12074
12269
|
case 'minority': {
|
|
12075
12270
|
preprocess_1.doPreprocessData(s);
|
|
12076
12271
|
analyze_1.doAnalyzeDistricts(s);
|
|
12077
|
-
minority_1.doMajorityMinorityDistricts(s);
|
|
12272
|
+
let t1 = minority_1.doMajorityMinorityDistricts(s);
|
|
12273
|
+
results_1.doAnalyzePostProcessing(s);
|
|
12274
|
+
// echoTestResult("Majority-Minority Districts:", t1); TODO
|
|
12078
12275
|
break;
|
|
12079
12276
|
}
|
|
12277
|
+
// TODO - DASHBOARD: Print the structures out
|
|
12080
12278
|
case 'analyze': {
|
|
12081
|
-
console.log("");
|
|
12082
|
-
console.log("Analyzing a plan ...");
|
|
12083
|
-
console.log("");
|
|
12084
12279
|
s.analyzePlan(bLog);
|
|
12280
|
+
let planAnalytics = s.getPlanAnalytics(bLog);
|
|
12281
|
+
let districtStatistics = s.getDistrictStatistics(bLog);
|
|
12282
|
+
echoPlanAnalytics(planAnalytics);
|
|
12283
|
+
echoDistrictStatistics(districtStatistics);
|
|
12085
12284
|
break;
|
|
12086
12285
|
}
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
default:
|
|
12101
|
-
console.log(line['variant'], line['text']);
|
|
12102
|
-
break;
|
|
12103
|
-
}
|
|
12286
|
+
}
|
|
12287
|
+
console.log("Ending command @ ", new Date());
|
|
12288
|
+
// HELPER FUNCTIONS FOR ECHOING RESULTS
|
|
12289
|
+
function echoTestResult(test, t) {
|
|
12290
|
+
console.log("");
|
|
12291
|
+
console.log("Test:", test);
|
|
12292
|
+
console.log("Score:", t['score']);
|
|
12293
|
+
let keys = U.getObjectKeys(t['details']);
|
|
12294
|
+
if (keys.length > 0) {
|
|
12295
|
+
console.log("Details:");
|
|
12296
|
+
for (let i in keys) {
|
|
12297
|
+
let key = keys[i];
|
|
12298
|
+
console.log("-", key, "=", t['details'][key]);
|
|
12104
12299
|
}
|
|
12105
|
-
console.log("");
|
|
12106
|
-
break;
|
|
12107
12300
|
}
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12301
|
+
console.log("___");
|
|
12302
|
+
}
|
|
12303
|
+
// Prepare the items in a list for rendering
|
|
12304
|
+
function prepareListItems(list) {
|
|
12305
|
+
if (U.isArrayEmpty(list)) {
|
|
12306
|
+
return "";
|
|
12307
|
+
}
|
|
12308
|
+
else {
|
|
12309
|
+
let nItems = list.length;
|
|
12310
|
+
let listStr;
|
|
12311
|
+
switch (nItems) {
|
|
12312
|
+
case 1: {
|
|
12313
|
+
listStr = list[0];
|
|
12314
|
+
break;
|
|
12315
|
+
}
|
|
12316
|
+
case 2: {
|
|
12317
|
+
listStr = list[0] + " and " + list[1];
|
|
12318
|
+
break;
|
|
12319
|
+
}
|
|
12320
|
+
default: {
|
|
12321
|
+
let listWithCommas = list.join(', ');
|
|
12322
|
+
let lastCommaIndex = listWithCommas.length - ((list[list.length - 1].length) + 1);
|
|
12323
|
+
let beforeAnd = listWithCommas.substr(0, lastCommaIndex);
|
|
12324
|
+
let afterAnd = listWithCommas.substr(lastCommaIndex + 1);
|
|
12325
|
+
listStr = beforeAnd + " and " + afterAnd;
|
|
12326
|
+
break;
|
|
12327
|
+
}
|
|
12114
12328
|
}
|
|
12115
|
-
|
|
12116
|
-
break;
|
|
12329
|
+
return listStr;
|
|
12117
12330
|
}
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12331
|
+
}
|
|
12332
|
+
function formatNumber(n) {
|
|
12333
|
+
let p = S.PRECISION;
|
|
12334
|
+
return n.toLocaleString('en-US', { minimumFractionDigits: p, maximumFractionDigits: p });
|
|
12335
|
+
}
|
|
12336
|
+
function echoPlanAnalytics(pa) {
|
|
12337
|
+
console.log("Plan Analytics:");
|
|
12338
|
+
console.log("");
|
|
12339
|
+
console.log("Requirements:");
|
|
12340
|
+
console.log("* Score:", pa.requirements.score);
|
|
12341
|
+
console.log("* Metrics:");
|
|
12342
|
+
console.log(" - Complete:", pa.requirements.metrics.complete);
|
|
12343
|
+
console.log(" - Contiguous:", pa.requirements.metrics.contiguous);
|
|
12344
|
+
console.log(" - Free of holes:", pa.requirements.metrics.freeOfHoles);
|
|
12345
|
+
console.log(" - Equal population:", pa.requirements.metrics.equalPopulation);
|
|
12346
|
+
console.log("* Details:");
|
|
12347
|
+
console.log(" - Empty districts:", prepareListItems(pa.requirements.details.emptyDistricts));
|
|
12348
|
+
console.log(" - Unassigned features:", prepareListItems(pa.requirements.details.unassignedFeatures));
|
|
12349
|
+
console.log(" - Population deviation:", pa.requirements.details.populationDeviation);
|
|
12350
|
+
console.log(" - Deviation threshold:", pa.requirements.details.deviationThreshold);
|
|
12351
|
+
console.log(" - Discontiguous districts:", prepareListItems(pa.requirements.details.discontiguousDistricts));
|
|
12352
|
+
console.log(" - Embedded districts:", prepareListItems(pa.requirements.details.embeddedDistricts));
|
|
12353
|
+
console.log("* Datasets:");
|
|
12354
|
+
console.log("- Shapes:", pa.requirements.datasets.shapes);
|
|
12355
|
+
console.log("- Census:", pa.requirements.datasets.census);
|
|
12356
|
+
console.log("- VAP:", pa.requirements.datasets.vap);
|
|
12357
|
+
console.log("- Election:", pa.requirements.datasets.election);
|
|
12358
|
+
console.log("* Resources:");
|
|
12359
|
+
console.log("- State requirements:", pa.requirements.resources.stateReqs);
|
|
12360
|
+
console.log("");
|
|
12361
|
+
console.log("Compactness:");
|
|
12362
|
+
console.log("* Score:", pa.compactness.score);
|
|
12363
|
+
console.log("* Metrics:");
|
|
12364
|
+
console.log(" - Reock:", pa.compactness.metrics.reock);
|
|
12365
|
+
console.log(" - Polsby-Popper:", pa.compactness.metrics.polsby);
|
|
12366
|
+
console.log("* Details:");
|
|
12367
|
+
console.log(" - N/A");
|
|
12368
|
+
console.log("* Datasets:");
|
|
12369
|
+
console.log("- Shapes:", pa.compactness.datasets.shapes);
|
|
12370
|
+
console.log("- Census:", pa.compactness.datasets.census);
|
|
12371
|
+
console.log("- VAP:", pa.compactness.datasets.vap);
|
|
12372
|
+
console.log("- Election:", pa.compactness.datasets.election);
|
|
12373
|
+
console.log("* Resources:");
|
|
12374
|
+
console.log("- N/A");
|
|
12375
|
+
console.log("");
|
|
12376
|
+
console.log("Splitting:");
|
|
12377
|
+
console.log("* Score:", pa.splitting.score);
|
|
12378
|
+
console.log("* Metrics:");
|
|
12379
|
+
console.log(" - sqEnt_DCreduced:", pa.splitting.metrics.sqEnt_DCreduced);
|
|
12380
|
+
console.log(" - sqEnt_CDreduced:", pa.splitting.metrics.sqEnt_CDreduced);
|
|
12381
|
+
console.log("* Details:");
|
|
12382
|
+
console.log(" - countiesSplitUnexpectedly:", prepareListItems(pa.splitting.details.countiesSplitUnexpectedly));
|
|
12383
|
+
console.log(" - unexpectedAffected:", pa.splitting.details.unexpectedAffected);
|
|
12384
|
+
console.log(" - splitVTDs:", pa.splitting.details.splitVTDs);
|
|
12385
|
+
console.log("* Datasets:");
|
|
12386
|
+
console.log("- Shapes:", pa.splitting.datasets.shapes);
|
|
12387
|
+
console.log("- Census:", pa.splitting.datasets.census);
|
|
12388
|
+
console.log("- VAP:", pa.splitting.datasets.vap);
|
|
12389
|
+
console.log("- Election:", pa.splitting.datasets.election);
|
|
12390
|
+
console.log("* Resources:");
|
|
12391
|
+
console.log("- N/A");
|
|
12392
|
+
console.log("");
|
|
12393
|
+
console.log("Partisan:");
|
|
12394
|
+
console.log("* Score:", pa.partisan.score);
|
|
12395
|
+
console.log("* Metrics:");
|
|
12396
|
+
console.log(" - sqEnt_DCreduced:", pa.partisan.metrics.partisanBias);
|
|
12397
|
+
console.log(" - sqEnt_CDreduced:", pa.partisan.metrics.responsiveness);
|
|
12398
|
+
console.log("* Details:");
|
|
12399
|
+
console.log(" - N/A");
|
|
12400
|
+
console.log("* Datasets:");
|
|
12401
|
+
console.log("- Shapes:", pa.partisan.datasets.shapes);
|
|
12402
|
+
console.log("- Census:", pa.partisan.datasets.census);
|
|
12403
|
+
console.log("- VAP:", pa.partisan.datasets.vap);
|
|
12404
|
+
console.log("- Election:", pa.partisan.datasets.election);
|
|
12405
|
+
console.log("* Resources:");
|
|
12406
|
+
console.log(" - Plan Score:", pa.partisan.resources.planScore);
|
|
12407
|
+
console.log("");
|
|
12408
|
+
console.log("Minority:");
|
|
12409
|
+
console.log("* Score:", pa.minority.score);
|
|
12410
|
+
console.log("* Metrics:");
|
|
12411
|
+
console.log(" - nBlack37to50:", pa.minority.metrics.nBlack37to50);
|
|
12412
|
+
console.log(" - nBlackMajority:", pa.minority.metrics.nBlackMajority);
|
|
12413
|
+
console.log(" - nHispanic37to50:", pa.minority.metrics.nHispanic37to50);
|
|
12414
|
+
console.log(" - nHispanicMajority:", pa.minority.metrics.nHispanicMajority);
|
|
12415
|
+
console.log(" - nPacific37to50:", pa.minority.metrics.nPacific37to50);
|
|
12416
|
+
console.log(" - nPacificMajority:", pa.minority.metrics.nPacificMajority);
|
|
12417
|
+
console.log(" - nAsian37to50:", pa.minority.metrics.nAsian37to50);
|
|
12418
|
+
console.log(" - nAsianMajority:", pa.minority.metrics.nAsianMajority);
|
|
12419
|
+
console.log(" - nNative37to50:", pa.minority.metrics.nNative37to50);
|
|
12420
|
+
console.log(" - nNativeMajority:", pa.minority.metrics.nNativeMajority);
|
|
12421
|
+
console.log(" - nMinority37to50:", pa.minority.metrics.nMinority37to50);
|
|
12422
|
+
console.log(" - nMinorityMajority:", pa.minority.metrics.nMinorityMajority);
|
|
12423
|
+
console.log("* Details:");
|
|
12424
|
+
console.log(" - VAP:", pa.minority.details.vap);
|
|
12425
|
+
console.log(" - comboCategoroes:", pa.minority.details.comboCategories);
|
|
12426
|
+
console.log("* Datasets:");
|
|
12427
|
+
console.log("- Shapes:", pa.minority.datasets.shapes);
|
|
12428
|
+
console.log("- Census:", pa.minority.datasets.census);
|
|
12429
|
+
console.log("- VAP:", pa.minority.datasets.vap);
|
|
12430
|
+
console.log("- Election:", pa.minority.datasets.election);
|
|
12431
|
+
console.log("* Resources:");
|
|
12432
|
+
console.log("- N/A");
|
|
12433
|
+
console.log("");
|
|
12434
|
+
}
|
|
12435
|
+
function echoDistrictStatistics(ds) {
|
|
12436
|
+
console.log("District Statistics:");
|
|
12437
|
+
for (let row of ds.table) {
|
|
12438
|
+
let DistrictID = row[0 /* DistrictID */];
|
|
12439
|
+
let TotalPop = row[1 /* TotalPop */];
|
|
12440
|
+
let PopDevPct = row[2 /* PopDevPct */];
|
|
12441
|
+
let bEqualPop = row[3 /* bEqualPop */];
|
|
12442
|
+
let bNotEmpty = row[4 /* bNotEmpty */];
|
|
12443
|
+
let bContiguous = row[5 /* bContiguous */];
|
|
12444
|
+
let bNotEmbedded = row[6 /* bNotEmbedded */];
|
|
12445
|
+
let DemPct = row[7 /* DemPct */];
|
|
12446
|
+
let RepPct = row[8 /* RepPct */];
|
|
12447
|
+
let WhitePct = row[9 /* WhitePct */];
|
|
12448
|
+
let MinorityPct = row[10 /* MinorityPct */];
|
|
12449
|
+
let BlackPct = row[11 /* BlackPct */];
|
|
12450
|
+
let HispanicPct = row[12 /* HispanicPct */];
|
|
12451
|
+
let PacificPct = row[13 /* PacificPct */];
|
|
12452
|
+
let AsianPct = row[14 /* AsianPct */];
|
|
12453
|
+
let NativePct = row[15 /* NativePct */];
|
|
12454
|
+
console.log(DistrictID, formatNumber(TotalPop), formatNumber(PopDevPct), bEqualPop, bNotEmpty, bContiguous, bNotEmbedded, formatNumber(DemPct), formatNumber(RepPct), formatNumber(WhitePct), formatNumber(MinorityPct), formatNumber(BlackPct), formatNumber(HispanicPct), formatNumber(PacificPct), formatNumber(AsianPct), formatNumber(NativePct));
|
|
12133
12455
|
}
|
|
12456
|
+
console.log("");
|
|
12457
|
+
console.log("* Details:");
|
|
12458
|
+
console.log(" - N/A");
|
|
12459
|
+
console.log("* Datasets:");
|
|
12460
|
+
console.log("- Shapes:", ds.datasets.shapes);
|
|
12461
|
+
console.log("- Census:", ds.datasets.census);
|
|
12462
|
+
console.log("- VAP:", ds.datasets.vap);
|
|
12463
|
+
console.log("- Election:", ds.datasets.election);
|
|
12464
|
+
console.log("* Resources:");
|
|
12465
|
+
console.log("- N/A");
|
|
12466
|
+
console.log("");
|
|
12134
12467
|
}
|
|
12135
|
-
|
|
12468
|
+
exports.echoDistrictStatistics = echoDistrictStatistics;
|
|
12136
12469
|
// HELPERS TO LOAD SAMPLE DATA FROM DISK
|
|
12137
12470
|
// A clone of 'carefulRead' in DRA-CLI
|
|
12138
12471
|
function readJSONcareful(file) {
|
|
@@ -12153,7 +12486,7 @@ function readJSON(file) {
|
|
|
12153
12486
|
fullPath = file;
|
|
12154
12487
|
}
|
|
12155
12488
|
else {
|
|
12156
|
-
fullPath = path.
|
|
12489
|
+
fullPath = path.resolve(file);
|
|
12157
12490
|
}
|
|
12158
12491
|
return readJSONcareful(fullPath);
|
|
12159
12492
|
}
|
|
@@ -12162,7 +12495,7 @@ exports.readJSON = readJSON;
|
|
|
12162
12495
|
function readCSV(file) {
|
|
12163
12496
|
try {
|
|
12164
12497
|
let input = fs.readFileSync(file, 'utf8');
|
|
12165
|
-
let dictRows =
|
|
12498
|
+
let dictRows = sync_1.default(input, {
|
|
12166
12499
|
columns: true,
|
|
12167
12500
|
skip_empty_lines: true
|
|
12168
12501
|
});
|
|
@@ -12182,7 +12515,7 @@ function readPlanCSV(file) {
|
|
|
12182
12515
|
fullPath = file;
|
|
12183
12516
|
}
|
|
12184
12517
|
else {
|
|
12185
|
-
fullPath = path.
|
|
12518
|
+
fullPath = path.resolve(file);
|
|
12186
12519
|
}
|
|
12187
12520
|
var csvArray = readCSV(fullPath);
|
|
12188
12521
|
for (let dictRow of csvArray) {
|
|
@@ -12193,9 +12526,8 @@ function readPlanCSV(file) {
|
|
|
12193
12526
|
return plan;
|
|
12194
12527
|
}
|
|
12195
12528
|
exports.readPlanCSV = readPlanCSV;
|
|
12196
|
-
//
|
|
12529
|
+
// END
|
|
12197
12530
|
|
|
12198
|
-
/* WEBPACK VAR INJECTION */}.call(this, "/"))
|
|
12199
12531
|
|
|
12200
12532
|
/***/ }),
|
|
12201
12533
|
|