@dra2020/district-analytics 1.0.4 → 1.0.7

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.
Files changed (114) hide show
  1. package/.prettierrc +5 -0
  2. package/dist/_api.d.ts +27 -0
  3. package/dist/_data.d.ts +130 -0
  4. package/dist/analyze.d.ts +4 -0
  5. package/dist/cli.js +12091 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cohesive.d.ts +4 -0
  8. package/dist/compact.d.ts +5 -0
  9. package/dist/constants.d.ts +6 -0
  10. package/dist/district-analytics.js +102 -52
  11. package/dist/district-analytics.js.map +1 -0
  12. package/dist/equal.d.ts +4 -0
  13. package/dist/geofeature.d.ts +3 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/minority.d.ts +3 -0
  16. package/dist/political.d.ts +8 -0
  17. package/dist/preprocess.d.ts +2 -0
  18. package/dist/report.d.ts +15 -0
  19. package/dist/settings.d.ts +5 -0
  20. package/dist/test/_cli.d.ts +5 -0
  21. package/dist/types.d.ts +110 -0
  22. package/dist/utils.d.ts +28 -0
  23. package/dist/valid.d.ts +8 -0
  24. package/jestconfig.json +14 -0
  25. package/lib/HelloWorld.d.ts +3 -0
  26. package/lib/HelloWorld.js +11 -0
  27. package/lib/_api.js +91 -0
  28. package/lib/_api.js.map +1 -0
  29. package/lib/_cli.js +434 -0
  30. package/lib/_cli.js.map +1 -0
  31. package/lib/_data.js +425 -0
  32. package/lib/_data.js.map +1 -0
  33. package/lib/analyze.d.ts +3 -0
  34. package/lib/analyze.js +69 -0
  35. package/lib/analyze.js.map +1 -0
  36. package/lib/api.d.ts +34 -0
  37. package/lib/api.js +117 -0
  38. package/lib/api.js.map +1 -0
  39. package/lib/cli.d.ts +1 -0
  40. package/lib/cli.js +386 -0
  41. package/lib/cli.js.map +1 -0
  42. package/lib/cohesive.d.ts +4 -0
  43. package/lib/cohesive.js +132 -0
  44. package/lib/cohesive.js.map +1 -0
  45. package/lib/compact.d.ts +4 -0
  46. package/lib/compact.js +183 -0
  47. package/lib/compact.js.map +1 -0
  48. package/lib/constants.js +367 -0
  49. package/lib/constants.js.map +1 -0
  50. package/lib/data.js +188 -0
  51. package/lib/data.js.map +1 -0
  52. package/lib/equal.d.ts +4 -0
  53. package/lib/equal.js +59 -0
  54. package/lib/equal.js.map +1 -0
  55. package/lib/features.js +19 -0
  56. package/lib/features.js.map +1 -0
  57. package/lib/geofeature.js +112 -0
  58. package/lib/geofeature.js.map +1 -0
  59. package/lib/index.d.ts +5 -0
  60. package/lib/index.js +11 -0
  61. package/lib/index.js.map +1 -0
  62. package/lib/minority.d.ts +3 -0
  63. package/lib/minority.js +61 -0
  64. package/lib/minority.js.map +1 -0
  65. package/lib/political.d.ts +7 -0
  66. package/lib/political.js +88 -0
  67. package/lib/political.js.map +1 -0
  68. package/lib/preprocess.d.ts +4 -0
  69. package/lib/preprocess.js +101 -0
  70. package/lib/preprocess.js.map +1 -0
  71. package/lib/report.d.ts +14 -0
  72. package/lib/report.js +817 -0
  73. package/lib/report.js.map +1 -0
  74. package/lib/sample.d.ts +1 -0
  75. package/lib/sample.js +32 -0
  76. package/lib/sample.js.map +1 -0
  77. package/lib/scorecard.d.ts +4 -0
  78. package/lib/scorecard.js +237 -0
  79. package/lib/scorecard.js.map +1 -0
  80. package/lib/settings.d.ts +5 -0
  81. package/lib/settings.js +18 -0
  82. package/lib/settings.js.map +1 -0
  83. package/lib/types.d.ts +125 -0
  84. package/lib/types.js +7 -0
  85. package/lib/types.js.map +1 -0
  86. package/lib/utils.d.ts +20 -0
  87. package/lib/utils.js +223 -0
  88. package/lib/utils.js.map +1 -0
  89. package/lib/valid.d.ts +5 -0
  90. package/lib/valid.js +230 -0
  91. package/lib/valid.js.map +1 -0
  92. package/main.js +4 -0
  93. package/package.json +2 -7
  94. package/src/_api.ts +121 -0
  95. package/src/_data.ts +595 -0
  96. package/src/analyze.ts +92 -0
  97. package/src/cohesive.ts +156 -0
  98. package/src/compact.ts +208 -0
  99. package/src/constants.ts +371 -0
  100. package/src/equal.ts +75 -0
  101. package/src/geofeature.ts +138 -0
  102. package/src/index.ts +6 -0
  103. package/src/minority.ts +70 -0
  104. package/src/political.ts +114 -0
  105. package/src/preprocess.ts +132 -0
  106. package/src/report.ts +1022 -0
  107. package/src/settings.ts +20 -0
  108. package/src/types.ts +185 -0
  109. package/src/utils.ts +245 -0
  110. package/src/valid.ts +275 -0
  111. package/tsconfig.json +25 -0
  112. package/tslint.json +3 -0
  113. package/types/polygon-clipping/index.d.ts +1 -0
  114. package/webpack.config.js +73 -0
package/src/valid.ts ADDED
@@ -0,0 +1,275 @@
1
+ //
2
+ // MAP/PLAN VALIDATIONS
3
+ //
4
+
5
+ import * as T from './types'
6
+ import * as U from './utils';
7
+ import * as S from './settings';
8
+
9
+ import * as D from './_data'
10
+ import { AnalyticsSession } from './_api';
11
+
12
+ //
13
+ // COMPLETE - Are all geo's assigned to a district, and do all districts have
14
+ // at least one geo assigned to them?
15
+ //
16
+
17
+ export function doIsComplete(s: AnalyticsSession): T.TestEntry {
18
+ let test = s.getTest(T.Test.Complete) as T.TestEntry;
19
+
20
+ let bAllAssigned = true;
21
+ let bNoneEmpty = true;
22
+
23
+ let unassignedFeatures: string[] = [];
24
+ let emptyDistricts: number[] = [];
25
+
26
+ // Get the by district results, including the dummy unassigned district,
27
+ // but ignoring the N+1 summary district
28
+ let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
29
+ bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
30
+
31
+ // Are all features assigned to districts?
32
+ // Check the dummy district that holds any unassigned features.
33
+ bAllAssigned = (!bNotEmptyByDistrict[S.NOT_ASSIGNED]);
34
+ if (!bAllAssigned) {
35
+ let unassignedDistrict = s.plan.geoIDsForDistrictID(S.NOT_ASSIGNED);
36
+ unassignedFeatures = Array.from(unassignedDistrict);
37
+ unassignedFeatures = unassignedFeatures.slice(0, S.NUMBER_OF_ITEMS_TO_REPORT);
38
+ }
39
+
40
+ // Do all real districts have at least one feature assigned to them?
41
+ bNoneEmpty = U.andArray(bNotEmptyByDistrict.slice(1));
42
+
43
+ // Case 1 - One or more districts are missing:
44
+ // The # of enumerated districts minus the dummy NOT_ASSIGNED one should
45
+ // equal the number apportioned districts. This guards against a district
46
+ // not being included in a map that is imported.
47
+ //
48
+ // TODO - I'm no longer checking for this, but DRA should!
49
+
50
+ // Case 2 - Or a district is explicitly named but empty:
51
+ // Note, this can happen if a district is created, and then all features
52
+ // are removed from it (in DRA).
53
+
54
+ // Populate the test entry
55
+ test['score'] = bAllAssigned && bNoneEmpty;
56
+ if (!bAllAssigned) {
57
+ test['details']['unassignedFeatures'] = unassignedFeatures;
58
+ }
59
+
60
+ if (!bNoneEmpty) {
61
+ test['details']['emptyDistricts'] = emptyDistricts;
62
+ }
63
+
64
+ // Populate the N+1 summary "district" in district.statistics
65
+ let bNotEmpty = s.districts.statistics[D.DistrictField.bNotEmpty];
66
+ let summaryRow = s.districts.numberOfRows() - 1;
67
+
68
+ bNotEmpty[summaryRow] = test['score'];
69
+
70
+ return test;
71
+ }
72
+
73
+ //
74
+ // CONTIGUOUS - Is each district in a plan fully connected?
75
+ //
76
+ // NOTE - To check "operational contiguity," we need to use a graph, i.e.,
77
+ // we can't rely on just the geometric contiguity of shapes in a shapefile.
78
+ //
79
+ // To test this, load the NC 2010 map 'SAMPLE-BG-map-discontiguous.csv'.
80
+ //
81
+
82
+ export function doIsContiguous(s: AnalyticsSession): T.TestEntry {
83
+ let test = s.getTest(T.Test.Contiguous) as T.TestEntry;
84
+
85
+ // Get the contiguity of each district. Ignore dummy unassigned district
86
+ // and the N+1 summary district.
87
+ let bContiguousByDistrict = s.districts.statistics[D.DistrictField.bContiguous];
88
+ bContiguousByDistrict = bContiguousByDistrict.slice(1, -1);
89
+
90
+ // If any real districts aren't contiguous, mark the plan as not contiguous
91
+ let bMapContiguous = U.andArray(bContiguousByDistrict);
92
+
93
+ // If the map is not contiguous, log the offending districts.
94
+ let discontiguousDistricts: number[] = [];
95
+ let districtID = 1;
96
+ bContiguousByDistrict.forEach(function (bDistrictContiguous: boolean): void {
97
+ if (!bDistrictContiguous) discontiguousDistricts.push(districtID);
98
+ districtID += 1;
99
+ });
100
+
101
+ // Populate the test entry
102
+ test['score'] = bMapContiguous;
103
+ if (!bMapContiguous) {
104
+ test['details'] = { 'discontiguousDistricts': discontiguousDistricts };
105
+ }
106
+
107
+ // Populate the N+1 summary "district" in district.statistics
108
+ let bContiguous = s.districts.statistics[D.DistrictField.bContiguous];
109
+ let summaryRow = s.districts.numberOfRows() - 1;
110
+
111
+ bContiguous[summaryRow] = test['score'];
112
+
113
+ return test;
114
+ }
115
+
116
+ // Are the features in a district fully connected?
117
+ export function isConnected(districtGeos: Set<string>, graph: D.Graph): boolean {
118
+ // export function isConnected(districtGeos: Set<string>, graph: T.ContiguityGraph): boolean {
119
+ // TODO - Terry, why does this constructor need a <T> type specification?
120
+ let visited = new Set<string>();
121
+ let toProcess: string[] = [];
122
+
123
+ // Start processing with the first geoID in the district
124
+ let iter = districtGeos.values();
125
+ toProcess.push(iter.next().value);
126
+
127
+ // While there are geoIDs in the district that haven't been processed
128
+ while (toProcess.length > 0) {
129
+ // Grab a geoID and process it
130
+ let node = toProcess.pop() as string;
131
+ visited.add(node);
132
+
133
+ // Get its actual, in-state neighbors
134
+ let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
135
+
136
+ // Add neighbors to visit, if they're in the same district Y haven't already been visited
137
+ let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
138
+ // TODO - Terry, is this the quickest/best way to do this?
139
+ toProcess.push(...neighborsToVisit);
140
+ }
141
+
142
+ // Stop when you've visited all the geoIDs in the district
143
+ return visited.size == districtGeos.size;
144
+ }
145
+
146
+
147
+ //
148
+ // FREE OF HOLES - Are any districts fully embedded w/in another district?
149
+ //
150
+ // A district is NOT a "donut hole" district:
151
+ // - If any neighbor is 'OUT_OF_STATE'; or
152
+ // - If there are 2 or more neighboring districts.
153
+ //
154
+ // To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
155
+ // Buncombe County (37021), is a donut hole w/in District 3.
156
+ //
157
+ // TODO - Optimize this to take advantage of district boundary info, if/when
158
+ // we cache one to optimize compactness.
159
+ //
160
+
161
+ export function doIsFreeOfHoles(s: AnalyticsSession): T.TestEntry {
162
+ let test = s.getTest(T.Test.FreeOfHoles) as T.TestEntry;
163
+
164
+ // Initialize values
165
+ let bFreeOfHoles = true;
166
+ let embeddedDistricts: number[] = [];
167
+
168
+ // Get the embeddedness of each district. Ignore dummy unassigned district
169
+ // and the N+1 summary district.
170
+ let bNotEmbeddedByDistrict = s.districts.statistics[D.DistrictField.bNotEmbedded];
171
+ bNotEmbeddedByDistrict = bNotEmbeddedByDistrict.slice(1, -1);
172
+
173
+ let districtID = 1;
174
+ bNotEmbeddedByDistrict.forEach(function (bDistrictNotEmbedded: boolean): void {
175
+ if (!bDistrictNotEmbedded) {
176
+ embeddedDistricts.push(districtID);
177
+ bFreeOfHoles = false;
178
+ }
179
+ districtID += 1;
180
+ });
181
+
182
+ // Populate the test entry
183
+ test['score'] = bFreeOfHoles;
184
+ if (!bFreeOfHoles) {
185
+ test['details'] = { 'embeddedDistricts': embeddedDistricts };
186
+ }
187
+
188
+ // Populate the N+1 summary "district" in district.statistics
189
+ let bNotEmbedded = s.districts.statistics[D.DistrictField.bNotEmbedded];
190
+ let summaryRow = s.districts.numberOfRows() - 1;
191
+
192
+ bNotEmbedded[summaryRow] = test['score'];
193
+
194
+ return test;
195
+ }
196
+
197
+ // Test whether one district is embedded w/in any other.
198
+ export function isEmbedded(districtID: number, geoIDs: Set<string>, plan: D.Plan, graph: D.Graph): boolean {
199
+ // TODO - Make "features" = "geoIDs." These aren't "features" proper, just
200
+ // identifier strings.
201
+ let features = geoIDs;
202
+ let planByGeo = plan.byGeoID();
203
+
204
+ // Assume the district is embedded
205
+ let bEmbedded = true;
206
+ // Keep track of the neighoring districts
207
+ let neighboringDistricts = new Set();
208
+
209
+ // TODO - Use just the boundary features, when available
210
+ // Get the features for the real district
211
+ let featuresToCheck = Array.from(features);
212
+
213
+ // If the district has features, check whether it is embedded
214
+ if (!(U.isArrayEmpty(featuresToCheck))) {
215
+ // For each feature that needs to be checked (see above)
216
+ for (let feature of featuresToCheck) {
217
+ // Get its neighbors (including the virtual "out of state" ones)
218
+ let neighbors = graph.peerNeighbors(feature);
219
+
220
+ for (let neighbor of neighbors) {
221
+ if (U.isOutOfState(neighbor)) {
222
+ bEmbedded = false;
223
+ // No need to check any more neighbors
224
+ break;
225
+ }
226
+ else {
227
+ let neighboringDistrict = U.getDistrict(planByGeo, neighbor);
228
+
229
+ // Assume that a missing district assignment (= None) means that the
230
+ // feature is "water-only" AND part of the state border (vs. internal)
231
+ // and, therefore, not in the plan/map.
232
+
233
+ if (neighboringDistrict == undefined) {
234
+ bEmbedded = false;
235
+ // No need to check any more neighbors
236
+ break;
237
+ }
238
+ else {
239
+ // TODO - Since we're checking *all* features in a district right
240
+ // now, not just boundary features and neighbors in other districts,
241
+ // prune out the current district. If/when we optimize compactness
242
+ // to cache district boundaries (as before in my Python implementation),
243
+ // we won't have to guard adding "neighboring" districts in this way.
244
+ if (neighboringDistrict != districtID) {
245
+ neighboringDistricts.add(neighboringDistrict);
246
+ }
247
+
248
+ if (neighboringDistricts.size > 1) {
249
+ bEmbedded = false;
250
+ // No need to check any more neighbors
251
+ break;
252
+ }
253
+ }
254
+ }
255
+ }
256
+ // If a district is not embedded, there's no need to check anymore
257
+ // border geos.
258
+ if (!bEmbedded) {
259
+ break;
260
+ }
261
+ }
262
+
263
+ }
264
+
265
+ return bEmbedded;
266
+ }
267
+
268
+ // TODO - MIXED MAPS
269
+ // - When we generalize to mixed maps, the determination of "neighbors in
270
+ // the map" -- which is the core function in determining connectedness -- becomes
271
+ // *much* more complicated and dynamic.
272
+ // - I can write up (if not implement) the logic for this. It is a bit tricky
273
+ // and requires special preprocessing of the summary level hierarchy (which I
274
+ // also have a script for that we can repurpose) to distinguish between 'interior'
275
+ // and 'edge' children.
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "declarationDir": "dist/",
7
+ "strict": true,
8
+ "types": [
9
+ "node"
10
+ ],
11
+ "lib": [
12
+ "es2016"
13
+ ],
14
+ "sourceMap": true
15
+ },
16
+ "include": [
17
+ "src/**/*",
18
+ "types/**/*"
19
+ ],
20
+ "exclude": [
21
+ "node_modules",
22
+ "**/_tests__/*",
23
+ "data"
24
+ ]
25
+ }
package/tslint.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["tslint:recommended", "tslint-config-prettier"]
3
+ }
@@ -0,0 +1 @@
1
+ declare module 'polygon-clipping';
@@ -0,0 +1,73 @@
1
+ var path = require('path');
2
+ var fs = require('fs');
3
+ var externalModules = {};
4
+ fs.readdirSync('node_modules/@dra2020')
5
+ .forEach((mod) => {
6
+ mod = '@dra2020/' + mod;
7
+ externalModules[mod] = 'commonjs ' + mod;
8
+ });
9
+
10
+ var libConfig = {
11
+ entry: {
12
+ library: './src/index.ts'
13
+ },
14
+ target: 'node',
15
+ mode: 'development',
16
+ output: {
17
+ library: 'district-analytics',
18
+ libraryTarget: 'umd',
19
+ path: __dirname + '/dist',
20
+ filename: 'district-analytics.js'
21
+ },
22
+
23
+ // Enable source maps
24
+ devtool: "source-map",
25
+
26
+ externals: externalModules,
27
+
28
+ module: {
29
+ rules: [
30
+ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
31
+ { test: /\.json$/, loader: 'json-loader' },
32
+ { test: /\.js$/, enforce: "pre", loader: "source-map-loader" }
33
+ ]
34
+ },
35
+
36
+ resolve: {
37
+ extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
38
+ }
39
+
40
+ };
41
+
42
+ var testConfig = {
43
+ entry: './test/_cli.ts',
44
+ target: 'node',
45
+ mode: 'development',
46
+ output: {
47
+ path: __dirname + '/dist',
48
+ filename: 'cli.js'
49
+ },
50
+
51
+ // Enable source maps
52
+ devtool: "source-map",
53
+
54
+ externals: {
55
+ "yargs": "commonjs yargs"
56
+ },
57
+
58
+
59
+ module: {
60
+ rules: [
61
+ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
62
+ { test: /\.json$/, loader: 'json-loader' },
63
+ { test: /\.js$/, enforce: "pre", loader: "source-map-loader" }
64
+ ]
65
+ },
66
+
67
+ resolve: {
68
+ extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
69
+ }
70
+
71
+ };
72
+
73
+ module.exports = [ libConfig, testConfig ];