diff --git a/tools/events-automation/README.md b/tools/events-automation/README.md new file mode 100644 index 000000000..baa4a3441 --- /dev/null +++ b/tools/events-automation/README.md @@ -0,0 +1,73 @@ +# Event Sink +The maintainer of the Events section is faced with manual work; this process automates generation of a draft events list using various Event Source Modules that gather event information, and an Event Sink that sorts, groups and formats the events into a pre-determined markdown format. + +## Getting Started: +### Pre-Requisites: +- Event sink requires Python3 installation. +- For specific module requirements: `pip install -r requirements.txt` +- See https://geopy.readthedocs.io/en/stable/# for `geopy` module documentation. + +### Running: +Before running please check that all Event Source module function calls are included in `event_list` (function calls should concatenate into a single list of event objects). + +To run this code: + +```py +pip install -r requirements.txt +python3 main.py +``` + +### How to Add a New Event Source Module: +- To write a new event source module, it must be written in python. Event sources should be a function that passes no parameters and returns a list of events with required variables detailed above. If the event source has additional requirements, that should be added to `requirements.txt`. The event source should detail any specific run instructions in it's own documentation. Look at `test_events.py` for bare-minimum event list output. +- To run a new event source, import the new module from new event source and add the function call to `event_list`. + +## Architecture: +### Event Class: +**Required Variables** +- `name` (string): Title of the event. +- `location` (string): Location of the event either in full detail (e.g. `"111 test st, city, country, postcode"`) to be formatted by `geopy` module and `format_location()` in event sink, or in `"city, state/territory, country"` format with state/territory details included if the location is in Australia, Canada or United States. See code sample included below for an example of location formatting. + - Note: If location string is in `"city, state, country"` format, for both state/territory and country ISO alpha-2 codes must be used (e.g. AU for Australia, CA for California). +- `date` (date or datetime): Date of event in the locations local time (NOT the local time of where the program is being run). +- `url` (string): Unique URL for event page details. +- `virtual` (boolean): If event is online. +- `organizerName` (string): Name of event organiser. +- `organizerUrl` (string): URL for event organiser webpage. + +**Additional Variable(s) for Internal Use:** +- `duplicate` (boolean): Flag for potential event duplicate based on evaluation during event sink. + +**Code Sample for Creating an Event:** +```py +Event(name="Test Event", location="Melbourne, VIC, AU", date=date.today(), url="website3.com", virtual=True, organizerName="Test Organizer", organizerUrl="testorg.com") +``` + +### Within Scope of Event Sink: +- The event sink will take a list of event objects (see `test_events.py` for example data), format the date and location data (if not done already), filter out events that are outside of the pre-determined 'date window' for the current TWiR issue then sort the events by date and then location alphabetically. After this process the list is then split via virtual or continent, and any potential duplicate events within the list are flagged (through comparison of event data). Finally, the event sink will output the details of the finalised list of events in a pre-determined markdown format, complete with virtual/continent headers. +- Note that potential duplicate events will be flagged with a `** NOTE POTENTIAL DUPLICATE: **` warning immediately preceding the event information. + + +### Out of Scope: +- The purpose of the event sink is to cross-reference and curate data from various sources. It shouldn't be responsible for gathering or adding required fields of data into the Event class. Any edge cases should be managed by the event sources. + +### Expected Output: +Example Output from `test_events.py` data: +``` +### Virtual: + +* 2024-04-12 | Virtual (Dublin, IE) | [Test Organizer](testorg.com) + *[**Test Event 1**](website1.com) + +### North America: + +* 2024-04-03 | New York, NY, US | [Test Organizer](testorg.com) + *[**Test Event 7**](website7.com) +* 2024-04-04 | San Francisco, CA, US | [Test Organizer](testorg.com) + *[**Test Event 6**](website6.com) +* 2024-04-18 | Indianapolis, IN, US | [Test Organizer](testorg.com) + *[**Test Event 2**](website2.com) + +### Oceania: + +* 2024-04-04 | Sydney, NSW, AU | [Test Organizer](testorg.com) + *[**Test Event 4**](website4.com) +``` diff --git a/tools/events-automation/country_code_to_continent.py b/tools/events-automation/country_code_to_continent.py new file mode 100644 index 000000000..03be89754 --- /dev/null +++ b/tools/events-automation/country_code_to_continent.py @@ -0,0 +1,256 @@ +# Takes an ISO alpha-2 country code and returns the continent. +# Mapping of ISO alpha-2 Country Codes to Continent, from Wikipedia. + +COUNTRY_CODE_TO_CONTINENT = { + 'AB': 'Asia', + 'AD': 'Europe', + 'AE': 'Asia', + 'AF': 'Asia', + 'AG': 'North America', + 'AI': 'North America', + 'AL': 'Europe', + 'AM': 'Asia', + 'AO': 'Africa', + 'AR': 'South America', + 'AS': 'Oceania', + 'AT': 'Europe', + 'AU': 'Oceania', + 'AW': 'North America', + 'AX': 'Europe', + 'AZ': 'Asia', + 'BA': 'Europe', + 'BB': 'North America', + 'BD': 'Asia', + 'BE': 'Europe', + 'BF': 'Africa', + 'BG': 'Europe', + 'BH': 'Asia', + 'BI': 'Africa', + 'BJ': 'Africa', + 'BL': 'North America', + 'BM': 'North America', + 'BN': 'Asia', + 'BO': 'South America', + 'BQ': 'North America', + 'BR': 'South America', + 'BS': 'North America', + 'BT': 'Asia', + 'BV': 'Antarctica', + 'BW': 'Africa', + 'BY': 'Europe', + 'BZ': 'North America', + 'CA': 'North America', + 'CC': 'Asia', + 'CD': 'Africa', + 'CF': 'Africa', + 'CG': 'Africa', + 'CH': 'Europe', + 'CI': 'Africa', + 'CK': 'Oceania', + 'CL': 'South America', + 'CM': 'Africa', + 'CN': 'Asia', + 'CO': 'South America', + 'CR': 'North America', + 'CU': 'North America', + 'CV': 'Africa', + 'CW': 'North America', + 'CX': 'Asia', + 'CY': 'Asia', + 'CZ': 'Europe', + 'DE': 'Europe', + 'DJ': 'Africa', + 'DK': 'Europe', + 'DM': 'North America', + 'DO': 'North America', + 'DZ': 'Africa', + 'EC': 'South America', + 'EE': 'Europe', + 'EG': 'Africa', + 'ER': 'Africa', + 'ES': 'Europe', + 'ET': 'Africa', + 'FI': 'Europe', + 'FJ': 'Oceania', + 'FK': 'South America', + 'FM': 'Oceania', + 'FO': 'Europe', + 'FR': 'Europe', + 'GA': 'Africa', + 'GB': 'Europe', + 'GD': 'North America', + 'GE': 'Asia', + 'GF': 'South America', + 'GG': 'Europe', + 'GH': 'Africa', + 'GI': 'Europe', + 'GL': 'North America', + 'GM': 'Africa', + 'GN': 'Africa', + 'GP': 'North America', + 'GQ': 'Africa', + 'GR': 'Europe', + 'GS': 'South America', + 'GT': 'North America', + 'GU': 'Oceania', + 'GW': 'Africa', + 'GY': 'South America', + 'HK': 'Asia', + 'HM': 'Antarctica', + 'HN': 'North America', + 'HR': 'Europe', + 'HT': 'North America', + 'HU': 'Europe', + 'ID': 'Asia', + 'IE': 'Europe', + 'IL': 'Asia', + 'IM': 'Europe', + 'IN': 'Asia', + 'IO': 'Asia', + 'IQ': 'Asia', + 'IR': 'Asia', + 'IS': 'Europe', + 'IT': 'Europe', + 'JE': 'Europe', + 'JM': 'North America', + 'JO': 'Asia', + 'JP': 'Asia', + 'KE': 'Africa', + 'KG': 'Asia', + 'KH': 'Asia', + 'KI': 'Oceania', + 'KM': 'Africa', + 'KN': 'North America', + 'KP': 'Asia', + 'KR': 'Asia', + 'KW': 'Asia', + 'KY': 'North America', + 'KZ': 'Asia', + 'LA': 'Asia', + 'LB': 'Asia', + 'LC': 'North America', + 'LI': 'Europe', + 'LK': 'Asia', + 'LR': 'Africa', + 'LS': 'Africa', + 'LT': 'Europe', + 'LU': 'Europe', + 'LV': 'Europe', + 'LY': 'Africa', + 'MA': 'Africa', + 'MC': 'Europe', + 'MD': 'Europe', + 'ME': 'Europe', + 'MF': 'North America', + 'MG': 'Africa', + 'MH': 'Oceania', + 'MK': 'Europe', + 'ML': 'Africa', + 'MM': 'Asia', + 'MN': 'Asia', + 'MO': 'Asia', + 'MP': 'Oceania', + 'MQ': 'North America', + 'MR': 'Africa', + 'MS': 'North America', + 'MT': 'Europe', + 'MU': 'Africa', + 'MV': 'Asia', + 'MW': 'Africa', + 'MX': 'North America', + 'MY': 'Asia', + 'MZ': 'Africa', + 'NA': 'Africa', + 'NC': 'Oceania', + 'NE': 'Africa', + 'NF': 'Oceania', + 'NG': 'Africa', + 'NI': 'North America', + 'NL': 'Europe', + 'NO': 'Europe', + 'NP': 'Asia', + 'NR': 'Oceania', + 'NU': 'Oceania', + 'NZ': 'Oceania', + 'OM': 'Asia', + 'OS': 'Asia', + 'PA': 'North America', + 'PE': 'South America', + 'PF': 'Oceania', + 'PG': 'Oceania', + 'PH': 'Asia', + 'PK': 'Asia', + 'PL': 'Europe', + 'PM': 'North America', + 'PR': 'North America', + 'PS': 'Asia', + 'PT': 'Europe', + 'PW': 'Oceania', + 'PY': 'South America', + 'QA': 'Asia', + 'RE': 'Africa', + 'RO': 'Europe', + 'RS': 'Europe', + 'RU': 'Europe', + 'RW': 'Africa', + 'SA': 'Asia', + 'SB': 'Oceania', + 'SC': 'Africa', + 'SD': 'Africa', + 'SE': 'Europe', + 'SG': 'Asia', + 'SH': 'Africa', + 'SI': 'Europe', + 'SJ': 'Europe', + 'SK': 'Europe', + 'SL': 'Africa', + 'SM': 'Europe', + 'SN': 'Africa', + 'SO': 'Africa', + 'SR': 'South America', + 'SS': 'Africa', + 'ST': 'Africa', + 'SV': 'North America', + 'SY': 'Asia', + 'SZ': 'Africa', + 'TC': 'North America', + 'TD': 'Africa', + 'TG': 'Africa', + 'TH': 'Asia', + 'TJ': 'Asia', + 'TK': 'Oceania', + 'TM': 'Asia', + 'TN': 'Africa', + 'TO': 'Oceania', + 'TP': 'Asia', + 'TR': 'Asia', + 'TT': 'North America', + 'TV': 'Oceania', + 'TW': 'Asia', + 'TZ': 'Africa', + 'UA': 'Europe', + 'UG': 'Africa', + 'US': 'North America', + 'UY': 'South America', + 'UZ': 'Asia', + 'VC': 'North America', + 'VE': 'South America', + 'VG': 'North America', + 'VI': 'North America', + 'VN': 'Asia', + 'VU': 'Oceania', + 'WF': 'Oceania', + 'WS': 'Oceania', + 'XK': 'Europe', + 'YE': 'Asia', + 'YT': 'Africa', + 'ZA': 'Africa', + 'ZM': 'Africa', + 'ZW': 'Africa', +} + + +def country_code_to_continent(country_code): + # Returns the continent a country code belongs to. + return COUNTRY_CODE_TO_CONTINENT[country_code] + diff --git a/tools/events-automation/country_to_abbrev.py b/tools/events-automation/country_to_abbrev.py new file mode 100644 index 000000000..6168f18bf --- /dev/null +++ b/tools/events-automation/country_to_abbrev.py @@ -0,0 +1,281 @@ +# Takes a country and returns it's ISO alpha-2 code. + +COUNTRY_TO_ABBREV = { + 'Abkhazia': 'AB', + 'Afghanistan': 'AF', + 'Albania': 'AL', + 'Algeria': 'DZ', + 'American Samoa': 'AS', + 'Andorra': 'AD', + 'Angola': 'AO', + 'Anguilla': 'AI', + 'Antigua and Barbuda': 'AG', + 'Argentina': 'AR', + 'Armenia': 'AM', + 'Aruba': 'AW', + 'Australia': 'AU', + 'Austria': 'AT', + 'Azerbaijan': 'AZ', + 'Bahamas': 'BS', + 'Bahrain': 'BH', + 'Bangladesh': 'BD', + 'Barbados': 'BB', + 'Belarus': 'BY', + 'Belgium': 'BE', + 'Belize': 'BZ', + 'Benin': 'BJ', + 'Bermuda': 'BM', + 'Bhutan': 'BT', + 'Bolivia': 'BO', + 'Bonaire': 'BQ', + 'Bosnia and Herzegovina': 'BA', + 'Botswana': 'BW', + 'Bouvet Island': 'BV', + 'Brazil': 'BR', + 'British Indian Ocean Territory': 'IO', + 'British Virgin Islands': 'VG', + 'Virgin Islands, British': 'VG', + 'Brunei': 'BN', + 'Brunei Darussalam': 'BN', + 'Bulgaria': 'BG', + 'Burkina Faso': 'BF', + 'Burundi': 'BI', + 'Cambodia': 'KH', + 'Cameroon': 'CM', + 'Canada': 'CA', + 'Cape Verde': 'CV', + 'Cayman Islands': 'KY', + 'Central African Republic': 'CF', + 'Chad': 'TD', + 'Chile': 'CL', + 'China': 'CN', + 'Christmas Island': 'CX', + 'Cocos (Keeling) Islands': 'CC', + 'Colombia': 'CO', + 'Comoros': 'KM', + 'Congo': 'CG', + 'Congo, Republic of': 'CG', + 'Republic of the Congo': 'CG', + 'Cook Islands': 'CK', + 'Costa Rica': 'CR', + 'Croatia': 'HR', + 'Cuba': 'CU', + 'Curaçao': 'CW', + 'Cyprus': 'CY', + 'Czech Republic': 'CZ', + 'Congo, Democratic Republic of': 'CD', + 'Democratic Republic of the Congo': 'CD', + 'Denmark': 'DK', + 'Djibouti': 'DJ', + 'Dominica': 'DM', + 'Dominican Republic': 'DO', + 'East Timor': 'TP', + 'Ecuador': 'EC', + 'Egypt': 'EG', + 'El Salvador': 'SV', + 'Equatorial Guinea': 'GQ', + 'Eritrea': 'ER', + 'Estonia': 'EE', + 'Ethiopia': 'ET', + 'Falkland Islands': 'FK', + 'Faroe Islands': 'FO', + 'Fiji': 'FJ', + 'Finland': 'FI', + 'France': 'FR', + 'French Guiana': 'GF', + 'French Polynesia': 'PF', + 'Gabon': 'GA', + 'Gambia': 'GM', + 'Georgia': 'GE', + 'Germany': 'DE', + 'Ghana': 'GH', + 'Gibraltar': 'GI', + 'Greece': 'GR', + 'Greenland': 'GL', + 'Grenada': 'GD', + 'Guadeloupe': 'GP', + 'Great Britain': 'GB', + 'Guam': 'GU', + 'Guatemala': 'GT', + 'Guernsey': 'GG', + 'Guinea': 'GN', + 'Guinea-Bissau': 'GW', + 'Guyana': 'GY', + 'Haiti': 'HT', + 'Heard Island and McDonald Islands': 'HM', + 'Honduras': 'HN', + 'Hong Kong': 'HK', + 'Hungary': 'HU', + 'Iceland': 'IS', + 'India': 'IN', + 'Indonesia': 'ID', + 'Iran': 'IR', + 'Iraq': 'IQ', + 'Ireland': 'IE', + 'Isle of Man': 'IM', + 'Islamic Republic of Iran': 'IR', + 'Israel': 'IL', + 'Italy': 'IT', + 'Ivory Coast': 'CI', + 'Jamaica': 'JM', + 'Japan': 'JP', + 'Jersey': 'JE', + 'Jordan': 'JO', + 'Kazakhstan': 'KZ', + 'Kenya': 'KE', + "Korea, Democratic People's Republic of": 'KP', + 'Kiribati': 'KI', + 'Korea, Republic Of': 'KR', + 'Kosovo': 'XK', + 'Kuwait': 'KW', + 'Kyrgyzstan': 'KG', + 'Laos': 'LA', + "Lao People's Democratic Republic": 'LA', + 'Latvia': 'LV', + 'Lebanon': 'LB', + 'Lesotho': 'LS', + 'Liberia': 'LR', + 'Libya': 'LY', + 'Liechtenstein': 'LI', + 'Lithuania': 'LT', + 'Luxembourg': 'LU', + 'Macau': 'MO', + 'Macedonia': 'MK', + 'Macedonia, The Former Yugoslav Republic Of': 'MK', + 'Madagascar': 'MG', + 'Malawi': 'MW', + 'Malaysia': 'MY', + 'Maldives': 'MV', + 'Mali': 'ML', + 'Malta': 'MT', + 'Marshall Islands': 'MH', + 'Martinique': 'MQ', + 'Mauritania': 'MR', + 'Mauritius': 'MU', + 'Mayotte': 'YT', + 'Mexico': 'MX', + 'Micronesia': 'FM', + 'Micronesia, Federated States of': 'FM', + 'Moldova': 'MD', + 'Moldova, Republic Of': 'MD', + 'Monaco': 'MC', + 'Mongolia': 'MN', + 'Montenegro': 'ME', + 'Montserrat': 'MS', + 'Morocco': 'MA', + 'Mozambique': 'MZ', + 'Myanmar': 'MM', + 'Namibia': 'NA', + 'Nauru': 'NR', + 'Nepal': 'NP', + 'Netherlands': 'NL', + 'New Caledonia': 'NC', + 'New Zealand': 'NZ', + 'Nicaragua': 'NI', + 'Niger': 'NE', + 'Nigeria': 'NG', + 'Niue': 'NU', + 'Norfolk Island': 'NF', + 'North Korea': 'KP', + 'Northern Cyprus': 'CY', + 'Northern Mariana Islands': 'MP', + 'Norway': 'NO', + 'Oman': 'OM', + 'Pakistan': 'PK', + 'Palau': 'PW', + 'Palestine': 'PS', + 'Panama': 'PA', + 'Papua New Guinea': 'PG', + 'Paraguay': 'PY', + 'Peru': 'PE', + 'Philippines': 'PH', + 'Poland': 'PL', + 'Portugal': 'PT', + 'Puerto Rico': 'PR', + 'Qatar': 'QA', + 'Romania': 'RO', + 'Russia': 'RU', + 'Russian Federation': 'RU', + 'Rwanda': 'RW', + 'Réunion': 'RE', + 'Saba': 'BQ', + 'Saint Barthélemy': 'BL', + 'Saint Helena, Ascension and Tristan da Cunha': 'SH', + 'Saint Kitts and Nevis': 'KN', + 'St. Kitts and Nevis': 'KN', + 'Saint Lucia': 'LC', + 'St. Lucia': 'LC', + 'Saint Martin': 'MF', + 'St. Martin': 'MF', + 'Saint Pierre and Miquelon': 'PM', + 'St. Pierre and Miquelon': 'PM', + 'Saint Vincent and the Grenadines': 'VC', + 'St. Vincent and The Grenadines': 'VC', + 'Samoa': 'WS', + 'San Marino': 'SM', + 'Saudi Arabia': 'SA', + 'Senegal': 'SN', + 'Serbia': 'RS', + 'Seychelles': 'SC', + 'Sierra Leone': 'SL', + 'Singapore': 'SG', + 'Sint Eustatius': 'BQ', + 'Slovakia': 'SK', + 'Slovenia': 'SI', + 'Solomon Islands': 'SB', + 'Somalia': 'SO', + 'Somaliland': 'SO', + 'South Africa': 'ZA', + 'South Georgia and the South Sandwich Islands': 'GS', + 'South Korea': 'KR', + 'South Ossetia': 'OS', + 'South Sudan': 'SS', + 'Spain': 'ES', + 'Sri Lanka': 'LK', + 'Sudan': 'SD', + 'Suriname': 'SR', + 'Svalbard': 'SJ', + 'Swaziland': 'SZ', + 'Sweden': 'SE', + 'Switzerland': 'CH', + 'Syria': 'SY', + 'Syrian Arab Republic': 'SY', + 'São Tomé and Príncipe': 'ST', + 'Taiwan': 'TW', + 'Taiwan, Province of China': 'TW', + 'Tajikistan': 'TJ', + 'Tanzania': 'TZ', + 'Tanzania, United Republic Of': 'TZ', + 'Thailand': 'TH', + 'Togo': 'TG', + 'Tokelau': 'TK', + 'Tonga': 'TO', + 'Trinidad and Tobago': 'TT', + 'Tunisia': 'TN', + 'Turkey': 'TR', + 'Turkmenistan': 'TM', + 'Turks and Caicos Islands': 'TC', + 'Turks and Caicos': 'TC', + 'Tuvalu': 'TV', + 'Uganda': 'UG', + 'Ukraine': 'UA', + 'United Arab Emirates': 'AE', + 'United Kingdom': 'GB', + 'United States Virgin Islands': 'VI', + 'United States': 'US', + 'United States of America': 'US', + 'Uruguay': 'UY', + 'Uzbekistan': 'UZ', + 'Vanuatu': 'VU', + 'Venezuela': 'VE', + 'Vietnam': 'VN', + 'Wallis and Futuna': 'WF', + 'Yemen': 'YE', + 'Zambia': 'ZM', + 'Zimbabwe': 'ZW', + 'Åland Islands': 'AX', +} + + +def country_to_abbrev(country): + return COUNTRY_TO_ABBREV[country] diff --git a/tools/events-automation/event.py b/tools/events-automation/event.py new file mode 100644 index 000000000..3af3037dd --- /dev/null +++ b/tools/events-automation/event.py @@ -0,0 +1,47 @@ +import datetime +from geopy.geocoders import Nominatim +from state_territory_to_abbrev import au_state_territory_to_abbrev, us_state_to_abbrev, ca_state_territory_to_abbrev + +class Event(): + def __init__(self, name, location, date, url, virtual, organizerName, organizerUrl, duplicate=False) -> None: + self.name = name + self.location = location + self.date = date + self.url = url + self.virtual = virtual + self.organizerName = organizerName + self.organizerUrl = organizerUrl + self.duplicate = duplicate + + def to_markdown_string(self) -> str: + if self.virtual: + return f'* {self.date} | Virtual ({self.location}) | [{self.organizerName}]({self.organizerUrl})\n\t*[**{self.name}**]({self.url})' + else: + return f'* {self.date} | {self.location} | [{self.organizerName}]({self.organizerUrl})\n\t*[**{self.name}**]({self.url})' + + def format_date(self): + # Formats datetime data into date. + if isinstance(self.date, datetime.datetime): + self.date = self.date.date() + + def format_location(self): + # Formats location data into (city, +/-state, country). + geocoder = Nominatim(user_agent="TWiR", timeout=5) + locationData = geocoder.geocode(self.location, language="en", addressdetails=True).raw["address"] + + country_code, city = locationData["country_code"].upper(), locationData.get("city", locationData.get("town", locationData.get("village", "**NO CITY DATA**"))) + if country_code in ["AU", "CA", "US"]: + state = locationData.get("state", locationData.get("territory", "**NO STATE DATA**")) + if state == "**NO STATE DATA**": + state_abbrev = state + elif country_code == "AU": + state_abbrev = au_state_territory_to_abbrev(state) + elif country_code == "CA": + state_abbrev = ca_state_territory_to_abbrev(state) + elif country_code == "US": + state_abbrev = us_state_to_abbrev(state) + self.location = f'{city}, {state_abbrev}, {country_code}' + else: + self.location = f'{city}, {country_code}' + + diff --git a/tools/events-automation/main.py b/tools/events-automation/main.py new file mode 100644 index 000000000..fde3ceb6d --- /dev/null +++ b/tools/events-automation/main.py @@ -0,0 +1,102 @@ +# import all the event sources & event sink +# collect all the events from the event sources +# call event sink with our collected events +# print to console / output to file formatted markdown + +from test_events import get_test_events +from datetime import date, timedelta +from country_code_to_continent import country_code_to_continent + +# TODO: Flagged events list handling. + +def main(): + # Get Events list from Event Sources. + event_list = get_test_events() + + # Format date and location data. + format_data(event_list) + + # Remove events outside of date range. + date_window_filter(event_list) + + # Sort remaining events by date, then location. + event_list.sort(key=lambda event: (event.date, event.location)) + + # Flag potential duplicate events. + potential_duplicate(event_list) + + # Group by virtual or by continent. + event_list = group_virtual_continent(event_list) + + # Output Sorted Event List. + output_to_screen(event_list) + + +def output_to_screen(event_list): + # Outputs sorted Event List to terminal screen. + # Output Virtual Events: + if "Virtual" in event_list: + print("### Virtual:\n") + output_event_details(event_list["Virtual"]) + del event_list["Virtual"] + + # Output Non-Virtual Events: + for key, value in dict(sorted(event_list.items())).items(): + print(f'### {key}:\n') + output_event_details(value) + + +def output_event_details(event_group): + # Outputs event details + for event in event_group: + if event.duplicate: + print("** NOTE POTENTIAL DUPLICATE: **") + print(event.to_markdown_string()) + print() + + +def format_data(event_list): + # Formats date and location data into specified format. + for event in event_list: + event.format_date() + event.format_location() + + +def date_window_filter(event_list): + # Removes Events that are outside current date window. + # Date window = closest wednesday + 5 weeks. + start_date = date.today() + while start_date.weekday() != 2: + start_date = start_date + timedelta(days=1) + + for event in event_list: + if not (start_date <= event.date <= start_date + timedelta(weeks=5)): + event_list.remove(event) + + +def group_virtual_continent(event_list): + # Return dictionary of events separated in virtual and by continent. + separated_event_list = {} + + for event in event_list: + # Separates Events by Virtual or by Continent + key = "Virtual" if event.virtual else country_code_to_continent(event.location[-2:]) + separated_event_list.setdefault(key, []).append(event) + + return separated_event_list + + +def potential_duplicate(event_list): + # Identifies possible duplicate Events within Event List. + for i in range(len(event_list)): + for j in range(i+1, len(event_list)): + if event_list[i].date == event_list[j].date and \ + event_list[i].url == event_list[j].url and \ + event_list[i].name == event_list[j].name and \ + event_list[i].organizerName == event_list[j].organizerName and \ + event_list[i].location == event_list[j].location: + event_list[i].duplicate = True + + +if __name__ == "__main__": + main() diff --git a/tools/events-automation/requirements.txt b/tools/events-automation/requirements.txt new file mode 100644 index 000000000..ac07547e8 --- /dev/null +++ b/tools/events-automation/requirements.txt @@ -0,0 +1 @@ +geopy diff --git a/tools/events-automation/state_territory_to_abbrev.py b/tools/events-automation/state_territory_to_abbrev.py new file mode 100644 index 000000000..3437a3508 --- /dev/null +++ b/tools/events-automation/state_territory_to_abbrev.py @@ -0,0 +1,106 @@ +# Returns the abbreviated version of state/territory name for AU, CA, and US. +# Information from Wikipedia. + +US_STATE_TO_ABBREV = { + "Alabama": "AL", + "Alaska": "AK", + "Arizona": "AZ", + "Arkansas": "AR", + "California": "CA", + "Colorado": "CO", + "Connecticut": "CT", + "Delaware": "DE", + "Florida": "FL", + "Georgia": "GA", + "Hawaii": "HI", + "Idaho": "ID", + "Illinois": "IL", + "Indiana": "IN", + "Iowa": "IA", + "Kansas": "KS", + "Kentucky": "KY", + "Louisiana": "LA", + "Maine": "ME", + "Maryland": "MD", + "Massachusetts": "MA", + "Michigan": "MI", + "Minnesota": "MN", + "Mississippi": "MS", + "Missouri": "MO", + "Montana": "MT", + "Nebraska": "NE", + "Nevada": "NV", + "New Hampshire": "NH", + "New Jersey": "NJ", + "New Mexico": "NM", + "New York": "NY", + "North Carolina": "NC", + "North Dakota": "ND", + "Ohio": "OH", + "Oklahoma": "OK", + "Oregon": "OR", + "Pennsylvania": "PA", + "Rhode Island": "RI", + "South Carolina": "SC", + "South Dakota": "SD", + "Tennessee": "TN", + "Texas": "TX", + "Utah": "UT", + "Vermont": "VT", + "Virginia": "VA", + "Washington": "WA", + "West Virginia": "WV", + "Wisconsin": "WI", + "Wyoming": "WY", + "District of Columbia": "DC", + "American Samoa": "AS", + "Guam": "GU", + "Northern Mariana Islands": "MP", + "Puerto Rico": "PR", + "United States Minor Outlying Islands": "UM", + "U.S. Virgin Islands": "VI", + "**NO STATE DATA**": "**NO STATE DATA**", +} + +CA_STATE_TERRITORY_TO_ABBREV = { + "Alberta": "AB", + "British Columbia": "BC", + "Manitoba": "MB", + "New Brunswick": "NB", + "Newfoundland and Labrador": "NL", + "Northwest Territories": "NT", + "Nova Scotia": "NS", + "Nunavut": "NU", + "Ontario": "ON", + "Prince Edward Island": "PE", + "Quebec": "QC", + "Saskatchewan": "SK", + "Yukon": "YT", + "**NO STATE DATA**": "**NO STATE DATA**", +} + +AU_STATE_TERRITORY_TO_ABBREV = { + "Australian Capital Territory": "ACT", + "New South Wales": "NSW", + "Northern Territory": "NT", + "Queensland": "QLD", + "South Australia": "SA", + "Tasmania": "TAS", + "Victoria": "VIC", + "Western Australia": "WA", + "**NO STATE DATA**": "**NO STATE DATA**", +} + +def us_state_to_abbrev(state): + # Returns the abbreviated alpha code for input state. + return US_STATE_TO_ABBREV[state] + + +def ca_state_territory_to_abbrev(state): + # Returns the abbreviated alpha code for input state/territory. + return CA_STATE_TERRITORY_TO_ABBREV[state] + + +def au_state_territory_to_abbrev(state): + # Returns the abbreviated alpha code for input state/territory. + return AU_STATE_TERRITORY_TO_ABBREV[state] diff --git a/tools/events-automation/test_events.py b/tools/events-automation/test_events.py new file mode 100644 index 000000000..bbff39d90 --- /dev/null +++ b/tools/events-automation/test_events.py @@ -0,0 +1,15 @@ +from event import Event +from typing import List +from datetime import date, timedelta + + +def get_test_events() -> List[Event]: + return [Event(name="Test Event 1", location="Dublin, IE", date=date.today() + timedelta(days=2), url="website1.com", virtual=True, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 2", location="Indianapolis, IN, US", date=date.today() + timedelta(weeks=2), url="website2.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 3", location="Melbourne, VIC, AU", date=date.today(), url="website3.com", virtual=True, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 4", location="Sydney, NSW, AU", date=date.today(), url="website4.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 5", location="Melbourne, VIC, AU", date=date.today(), url="website5.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 6", location="San Francisco, CA, US", date=date.today(), url="website6.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 7", location="New York, NY, US", date=date.today() - timedelta(days=1), url="website7.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com"), + Event(name="Test Event 7", location="New York, NY, US", date=date.today() - timedelta(days=1), url="website7.com", virtual=False, organizerName="Test Organizer", organizerUrl="testorg.com") + ]