SEO: hand-curated notable weather event per city (falls back to blurb) (#100)

Auto-sourcing a notable weather event from Wikipedia search proved unreliable
(wrong matches, low coverage), so city_events.py is a small, fact-checked list of
one iconic event per city (Katrina/New Orleans, Harvey/Houston, the 1952 Great
Smog/London, the 2021 heat dome/Seattle, ...) — 31 cities, each with a Wikipedia
link. The city page shows a 'Notable weather in {city}' callout when one exists and
otherwise just the flavor blurb. Tests check the curated slugs are all valid and the
callout renders (and is absent for uncurated cities).
This commit is contained in:
Emi Griffith 2026-07-15 18:23:45 -07:00 committed by GitHub
parent 7adcb3f77b
commit a705c154cd
4 changed files with 148 additions and 1 deletions

119
city_events.py Normal file
View file

@ -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 201920 '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 201011 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)

View file

@ -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",

View file

@ -40,6 +40,13 @@
</section>
{% endif %}
{% if event %}
<section class="city-event">
<h2>Notable weather in {{ name }}</h2>
<p>{{ event.text }}{% if event.url %} <a href="{{ event.url }}" rel="nofollow">Read more →</a>{% endif %}</p>
</section>
{% endif %}
<section class="climate-normals">
<h2>{{ name }} average temperatures by month</h2>
<p>Typical daily high and low, and average precipitation, for each month in {{ display }},

View file

@ -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