@dra2020/district-analytics 2.0.9 → 3.0.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.
@@ -118,9 +118,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
118
118
  Object.defineProperty(exports, "__esModule", { value: true });
119
119
  const preprocess_1 = __webpack_require__(/*! ./preprocess */ "./src/preprocess.ts");
120
120
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
121
+ const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
121
122
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
122
123
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
123
- const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
124
124
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
125
125
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
126
126
  class AnalyticsSession {
@@ -174,6 +174,11 @@ class AnalyticsSession {
174
174
  preprocess_1.doPreprocessData(this, bLog);
175
175
  analyze_1.doAnalyzeDistricts(this, bLog);
176
176
  analyze_1.doAnalyzePlan(this, bLog);
177
+ // TODO - SCORE
178
+ this._profile = score_1.profilePlan(this, bLog);
179
+ // this._profile = Score.sampleProfile;
180
+ this._scorecard = score_1.scorePlan(this._profile, bLog);
181
+ // TODO - SCORE
177
182
  results_1.doAnalyzePostProcessing(this, bLog);
178
183
  }
179
184
  catch (_a) {
@@ -183,92 +188,45 @@ class AnalyticsSession {
183
188
  return true;
184
189
  }
185
190
  // NOTE - This assumes that analyzePlan() has been run!
186
- getPlanAnalytics(bLog = false) {
187
- return results_2.preparePlanAnalytics(this, bLog);
188
- }
189
- // NOTE - This assumes that analyzePlan() has been run!
190
191
  getDistrictStatistics(bLog = false) {
191
192
  return results_2.prepareDistrictStatistics(this, bLog);
192
193
  }
194
+ // TODO - SCORE
195
+ getPlanProfile(bLog = false) {
196
+ return this._profile;
197
+ }
198
+ // TODO - SCORE
199
+ getPlanScorecard(bLog = false) {
200
+ return this._scorecard;
201
+ }
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
+ // TODO - SCORE
210
+ // NOTE - This assumes that analyzePlan() has been run!
211
+ getRequirementsChecklist(bLog = false) {
212
+ return results_2.prepareRequirementsChecklist(this, bLog);
213
+ }
193
214
  // NOTE - This assumes that analyzePlan() has been run!
194
215
  getDiscontiguousDistrictFeatures(bLog = false) {
195
216
  // Get the (possibly empty) list of discontiguous district IDs
196
- const contiguousTest = this.getTest(1 /* Contiguous */);
197
- const discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
217
+ let contiguousTest = this.getTest(1 /* Contiguous */);
218
+ let discontiguousDistrictIDs = contiguousTest['details']['discontiguousDistricts'] || [];
198
219
  // Convert them into a (possibly empty) list of features
199
220
  let discontiguousDistrictFeatures = { type: 'FeatureCollection', features: [] };
200
221
  if (!(U.isArrayEmpty(discontiguousDistrictIDs))) {
201
222
  for (let id of discontiguousDistrictIDs) {
202
223
  let poly = this.districts.getDistrictShapeByID(id);
203
- if (poly) {
204
- // If a district has a shape & it is not contiguous, by definition,
205
- // it will be a Multipolygon, i.e., it will have multiple pieces, some
206
- // possibly embedded w/in other districts. Get & add all the pieces.
207
- const districtParts = geofeature_1.polyParts(poly);
208
- discontiguousDistrictFeatures.features.push(...districtParts.features);
209
- // discontiguousDistrictFeatures.features.push(poly);
210
- }
224
+ if (poly)
225
+ discontiguousDistrictFeatures.features.push(poly);
211
226
  }
212
227
  }
213
228
  return discontiguousDistrictFeatures;
214
229
  }
215
- // Comments clipped from dra-client geodistrict.ts.
216
- // Discontiguous polygons are:
217
- // 1. All polygons in a multi-polygon; and
218
- // 2. All holes in a otherwise cohesive polygon.
219
- // Note that all non-cohesive features are always simple polygons.
220
- /*
221
- let i: number, j: number;
222
- let nPoly: number = 0;
223
- for (i = 0;nPoly == 0 && i < this.cacheDistricts.features.length;i++)
224
- {
225
- let f = this.cacheDistricts.features[i];
226
-
227
- if (f.geometry.type === 'MultiPolygon')
228
- nPoly += f.geometry.coordinates.length;
229
- else if (f.geometry.type === 'Polygon' && f.geometry.coordinates.length)
230
- nPoly += (f.geometry.coordinates.length - 1);
231
- }
232
- if (nPoly)
233
- {
234
- this.cacheNoncohesive = {type: 'FeatureCollection', features: []};
235
- let af: any = this.cacheNoncohesive.features;
236
- let oUnique: any = {};
237
-
238
- // First add discontiguous polygons
239
- for (i = 0;i < this.cacheDistricts.features.length;i++)
240
- {
241
- let f = this.cacheDistricts.features[i];
242
-
243
- if (f.geometry.type === 'MultiPolygon')
244
- {
245
- // Push all non-contiguous polygons
246
- for (j = 0;j < f.geometry.coordinates.length;j++)
247
- {
248
- let p: any = f.geometry.coordinates[j];
249
- oUnique[Hash.qhash(p[0])] = true;
250
- af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: p}});
251
- }
252
- }
253
- }
254
-
255
- // Now add unique holes
256
- for (i = 0;i < this.cacheDistricts.features.length;i++)
257
- {
258
- let f = this.cacheDistricts.features[i];
259
-
260
- if (f.geometry.type === 'Polygon')
261
- {
262
- // Push all holes from this polygon
263
- for (j = 1;j < f.geometry.coordinates.length;j++)
264
- {
265
- let p: any = f.geometry.coordinates[j];
266
- if (oUnique[Hash.qhash(p)] === undefined)
267
- af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: [p]}});
268
- }
269
- }
270
- }
271
- } */
272
230
  // HELPERS USED INTERNALLY
273
231
  // Get an individual test, so you can drive UI with the results.
274
232
  getTest(testID) {
@@ -410,7 +368,7 @@ class Districts {
410
368
  nMissingDataset = 0;
411
369
  nMissingProperty = 0;
412
370
  // Compute these once per recalc cycle
413
- let targetSize = this._session.state.totalPop / this._session.state.nDistricts;
371
+ let targetSize = Math.round(this._session.state.totalPop / this._session.state.nDistricts);
414
372
  let deviationThreshold = this._session.populationDeviationThreshold();
415
373
  let planByDistrict = this._session.plan.byDistrictID();
416
374
  let plan = this._session.plan;
@@ -462,6 +420,7 @@ class Districts {
462
420
  let outerThis = this;
463
421
  // Default the pop dev % for the dummy Unassigned district to 0%.
464
422
  // Default the pop dev % for real (1–N) but empty districts to 100%.
423
+ // TODO - SCORE
465
424
  let popDevPct = (i > 0) ? (targetSize / targetSize) : 0 / targetSize;
466
425
  // Get the geoIDs assigned to the district
467
426
  // Guard against empty districts
@@ -488,6 +447,7 @@ class Districts {
488
447
  // NOTE - SPLITTING
489
448
  // Total population by counties w/in a district,
490
449
  // except the dummy unassigned district 0
450
+ // TODO - VFEATURE
491
451
  if (i > 0)
492
452
  countySplits[outerThis.getCountyIndex(geoID)] += featurePop;
493
453
  // Democratic and Republican vote totals
@@ -661,8 +621,10 @@ class Districts {
661
621
  compact_1.extractDistrictProperties(this._session, bLog);
662
622
  }
663
623
  getCountyIndex(geoID) {
664
- let countyGeoID = U.parseGeoID(geoID)['county'];
665
- let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
624
+ // TODO - VFEATURE
625
+ let countyFIPS = U.parseGeoID(geoID)['county'];
626
+ // let countyGeoID = U.parseGeoID(geoID)['county'] as string;
627
+ // let countyFIPS = U.getFIPSFromCountyGeoID(countyGeoID);
666
628
  let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
667
629
  return countyIndex;
668
630
  }
@@ -679,8 +641,9 @@ class Features {
679
641
  nFeatures() { return this._data.features.length; }
680
642
  featureByIndex(i) { return this._data.features[i]; }
681
643
  geoIDForFeature(f) {
644
+ // TODO - 2020
682
645
  // GEOIDs will be one of these properties
683
- let value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
646
+ const value = f.properties['GEOID10'] || f.properties['GEOID20'] || f.properties['GEOID'];
684
647
  return value;
685
648
  }
686
649
  fieldForFeature(f, dt, fk) {
@@ -1461,6 +1424,7 @@ function doPolsbyPopper(s, bLog = false) {
1461
1424
  }
1462
1425
  exports.doPolsbyPopper = doPolsbyPopper;
1463
1426
  // HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
1427
+ // TODO - SCORE: Create an array, as opposed to a dict
1464
1428
  function extractDistrictProperties(s, bLog = false) {
1465
1429
  // NOTE - I am assuming that district IDs are integers 1–N
1466
1430
  for (let i = 1; i <= s.state.nDistricts; i++) {
@@ -1619,23 +1583,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
1619
1583
  };
1620
1584
  Object.defineProperty(exports, "__esModule", { value: true });
1621
1585
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1622
- // HELPER
1623
- function polyParts(poly) {
1624
- let parts = { type: 'FeatureCollection', features: [] };
1625
- let af = parts.features;
1626
- if (poly.geometry.type === 'MultiPolygon') {
1627
- // Push all non-contiguous polygons
1628
- for (let j = 0; j < poly.geometry.coordinates.length; j++) {
1629
- let onePoly = poly.geometry.coordinates[j];
1630
- af.push({ type: 'Feature', properties: { id: `${af.length + 1}` }, geometry: { type: 'Polygon', coordinates: onePoly } });
1631
- }
1632
- }
1633
- else {
1634
- parts.features.push(poly);
1635
- }
1636
- return parts;
1637
- }
1638
- exports.polyParts = polyParts;
1639
1586
  // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1640
1587
  // TODO - POLY: Confirm Cartesian calculations
1641
1588
  function gfArea(poly) {
@@ -1961,8 +1908,10 @@ function doPreprocessCensus(s, bLog = false) {
1961
1908
  // Sum total population across the state
1962
1909
  s.state.totalPop += value;
1963
1910
  // Get the county FIPS code for the feature
1964
- let county = U.parseGeoID(geoID)['county'];
1965
- let countyFIPS = U.getFIPSFromCountyGeoID(county);
1911
+ // TODO - VFEATURE
1912
+ let countyFIPS = U.parseGeoID(geoID)['county'];
1913
+ // let county = U.parseGeoID(geoID)['county'] as string;
1914
+ // let countyFIPS = U.getFIPSFromCountyGeoID(county);
1966
1915
  // If a subtotal for the county doesn't exist, initialize one
1967
1916
  if (!(U.keyExists(countyFIPS, totalByCounty))) {
1968
1917
  totalByCounty[countyFIPS] = 0;
@@ -1980,7 +1929,7 @@ function doPreprocessCensus(s, bLog = false) {
1980
1929
  let fipsCodes = U.getObjectKeys(totalByCounty);
1981
1930
  // Sort the results
1982
1931
  fipsCodes = fipsCodes.sort();
1983
- // NOTE - SPLITTING
1932
+ // TODO - SCORE: This was added for SPLITTING
1984
1933
  // Add a dummy county, for county-district splitting analysis
1985
1934
  fipsCodes.unshift('000');
1986
1935
  // Create the ID-ordinal map
@@ -2067,7 +2016,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
2067
2016
  };
2068
2017
  Object.defineProperty(exports, "__esModule", { value: true });
2069
2018
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2070
- const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2071
2019
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
2072
2020
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
2073
2021
  const state_reqs_json_1 = __importDefault(__webpack_require__(/*! ../static/state-reqs.json */ "./static/state-reqs.json"));
@@ -2139,7 +2087,7 @@ let samplePartisan = {
2139
2087
  planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2140
2088
  }
2141
2089
  };
2142
- // TODO - MINORITY: This category is still being fleshed out.
2090
+ // TODO - SCORE: Add a minority report
2143
2091
  let sampleMinority = {
2144
2092
  score: null,
2145
2093
  metrics: {
@@ -2174,7 +2122,233 @@ exports.samplePlanAnalytics = {
2174
2122
  partisan: samplePartisan,
2175
2123
  minority: sampleMinority
2176
2124
  };
2177
- function preparePlanAnalytics(s, bLog = false) {
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
+ function prepareRequirementsChecklist(s, bLog = false) {
2178
2352
  if (!(s.bPostProcessingDone)) {
2179
2353
  doAnalyzePostProcessing(s);
2180
2354
  }
@@ -2234,142 +2408,9 @@ function preparePlanAnalytics(s, bLog = false) {
2234
2408
  }
2235
2409
  };
2236
2410
  }
2237
- // COMPACTNESS CATEGORY
2238
- let paCompactness;
2239
- {
2240
- let reockWeight = 0.5;
2241
- let polsbyWeight = 1.0 - reockWeight;
2242
- let reockTest = s.getTest(5 /* Reock */);
2243
- let polsbyTest = s.getTest(6 /* PolsbyPopper */);
2244
- let normalizedReock = reockTest['normalizedScore'];
2245
- let normalizedPolsby = reockTest['normalizedScore'];
2246
- let compactnessScore = U.trim((reockWeight * normalizedReock) + (polsbyWeight * normalizedPolsby), 0);
2247
- let reockMetric = U.deepCopy(reockTest['score']);
2248
- let polsbyMetric = U.deepCopy(polsbyTest['score']);
2249
- // Populate the category
2250
- paCompactness = {
2251
- score: compactnessScore,
2252
- metrics: {
2253
- reock: reockMetric,
2254
- polsby: polsbyMetric
2255
- },
2256
- details: {
2257
- // None at this time
2258
- },
2259
- datasets: {
2260
- // NOTE - DATASETS
2261
- shapes: U.deepCopy(s.config['descriptions']['SHAPES'])
2262
- // shapes: "2010 VTD shapes"
2263
- },
2264
- resources: {
2265
- // None at this time
2266
- }
2267
- };
2268
- }
2269
- // SPLITTING CATEGORY
2270
- let paSplitting;
2271
- {
2272
- let unexpectedCountySplittingTest = s.getTest(7 /* UnexpectedCountySplits */);
2273
- let VTDSplitsTest = s.getTest(10 /* VTDSplits */);
2274
- let countySplittingTest = s.getTest(8 /* CountySplitting */);
2275
- let districtSplittingTest = s.getTest(9 /* DistrictSplitting */);
2276
- let unexpectedAffectedMetric = U.deepCopy(unexpectedCountySplittingTest['score']);
2277
- let countiesSplitUnexpectedlyDetail = U.deepCopy(unexpectedCountySplittingTest['details']['countiesSplitUnexpectedly']);
2278
- let nVTDSplitsMetric = U.deepCopy(VTDSplitsTest['score']);
2279
- let splitVTDsDetail = U.deepCopy(VTDSplitsTest['details']['splitVTDs']);
2280
- let SqEnt_DCreducedMetric = U.deepCopy(countySplittingTest['score']);
2281
- let SqEnt_CDreducedMetric = U.deepCopy(districtSplittingTest['score']);
2282
- let countySplittingNormalized = countySplittingTest['normalizedScore'];
2283
- let districtSplittingNormalized = districtSplittingTest['normalizedScore'];
2284
- let splittingScore = U.trim((S.COUNTY_SPLITTING_WEIGHT * countySplittingNormalized) +
2285
- +(S.DISTRICT_SPLITTING_WEIGHT * districtSplittingNormalized), 0);
2286
- paSplitting = {
2287
- score: splittingScore,
2288
- metrics: {
2289
- sqEnt_DCreduced: SqEnt_DCreducedMetric,
2290
- sqEnt_CDreduced: SqEnt_CDreducedMetric
2291
- // NOTE - The un-reduced raw values
2292
- // sqEnt_DC : SqEnt_DCMetric,
2293
- // sqEnt_CD : SqEnt_CDMetric
2294
- },
2295
- details: {
2296
- countiesSplitUnexpectedly: countiesSplitUnexpectedlyDetail,
2297
- unexpectedAffected: unexpectedAffectedMetric,
2298
- nSplitVTDs: nVTDSplitsMetric,
2299
- splitVTDs: splitVTDsDetail
2300
- },
2301
- datasets: {
2302
- // None at this time
2303
- },
2304
- resources: {
2305
- // None at this time
2306
- }
2307
- };
2308
- }
2309
- // PARTISAN CATEGORY
2310
- //
2311
- // TODO - PARTISAN: This category is still being fleshed out. Just an example below.
2312
- let paPartisan;
2313
- {
2314
- paPartisan = {
2315
- score: 100,
2316
- metrics: {
2317
- partisanBias: 0.15,
2318
- responsiveness: 2.0
2319
- },
2320
- details: {},
2321
- datasets: {
2322
- election: "2016 Presidential, US Senate, Governor, and AG election results"
2323
- },
2324
- resources: {
2325
- planScore: "https://planscore.org/plan.html?20180219T202039.596761160Z"
2326
- }
2327
- };
2328
- }
2329
- // MINORITY CATEGORY
2330
- //
2331
- // TODO - MINORITY: This category is still being fleshed out. Just an example below.
2332
- let paMinority;
2333
- {
2334
- paMinority = {
2335
- score: null,
2336
- metrics: {
2337
- nBlack37to50: 1,
2338
- nBlackMajority: 12,
2339
- nHispanic37to50: 0,
2340
- nHispanicMajority: 0,
2341
- nPacific37to50: 0,
2342
- nPacificMajority: 0,
2343
- nAsian37to50: 0,
2344
- nAsianMajority: 0,
2345
- nNative37to50: 0,
2346
- nNativeMajority: 0,
2347
- nMinority37to50: 0,
2348
- nMinorityMajority: 0,
2349
- averageDVoteShare: 0.90
2350
- },
2351
- details: {
2352
- vap: true,
2353
- comboCategories: true
2354
- },
2355
- datasets: {
2356
- vap: "2010 Voting Age Population"
2357
- },
2358
- resources: {}
2359
- };
2360
- }
2361
- // PLAN ANALYTICS
2362
- let pa = {
2363
- requirements: paRequirements,
2364
- compactness: paCompactness,
2365
- // TODO - Not implemented yet
2366
- splitting: paSplitting,
2367
- partisan: paPartisan,
2368
- minority: paMinority
2369
- };
2370
- return pa;
2411
+ return paRequirements;
2371
2412
  }
2372
- exports.preparePlanAnalytics = preparePlanAnalytics;
2413
+ exports.prepareRequirementsChecklist = prepareRequirementsChecklist;
2373
2414
  // Example
2374
2415
  exports.sampleDistrictStatistics = {
2375
2416
  table: [
@@ -2625,6 +2666,127 @@ function doAnalyzePostProcessing(s, bLog = false) {
2625
2666
  exports.doAnalyzePostProcessing = doAnalyzePostProcessing;
2626
2667
 
2627
2668
 
2669
+ /***/ }),
2670
+
2671
+ /***/ "./src/score.ts":
2672
+ /*!**********************!*\
2673
+ !*** ./src/score.ts ***!
2674
+ \**********************/
2675
+ /*! no static exports found */
2676
+ /***/ (function(module, exports, __webpack_require__) {
2677
+
2678
+ "use strict";
2679
+
2680
+ //
2681
+ // SCORING
2682
+ //
2683
+ var __importStar = (this && this.__importStar) || function (mod) {
2684
+ if (mod && mod.__esModule) return mod;
2685
+ var result = {};
2686
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
2687
+ result["default"] = mod;
2688
+ return result;
2689
+ };
2690
+ Object.defineProperty(exports, "__esModule", { value: true });
2691
+ const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
2692
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2693
+ const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
2694
+ // PROFILE A PLAN
2695
+ function profilePlan(s, bLog = false) {
2696
+ const state = s.state.xx;
2697
+ const planName = s.title;
2698
+ const nDistricts = s.state.nDistricts;
2699
+ const nCounties = s.counties.nCounties;
2700
+ const targetSize = Math.round(s.state.totalPop / nDistricts);
2701
+ const popByDistrict = U.deepCopy(s.districts.statistics[D.DistrictField.TotalPop].slice(1, -1));
2702
+ const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
2703
+ const splits = makeNakedCxD(s);
2704
+ const summaryRow = s.districts.numberOfRows() - 1;
2705
+ const statewideVf = s.districts.statistics[D.DistrictField.DemPct][summaryRow];
2706
+ const vpiArray = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1));
2707
+ const demographicsByDistrict = makeArrayOfDemographics(s);
2708
+ const profile = {
2709
+ state: state,
2710
+ planName: planName,
2711
+ nDistricts: nDistricts,
2712
+ nCounties: nCounties,
2713
+ legislativeDistricts: s.legislativeDistricts,
2714
+ populationProfile: {
2715
+ TotalPopByDistrict: popByDistrict,
2716
+ targetSize: targetSize
2717
+ },
2718
+ compactnessProfile: {
2719
+ GeometryByDistrict: geoPropsByDistrict
2720
+ },
2721
+ splittingProfile: {
2722
+ CountyPopByDistrict: splits
2723
+ },
2724
+ partisanProfile: {
2725
+ statewideVf: statewideVf,
2726
+ VfArray: vpiArray
2727
+ },
2728
+ demographicProfile: {
2729
+ DemographicsByDistrict: demographicsByDistrict
2730
+ }
2731
+ };
2732
+ return profile;
2733
+ }
2734
+ exports.profilePlan = profilePlan;
2735
+ // NOTE - The CxD splits structure from _data.ts includes dummy districts for
2736
+ // unassigned precincts & state summary and an extra 0 county. But dra-score takes
2737
+ // a simple 1–D x 1–C splits array (zero-based, of course).
2738
+ function makeNakedCxD(s, bLog = false) {
2739
+ const adornedCxD = s.districts.statistics[D.DistrictField.CountySplits];
2740
+ let CxD = [];
2741
+ // Remove the unassigned & total dummy "districts"
2742
+ for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2743
+ // TODO - SCORE: OH has an extra county!?!
2744
+ const splits = U.deepCopy(adornedCxD[districtID].slice(1));
2745
+ CxD.push(splits);
2746
+ }
2747
+ return CxD;
2748
+ }
2749
+ // TODO - SCORE: Convert dict of geo props to array by district index
2750
+ function makeArrayOfGeoProps(s, bLog = false) {
2751
+ let geometryByDistrict = [];
2752
+ for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2753
+ let districtProps = s.districts.getGeoProperties(districtID);
2754
+ // Guard against no shape and no properties
2755
+ if (districtProps) {
2756
+ let a = districtProps[0 /* Area */];
2757
+ let p = districtProps[2 /* Perimeter */];
2758
+ let d = districtProps[1 /* Diameter */];
2759
+ // Save each triple
2760
+ geometryByDistrict.push([a, p, d]);
2761
+ }
2762
+ }
2763
+ return geometryByDistrict;
2764
+ }
2765
+ function makeArrayOfDemographics(s, bLog = false) {
2766
+ let demographicsArray = [];
2767
+ // Remove the unassigned & total dummy "districts"
2768
+ for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
2769
+ const districtDemographics = [
2770
+ U.deepCopy(s.districts.statistics[D.DistrictField.WhitePct][districtID]),
2771
+ U.deepCopy(s.districts.statistics[D.DistrictField.MinorityPct][districtID]),
2772
+ U.deepCopy(s.districts.statistics[D.DistrictField.BlackPct][districtID]),
2773
+ U.deepCopy(s.districts.statistics[D.DistrictField.HispanicPct][districtID]),
2774
+ U.deepCopy(s.districts.statistics[D.DistrictField.PacificPct][districtID]),
2775
+ U.deepCopy(s.districts.statistics[D.DistrictField.AsianPct][districtID]),
2776
+ U.deepCopy(s.districts.statistics[D.DistrictField.NativePct][districtID])
2777
+ ];
2778
+ demographicsArray.push(districtDemographics);
2779
+ }
2780
+ return demographicsArray;
2781
+ }
2782
+ // SCORE A PLAN
2783
+ function scorePlan(p, bLog = false) {
2784
+ let scorer = new Score.Scorer();
2785
+ return scorer.score(p);
2786
+ }
2787
+ exports.scorePlan = scorePlan;
2788
+
2789
+
2628
2790
  /***/ }),
2629
2791
 
2630
2792
  /***/ "./src/settings.ts":
@@ -2675,6 +2837,10 @@ exports.DISTRICT_SPLITTING_WEIGHT = 1.0 - exports.COUNTY_SPLITTING_WEIGHT;
2675
2837
  // TYPE DEFINITIONS
2676
2838
  //
2677
2839
  Object.defineProperty(exports, "__esModule", { value: true });
2840
+ // TODO - SCORE
2841
+ var dra_score_1 = __webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score");
2842
+ exports.sampleProfile = dra_score_1.sampleProfile;
2843
+ exports.sampleScorecard = dra_score_1.sampleScorecard;
2678
2844
  // END
2679
2845
 
2680
2846
 
@@ -2727,27 +2893,50 @@ function getDistrict(plan, geoID) {
2727
2893
  }
2728
2894
  exports.getDistrict = getDistrict;
2729
2895
  // WORKING WITH GEOIDS
2896
+ // TODO - VFEATURE
2730
2897
  function parseGeoID(geoID) {
2731
- let parts = {};
2732
- parts['state'] = geoID.substring(0, 2);
2733
- parts['county'] = geoID.substring(0, 5);
2734
- let l = geoID.length;
2735
- if (l >= 11) {
2736
- parts['tract'] = geoID.substring(0, 11);
2737
- }
2738
- if (l >= 12) {
2739
- parts['bg'] = geoID.substring(0, 12);
2740
- }
2741
- if (l == 15) {
2742
- parts['block'] = geoID;
2743
- }
2898
+ let bVfeature = false;
2899
+ // Rewrite vfeature GEOIDs to enable lexical parsing of higher-level parts
2900
+ // Template: vfeature_{geoid}_{chunk}_hash
2901
+ // Example: vfeature_39153153ASV_0_28e0bc2c8163e5982e1da2d61e2388a8325c575e
2902
+ if (geoID.indexOf('vfeature') >= 0) {
2903
+ bVfeature = true;
2904
+ // Strip off leading 'vfeature_'
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;
2910
+ }
2911
+ const parts = {
2912
+ vfeature: bVfeature,
2913
+ state: geoID.substring(0, 2),
2914
+ county: geoID.substring(2, 5),
2915
+ rest: geoID.slice(5)
2916
+ };
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
+ // }
2744
2930
  return parts;
2745
2931
  }
2746
2932
  exports.parseGeoID = parseGeoID;
2747
- function getFIPSFromCountyGeoID(geoID) {
2748
- return geoID.substring(2, 5);
2749
- }
2750
- exports.getFIPSFromCountyGeoID = getFIPSFromCountyGeoID;
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
+ // }
2751
2940
  function isWaterOnly(geoID) {
2752
2941
  let waterOnlySignature = 'ZZZZZZ';
2753
2942
  if (geoID.indexOf(waterOnlySignature) >= 0)
@@ -3186,6 +3375,17 @@ module.exports = JSON.parse("{\"AL\":\"https://www.brennancenter.org/sites/defau
3186
3375
 
3187
3376
  /***/ }),
3188
3377
 
3378
+ /***/ "@dra2020/dra-score":
3379
+ /*!*************************************!*\
3380
+ !*** external "@dra2020/dra-score" ***!
3381
+ \*************************************/
3382
+ /*! no static exports found */
3383
+ /***/ (function(module, exports) {
3384
+
3385
+ module.exports = require("@dra2020/dra-score");
3386
+
3387
+ /***/ }),
3388
+
3189
3389
  /***/ "@dra2020/poly":
3190
3390
  /*!********************************!*\
3191
3391
  !*** external "@dra2020/poly" ***!