diff --git a/city_events.py b/city_events.py new file mode 100644 index 0000000..7fb08a2 --- /dev/null +++ b/city_events.py @@ -0,0 +1,119 @@ +"""Hand-curated "notable weather/climate event" per city. + +Unlike the auto-fetched Wikipedia summaries (cities_flavor.json), these are written +and fact-checked by hand — a keyword search can't reliably tell the *right* iconic +event from a wrong one, so this stays a small, verified, editable list. Cities not +listed here simply show their flavor blurb instead. Add entries as {slug: {text, url}}; +`text` should be one or two accurate sentences, `url` the Wikipedia event article. +""" + +EVENTS: dict[str, dict] = { + # --- United States --- + "new-orleans-louisiana-us": { + "text": "In August 2005, Hurricane Katrina overwhelmed the city's levees and flooded roughly 80% of New Orleans, one of the deadliest and costliest disasters in U.S. history.", + "url": "https://en.wikipedia.org/wiki/Effects_of_Hurricane_Katrina_in_New_Orleans"}, + "houston-texas-us": { + "text": "In August 2017, Hurricane Harvey stalled over the region and dropped more than 50 inches of rain — a record for a U.S. tropical cyclone — causing catastrophic flooding across Houston.", + "url": "https://en.wikipedia.org/wiki/Hurricane_Harvey"}, + "chicago-illinois-us": { + "text": "A brutal heat wave in July 1995 pushed the heat index past 120°F and caused an estimated 700 heat-related deaths in Chicago, reshaping how the city prepares for extreme heat.", + "url": "https://en.wikipedia.org/wiki/1995_Chicago_heat_wave"}, + "new-york-city-new-york-us": { + "text": "In October 2012, Hurricane Sandy drove a record storm surge into New York City, flooding subway tunnels and much of Lower Manhattan and knocking out power to hundreds of thousands.", + "url": "https://en.wikipedia.org/wiki/Hurricane_Sandy"}, + "miami-florida-us": { + "text": "In August 1992, Category 5 Hurricane Andrew devastated the Miami metropolitan area just south of the city, destroying tens of thousands of homes around Homestead.", + "url": "https://en.wikipedia.org/wiki/Hurricane_Andrew"}, + "boston-massachusetts-us": { + "text": "The Blizzard of 1978 paralyzed Boston for a week, dropping about 27 inches of snow, stranding thousands of motorists, and flooding coastal neighborhoods.", + "url": "https://en.wikipedia.org/wiki/Northeastern_United_States_blizzard_of_1978"}, + "seattle-washington-us": { + "text": "During the June 2021 Western North America heat wave, Seattle hit an all-time record 108°F — a temperature the normally mild city had never before approached.", + "url": "https://en.wikipedia.org/wiki/2021_Western_North_America_heat_wave"}, + "portland-oregon-us": { + "text": "In the June 2021 heat dome, Portland reached an all-time record 116°F across three consecutive record-shattering days.", + "url": "https://en.wikipedia.org/wiki/2021_Western_North_America_heat_wave"}, + "phoenix-arizona-us": { + "text": "In July 2023, Phoenix endured a record 31 straight days at or above 110°F during one of the most extreme urban heat waves ever recorded in the U.S.", + "url": "https://en.wikipedia.org/wiki/2023_North_American_heat_waves"}, + "oklahoma-city-oklahoma-us": { + "text": "Deep in Tornado Alley, the Oklahoma City area was struck by the May 3, 1999 tornado, which produced the highest wind speed ever measured on Earth, around 302 mph.", + "url": "https://en.wikipedia.org/wiki/1999_Bridge_Creek%E2%80%93Moore_tornado"}, + + # --- Canada --- + "toronto-ontario-ca": { + "text": "In December 2013, a major ice storm coated Toronto in freezing rain, downing trees and power lines and leaving hundreds of thousands without electricity over the holidays.", + "url": "https://en.wikipedia.org/wiki/December_2013_North_American_storm_complex"}, + "calgary-alberta-ca": { + "text": "The June 2013 Alberta floods, among the costliest disasters in Canadian history, forced the evacuation of downtown Calgary as the Bow and Elbow rivers overflowed.", + "url": "https://en.wikipedia.org/wiki/2013_Alberta_floods"}, + "montreal-quebec-ca": { + "text": "The January 1998 ice storm encased Montreal in ice, collapsing power pylons and leaving parts of the region without electricity for weeks in midwinter.", + "url": "https://en.wikipedia.org/wiki/January_1998_North_American_ice_storm"}, + + # --- UK / Europe --- + "london-england-gb": { + "text": "The Great Smog of December 1952 blanketed London in toxic coal fog for days, killing thousands and prompting the UK's landmark Clean Air Act.", + "url": "https://en.wikipedia.org/wiki/Great_Smog_of_London"}, + "paris-ile-de-france-fr": { + "text": "The August 2003 European heat wave was catastrophic around Paris, contributing to roughly 15,000 heat-related deaths across France.", + "url": "https://en.wikipedia.org/wiki/2003_European_heat_wave"}, + + # --- Australia --- + "sydney-new-south-wales-au": { + "text": "During the 2019–20 'Black Summer' bushfires, hazardous smoke repeatedly blanketed Sydney, pushing its air quality to some of the worst of any major city.", + "url": "https://en.wikipedia.org/wiki/2019%E2%80%9320_Australian_bushfire_season"}, + "melbourne-victoria-au": { + "text": "On 'Black Saturday' in February 2009, extreme heat and wind drove bushfires across Victoria, around Melbourne, that killed 173 people — Australia's deadliest bushfires.", + "url": "https://en.wikipedia.org/wiki/Black_Saturday_bushfires"}, + "brisbane-queensland-au": { + "text": "The 2010–11 Queensland floods peaked in January 2011 when the Brisbane River burst its banks, flooding thousands of homes and businesses across the city.", + "url": "https://en.wikipedia.org/wiki/2010%E2%80%9311_Queensland_floods"}, + + # --- Asia --- + "mumbai-maharashtra-in": { + "text": "On 26 July 2005, a record ~944 mm of rain fell on Mumbai in a single day, paralyzing the city and killing hundreds in the Maharashtra floods.", + "url": "https://en.wikipedia.org/wiki/Maharashtra_floods_of_2005"}, + "chennai-tamil-nadu-in": { + "text": "The December 2015 South Indian floods submerged much of Chennai after its heaviest rains in over a century, displacing hundreds of thousands.", + "url": "https://en.wikipedia.org/wiki/2015_South_India_floods"}, + "kolkata-west-bengal-in": { + "text": "In May 2020, Cyclone Amphan — the most powerful storm recorded in the Bay of Bengal — struck Kolkata directly, uprooting trees and cutting power across the city.", + "url": "https://en.wikipedia.org/wiki/Cyclone_Amphan"}, + "karachi-sindh-pk": { + "text": "A severe heat wave in June 2015 killed an estimated 1,200 people in Karachi as temperatures reached about 45°C during Ramadan.", + "url": "https://en.wikipedia.org/wiki/2015_Pakistani_heat_wave"}, + "manila-national-capital-region-ph": { + "text": "In September 2009, Tropical Storm Ketsana (Ondoy) dumped a month's worth of rain on Metro Manila in hours, causing some of the worst flooding the capital had ever seen.", + "url": "https://en.wikipedia.org/wiki/Tropical_Storm_Ketsana"}, + "jakarta-id": { + "text": "Low-lying, subsiding Jakarta floods most rainy seasons; the January 2020 floods were among the worst, inundating large areas of the capital and displacing tens of thousands.", + "url": "https://en.wikipedia.org/wiki/2020_Jakarta_flood"}, + "bangkok-th": { + "text": "The 2011 Thailand floods, the country's worst in decades, inundated areas around Bangkok for months and disrupted global electronics and auto supply chains.", + "url": "https://en.wikipedia.org/wiki/2011_Thailand_floods"}, + "dhaka-dhaka-division-bd": { + "text": "Low-lying Dhaka floods severely in heavy monsoons; the 1998 floods submerged about two-thirds of Bangladesh and much of the capital for weeks.", + "url": "https://en.wikipedia.org/wiki/1998_Bangladesh_flood"}, + "beijing-cn": { + "text": "On 21 July 2012, Beijing's heaviest rain in six decades triggered sudden flooding that killed at least 79 people and stranded thousands.", + "url": "https://en.wikipedia.org/wiki/2012_Beijing_flood"}, + "hong-kong-hk": { + "text": "Super Typhoon Mangkhut battered Hong Kong in September 2018 with fierce winds and a storm surge, flooding low-lying districts and swaying skyscrapers.", + "url": "https://en.wikipedia.org/wiki/Typhoon_Mangkhut"}, + "tokyo-jp": { + "text": "In October 2019, Typhoon Hagibis — one of the most powerful to strike Japan in decades — brought record rainfall and widespread flooding to the Tokyo region.", + "url": "https://en.wikipedia.org/wiki/Typhoon_Hagibis"}, + + # --- Africa --- + "cape-town-western-cape-za": { + "text": "From 2015 to 2018 a severe drought pushed Cape Town toward 'Day Zero' — the day municipal taps would be shut off — narrowly averted through drastic water conservation.", + "url": "https://en.wikipedia.org/wiki/Cape_Town_water_crisis"}, + "durban-kwazulu-natal-za": { + "text": "In April 2022, extreme rainfall triggered catastrophic floods and landslides around Durban, killing more than 400 people in one of South Africa's deadliest storms.", + "url": "https://en.wikipedia.org/wiki/2022_KwaZulu-Natal_floods"}, +} + + +def get(slug: str) -> dict | None: + return EVENTS.get(slug) diff --git a/content.py b/content.py index c0e2ae8..4f9f218 100644 --- a/content.py +++ b/content.py @@ -16,6 +16,7 @@ from fastapi.responses import PlainTextResponse from jinja2 import Environment, FileSystemLoader, select_autoescape import cities +import city_events import climate import grading import grid @@ -211,6 +212,7 @@ def _city_context(request, city, cell, history) -> dict: # Unique editorial blurb (Wikipedia, CC BY-SA) so the page isn't just templated # stats, and a travel/comfort CTA that pre-fills this city on the compare page. flavor = cities.flavor(city["slug"]) + event = city_events.get(city["slug"]) # hand-curated; None → page falls back to the blurb compare_url = f"{BASE}/compare#loc={city['lat']:.4f},{city['lon']:.4f}" breadcrumb = [("Home", f"{BASE}/"), ("Climate", f"{BASE}/climate")] @@ -247,7 +249,7 @@ def _city_context(request, city, cell, history) -> dict: "year_range": year_range, "n_years": year_range[1] - year_range[0], "months": months, "warmest": warmest, "coldest": coldest, "wettest": wettest, "records": records, "today": _today_vs_normal(history, cell), - "tool_hash": tool_hash, "flavor": flavor, "compare_url": compare_url, + "tool_hash": tool_hash, "flavor": flavor, "event": event, "compare_url": compare_url, "breadcrumb": breadcrumb, "canonical_path": f"/climate/{city['slug']}", "page_title": f"{display} climate: average temperatures, records & how today compares", diff --git a/templates/city.html.j2 b/templates/city.html.j2 index faafad5..e060575 100644 --- a/templates/city.html.j2 +++ b/templates/city.html.j2 @@ -40,6 +40,13 @@ {% endif %} + {% if event %} +
+

Notable weather in {{ name }}

+

{{ event.text }}{% if event.url %} Read more →{% endif %}

+
+ {% endif %} +

{{ name }} average temperatures by month

Typical daily high and low, and average precipitation, for each month in {{ display }}, diff --git a/tests/test_content.py b/tests/test_content.py index 466aeff..b4f3c5e 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -60,6 +60,25 @@ def test_city_404(client): assert client.get(f"{B}/climate/nope-not-a-city").status_code == 404 +def test_curated_events_have_valid_slugs(): + import city_events + slugs = set(cities.all_slugs()) + assert len(city_events.EVENTS) >= 20 + assert all(s in slugs for s in city_events.EVENTS), \ + [s for s in city_events.EVENTS if s not in slugs] + + +def test_curated_event_renders(client): + b = client.get(f"{B}/climate/new-orleans-louisiana-us").text + assert "Notable weather in New Orleans" in b + assert "Hurricane Katrina" in b + + +def test_uncurated_city_has_no_event_section(client): + b = client.get(f"{B}/climate/shanghai-cn").text + assert "city-event" not in b # falls back to the flavor blurb + + def test_city_travel_cta_prefills_compare(client): b = client.get(f"{B}/climate/{SLUG}").text assert "Thinking of visiting" in b