@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 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 shift = config_json_1.default.minority.shift;
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 offset = 1; // Don't process 'White'
89361
- const nDemos = 6 /* Total */; // Ditto
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 + 1); // Add a row for the 'Total' row
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
- // Accumulate the proportional values for each demographic, ignoring the first
89375
- // all Minority demographic.
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 proportion = U.deepCopy(demosByDistrict[i][j + offset]); // Skip the 'White' entries
89388
- if (exceedsMinimumThreshold(proportion)) {
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
- const bucket = (exceedsMaximumThreshold(proportion)) ? 1 : 0;
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(proportion);
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 = bucketsByDemo[6 /* Total */][4 /* PropSeats */];
89409
- const pCd = bucketsByDemo[0 /* Minority */][4 /* PropSeats */];
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
- const shift = C.minorityShift();
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 exceedsMinimumThreshold(Mf) {
89466
- const threshold = C.minorityOpportunityRange()[C.BEG];
89467
- return Mf >= threshold;
89468
- }
89469
- function exceedsMaximumThreshold(Mf) {
89470
- const threshold = C.minorityOpportunityRange()[C.END];
89471
- return Mf > threshold;
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
- function isWaterOnly(geoID) {
104721
- let waterOnlySignature = 'ZZZZZZ';
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
- test['score'] = bAllAssigned && bNoneEmpty;
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
  }