Skip to content

Commit e9e6ef0

Browse files
pwolaninclaude
andauthored
2026 data updates (#367)
2026 data update - maybe missing winners of tied elections for committee person Fix committee ID A/B suffix assignment in py code support non-default environment for local dev Co-authored-by: Peter Wolanin <107691+pwolanin@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 906d552 commit e9e6ef0

83 files changed

Lines changed: 31889 additions & 1443 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cypress/support/e2e.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { mount } from 'cypress/vue';
22
import Api from '../../src/api';
3-
import { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN } from '../../src/config'
3+
import { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ENVIRONMENT } from '../../src/config'
44

5-
const api = new Api(CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN)
5+
const api = new Api(CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ENVIRONMENT)
66

77
Cypress.Commands.add('mount', mount);
88
Cypress.Commands.add('fetchLeaders', () => cy.wrap(api.fetchLeaders()));

data-scripts/cli.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def voters(registry_file, turnout_file):
3131
@click.argument('committee_file', type=click.Path())
3232
def committee(committee_file):
3333
"""Cleans committee person file"""
34-
process_committee(committee_file).tojson()
34+
process_committee(committee_file).tojson(indent=4)
3535

3636
@cli.command()
3737
@click.option('--out', '-o', 'output_dir', type=click.Path(),
@@ -94,10 +94,12 @@ def export_leaders_cmd(space_id, api_key, environment_id,
9494
help='Contentful.com Content type ID')
9595
@click.option('--apikey', 'api_key', required=True,
9696
help='Contentful.com API key')
97-
def drop_contentful(space_id, content_type, api_key):
97+
@click.option('--environment', '-e', 'environment_id', default='master',
98+
help='Contentful.com environment ID (default: master)')
99+
def drop_contentful(space_id, content_type, api_key, environment_id):
98100
"""Drops all entries from a contentful.com content type.
99101
Processes 1,000 records at a time."""
100-
process_drop(space_id, content_type, api_key)
102+
process_drop(space_id, content_type, api_key, environment_id)
101103

102104
if __name__ == '__main__':
103105
cli()

data-scripts/committee-id.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
fwrite(STDERR, "UTF-8 BOM not found\n");
3535
}
3636
$row = array_map('trim', str_getcsv($line));
37-
$expected_keys = ['Race', 'Party', 'Name', 'Add1', 'Add2', 'Zip'];
37+
$expected_keys = ['Race', 'Party', 'Name', 'Address', 'Zip'];
3838
if (count(array_intersect($row, $expected_keys)) != count($expected_keys)) {
3939
throw new \Exception('Missing expected header');
4040
}
@@ -59,7 +59,7 @@
5959
$new_row['division'] = (int) $matches[2];
6060
$new_row['party'] = mb_strtolower($row[$map['Party']]);
6161
$new_row['zip'] = $row[$map['Zip']];
62-
$address = $row[$map['Add1']] . ($row[$map['Add2']] ? ', ' . $row[$map['Add2']] : '');
62+
$address = $row[$map['Address']];
6363
$new_row['address'] = mb_convert_case($address, MB_CASE_TITLE);
6464
$new_row['fullName'] = mb_convert_case($row[$map['Name']], MB_CASE_TITLE);
6565
$rows[] = $new_row;

data-scripts/committee.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,47 @@
11
import petl as etl
22

3+
34
def create_ward(row):
5+
# Incoming data like: "21-20 DEM"
46
return int(row['ward_div'].split('-')[0].lstrip('0'))
57

8+
69
def create_division(row):
710
# Delete "DEM" or "REP" from the end of the name
811
# Get characters after -, trim leading zeros, convert to int
912
return int(row['ward_div'].split('-')[1].lstrip('0').strip('DEM').strip('REP'))
1013

14+
1115
def process_committee(filepath):
1216
table = etl.fromcsv(filepath) \
1317
.rename({'Race': 'ward_div',
1418
'Party': 'party',
1519
'Name': 'fullName',
16-
'Add1': 'address',
20+
'Address': 'address',
1721
'Zip': 'zip'}) \
1822
.cut('ward_div', 'party', 'fullName',
1923
'address', 'zip') \
20-
.convert({'fullName': 'title',
21-
'address': 'title'}) \
22-
.convert('party', 'lower') \
24+
.convert({'ward_div': 'strip',
25+
'fullName': lambda v: v.strip().title(),
26+
'address': lambda v: v.strip().title(),
27+
'zip': 'strip'}) \
28+
.convert('party', lambda v: v.strip().lower()) \
2329
.addfield('ward', create_ward, index=0) \
24-
.addfield('division', create_division, index=1) \
25-
.cutout('ward_div')
26-
27-
return table
30+
.addfield('division', create_division, index=1)
31+
32+
# Assign the A/B suffix in a single deterministic pass over the
33+
# materialized rows. petl re-invokes row functions an unspecified number
34+
# of times (tables are lazy and re-iterable), so the first-seen suffix
35+
# cannot be derived from mutable state inside a petl transform -- doing so
36+
# makes every row 'B'. Build the IDs here instead.
37+
seen = set()
38+
rows = []
39+
for rec in etl.dicts(table):
40+
wd = rec['ward_div']
41+
suffix = 'A' if wd not in seen else 'B'
42+
seen.add(wd)
43+
rec['ID'] = (wd + ' ' + suffix).replace(' ', '-')
44+
rows.append(rec)
45+
46+
return etl.fromdicts(rows) \
47+
.cut('ID', 'ward', 'division', 'party', 'fullName', 'address', 'zip')

data-scripts/contentful.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ def process_fetch(space_id, content_type_id, api_key, environment_id='master'):
7272
records.append(record)
7373
return records
7474

75-
def process_drop(space_id, content_type_id, api_key):
75+
def process_drop(space_id, content_type_id, api_key, environment_id='master'):
7676
client = Client(api_key)
77-
content_type = client.content_types(space_id).find(content_type_id)
77+
content_type = client.content_types(space_id, environment_id).find(content_type_id)
7878
entries = content_type.entries().all({ 'limit': 1000 })
7979

8080
for entry in tqdm(entries):

0 commit comments

Comments
 (0)