@dra2020/district-analytics 7.1.7 → 8.1.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 +120 -39
- package/dist/cli.js.map +1 -1
- package/dist/district-analytics.js +78 -5
- package/dist/district-analytics.js.map +1 -1
- package/dist/src/_data.d.ts +4 -28
- package/dist/src/types.d.ts +25 -1
- package/dist/src/utils.d.ts +3 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -89056,7 +89056,7 @@ exports.scorePolsbyPopper = scorePolsbyPopper;
|
|
|
89056
89056
|
/*! exports provided: partisan, minority, compactness, splitting, popdev, default */
|
|
89057
89057
|
/***/ (function(module) {
|
|
89058
89058
|
|
|
89059
|
-
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"range\":[0,0.75],\"distribution\":[0.25,0.75],\"simpleRange\":[0.45,0.55],\"weight\":[0,20]},\"bonus\":2},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":0.15,\"coalition\":{\"weight\":0.5}},\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50}},\"splitting\":{\"county\":{\"range\":[[1.26,1.68],[1.09,1.45]],\"weight\":50},\"district\":{\"range\":[[1.26,1.68],[1.09,1.45]],\"weight\":50}},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]]}}");
|
|
89059
|
+
module.exports = JSON.parse("{\"partisan\":{\"bias\":{\"range\":[0,0.2],\"weight\":[50,80]},\"impact\":{\"weight\":[50,0],\"threshold\":4},\"competitiveness\":{\"range\":[0,0.75],\"distribution\":[0.25,0.75],\"simpleRange\":[0.45,0.55],\"weight\":[0,20]},\"bonus\":2},\"minority\":{\"range\":[0.37,0.5],\"distribution\":[0.25,0.75],\"shift\":[0.15,0.5],\"coalition\":{\"weight\":0.5}},\"compactness\":{\"reock\":{\"range\":[0.25,0.5],\"weight\":50},\"polsby\":{\"range\":[0.1,0.5],\"weight\":50}},\"splitting\":{\"county\":{\"range\":[[1.26,1.68],[1.09,1.45]],\"weight\":50},\"district\":{\"range\":[[1.26,1.68],[1.09,1.45]],\"weight\":50}},\"popdev\":{\"range\":[[0.0075,0.002],[0.1,-1]]}}");
|
|
89060
89060
|
|
|
89061
89061
|
/***/ }),
|
|
89062
89062
|
|
|
@@ -89160,11 +89160,20 @@ function minorityOpportunityDistribution(overridesJSON) {
|
|
|
89160
89160
|
return dist;
|
|
89161
89161
|
}
|
|
89162
89162
|
exports.minorityOpportunityDistribution = minorityOpportunityDistribution;
|
|
89163
|
+
// For Black VAP %
|
|
89163
89164
|
function minorityShift(overridesJSON) {
|
|
89164
|
-
const
|
|
89165
|
+
const BLACK = 0;
|
|
89166
|
+
const shift = config_json_1.default.minority.shift[BLACK];
|
|
89165
89167
|
return shift;
|
|
89166
89168
|
}
|
|
89167
89169
|
exports.minorityShift = minorityShift;
|
|
89170
|
+
// Dilution for other demos
|
|
89171
|
+
function minorityShiftDilution(overridesJSON) {
|
|
89172
|
+
const DILUTION = 1;
|
|
89173
|
+
const shift = config_json_1.default.minority.shift[DILUTION];
|
|
89174
|
+
return shift;
|
|
89175
|
+
}
|
|
89176
|
+
exports.minorityShiftDilution = minorityShiftDilution;
|
|
89168
89177
|
function coalitionDistrictWeight(overridesJSON) {
|
|
89169
89178
|
const weight = config_json_1.default.minority.coalition.weight;
|
|
89170
89179
|
return weight;
|
|
@@ -89357,47 +89366,36 @@ const normalize_1 = __webpack_require__(/*! ./normalize */ "./src/normalize.ts")
|
|
|
89357
89366
|
const partisan_1 = __webpack_require__(/*! ./partisan */ "./src/partisan.ts");
|
|
89358
89367
|
function evalMinorityOpportunity(p, bLog = false) {
|
|
89359
89368
|
// Initialize arrays for results
|
|
89360
|
-
const
|
|
89361
|
-
const
|
|
89362
|
-
// const nDemos = T.DemographicField.Native + 1 - offset; // Ditto
|
|
89369
|
+
const nDemos = 5 /* Native */ + 1; // Profile includes 'White'
|
|
89370
|
+
const offset = 1; // But don't process 'White'
|
|
89363
89371
|
const demosByDistrict = p.demographicProfile.mfArrayByDistrict;
|
|
89364
89372
|
// Initialize the demographic buckets
|
|
89365
89373
|
// Get the statewide minority VAP/CVAP % (ignore 'White')
|
|
89366
89374
|
const vapPctArray = p.demographicProfile.stateMfArray.slice(1);
|
|
89367
89375
|
// Determine proportional minority districts by demographic (ignoring'White')
|
|
89368
89376
|
const districtsByDemo = calcDistrictsByDemo(vapPctArray, p.nDistricts);
|
|
89369
|
-
let bucketsByDemo = new Array(nDemos
|
|
89377
|
+
let bucketsByDemo = new Array(nDemos);
|
|
89370
89378
|
let totalProportional = 0;
|
|
89371
89379
|
for (let j = 0; j < nDemos; j++) {
|
|
89372
89380
|
const vapPct = vapPctArray[j];
|
|
89373
89381
|
const prop = districtsByDemo[j];
|
|
89374
|
-
|
|
89375
|
-
//
|
|
89382
|
+
bucketsByDemo[j] = [0, 0, 0, 0, 0, 0, vapPct, prop];
|
|
89383
|
+
// Sum the prop for each individual race/ethnicity demographic
|
|
89376
89384
|
if (j > 0)
|
|
89377
89385
|
totalProportional += prop;
|
|
89378
|
-
bucketsByDemo[j] = [0, 0, 0, vapPct, prop]; // Five conceptual columns for each demographic
|
|
89379
89386
|
}
|
|
89380
|
-
// Add a 'Total' row
|
|
89381
|
-
bucketsByDemo[nDemos] = [0, 0, 0, 0, totalProportional];
|
|
89382
89387
|
let opptyByDemo = U.initArray(nDemos, 0.0); // A state-level value
|
|
89383
89388
|
// For each district
|
|
89384
89389
|
for (let i = 0; i < p.nDistricts; i++) {
|
|
89385
89390
|
// Find the opportunities for minority representation
|
|
89386
89391
|
for (let j = 0; j < nDemos; j++) {
|
|
89387
|
-
const
|
|
89388
|
-
|
|
89392
|
+
const Mf = demosByDistrict[i][j + offset]; // Skip the 'White' entries
|
|
89393
|
+
const bucket = bucketVAPPct(Mf);
|
|
89394
|
+
if (bucket > 0) {
|
|
89389
89395
|
// Bucket opportunity districts for each demographic
|
|
89390
|
-
|
|
89391
|
-
bucketsByDemo[j][bucket] += 1;
|
|
89392
|
-
bucketsByDemo[j][2 /* Total */] += 1;
|
|
89396
|
+
bucketsByDemo[j][bucket - 1] += 1; // Zero-based array
|
|
89393
89397
|
// Accumulate seat probabilities
|
|
89394
|
-
opptyByDemo[j] += estMinorityOpportunity(
|
|
89395
|
-
// Also accumulate the 'Total' row summing the opportunities for each demographic,
|
|
89396
|
-
// ignoring the first all Minority demographic.
|
|
89397
|
-
if (j > 0) {
|
|
89398
|
-
bucketsByDemo[nDemos][bucket] += 1;
|
|
89399
|
-
bucketsByDemo[nDemos][2 /* Total */] += 1;
|
|
89400
|
-
}
|
|
89398
|
+
opptyByDemo[j] += estMinorityOpportunity(Mf, j);
|
|
89401
89399
|
}
|
|
89402
89400
|
}
|
|
89403
89401
|
}
|
|
@@ -89405,8 +89403,8 @@ function evalMinorityOpportunity(p, bLog = false) {
|
|
|
89405
89403
|
const oD = U.sumArray(opptyByDemo.slice(1)); // Sum individual demos, skipping all minorities
|
|
89406
89404
|
const cD = opptyByDemo[0 /* Minority */]; // All minorities
|
|
89407
89405
|
// The # of proportional districts for each separate demographic and all minorities
|
|
89408
|
-
const pOd =
|
|
89409
|
-
const pCd = bucketsByDemo[0 /* Minority */][
|
|
89406
|
+
const pOd = totalProportional;
|
|
89407
|
+
const pCd = bucketsByDemo[0 /* Minority */][7 /* PropSeats */];
|
|
89410
89408
|
// Score opportunity
|
|
89411
89409
|
const score = scoreMinority(oD, pOd, cD, pCd);
|
|
89412
89410
|
let mS = {
|
|
@@ -89447,13 +89445,15 @@ exports.calcDistrictsByDemo = calcDistrictsByDemo;
|
|
|
89447
89445
|
// NOTE - Sam Wang suggest 90% probability for a 37% district. That seems a little
|
|
89448
89446
|
// too abrupt and all or nothing, so I backed off to the ~70%.
|
|
89449
89447
|
//
|
|
89450
|
-
function estMinorityOpportunity(Mf) {
|
|
89448
|
+
function estMinorityOpportunity(Mf, demo) {
|
|
89451
89449
|
// NOTE - Switch to compress the probability distribution
|
|
89452
89450
|
const bCompress = false;
|
|
89453
89451
|
const dist = bCompress ? C.minorityOpportunityDistribution() : [0.0, 1.0];
|
|
89454
89452
|
const range = C.minorityOpportunityRange();
|
|
89455
89453
|
const _normalizer = new normalize_1.Normalizer(Mf);
|
|
89456
|
-
|
|
89454
|
+
let shift = C.minorityShift(); // For Black VAP % (and Minority)
|
|
89455
|
+
if (demo && (demo > 1)) // For other non-Black demos,
|
|
89456
|
+
shift *= C.minorityShiftDilution(); // dilute the Black shift (by half)
|
|
89457
89457
|
_normalizer.wipNum += shift;
|
|
89458
89458
|
_normalizer.clip(dist[C.BEG], dist[C.END]);
|
|
89459
89459
|
_normalizer.unitize(dist[C.BEG], dist[C.END]);
|
|
@@ -89462,13 +89462,21 @@ function estMinorityOpportunity(Mf) {
|
|
|
89462
89462
|
}
|
|
89463
89463
|
exports.estMinorityOpportunity = estMinorityOpportunity;
|
|
89464
89464
|
// HELPERS
|
|
89465
|
-
function
|
|
89466
|
-
|
|
89467
|
-
|
|
89468
|
-
|
|
89469
|
-
|
|
89470
|
-
|
|
89471
|
-
|
|
89465
|
+
function bucketVAPPct(Mf) {
|
|
89466
|
+
if (Mf < 0.35)
|
|
89467
|
+
return 0;
|
|
89468
|
+
else if ((Mf >= 0.35) && (Mf < 0.40))
|
|
89469
|
+
return 1;
|
|
89470
|
+
else if ((Mf >= 0.40) && (Mf < 0.45))
|
|
89471
|
+
return 2;
|
|
89472
|
+
else if ((Mf >= 0.45) && (Mf < 0.50))
|
|
89473
|
+
return 3;
|
|
89474
|
+
else if ((Mf >= 0.50) && (Mf < 0.55))
|
|
89475
|
+
return 4;
|
|
89476
|
+
else if ((Mf >= 0.55) && (Mf < 0.60))
|
|
89477
|
+
return 5;
|
|
89478
|
+
else // Mf >= 0.60
|
|
89479
|
+
return 6;
|
|
89472
89480
|
}
|
|
89473
89481
|
function calcProportionalDistricts(proportion, nDistricts) {
|
|
89474
89482
|
const roundUp = 0.0;
|
|
@@ -103407,6 +103415,41 @@ class Districts {
|
|
|
103407
103415
|
}
|
|
103408
103416
|
}
|
|
103409
103417
|
exports.Districts = Districts;
|
|
103418
|
+
// CLASSES, ETC. FOR FEATURE & COUNTY DATA
|
|
103419
|
+
// Types of datasets by feature
|
|
103420
|
+
/* 08-13-2020 - Moved to types.ts
|
|
103421
|
+
export const enum Dataset
|
|
103422
|
+
{
|
|
103423
|
+
SHAPES = "SHAPES",
|
|
103424
|
+
CENSUS = "CENSUS",
|
|
103425
|
+
VAP = "VAP",
|
|
103426
|
+
ELECTION = "ELECTION"
|
|
103427
|
+
}
|
|
103428
|
+
*/
|
|
103429
|
+
/* 08-13-2020 - Moved to types.ts
|
|
103430
|
+
export type DatasetKeys = {
|
|
103431
|
+
SHAPES: string; // A shapefile
|
|
103432
|
+
CENSUS: string; // A total population Census dataset
|
|
103433
|
+
VAP: string; // A voting age (or citizen voting age) dataset
|
|
103434
|
+
ELECTION: string; // An election dataset
|
|
103435
|
+
}
|
|
103436
|
+
*/
|
|
103437
|
+
// Identifiers of fields for each feature in the datasets
|
|
103438
|
+
/* 08-13-2020 - Moved to types.ts
|
|
103439
|
+
export const enum FeatureField
|
|
103440
|
+
{
|
|
103441
|
+
TotalPop = "Tot",
|
|
103442
|
+
WhitePop = "Wh",
|
|
103443
|
+
BlackPop = "BlC",
|
|
103444
|
+
HispanicPop = "His",
|
|
103445
|
+
AsianPop = "AsnC",
|
|
103446
|
+
PacificPop = "PacC",
|
|
103447
|
+
NativePop = "NatC",
|
|
103448
|
+
DemVotes = "D",
|
|
103449
|
+
RepVotes = "R",
|
|
103450
|
+
TotalVotes = "Tot"
|
|
103451
|
+
}
|
|
103452
|
+
*/
|
|
103410
103453
|
// Wrap data by feature, to abstract the specifics of the internal structure
|
|
103411
103454
|
class Features {
|
|
103412
103455
|
constructor(s, data, keys) {
|
|
@@ -103997,8 +104040,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
103997
104040
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
103998
104041
|
const majority_minority_json_1 = __importDefault(__webpack_require__(/*! ../static/majority-minority.json */ "./static/majority-minority.json"));
|
|
103999
104042
|
const vra5_preclearance_json_1 = __importDefault(__webpack_require__(/*! ../static/vra5-preclearance.json */ "./static/vra5-preclearance.json"));
|
|
104000
|
-
// import stateContacts from '../static/state-contacts.json';
|
|
104001
|
-
// import demographicDefs from '../static/demographic-defns.json';
|
|
104002
104043
|
// TODO - 2020: Update/revise this, when the update comes out in September:
|
|
104003
104044
|
// Sources for majority-minority info:
|
|
104004
104045
|
// - https://en.wikipedia.org/wiki/List_of_majority-minority_United_States_congressional_districts
|
|
@@ -104717,14 +104758,31 @@ function parseGeoID(geoID) {
|
|
|
104717
104758
|
return parts;
|
|
104718
104759
|
}
|
|
104719
104760
|
exports.parseGeoID = parseGeoID;
|
|
104720
|
-
|
|
104721
|
-
|
|
104761
|
+
// 08-13-2020 - Enhanced completeness checking.
|
|
104762
|
+
function isWaterOnly(geoID, s) {
|
|
104763
|
+
const waterOnlySignature = 'ZZZZZZ';
|
|
104722
104764
|
if (geoID.indexOf(waterOnlySignature) >= 0)
|
|
104723
104765
|
return true;
|
|
104766
|
+
if (s) {
|
|
104767
|
+
// If called with a session, get the feature ...
|
|
104768
|
+
const featureID = s.features.featureID(geoID);
|
|
104769
|
+
const f = s.features.featureByIndex(featureID);
|
|
104770
|
+
// ... and also check the 'ALAND' property
|
|
104771
|
+
const bNoLand = ((f.properties['ALAND10'] !== undefined) && (f.properties['ALAND10'] == 0)) ? true : false;
|
|
104772
|
+
return bNoLand;
|
|
104773
|
+
}
|
|
104724
104774
|
else
|
|
104725
104775
|
return false;
|
|
104726
104776
|
}
|
|
104727
104777
|
exports.isWaterOnly = isWaterOnly;
|
|
104778
|
+
function isUninhabited(geoID, s) {
|
|
104779
|
+
const featureID = s.features.featureID(geoID);
|
|
104780
|
+
const f = s.features.featureByIndex(featureID);
|
|
104781
|
+
const totalPop = s.features.fieldForFeature(f, "CENSUS" /* CENSUS */, "Tot" /* TotalPop */);
|
|
104782
|
+
const bUninhabited = (totalPop > 0) ? false : true;
|
|
104783
|
+
return bUninhabited;
|
|
104784
|
+
}
|
|
104785
|
+
exports.isUninhabited = isUninhabited;
|
|
104728
104786
|
// NORMALIZING RESULTS
|
|
104729
104787
|
function normalize(rawScore, testScale) {
|
|
104730
104788
|
let rangeMin = testScale.scale[0];
|
|
@@ -104934,10 +104992,26 @@ function doIsComplete(s, bLog = false) {
|
|
|
104934
104992
|
// Check the dummy district that holds any unassigned features.
|
|
104935
104993
|
let unassignedFeatures = [];
|
|
104936
104994
|
let bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
|
|
104995
|
+
let bAllNonWaterOnlyAssigned = bAllAssigned ? true : false;
|
|
104937
104996
|
if (!bAllAssigned) {
|
|
104938
104997
|
let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
|
|
104939
104998
|
unassignedFeatures = Array.from(unassignedDistrict);
|
|
104940
104999
|
// unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
|
|
105000
|
+
// 08-13-2020 - Enhanced completeness checking.
|
|
105001
|
+
// Are any of the unassigned features not water-only -or- inhabited?
|
|
105002
|
+
// Check the official congressional maps for CT, KY, IL, and MI.
|
|
105003
|
+
bAllNonWaterOnlyAssigned = !unassignedFeatures.some(function (geoID) {
|
|
105004
|
+
if (U.isWaterOnly(geoID, s)) {
|
|
105005
|
+
console.log("Unassigned water-only feature ignored in completeness check: ", geoID);
|
|
105006
|
+
return false;
|
|
105007
|
+
}
|
|
105008
|
+
if (U.isUninhabited(geoID, s)) {
|
|
105009
|
+
console.log("Uninhabited feature ignored in completeness check: ", geoID);
|
|
105010
|
+
return false;
|
|
105011
|
+
}
|
|
105012
|
+
// Not water-only and inhabited
|
|
105013
|
+
return true;
|
|
105014
|
+
});
|
|
104941
105015
|
}
|
|
104942
105016
|
// Do all real districts have at least one feature assigned to them?
|
|
104943
105017
|
let emptyDistricts = [];
|
|
@@ -104961,7 +105035,14 @@ function doIsComplete(s, bLog = false) {
|
|
|
104961
105035
|
// Note, this can happen if a district is created, and then all features
|
|
104962
105036
|
// are removed from it (in DRA).
|
|
104963
105037
|
// Populate the test entry
|
|
104964
|
-
|
|
105038
|
+
// 08-13-2020 - Revised completeness check:
|
|
105039
|
+
// A plan is complete if:
|
|
105040
|
+
// * All inhabited, not water-only districts are assigned to districts.
|
|
105041
|
+
// * No districts are empty, i.e., each has one or more geos assigned to them.
|
|
105042
|
+
// Note: We're not checking (in _data.ts) whether those geos are water-only
|
|
105043
|
+
// or have any population, but that's a real edge case.
|
|
105044
|
+
test['score'] = bAllNonWaterOnlyAssigned && bNoneEmpty;
|
|
105045
|
+
// test['score'] = bAllAssigned && bNoneEmpty;
|
|
104965
105046
|
if (!bAllAssigned) {
|
|
104966
105047
|
test['details']['unassignedFeatures'] = unassignedFeatures;
|
|
104967
105048
|
}
|