bolster.data_sources.eoni

Northern Ireland Electoral Office (EONI) Election Data Integration.

Data Source: The Electoral Office for Northern Ireland provides official election results and data through their website at https://www.eoni.org.uk. This module accesses NI Assembly election results from 2003 onwards, including constituency-level results, candidate information, and vote tallies for all electoral areas in Northern Ireland.

Update Frequency: Electoral data is updated after each election cycle. NI Assembly elections typically occur every 4-5 years, with the most recent elections in 2022, 2017, and 2016. Historical data remains static once published, with occasional corrections or clarifications.

Example

Retrieve election results for the 2022 NI Assembly election:

>>> from bolster.data_sources import eoni
>>> results_2022 = eoni.get_results(2022)
>>> isinstance(results_2022, dict)
True
>>> len(results_2022) > 0
True
>>> constituency = next(iter(results_2022))
>>> isinstance(constituency, str)
True
>>> 'candidates' in results_2022[constituency]
True

The module supports automated ingestion of NI Assembly election results with constituency-level detail and candidate performance data.

Implementation Status: ✅ 2022, 2017, 2016 elections supported ⏳ 2011, 2007, 2003 elections (planned)

Attributes

logger

Functions

get_page(path)

For a given path (within EONI.org.uk), get the response as a BeautifulSoup instance.

find_xls_links_in_page(page)

Walk through a BeautifulSoup page and iterate through '(XLS)' suffixed links.

normalise_constituencies(cons_str)

Some constituencies change names or cases etc.

get_metadata_from_df(df)

Extract Ballot metadata from the table header(s) of an XLS formatted result sheet, as output from get_excel_dataframe.

get_candidates_from_df(df)

Extract Candidates name and party columns from first stage sheet.

get_stage_votes_from_df(df)

Extract the votes from each stage as a mapped column for each stage, i.e. stages 1...N.

get_stage_transfers_from_df(df)

Extract the transfers from each stage as a mapped column for each stage, i.e. stages 2...N.

extract_stage_n_votes(df, n)

Extract the votes from a given stage N.

extract_stage_n_transfers(df, n)

Extract the votes from a given stage N.

get_results_from_sheet(sheet_url)

Download and parse election results from an Excel sheet URL.

get_results(year)

Get election results for a specific year from EONI website.

validate_election_results(results)

Validate election results data integrity.

Module Contents

bolster.data_sources.eoni.logger[source]
bolster.data_sources.eoni.get_page(path)[source]

For a given path (within EONI.org.uk), get the response as a BeautifulSoup instance.

Note

EONI is trying to block people from scraping and will return a 403 error if you don’t pass a ‘conventional’ user agent

>>> page = get_page("/Elections/")
>>> page.find('title').contents[0].strip()
'Elections | The Electoral Office for Northern Ireland'

Walk through a BeautifulSoup page and iterate through ‘(XLS)’ suffixed links.

(Primarily Used for ‘Results’ pages within given elections)

#WTF Was starting to do some consistency checks between elections to make sure all is kosher, and was wondering why I had a Strangford listing in 2017 but not 2022; # As a cross-check on the result page, I walk the links in the right colum of the page, looking for links that have text that ends (XLS). Pretty simple you might think. Except the Strangford link ends in (XLS and then a random closing ) text string is added to the end.

>>> page = get_page("/results-data/ni-assembly-election-2022-results/")
>>> len(list(find_xls_links_in_page(page)))
18
>>> next(find_xls_links_in_page(page))
'https://www.eoni.org.uk/media/omtlpqow/ni-assembly-election-2022-result-sheet-belfast-east-xls.xlsx'
bolster.data_sources.eoni.normalise_constituencies(cons_str)[source]

Some constituencies change names or cases etc.

Use this function to take external/unconventional inputs and project them into a normalised format.

>>> normalise_constituencies('Newry & Armagh')
'newry and armagh'
bolster.data_sources.eoni.get_metadata_from_df(df)[source]

Extract Ballot metadata from the table header(s) of an XLS formatted result sheet, as output from get_excel_dataframe.

# TODO this could probably be done better as a dataclass

Returns:

dict of

’stage’: int, ‘date’: datetime ‘constituency’: str (lower) ‘eligible_electorate’: int ‘votes_polled’: int ‘number_to_be_elected’: int ‘invalid_votes’: int ‘electoral_quota’: int

Return type:

dict[str, int | str | datetime.datetime]

bolster.data_sources.eoni.get_candidates_from_df(df)[source]

Extract Candidates name and party columns from first stage sheet.

bolster.data_sources.eoni.get_stage_votes_from_df(df)[source]

Extract the votes from each stage as a mapped column for each stage, i.e. stages 1…N.

bolster.data_sources.eoni.get_stage_transfers_from_df(df)[source]

Extract the transfers from each stage as a mapped column for each stage, i.e. stages 2…N.

bolster.data_sources.eoni.extract_stage_n_votes(df, n)[source]

Extract the votes from a given stage N.

Note: This will include trailing, unaligned Nones which must be cleaned up at the Ballot level

bolster.data_sources.eoni.extract_stage_n_transfers(df, n)[source]

Extract the votes from a given stage N.

Note: This will include trailing, unaligned Nones which must be cleaned up at the Ballot level Stage Transfers are associated with the ‘next’ stage, i.e. stage 1 has no transfers

bolster.data_sources.eoni.get_results_from_sheet(sheet_url)[source]

Download and parse election results from an Excel sheet URL.

bolster.data_sources.eoni.get_results(year)[source]

Get election results for a specific year from EONI website.

bolster.data_sources.eoni.validate_election_results(results)[source]

Validate election results data integrity.

Parameters:

results (dict[str, dict]) – Dictionary of election results by constituency

Returns:

True if validation passes, False otherwise

Return type:

bool