@dra2020/district-analytics 3.0.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2692 -816
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +258 -357
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/_api.d.ts +2 -1
- package/dist/src/geofeature.d.ts +2 -0
- package/dist/src/score.d.ts +1 -1
- package/package.json +3 -2
|
@@ -116,11 +116,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
116
116
|
return result;
|
|
117
117
|
};
|
|
118
118
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
119
|
+
const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
|
|
119
120
|
const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
|
|
120
121
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
121
122
|
const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
|
|
122
123
|
const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
123
124
|
const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
|
|
125
|
+
const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
|
|
124
126
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
125
127
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
126
128
|
class AnalyticsSession {
|
|
@@ -140,45 +142,48 @@ class AnalyticsSession {
|
|
|
140
142
|
this.features = new D.Features(this, SessionRequest['data'], this.config['datasets']);
|
|
141
143
|
this.plan = new D.Plan(this, SessionRequest['plan']);
|
|
142
144
|
this.districts = new D.Districts(this, SessionRequest['districtShapes']);
|
|
143
|
-
// TODO -
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
// TODO - SCORE: Toggle
|
|
146
|
+
if (this.useLegacy()) {
|
|
147
|
+
console.log("Using legacy district-analytics.");
|
|
148
|
+
// NOTE: I've pulled these out of the individual analytics to here. Eventually,
|
|
149
|
+
// we could want them to passed into an analytics session as data, along with
|
|
150
|
+
// everything else. For now, this keeps branching out of the main code.
|
|
151
|
+
results_1.doConfigureScales(this);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.log("Using dra-score analytics.");
|
|
155
|
+
// TODO - SCORE: Temporary HACK. Query dra-score for threshold.
|
|
156
|
+
results_1.doConfigureScales(this);
|
|
157
|
+
}
|
|
154
158
|
}
|
|
155
159
|
processConfig(config) {
|
|
156
160
|
// NOTE - Session settings are required:
|
|
157
161
|
// - Analytics suites can be defaulted to all with [], but
|
|
158
162
|
// - Dataset keys must be explicitly specified with 'dataset'
|
|
159
|
-
// TODO -
|
|
160
|
-
|
|
161
|
-
// If the config passed in has no suites = [], use the default suites
|
|
162
|
-
if (U.isArrayEmpty(config['suites'])) {
|
|
163
|
-
config['suites'] = defaultSuites;
|
|
164
|
-
}
|
|
163
|
+
// TODO - SCORE: Delete
|
|
164
|
+
config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
|
|
165
165
|
// Default the Census & redistricting cycle to 2010
|
|
166
166
|
if (!(U.keyExists('cycle', config)))
|
|
167
167
|
config['cycle'] = 2010;
|
|
168
168
|
return config;
|
|
169
169
|
}
|
|
170
|
+
// TODO - SCORE: Toggle = Invert this logic
|
|
171
|
+
useLegacy() {
|
|
172
|
+
// TODO - SCORE: Opt-out
|
|
173
|
+
return (U.keyExists('useScore', this.config) && (this.config['useScore'] != null) && (!(this.config['useScore']))) ? true : false;
|
|
174
|
+
// TODO - SCORE: Opt-in
|
|
175
|
+
// return (U.keyExists('useScore', this.config) && (this.config['useScore'])) ? false : true;
|
|
176
|
+
}
|
|
170
177
|
// Using the the data in the analytics session, calculate all the
|
|
171
178
|
// analytics & validations, saving/updating the individual test results.
|
|
172
|
-
analyzePlan(bLog = false) {
|
|
179
|
+
analyzePlan(bLog = false, overridesJSON) {
|
|
173
180
|
try {
|
|
174
181
|
preprocess_1.doPreprocessData(this, bLog);
|
|
175
182
|
analyze_1.doAnalyzeDistricts(this, bLog);
|
|
176
183
|
analyze_1.doAnalyzePlan(this, bLog);
|
|
177
184
|
// TODO - SCORE
|
|
178
185
|
this._profile = score_1.profilePlan(this, bLog);
|
|
179
|
-
|
|
180
|
-
this._scorecard = score_1.scorePlan(this._profile, bLog);
|
|
181
|
-
// TODO - SCORE
|
|
186
|
+
this._scorecard = score_1.scorePlan(this, this._profile, bLog, overridesJSON);
|
|
182
187
|
results_1.doAnalyzePostProcessing(this, bLog);
|
|
183
188
|
}
|
|
184
189
|
catch (_a) {
|
|
@@ -199,13 +204,6 @@ class AnalyticsSession {
|
|
|
199
204
|
getPlanScorecard(bLog = false) {
|
|
200
205
|
return this._scorecard;
|
|
201
206
|
}
|
|
202
|
-
/* TODO - SCORE
|
|
203
|
-
// NOTE - This assumes that analyzePlan() has been run!
|
|
204
|
-
getPlanAnalytics(bLog: boolean = false): PlanAnalytics
|
|
205
|
-
{
|
|
206
|
-
return preparePlanAnalytics(this, bLog);
|
|
207
|
-
}
|
|
208
|
-
*/
|
|
209
207
|
// TODO - SCORE
|
|
210
208
|
// NOTE - This assumes that analyzePlan() has been run!
|
|
211
209
|
getRequirementsChecklist(bLog = false) {
|
|
@@ -214,19 +212,82 @@ class AnalyticsSession {
|
|
|
214
212
|
// NOTE - This assumes that analyzePlan() has been run!
|
|
215
213
|
getDiscontiguousDistrictFeatures(bLog = false) {
|
|
216
214
|
// Get the (possibly empty) list of discontiguous district IDs
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
const contiguousTest = this.getTest(1 /* Contiguous */);
|
|
216
|
+
const discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
|
|
219
217
|
// Convert them into a (possibly empty) list of features
|
|
220
218
|
let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
|
|
221
219
|
if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
|
|
222
220
|
for (let id of discontiguousDistrictIDs) {
|
|
223
221
|
let poly = this.districts.getDistrictShapeByID(id);
|
|
224
|
-
if (poly)
|
|
225
|
-
|
|
222
|
+
if (poly) {
|
|
223
|
+
// If a district has a shape & it is not contiguous, by definition,
|
|
224
|
+
// it will be a Multipolygon, i.e., it will have multiple pieces, some
|
|
225
|
+
// possibly embedded w/in other districts. Get & add all the pieces.
|
|
226
|
+
const districtParts = geofeature_1.polyParts(poly);
|
|
227
|
+
discontiguousDistrictFeatures.features.push(...districtParts.features);
|
|
228
|
+
// discontiguousDistrictFeatures.features.push(poly);
|
|
229
|
+
}
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
232
|
return discontiguousDistrictFeatures;
|
|
229
233
|
}
|
|
234
|
+
// Comments clipped from dra-client geodistrict.ts.
|
|
235
|
+
// Discontiguous polygons are:
|
|
236
|
+
// 1. All polygons in a multi-polygon; and
|
|
237
|
+
// 2. All holes in a otherwise cohesive polygon.
|
|
238
|
+
// Note that all non-cohesive features are always simple polygons.
|
|
239
|
+
/*
|
|
240
|
+
let i: number, j: number;
|
|
241
|
+
let nPoly: number = 0;
|
|
242
|
+
for (i = 0;nPoly == 0 && i < this.cacheDistricts.features.length;i++)
|
|
243
|
+
{
|
|
244
|
+
let f = this.cacheDistricts.features[i];
|
|
245
|
+
|
|
246
|
+
if (f.geometry.type === 'MultiPolygon')
|
|
247
|
+
nPoly += f.geometry.coordinates.length;
|
|
248
|
+
else if (f.geometry.type === 'Polygon' && f.geometry.coordinates.length)
|
|
249
|
+
nPoly += (f.geometry.coordinates.length - 1);
|
|
250
|
+
}
|
|
251
|
+
if (nPoly)
|
|
252
|
+
{
|
|
253
|
+
this.cacheNoncohesive = {type: 'FeatureCollection', features: []};
|
|
254
|
+
let af: any = this.cacheNoncohesive.features;
|
|
255
|
+
let oUnique: any = {};
|
|
256
|
+
|
|
257
|
+
// First add discontiguous polygons
|
|
258
|
+
for (i = 0;i < this.cacheDistricts.features.length;i++)
|
|
259
|
+
{
|
|
260
|
+
let f = this.cacheDistricts.features[i];
|
|
261
|
+
|
|
262
|
+
if (f.geometry.type === 'MultiPolygon')
|
|
263
|
+
{
|
|
264
|
+
// Push all non-contiguous polygons
|
|
265
|
+
for (j = 0;j < f.geometry.coordinates.length;j++)
|
|
266
|
+
{
|
|
267
|
+
let p: any = f.geometry.coordinates[j];
|
|
268
|
+
oUnique[Hash.qhash(p[0])] = true;
|
|
269
|
+
af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: p}});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Now add unique holes
|
|
275
|
+
for (i = 0;i < this.cacheDistricts.features.length;i++)
|
|
276
|
+
{
|
|
277
|
+
let f = this.cacheDistricts.features[i];
|
|
278
|
+
|
|
279
|
+
if (f.geometry.type === 'Polygon')
|
|
280
|
+
{
|
|
281
|
+
// Push all holes from this polygon
|
|
282
|
+
for (j = 1;j < f.geometry.coordinates.length;j++)
|
|
283
|
+
{
|
|
284
|
+
let p: any = f.geometry.coordinates[j];
|
|
285
|
+
if (oUnique[Hash.qhash(p)] === undefined)
|
|
286
|
+
af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: [p]}});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} */
|
|
230
291
|
// HELPERS USED INTERNALLY
|
|
231
292
|
// Get an individual test, so you can drive UI with the results.
|
|
232
293
|
getTest(testID) {
|
|
@@ -242,9 +303,21 @@ class AnalyticsSession {
|
|
|
242
303
|
// Return a pointer to the the test entry for this test
|
|
243
304
|
return this.tests[testID];
|
|
244
305
|
}
|
|
245
|
-
// NOTE - Not sure why this has to be up here
|
|
306
|
+
// NOTE - Not sure why this has to be up here ...
|
|
246
307
|
populationDeviationThreshold() {
|
|
247
|
-
|
|
308
|
+
// TODO - SCORE: Toggle
|
|
309
|
+
if (this.useLegacy()) {
|
|
310
|
+
return 1 - this.testScales[4 /* PopulationDeviation */]['scale'][0];
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// NOTE - This assumes the plan has been profiled
|
|
314
|
+
const scorer = new Score.Scorer();
|
|
315
|
+
const popdev = scorer.populationDeviationThreshold(this.legislativeDistricts);
|
|
316
|
+
return popdev;
|
|
317
|
+
// TODO - SCORE: Temporary HACK. Query dra-score for threshold.
|
|
318
|
+
// NOTE - The plan may not have been scored yet, i.e., no scorecard yet.
|
|
319
|
+
// return 1 - this.testScales[T.Test.PopulationDeviation]['scale'][0];
|
|
320
|
+
}
|
|
248
321
|
}
|
|
249
322
|
}
|
|
250
323
|
exports.AnalyticsSession = AnalyticsSession;
|
|
@@ -420,7 +493,6 @@ class Districts {
|
|
|
420
493
|
let outerThis = this;
|
|
421
494
|
// Default the pop dev % for the dummy Unassigned district to 0%.
|
|
422
495
|
// Default the pop dev % for real (1–N) but empty districts to 100%.
|
|
423
|
-
// TODO - SCORE
|
|
424
496
|
let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
|
|
425
497
|
// Get the geoIDs assigned to the district
|
|
426
498
|
// Guard against empty districts
|
|
@@ -447,7 +519,6 @@ class Districts {
|
|
|
447
519
|
// NOTE - SPLITTING
|
|
448
520
|
// Total population by counties w/in a district,
|
|
449
521
|
// except the dummy unassigned district 0
|
|
450
|
-
// TODO - VFEATURE
|
|
451
522
|
if (i > 0)
|
|
452
523
|
countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
|
|
453
524
|
// Democratic and Republican vote totals
|
|
@@ -621,10 +692,7 @@ class Districts {
|
|
|
621
692
|
compact_1.extractDistrictProperties(this._session, bLog);
|
|
622
693
|
}
|
|
623
694
|
getCountyIndex(geoID) {
|
|
624
|
-
// TODO - VFEATURE
|
|
625
695
|
let countyFIPS = U.parseGeoID(geoID)['county'];
|
|
626
|
-
// let countyGeoID = U.parseGeoID(geoID)['county'] as string;
|
|
627
|
-
// let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
|
|
628
696
|
let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
|
|
629
697
|
return countyIndex;
|
|
630
698
|
}
|
|
@@ -873,11 +941,45 @@ exports.doAnalyzeDistricts = doAnalyzeDistricts;
|
|
|
873
941
|
// NOTE - I could make this table-driven, but I'm thinking that the explicit
|
|
874
942
|
// calls might make chunking for aync easier.
|
|
875
943
|
function doAnalyzePlan(s, bLog = false) {
|
|
876
|
-
// TODO -
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
944
|
+
// TODO - SCORE: Toggle
|
|
945
|
+
if (s.useLegacy()) {
|
|
946
|
+
// Disable most legacy analytics
|
|
947
|
+
// Get the requested suites, and only execute those tests
|
|
948
|
+
let requestedSuites = s.config['suites'];
|
|
949
|
+
// Tests in the "Legal" suite, i.e., pass/ fail constraints
|
|
950
|
+
if (requestedSuites.includes(0 /* Legal */)) {
|
|
951
|
+
s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
|
|
952
|
+
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
|
|
953
|
+
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
|
|
954
|
+
s.tests[4 /* PopulationDeviation */] = equal_1.doPopulationDeviation(s, bLog);
|
|
955
|
+
// NOTE - I can't check whether a population deviation is legal or not, until
|
|
956
|
+
// the raw % is normalized. A zero (0) would mean "too much" / "not legal," for
|
|
957
|
+
// the given type of district (CD vs. LD). The EqualPopulation test is derived
|
|
958
|
+
// from PopulationDeviation, as part of scorecard or test log preparation.
|
|
959
|
+
// Create an empty test entry here though ...
|
|
960
|
+
s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
|
|
961
|
+
}
|
|
962
|
+
// Tests in the "Fair" suite
|
|
963
|
+
if (requestedSuites.includes(1 /* Fair */)) {
|
|
964
|
+
s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
|
|
965
|
+
s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
|
|
966
|
+
s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
|
|
967
|
+
s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
|
|
968
|
+
s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
|
|
969
|
+
s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
|
|
970
|
+
}
|
|
971
|
+
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
972
|
+
if (requestedSuites.includes(2 /* Best */)) {
|
|
973
|
+
s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
|
|
974
|
+
s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
|
|
975
|
+
s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
|
|
976
|
+
s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
|
|
977
|
+
s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
|
|
978
|
+
s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
// TODO - SCORE: Except these. Continue to do these here vs. dra-score.
|
|
881
983
|
s.tests[0 /* Complete */] = valid_1.doIsComplete(s, bLog);
|
|
882
984
|
s.tests[1 /* Contiguous */] = valid_1.doIsContiguous(s, bLog);
|
|
883
985
|
s.tests[2 /* FreeOfHoles */] = valid_1.doIsFreeOfHoles(s, bLog);
|
|
@@ -888,24 +990,8 @@ function doAnalyzePlan(s, bLog = false) {
|
|
|
888
990
|
// from PopulationDeviation, as part of scorecard or test log preparation.
|
|
889
991
|
// Create an empty test entry here though ...
|
|
890
992
|
s.tests[3 /* EqualPopulation */] = s.getTest(3 /* EqualPopulation */);
|
|
891
|
-
}
|
|
892
|
-
// Tests in the "Fair" suite
|
|
893
|
-
if (requestedSuites.includes(1 /* Fair */)) {
|
|
894
|
-
s.tests[11 /* SeatsBias */] = political_1.doSeatsBias(s, bLog);
|
|
895
|
-
s.tests[12 /* VotesBias */] = political_1.doVotesBias(s, bLog);
|
|
896
|
-
s.tests[13 /* Responsiveness */] = political_1.doResponsiveness(s, bLog);
|
|
897
|
-
s.tests[14 /* ResponsiveDistricts */] = political_1.doResponsiveDistricts(s, bLog);
|
|
898
|
-
s.tests[15 /* EfficiencyGap */] = political_1.doEfficiencyGap(s, bLog);
|
|
899
|
-
s.tests[16 /* MajorityMinorityDistricts */] = minority_1.doMajorityMinorityDistricts(s, bLog);
|
|
900
|
-
}
|
|
901
|
-
// Tests in the "Best" suite, i.e., criteria for better/worse
|
|
902
|
-
if (requestedSuites.includes(2 /* Best */)) {
|
|
903
|
-
s.tests[5 /* Reock */] = compact_1.doReock(s, bLog);
|
|
904
|
-
s.tests[6 /* PolsbyPopper */] = compact_1.doPolsbyPopper(s, bLog);
|
|
905
993
|
s.tests[7 /* UnexpectedCountySplits */] = cohesive_1.doFindCountiesSplitUnexpectedly(s, bLog);
|
|
906
994
|
s.tests[10 /* VTDSplits */] = cohesive_1.doFindSplitVTDs(s, bLog);
|
|
907
|
-
s.tests[8 /* CountySplitting */] = cohesive_1.doCountySplitting(s, bLog);
|
|
908
|
-
s.tests[9 /* DistrictSplitting */] = cohesive_1.doDistrictSplitting(s, bLog);
|
|
909
995
|
}
|
|
910
996
|
// Enable a Test Log and Scorecard to be generated
|
|
911
997
|
s.bPlanAnalyzed = true;
|
|
@@ -1502,6 +1588,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
1502
1588
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1503
1589
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1504
1590
|
const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
1591
|
+
// TODO - SCORE: Delete
|
|
1505
1592
|
function doPopulationDeviation(s, bLog = false) {
|
|
1506
1593
|
let test = s.getTest(4 /* PopulationDeviation */);
|
|
1507
1594
|
let targetSize = s.state.totalPop / s.state.nDistricts;
|
|
@@ -1540,8 +1627,8 @@ function doHasEqualPopulations(s, bLog = false) {
|
|
|
1540
1627
|
let test = s.getTest(3 /* EqualPopulation */);
|
|
1541
1628
|
// Get the normalized population deviation %
|
|
1542
1629
|
let popDevTest = s.getTest(4 /* PopulationDeviation */);
|
|
1543
|
-
|
|
1544
|
-
|
|
1630
|
+
const popDevPct = popDevTest['score'];
|
|
1631
|
+
const popDevNormalized = popDevTest['normalizedScore'];
|
|
1545
1632
|
// Populate the test entry
|
|
1546
1633
|
if (popDevNormalized > 0) {
|
|
1547
1634
|
test['score'] = true;
|
|
@@ -1583,6 +1670,23 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
1583
1670
|
};
|
|
1584
1671
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1585
1672
|
const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
|
|
1673
|
+
// HELPER
|
|
1674
|
+
function polyParts(poly) {
|
|
1675
|
+
let parts = { type: 'FeatureCollection', features: [] };
|
|
1676
|
+
let af = parts.features;
|
|
1677
|
+
if (poly.geometry.type === 'MultiPolygon') {
|
|
1678
|
+
// Push all non-contiguous polygons
|
|
1679
|
+
for (let j = 0; j < poly.geometry.coordinates.length; j++) {
|
|
1680
|
+
let onePoly = poly.geometry.coordinates[j];
|
|
1681
|
+
af.push({ type: 'Feature', properties: { id: `${af.length + 1}` }, geometry: { type: 'Polygon', coordinates: onePoly } });
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
parts.features.push(poly);
|
|
1686
|
+
}
|
|
1687
|
+
return parts;
|
|
1688
|
+
}
|
|
1689
|
+
exports.polyParts = polyParts;
|
|
1586
1690
|
// CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
|
|
1587
1691
|
// TODO - POLY: Confirm Cartesian calculations
|
|
1588
1692
|
function gfArea(poly) {
|
|
@@ -1908,10 +2012,7 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
1908
2012
|
// Sum total population across the state
|
|
1909
2013
|
s.state.totalPop += value;
|
|
1910
2014
|
// Get the county FIPS code for the feature
|
|
1911
|
-
// TODO - VFEATURE
|
|
1912
2015
|
let countyFIPS = U.parseGeoID(geoID)['county'];
|
|
1913
|
-
// let county = U.parseGeoID(geoID)['county'] as string;
|
|
1914
|
-
// let countyFIPS = U.getFIPSFromCountyGeoID(county);
|
|
1915
2016
|
// If a subtotal for the county doesn't exist, initialize one
|
|
1916
2017
|
if (!(U.keyExists(countyFIPS, totalByCounty))) {
|
|
1917
2018
|
totalByCounty[countyFIPS] = 0;
|
|
@@ -1929,8 +2030,8 @@ function doPreprocessCensus(s, bLog = false) {
|
|
|
1929
2030
|
let fipsCodes = U.getObjectKeys(totalByCounty);
|
|
1930
2031
|
// Sort the results
|
|
1931
2032
|
fipsCodes = fipsCodes.sort();
|
|
1932
|
-
//
|
|
1933
|
-
// Add a dummy county, for county-district splitting analysis
|
|
2033
|
+
// NOTE - This was added for the legacy SPLITTING implementation.
|
|
2034
|
+
// Add a dummy county, for county-district splitting analysis.
|
|
1934
2035
|
fipsCodes.unshift('000');
|
|
1935
2036
|
// Create the ID-ordinal map
|
|
1936
2037
|
for (let i in fipsCodes) {
|
|
@@ -2020,6 +2121,7 @@ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
|
|
|
2020
2121
|
const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
|
|
2021
2122
|
const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
|
|
2022
2123
|
// Example
|
|
2124
|
+
// TODO - DELETE?
|
|
2023
2125
|
let sampleRequirements = {
|
|
2024
2126
|
score: 2 /* Red */,
|
|
2025
2127
|
metrics: {
|
|
@@ -2043,6 +2145,7 @@ let sampleRequirements = {
|
|
|
2043
2145
|
stateReqs: "https://www.brennancenter.org/sites/default/files/publications/2019_06_50States_FINALsinglepages_20.pdf"
|
|
2044
2146
|
}
|
|
2045
2147
|
};
|
|
2148
|
+
// TODO - DELETE
|
|
2046
2149
|
let sampleCompactness = {
|
|
2047
2150
|
score: 60,
|
|
2048
2151
|
metrics: {
|
|
@@ -2055,6 +2158,7 @@ let sampleCompactness = {
|
|
|
2055
2158
|
},
|
|
2056
2159
|
resources: {}
|
|
2057
2160
|
};
|
|
2161
|
+
// TODO - DELETE
|
|
2058
2162
|
let sampleSplitting = {
|
|
2059
2163
|
score: 73,
|
|
2060
2164
|
metrics: {
|
|
@@ -2072,7 +2176,7 @@ let sampleSplitting = {
|
|
|
2072
2176
|
datasets: {},
|
|
2073
2177
|
resources: {}
|
|
2074
2178
|
};
|
|
2075
|
-
// TODO -
|
|
2179
|
+
// TODO - DELETE
|
|
2076
2180
|
let samplePartisan = {
|
|
2077
2181
|
score: 100,
|
|
2078
2182
|
metrics: {
|
|
@@ -2087,7 +2191,7 @@ let samplePartisan = {
|
|
|
2087
2191
|
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2088
2192
|
}
|
|
2089
2193
|
};
|
|
2090
|
-
// TODO -
|
|
2194
|
+
// TODO - DELETE
|
|
2091
2195
|
let sampleMinority = {
|
|
2092
2196
|
score: null,
|
|
2093
2197
|
metrics: {
|
|
@@ -2114,6 +2218,7 @@ let sampleMinority = {
|
|
|
2114
2218
|
},
|
|
2115
2219
|
resources: {}
|
|
2116
2220
|
};
|
|
2221
|
+
// TODO - DELETE
|
|
2117
2222
|
exports.samplePlanAnalytics = {
|
|
2118
2223
|
requirements: sampleRequirements,
|
|
2119
2224
|
compactness: sampleCompactness,
|
|
@@ -2122,232 +2227,6 @@ exports.samplePlanAnalytics = {
|
|
|
2122
2227
|
partisan: samplePartisan,
|
|
2123
2228
|
minority: sampleMinority
|
|
2124
2229
|
};
|
|
2125
|
-
/* TODO - SCORE
|
|
2126
|
-
export function preparePlanAnalytics(s: AnalyticsSession, bLog: boolean = false): PlanAnalytics
|
|
2127
|
-
{
|
|
2128
|
-
if (!(s.bPostProcessingDone))
|
|
2129
|
-
{
|
|
2130
|
-
doAnalyzePostProcessing(s);
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
// REQUIREMENTS CATEGORY
|
|
2134
|
-
let paRequirements: RequirementsCategory;
|
|
2135
|
-
|
|
2136
|
-
{
|
|
2137
|
-
let completeTest = s.getTest(T.Test.Complete) as T.TestEntry;
|
|
2138
|
-
let contiguousTest = s.getTest(T.Test.Contiguous) as T.TestEntry;
|
|
2139
|
-
let freeOfHolesTest = s.getTest(T.Test.FreeOfHoles) as T.TestEntry;
|
|
2140
|
-
let equalPopulationTest = s.getTest(T.Test.EqualPopulation) as T.TestEntry;
|
|
2141
|
-
|
|
2142
|
-
// Combine individual checks into an overall score
|
|
2143
|
-
|
|
2144
|
-
// TODO - DASHBOARD: Until we add three-state support top to bottom in
|
|
2145
|
-
// requirements/validations, map booleans to tri-states here.
|
|
2146
|
-
let completeMetric = U.mapBooleanToTriState(completeTest['score'] as boolean);
|
|
2147
|
-
let contiguousMetric = U.mapBooleanToTriState(contiguousTest['score'] as boolean);
|
|
2148
|
-
let freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score'] as boolean);
|
|
2149
|
-
let equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score'] as boolean);
|
|
2150
|
-
|
|
2151
|
-
let reqScore: T.TriState = T.TriState.Green;
|
|
2152
|
-
let checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
|
|
2153
|
-
if (checks.includes(T.TriState.Yellow)) reqScore = T.TriState.Yellow;
|
|
2154
|
-
if (checks.includes(T.TriState.Red)) reqScore = T.TriState.Red;
|
|
2155
|
-
|
|
2156
|
-
// Get values to support details entries
|
|
2157
|
-
let unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
|
|
2158
|
-
let emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
|
|
2159
|
-
let discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
|
|
2160
|
-
let embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
|
|
2161
|
-
|
|
2162
|
-
let populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
|
|
2163
|
-
let deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
|
|
2164
|
-
|
|
2165
|
-
let xx: string = s.state.xx;
|
|
2166
|
-
// TODO - JSON: Is there a better / easier way to work with the variable?
|
|
2167
|
-
let stateReqsDict: T.Dict = allStateReqs;
|
|
2168
|
-
let reqLinkToStateReqs: string = stateReqsDict[xx];
|
|
2169
|
-
|
|
2170
|
-
// Populate the category
|
|
2171
|
-
paRequirements = {
|
|
2172
|
-
score: reqScore,
|
|
2173
|
-
metrics: {
|
|
2174
|
-
complete: completeMetric,
|
|
2175
|
-
contiguous: contiguousMetric,
|
|
2176
|
-
freeOfHoles: freeOfHolesMetric,
|
|
2177
|
-
equalPopulation: equalPopulationMetric
|
|
2178
|
-
},
|
|
2179
|
-
details: {
|
|
2180
|
-
unassignedFeatures: unassignedFeaturesDetail,
|
|
2181
|
-
emptyDistricts: emptyDistrictsDetail,
|
|
2182
|
-
discontiguousDistricts: discontiguousDistrictsDetail,
|
|
2183
|
-
embeddedDistricts: embeddedDistrictsDetail,
|
|
2184
|
-
populationDeviation: populationDeviationDetail,
|
|
2185
|
-
deviationThreshold: deviationThresholdDetail
|
|
2186
|
-
},
|
|
2187
|
-
datasets: {
|
|
2188
|
-
census: U.deepCopy(s.config['descriptions']['CENSUS']),
|
|
2189
|
-
},
|
|
2190
|
-
resources: {
|
|
2191
|
-
stateReqs: reqLinkToStateReqs
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
// COMPACTNESS CATEGORY
|
|
2197
|
-
let paCompactness: CompactnessCategory;
|
|
2198
|
-
{
|
|
2199
|
-
let reockWeight = 0.5;
|
|
2200
|
-
let polsbyWeight = 1.0 - reockWeight;
|
|
2201
|
-
|
|
2202
|
-
let reockTest = s.getTest(T.Test.Reock) as T.TestEntry;
|
|
2203
|
-
let polsbyTest = s.getTest(T.Test.PolsbyPopper) as T.TestEntry;
|
|
2204
|
-
|
|
2205
|
-
let normalizedReock = reockTest['normalizedScore'] as number;
|
|
2206
|
-
let normalizedPolsby = reockTest['normalizedScore'] as number;
|
|
2207
|
-
let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
|
|
2208
|
-
|
|
2209
|
-
let reockMetric = U.deepCopy(reockTest['score'] as number);
|
|
2210
|
-
let polsbyMetric = U.deepCopy(polsbyTest['score'] as number);
|
|
2211
|
-
|
|
2212
|
-
// Populate the category
|
|
2213
|
-
paCompactness = {
|
|
2214
|
-
score: compactnessScore,
|
|
2215
|
-
metrics: {
|
|
2216
|
-
reock: reockMetric,
|
|
2217
|
-
polsby: polsbyMetric
|
|
2218
|
-
},
|
|
2219
|
-
details: {
|
|
2220
|
-
// None at this time
|
|
2221
|
-
},
|
|
2222
|
-
datasets: {
|
|
2223
|
-
// NOTE - DATASETS
|
|
2224
|
-
shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
|
|
2225
|
-
// shapes: "2010 VTD shapes"
|
|
2226
|
-
},
|
|
2227
|
-
resources: {
|
|
2228
|
-
// None at this time
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
// SPLITTING CATEGORY
|
|
2234
|
-
|
|
2235
|
-
let paSplitting: SplittingCategory
|
|
2236
|
-
|
|
2237
|
-
{
|
|
2238
|
-
let unexpectedCountySplittingTest = s.getTest(T.Test.UnexpectedCountySplits) as T.TestEntry;
|
|
2239
|
-
let VTDSplitsTest = s.getTest(T.Test.VTDSplits) as T.TestEntry;
|
|
2240
|
-
let countySplittingTest = s.getTest(T.Test.CountySplitting) as T.TestEntry;
|
|
2241
|
-
let districtSplittingTest = s.getTest(T.Test.DistrictSplitting) as T.TestEntry;
|
|
2242
|
-
|
|
2243
|
-
let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
|
|
2244
|
-
let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
|
|
2245
|
-
|
|
2246
|
-
let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
|
|
2247
|
-
let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
|
|
2248
|
-
|
|
2249
|
-
let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
|
|
2250
|
-
let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
|
|
2251
|
-
|
|
2252
|
-
let countySplittingNormalized = countySplittingTest['normalizedScore'] as number;
|
|
2253
|
-
let districtSplittingNormalized = districtSplittingTest['normalizedScore'] as number;
|
|
2254
|
-
let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
|
|
2255
|
-
+ (S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
|
|
2256
|
-
|
|
2257
|
-
paSplitting = {
|
|
2258
|
-
score: splittingScore,
|
|
2259
|
-
metrics: {
|
|
2260
|
-
sqEnt_DCreduced: SqEnt_DCreducedMetric,
|
|
2261
|
-
sqEnt_CDreduced: SqEnt_CDreducedMetric
|
|
2262
|
-
// NOTE - The un-reduced raw values
|
|
2263
|
-
// sqEnt_DC : SqEnt_DCMetric,
|
|
2264
|
-
// sqEnt_CD : SqEnt_CDMetric
|
|
2265
|
-
},
|
|
2266
|
-
details: {
|
|
2267
|
-
countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
|
|
2268
|
-
unexpectedAffected: unexpectedAffectedMetric,
|
|
2269
|
-
nSplitVTDs: nVTDSplitsMetric,
|
|
2270
|
-
splitVTDs: splitVTDsDetail
|
|
2271
|
-
},
|
|
2272
|
-
datasets: {
|
|
2273
|
-
// None at this time
|
|
2274
|
-
},
|
|
2275
|
-
resources: {
|
|
2276
|
-
// None at this time
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
// PARTISAN CATEGORY
|
|
2282
|
-
//
|
|
2283
|
-
// TODO - PARTISAN: This category is still being fleshed out. Just an example below.
|
|
2284
|
-
let paPartisan: PartisanCategory;
|
|
2285
|
-
|
|
2286
|
-
{
|
|
2287
|
-
paPartisan = {
|
|
2288
|
-
score: 100,
|
|
2289
|
-
metrics: {
|
|
2290
|
-
partisanBias: 0.15,
|
|
2291
|
-
responsiveness: 2.0
|
|
2292
|
-
},
|
|
2293
|
-
details: {},
|
|
2294
|
-
datasets: {
|
|
2295
|
-
election: "2016 Presidential, US Senate, Governor, and AG election results"
|
|
2296
|
-
},
|
|
2297
|
-
resources: {
|
|
2298
|
-
planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
// MINORITY CATEGORY
|
|
2304
|
-
//
|
|
2305
|
-
// TODO - MINORITY: This category is still being fleshed out. Just an example below.
|
|
2306
|
-
let paMinority: MinorityCategory;
|
|
2307
|
-
|
|
2308
|
-
{
|
|
2309
|
-
paMinority = {
|
|
2310
|
-
score: null,
|
|
2311
|
-
metrics: {
|
|
2312
|
-
nBlack37to50: 1,
|
|
2313
|
-
nBlackMajority: 12,
|
|
2314
|
-
nHispanic37to50: 0,
|
|
2315
|
-
nHispanicMajority: 0,
|
|
2316
|
-
nPacific37to50: 0,
|
|
2317
|
-
nPacificMajority: 0,
|
|
2318
|
-
nAsian37to50: 0,
|
|
2319
|
-
nAsianMajority: 0,
|
|
2320
|
-
nNative37to50: 0,
|
|
2321
|
-
nNativeMajority: 0,
|
|
2322
|
-
nMinority37to50: 0,
|
|
2323
|
-
nMinorityMajority: 0,
|
|
2324
|
-
|
|
2325
|
-
averageDVoteShare: 0.90
|
|
2326
|
-
},
|
|
2327
|
-
details: {
|
|
2328
|
-
vap: true,
|
|
2329
|
-
comboCategories: true
|
|
2330
|
-
},
|
|
2331
|
-
datasets: {
|
|
2332
|
-
vap: "2010 Voting Age Population"
|
|
2333
|
-
},
|
|
2334
|
-
resources: {}
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
// PLAN ANALYTICS
|
|
2339
|
-
let pa: PlanAnalytics = {
|
|
2340
|
-
requirements: paRequirements,
|
|
2341
|
-
compactness: paCompactness,
|
|
2342
|
-
// TODO - Not implemented yet
|
|
2343
|
-
splitting: paSplitting,
|
|
2344
|
-
partisan: paPartisan,
|
|
2345
|
-
minority: paMinority
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
return pa;
|
|
2349
|
-
}
|
|
2350
|
-
*/
|
|
2351
2230
|
function prepareRequirementsChecklist(s, bLog = false) {
|
|
2352
2231
|
if (!(s.bPostProcessingDone)) {
|
|
2353
2232
|
doAnalyzePostProcessing(s);
|
|
@@ -2355,34 +2234,40 @@ function prepareRequirementsChecklist(s, bLog = false) {
|
|
|
2355
2234
|
// REQUIREMENTS CATEGORY
|
|
2356
2235
|
let paRequirements;
|
|
2357
2236
|
{
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2237
|
+
const completeTest = s.getTest(0 /* Complete */);
|
|
2238
|
+
const contiguousTest = s.getTest(1 /* Contiguous */);
|
|
2239
|
+
const freeOfHolesTest = s.getTest(2 /* FreeOfHoles */);
|
|
2240
|
+
const equalPopulationTest = s.getTest(3 /* EqualPopulation */);
|
|
2362
2241
|
// Combine individual checks into an overall score
|
|
2363
|
-
//
|
|
2242
|
+
// NOTE - Until we add three-state support top to bottom in
|
|
2364
2243
|
// requirements/validations, map booleans to tri-states here.
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2244
|
+
const completeMetric = U.mapBooleanToTriState(completeTest['score']);
|
|
2245
|
+
const contiguousMetric = U.mapBooleanToTriState(contiguousTest['score']);
|
|
2246
|
+
const freeOfHolesMetric = U.mapBooleanToTriState(freeOfHolesTest['score']);
|
|
2247
|
+
const equalPopulationMetric = U.mapBooleanToTriState(equalPopulationTest['score']);
|
|
2369
2248
|
let reqScore = 0 /* Green */;
|
|
2370
|
-
|
|
2249
|
+
const checks = [completeMetric, contiguousMetric, freeOfHolesMetric, equalPopulationMetric];
|
|
2371
2250
|
if (checks.includes(1 /* Yellow */))
|
|
2372
2251
|
reqScore = 1 /* Yellow */;
|
|
2373
2252
|
if (checks.includes(2 /* Red */))
|
|
2374
2253
|
reqScore = 2 /* Red */;
|
|
2375
2254
|
// Get values to support details entries
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2255
|
+
const unassignedFeaturesDetail = U.deepCopy(completeTest['details']['unassignedFeatures']) || [];
|
|
2256
|
+
const emptyDistrictsDetail = U.deepCopy(completeTest['details']['emptyDistricts']) || [];
|
|
2257
|
+
const discontiguousDistrictsDetail = U.deepCopy(contiguousTest['details']['discontiguousDistricts']) || [];
|
|
2258
|
+
const embeddedDistrictsDetail = U.deepCopy(freeOfHolesTest['details']['embeddedDistricts']) || [];
|
|
2259
|
+
// TODO - SCORE: DELETE - This code is hooked correctly to use dra-score
|
|
2260
|
+
// const scorecard = s._scorecard as Score.Scorecard;
|
|
2261
|
+
// const populationDeviation = scorecard.best.populationDeviation.raw;
|
|
2262
|
+
const populationDeviationDetail = U.deepCopy(equalPopulationTest['details']['deviation']);
|
|
2263
|
+
// console.log("Population deviations =", populationDeviationDetail, populationDeviation);
|
|
2264
|
+
// const deviationThreshold = scorecard.best.populationDeviation.notes['threshold'];
|
|
2265
|
+
const deviationThresholdDetail = U.trim(s.populationDeviationThreshold());
|
|
2266
|
+
// console.log("Population deviation thresholds =", deviationThresholdDetail, deviationThreshold);
|
|
2267
|
+
const xx = s.state.xx;
|
|
2383
2268
|
// TODO - JSON: Is there a better / easier way to work with the variable?
|
|
2384
|
-
|
|
2385
|
-
|
|
2269
|
+
const stateReqsDict = state_reqs_json_1.default;
|
|
2270
|
+
const reqLinkToStateReqs = stateReqsDict[xx];
|
|
2386
2271
|
// Populate the category
|
|
2387
2272
|
paRequirements = {
|
|
2388
2273
|
score: reqScore,
|
|
@@ -2645,19 +2530,30 @@ exports.doConfigureScales = doConfigureScales;
|
|
|
2645
2530
|
// Postprocess analytics - Normalize numeric results and derive secondary tests.
|
|
2646
2531
|
// Do this after analytics have been run and before preparing a test log or scorecard.
|
|
2647
2532
|
function doAnalyzePostProcessing(s, bLog = false) {
|
|
2648
|
-
//
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2533
|
+
// TODO - SCORE: Toggle
|
|
2534
|
+
if (s.useLegacy()) {
|
|
2535
|
+
// Normalize the raw scores for all the numerics tests
|
|
2536
|
+
let testResults = U.getNumericObjectKeys(testDefns);
|
|
2537
|
+
for (let testID of testResults) {
|
|
2538
|
+
if (testDefns[testID]['normalize']) {
|
|
2539
|
+
let testResult = s.getTest(testID);
|
|
2540
|
+
let rawScore = testResult['score'];
|
|
2541
|
+
let normalizedScore;
|
|
2542
|
+
normalizedScore = U.normalize(rawScore, s.testScales[testID]);
|
|
2543
|
+
testResult['normalizedScore'] = normalizedScore;
|
|
2544
|
+
// Add the scale used to normalize the raw score to the details
|
|
2545
|
+
testResult['details']['scale'] = s.testScales[testID].scale;
|
|
2546
|
+
}
|
|
2659
2547
|
}
|
|
2660
2548
|
}
|
|
2549
|
+
else {
|
|
2550
|
+
// Just populate the normalized population deviation score in the test
|
|
2551
|
+
const scorecard = s._scorecard;
|
|
2552
|
+
let test = s.getTest(4 /* PopulationDeviation */);
|
|
2553
|
+
test['normalizedScore'] = scorecard.traditionalPrinciples.populationDeviation.normalized;
|
|
2554
|
+
// TODO - DELETE
|
|
2555
|
+
// test['normalizedScore'] = scorecard.best.populationDeviation.normalized;
|
|
2556
|
+
}
|
|
2661
2557
|
// Derive secondary tests
|
|
2662
2558
|
analyze_1.doDeriveSecondaryTests(s, bLog);
|
|
2663
2559
|
// Toggle the semaphore, so postprocessing isn't for both the testlog & scorecard
|
|
@@ -2702,7 +2598,7 @@ function profilePlan(s, bLog = false) {
|
|
|
2702
2598
|
const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
|
|
2703
2599
|
const splits = makeNakedCxD(s);
|
|
2704
2600
|
const summaryRow = s.districts.numberOfRows() - 1;
|
|
2705
|
-
const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
|
|
2601
|
+
const statewideVf = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
|
|
2706
2602
|
const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
|
|
2707
2603
|
const demographicsByDistrict = makeArrayOfDemographics(s);
|
|
2708
2604
|
const profile = {
|
|
@@ -2740,13 +2636,11 @@ function makeNakedCxD(s, bLog = false) {
|
|
|
2740
2636
|
let CxD = [];
|
|
2741
2637
|
// Remove the unassigned & total dummy "districts"
|
|
2742
2638
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
2743
|
-
// TODO - SCORE: OH has an extra county!?!
|
|
2744
2639
|
const splits = U.deepCopy(adornedCxD[districtID].slice(1));
|
|
2745
2640
|
CxD.push(splits);
|
|
2746
2641
|
}
|
|
2747
2642
|
return CxD;
|
|
2748
2643
|
}
|
|
2749
|
-
// TODO - SCORE: Convert dict of geo props to array by district index
|
|
2750
2644
|
function makeArrayOfGeoProps(s, bLog = false) {
|
|
2751
2645
|
let geometryByDistrict = [];
|
|
2752
2646
|
for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
|
|
@@ -2780,9 +2674,30 @@ function makeArrayOfDemographics(s, bLog = false) {
|
|
|
2780
2674
|
return demographicsArray;
|
|
2781
2675
|
}
|
|
2782
2676
|
// SCORE A PLAN
|
|
2783
|
-
function scorePlan(p, bLog = false) {
|
|
2677
|
+
function scorePlan(s, p, bLog = false, overridesJSON) {
|
|
2784
2678
|
let scorer = new Score.Scorer();
|
|
2785
|
-
|
|
2679
|
+
const scorecard = scorer.score(p, overridesJSON);
|
|
2680
|
+
// TODO - SCORE: Toggle: Before returning, create a dummy population deviation
|
|
2681
|
+
// test, for doHasEqualPopulations() to use later. This is preserving the old
|
|
2682
|
+
// calling sequence.
|
|
2683
|
+
let test = s.getTest(4 /* PopulationDeviation */);
|
|
2684
|
+
// TODO - SCORE: U.trim(popDev)???
|
|
2685
|
+
// const popDev = scorecard.best.populationDeviation.raw;
|
|
2686
|
+
// Get the raw population deviation
|
|
2687
|
+
const popDev = scorecard.traditionalPrinciples.populationDeviation.raw;
|
|
2688
|
+
// Populate the test entry
|
|
2689
|
+
test['score'] = popDev;
|
|
2690
|
+
test['details'] = { 'maxDeviation': scorecard.traditionalPrinciples.populationDeviation.notes['maxDeviation'] };
|
|
2691
|
+
// TODO - DELETE
|
|
2692
|
+
// test['details'] = { 'maxDeviation': scorecard.best.populationDeviation.notes['maxDeviation'] };
|
|
2693
|
+
// Populate the N+1 summary "district" in district.statistics
|
|
2694
|
+
let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
|
|
2695
|
+
let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
|
|
2696
|
+
let summaryRow = s.districts.numberOfRows() - 1;
|
|
2697
|
+
totalPop[summaryRow] = p.populationProfile.targetSize;
|
|
2698
|
+
popDevPct[summaryRow] = popDev;
|
|
2699
|
+
//
|
|
2700
|
+
return scorecard;
|
|
2786
2701
|
}
|
|
2787
2702
|
exports.scorePlan = scorePlan;
|
|
2788
2703
|
|
|
@@ -2866,6 +2781,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
2866
2781
|
return result;
|
|
2867
2782
|
};
|
|
2868
2783
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2784
|
+
const DT = __importStar(__webpack_require__(/*! @dra2020/dra-types */ "@dra2020/dra-types"));
|
|
2869
2785
|
const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
|
|
2870
2786
|
// PLAN HELPERS
|
|
2871
2787
|
// Is a "neighbor" in state?
|
|
@@ -2893,7 +2809,6 @@ function getDistrict(plan, geoID) {
|
|
|
2893
2809
|
}
|
|
2894
2810
|
exports.getDistrict = getDistrict;
|
|
2895
2811
|
// WORKING WITH GEOIDS
|
|
2896
|
-
// TODO - VFEATURE
|
|
2897
2812
|
function parseGeoID(geoID) {
|
|
2898
2813
|
let bVfeature = false;
|
|
2899
2814
|
// Rewrite vfeature GEOIDs to enable lexical parsing of higher-level parts
|
|
@@ -2901,12 +2816,7 @@ function parseGeoID(geoID) {
|
|
|
2901
2816
|
// Example: vfeature_39153153ASV_0_28e0bc2c8163e5982e1da2d61e2388a8325c575e
|
|
2902
2817
|
if (geoID.indexOf('vfeature') >= 0) {
|
|
2903
2818
|
bVfeature = true;
|
|
2904
|
-
|
|
2905
|
-
let parentGeoID = geoID.slice(9);
|
|
2906
|
-
// Strip off trailing goo
|
|
2907
|
-
const uPos = parentGeoID.indexOf('_');
|
|
2908
|
-
parentGeoID = parentGeoID.slice(0, uPos);
|
|
2909
|
-
geoID = parentGeoID;
|
|
2819
|
+
geoID = DT.vgeoidToGeoid(geoID);
|
|
2910
2820
|
}
|
|
2911
2821
|
const parts = {
|
|
2912
2822
|
vfeature: bVfeature,
|
|
@@ -2914,29 +2824,9 @@ function parseGeoID(geoID) {
|
|
|
2914
2824
|
county: geoID.substring(2, 5),
|
|
2915
2825
|
rest: geoID.slice(5)
|
|
2916
2826
|
};
|
|
2917
|
-
// let l: number = geoID.length;
|
|
2918
|
-
// if (l >= 11)
|
|
2919
|
-
// {
|
|
2920
|
-
// parts['tract'] = geoID.substring(0, 11);
|
|
2921
|
-
// }
|
|
2922
|
-
// if (l >= 12)
|
|
2923
|
-
// {
|
|
2924
|
-
// parts['bg'] = geoID.substring(0, 12);
|
|
2925
|
-
// }
|
|
2926
|
-
// if (l == 15)
|
|
2927
|
-
// {
|
|
2928
|
-
// parts['block'] = geoID;
|
|
2929
|
-
// }
|
|
2930
2827
|
return parts;
|
|
2931
2828
|
}
|
|
2932
2829
|
exports.parseGeoID = parseGeoID;
|
|
2933
|
-
// TODO - VFEATURE
|
|
2934
|
-
// export function getFIPSFromCountyGeoID(geoID: string): string
|
|
2935
|
-
// {
|
|
2936
|
-
// const fips = geoID.substring(2, 5);
|
|
2937
|
-
// if (isNaN(Number(fips))) console.log("Non-numeric GEOID =", geoID);
|
|
2938
|
-
// return fips;
|
|
2939
|
-
// }
|
|
2940
2830
|
function isWaterOnly(geoID) {
|
|
2941
2831
|
let waterOnlySignature = 'ZZZZZZ';
|
|
2942
2832
|
if (geoID.indexOf(waterOnlySignature) >= 0)
|
|
@@ -3386,6 +3276,17 @@ module.exports = require("@dra2020/dra-score");
|
|
|
3386
3276
|
|
|
3387
3277
|
/***/ }),
|
|
3388
3278
|
|
|
3279
|
+
/***/ "@dra2020/dra-types":
|
|
3280
|
+
/*!*************************************!*\
|
|
3281
|
+
!*** external "@dra2020/dra-types" ***!
|
|
3282
|
+
\*************************************/
|
|
3283
|
+
/*! no static exports found */
|
|
3284
|
+
/***/ (function(module, exports) {
|
|
3285
|
+
|
|
3286
|
+
module.exports = require("@dra2020/dra-types");
|
|
3287
|
+
|
|
3288
|
+
/***/ }),
|
|
3289
|
+
|
|
3389
3290
|
/***/ "@dra2020/poly":
|
|
3390
3291
|
/*!********************************!*\
|
|
3391
3292
|
!*** external "@dra2020/poly" ***!
|