|
| 1 | +import JSZip from "jszip"; |
| 2 | +import {timeParse} from "d3-time-format"; |
| 3 | + |
| 4 | +// Data from https://rcvresults.multco.us/ |
| 5 | +const resultsUrls = [ |
| 6 | + {office: "district 1", resultsNum: 1, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland%20Councilor%20District%201/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland,%20Councilor,%20District%201.json"}, |
| 7 | + {office: "district 1", resultsNum: 2, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland%20Councilor%20District%201/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland,%20Councilor,%20District%201.json"}, |
| 8 | + {office: "district 1", resultsNum: 3, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland%20Councilor%20District%201/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland,%20Councilor,%20District%201.json"}, |
| 9 | + {office: "district 1", resultsNum: 4, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland%20Councilor%20District%201/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland,%20Councilor,%20District%201.json"}, |
| 10 | + {office: "district 1", resultsNum: 5, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland%20Councilor%20District%201/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland,%20Councilor,%20District%201.json"}, |
| 11 | + |
| 12 | + {office: "district 2", resultsNum: 1, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland%20Councilor%20District%202/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland,%20Councilor,%20District%202.json"}, |
| 13 | + {office: "district 2", resultsNum: 2, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland%20Councilor%20District%202/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland,%20Councilor,%20District%202.json"}, |
| 14 | + {office: "district 2", resultsNum: 3, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland%20Councilor%20District%202/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland,%20Councilor,%20District%202.json"}, |
| 15 | + {office: "district 2", resultsNum: 4, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland%20Councilor%20District%202/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland,%20Councilor,%20District%202.json"}, |
| 16 | + {office: "district 2", resultsNum: 5, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland%20Councilor%20District%202/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland,%20Councilor,%20District%202.json"}, |
| 17 | + |
| 18 | + {office: "district 3", resultsNum: 1, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland%20Councilor%20District%203/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland,%20Councilor,%20District%203.json"}, |
| 19 | + {office: "district 3", resultsNum: 2, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland%20Councilor%20District%203/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland,%20Councilor,%20District%203.json"}, |
| 20 | + {office: "district 3", resultsNum: 3, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland%20Councilor%20District%203/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland,%20Councilor,%20District%203.json"}, |
| 21 | + {office: "district 3", resultsNum: 4, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland%20Councilor%20District%203/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland,%20Councilor,%20District%203.json"}, |
| 22 | + {office: "district 3", resultsNum: 5, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland%20Councilor%20District%203/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland,%20Councilor,%20District%203.json"}, |
| 23 | + |
| 24 | + {office: "district 4", resultsNum: 1, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland%20Councilor%20District%204/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland,%20Councilor,%20District%204.json"}, |
| 25 | + {office: "district 4", resultsNum: 2, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland%20Councilor%20District%204/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland,%20Councilor,%20District%204.json"}, |
| 26 | + {office: "district 4", resultsNum: 3, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland%20Councilor%20District%204/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland,%20Councilor,%20District%204.json"}, |
| 27 | + {office: "district 4", resultsNum: 4, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland%20Councilor%20District%204/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland,%20Councilor,%20District%204.json"}, |
| 28 | + {office: "district 4", resultsNum: 5, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland%20Councilor%20District%204/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland,%20Councilor,%20District%204.json"}, |
| 29 | + |
| 30 | + {office: "mayor", resultsNum: 1, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland%20Mayor/a3df36c7-9b95-4614-a357-759ae2ca223f_City%20of%20Portland,%20Mayor.json"}, |
| 31 | + {office: "mayor", resultsNum: 2, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland%20Mayor/8ae97233-9022-48ba-9ff1-07cc5fb12141_City%20of%20Portland,%20Mayor.json"}, |
| 32 | + {office: "mayor", resultsNum: 3, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland%20Mayor/09dd9240-5e25-4fc7-8159-26717db95079_City%20of%20Portland,%20Mayor.json"}, |
| 33 | + {office: "mayor", resultsNum: 4, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland%20Mayor/0e40aff6-a890-4a8a-9bbc-ea6924944cb0_City%20of%20Portland,%20Mayor.json"}, |
| 34 | + {office: "mayor", resultsNum: 5, url: "https://mcdcselectionsrcvprdst.z5.web.core.windows.net/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland%20Mayor/65380232-cc8b-4692-b331-d8792a07891f_City%20of%20Portland,%20Mayor.json"}, |
| 35 | +]; |
| 36 | + |
| 37 | +const timestampParse = timeParse("%Y_%m_%d_%H_%M_%S"); |
| 38 | +function cleanData(data) { |
| 39 | + const dateStr = data.config.contest |
| 40 | + .replace(/district_\d/i, "") |
| 41 | + .replace(/^[^\d]+/i, ""); |
| 42 | + const timestamp = timestampParse(dateStr); |
| 43 | + |
| 44 | + return { |
| 45 | + ...data, |
| 46 | + config: { |
| 47 | + ...data.config, |
| 48 | + threshold: +data.config.threshold, |
| 49 | + timestamp, |
| 50 | + }, |
| 51 | + results: data.results.map(result => ({ |
| 52 | + ...result, |
| 53 | + tally: Object.fromEntries(Object.entries(result.tally).map(([k, v]) => [k, +v])), |
| 54 | + tallyResults: result.tallyResults.map(tallyResult => ({ |
| 55 | + ...tallyResult, |
| 56 | + transfers: Object.fromEntries(Object.entries(tallyResult.transfers).map(([k, v]) => [k, +v])), |
| 57 | + })), |
| 58 | + })), |
| 59 | + }; |
| 60 | +} |
| 61 | + |
| 62 | +const zip = new JSZip(); |
| 63 | +const voteSummaries = []; |
| 64 | + |
| 65 | +for (const {office, resultsNum, url} of resultsUrls) { |
| 66 | + const res = await fetch(url); |
| 67 | + if (!res.ok) throw new Error(`Error with request to ${url}: ${await res.body()}`); |
| 68 | + const rawData = await res.json(); |
| 69 | + const data = cleanData(rawData); |
| 70 | + zip.file(`${office.replace(' ', '_')}/results_${resultsNum}.json`, JSON.stringify(data)); |
| 71 | + |
| 72 | + voteSummaries.push({ |
| 73 | + office, |
| 74 | + resultsNum, |
| 75 | + timestamp: data.config.timestamp, |
| 76 | + totalVotes: Object.values(data.results[0].tally).reduce((acc, d) => acc + d, 0), |
| 77 | + }) |
| 78 | +} |
| 79 | + |
| 80 | +zip.file("summary.json", JSON.stringify(voteSummaries)); |
| 81 | + |
| 82 | +const buf = await zip.generateAsync({type: "nodebuffer"}); |
| 83 | +process.stdout.write(buf); |
0 commit comments