From 13000dbb59c1069c990f3d86434ec49b0e3a43b5 Mon Sep 17 00:00:00 2001 From: Gatt Date: Sat, 17 Oct 2026 23:07:55 +1100 Subject: [PATCH 1/2] map uses --- app/api/models/maps.py | 16 +++- app/domain/enums.py | 10 +++ app/domain/parsers/maps.py | 4 +- app/domain/utils/data/maps.csv | 120 ++++++++++++------------- tests/maps/parsers/test_maps_parser.py | 1 + tests/maps/test_maps_route.py | 85 ++++++++++++++++++ 6 files changed, 174 insertions(+), 62 deletions(-) diff --git a/app/api/models/maps.py b/app/api/models/maps.py index b53ca896..1c5bd5df 100644 --- a/app/api/models/maps.py +++ b/app/api/models/maps.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, Field, HttpUrl -from app.domain.enums import MapGamemode, MapKey +from app.domain.enums import MapGamemode, MapKey, MapUse class Map(BaseModel): @@ -21,6 +21,20 @@ class Map(BaseModel): ..., description="Main gamemodes on which the map is playable", ) + uses: list[MapUse] = Field( + ..., + min_length=1, + max_length=5, + description="Uses of the map: standard play, Stadium, Workshop, Arcade, or training", + examples=[ + ["standard"], + ["stadium"], + ["standard", "stadium"], + ["workshop"], + ["arcade"], + ["training"], + ], + ) location: str = Field( ..., description="Location of the map", diff --git a/app/domain/enums.py b/app/domain/enums.py index 6369ac88..2ccf8ac1 100644 --- a/app/domain/enums.py +++ b/app/domain/enums.py @@ -89,6 +89,16 @@ class HeroGamemode(StrEnum): STADIUM = "stadium" +class MapUse(StrEnum): + """Categories of play supported by Overwatch maps.""" + + ARCADE = "arcade" + STANDARD = "standard" + STADIUM = "stadium" + TRAINING = "training" + WORKSHOP = "workshop" + + # Dynamically create the MapKey enum by using the CSV File maps_data = read_csv_file("maps") MapKey = StrEnum( diff --git a/app/domain/parsers/maps.py b/app/domain/parsers/maps.py index 240cdf3a..366cf825 100644 --- a/app/domain/parsers/maps.py +++ b/app/domain/parsers/maps.py @@ -14,7 +14,8 @@ def parse_maps_csv() -> list[dict]: Parse maps list from CSV file Returns: - List of map dicts with keys: key, name, screenshot, gamemodes, location, country_code + List of map dicts with keys: key, name, screenshot, gamemodes, uses, + location, country_code """ csv_data = read_csv_file("maps") @@ -24,6 +25,7 @@ def parse_maps_csv() -> list[dict]: "name": map_dict["name"], "screenshot": get_static_url_maps(map_dict["key"]), "gamemodes": map_dict["gamemodes"].split(","), + "uses": map_dict["uses"].split(","), "location": map_dict["location"], "country_code": map_dict.get("country_code") or None, } diff --git a/app/domain/utils/data/maps.csv b/app/domain/utils/data/maps.csv index 81d0ce1e..e0caa30d 100644 --- a/app/domain/utils/data/maps.csv +++ b/app/domain/utils/data/maps.csv @@ -1,60 +1,60 @@ -key,name,gamemodes,location,country_code -aatlis,Aatlis,flashpoint,Morocco,MA -antarctic-peninsula,Antarctic Peninsula,control,Antarctica,AQ -anubis,Temple of Anubis,assault,"Giza Plateau, Egypt",EG -arena-victoriae,Arena Victoriae,control,"Colosseo, Rome, Italy",IT -ayutthaya,Ayutthaya,capture-the-flag,Thailand,TH -black-forest,Black Forest,elimination,Germany,DE -blizzard-world,Blizzard World,hybrid,"Irvine, California, United States",US -busan,Busan,control,South Korea,KR -castillo,Castillo,elimination,Mexico,MX -chateau-guillard,Château Guillard,"deathmatch,team-deathmatch","Annecy, France",FR -circuit-royal,Circuit Royal,escort,"Monte Carlo, Monaco",MC -colosseo,Colosseo,push,"Rome, Italy",IT -dorado,Dorado,escort,Mexico,MX -ecopoint-antarctica,Ecopoint: Antarctica,elimination,Antarctica,AQ -eichenwalde,Eichenwalde,hybrid,"Stuttgart, Germany",DE -esperanca,Esperança,push,Portugal,PT -gogadoro,Gogadoro,control,"Busan, South Korea",KR -hanamura,Hanamura,assault,"Tokyo, Japan",JP -hanaoka,Hanaoka,clash,"Tokyo, Japan",JP -havana,Havana,escort,"Havana, Cuba",CU -hollywood,Hollywood,hybrid,"Los Angeles, United States",US -horizon,Horizon Lunar Colony,assault,Earth's moon, -ilios,Ilios,control,Greece,GR -junkertown,Junkertown,escort,Central Australia,AU -lijiang-tower,Lijiang Tower,control,China,CN -kanezaka,Kanezaka,"deathmatch,team-deathmatch","Tokyo, Japan",JP -kings-row,King’s Row,hybrid,"London, United Kingdom",UK -malevento,Malevento,"deathmatch,team-deathmatch",Italy,IT -midtown,Midtown,hybrid,"New York, United States",US -necropolis,Necropolis,elimination,Egypt,EG -nepal,Nepal,control,Nepal,NP -neon-junction,Neon Junction,hybrid,Japan,JP -new-junk-city,New Junk City,flashpoint,Central Australia,AU -new-queen-street,New Queen Street,push,"Toronto, Canada",CA -numbani,Numbani,hybrid,Numbani (near Nigeria), -oasis,Oasis,control,Iraq,IQ -paraiso,Paraíso,hybrid,"Rio de Janeiro, Brazil",BR -paris,Paris,assault,"Paris, France",FR -petra,Petra,"deathmatch,team-deathmatch",Southern Jordan,JO -place-lacroix,Place Lacroix,push,"Paris, France",FR -powder-keg-mine,Powder Keg Mine,payload-race,"Deadlock Gorge, Arizona, United States",US -practice-range,Practice Range,practice-range,Swiss HQ,CH -redwood-dam,Redwood Dam,push,Gibraltar,GI -rialto,Rialto,escort,"Venice, Italy",IT -route-66,Route 66,escort,"Albuquerque, New Mexico, United States",US -runasapi,Runasapi,push,Peru,PE -samoa,Samoa,control,Samoa,WS -shambali-monastery,Shambali Monastery,escort,Nepal,NP -suravasa,Suravasa,flashpoint,India,IN -thames-district,Thames District,payload-race,"London, United Kingdom",UK -throne-of-anubis,Throne of Anubis,clash,"Giza Plateau, Egypt",EG -volskaya,Volskaya Industries,assault,"St. Petersburg, Russia",RU -watchpoint-gibraltar,Watchpoint: Gibraltar,escort,Gibraltar,GI -watchpoint-grimsvotn,Watchpoint: Grimsvotn,escort,Iceland,IS -workshop-chamber,Workshop Chamber,workshop,Earth, -workshop-expanse,Workshop Expanse,workshop,Earth, -workshop-green-screen,Workshop Green Screen,workshop,Earth, -workshop-island,Workshop Island,workshop,Earth, -wuxing-university,Wuxing University,control,"Chengdu, Sichuan, China",CN +key,name,gamemodes,location,country_code,uses +aatlis,Aatlis,flashpoint,Morocco,MA,standard +antarctic-peninsula,Antarctic Peninsula,control,Antarctica,AQ,standard +anubis,Temple of Anubis,assault,"Giza Plateau, Egypt",EG,arcade +arena-victoriae,Arena Victoriae,control,"Colosseo, Rome, Italy",IT,stadium +ayutthaya,Ayutthaya,capture-the-flag,Thailand,TH,arcade +black-forest,Black Forest,elimination,Germany,DE,arcade +blizzard-world,Blizzard World,hybrid,"Irvine, California, United States",US,standard +busan,Busan,control,South Korea,KR,"standard,stadium" +castillo,Castillo,elimination,Mexico,MX,arcade +chateau-guillard,Château Guillard,"deathmatch,team-deathmatch","Annecy, France",FR,arcade +circuit-royal,Circuit Royal,escort,"Monte Carlo, Monaco",MC,standard +colosseo,Colosseo,push,"Rome, Italy",IT,"standard,stadium" +dorado,Dorado,escort,Mexico,MX,standard +ecopoint-antarctica,Ecopoint: Antarctica,elimination,Antarctica,AQ,arcade +eichenwalde,Eichenwalde,hybrid,"Stuttgart, Germany",DE,standard +esperanca,Esperança,push,Portugal,PT,"standard,stadium" +gogadoro,Gogadoro,control,"Busan, South Korea",KR,stadium +grimsvotn,Grímsvötn,escort,Iceland,IS,standard +hanamura,Hanamura,assault,"Tokyo, Japan",JP,arcade +hanaoka,Hanaoka,clash,"Tokyo, Japan",JP,arcade +havana,Havana,escort,"Havana, Cuba",CU,standard +hollywood,Hollywood,hybrid,"Los Angeles, United States",US,standard +horizon,Horizon Lunar Colony,assault,Earth's moon,,arcade +ilios,Ilios,control,Greece,GR,"standard,stadium" +junkertown,Junkertown,escort,Central Australia,AU,standard +lijiang-tower,Lijiang Tower,control,China,CN,"standard,stadium" +kanezaka,Kanezaka,"deathmatch,team-deathmatch","Tokyo, Japan",JP,arcade +kings-row,King’s Row,hybrid,"London, United Kingdom",UK,standard +malevento,Malevento,"deathmatch,team-deathmatch",Italy,IT,arcade +midtown,Midtown,hybrid,"New York, United States",US,standard +necropolis,Necropolis,elimination,Egypt,EG,arcade +nepal,Nepal,control,Nepal,NP,"standard,stadium" +neon-junction,Neon Junction,hybrid,Japan,JP,standard +new-junk-city,New Junk City,flashpoint,Central Australia,AU,standard +new-queen-street,New Queen Street,push,"Toronto, Canada",CA,standard +numbani,Numbani,hybrid,Numbani (near Nigeria),,standard +oasis,Oasis,control,Iraq,IQ,"standard,stadium" +paraiso,Paraíso,hybrid,"Rio de Janeiro, Brazil",BR,standard +paris,Paris,assault,"Paris, France",FR,arcade +petra,Petra,"deathmatch,team-deathmatch",Southern Jordan,JO,arcade +place-lacroix,Place Lacroix,push,"Paris, France",FR,stadium +powder-keg-mine,Powder Keg Mine,payload-race,"Deadlock Gorge, Arizona, United States",US,stadium +practice-range,Practice Range,practice-range,Swiss HQ,CH,training +redwood-dam,Redwood Dam,push,Gibraltar,GI,stadium +rialto,Rialto,escort,"Venice, Italy",IT,standard +route-66,Route 66,escort,"Albuquerque, New Mexico, United States",US,standard +runasapi,Runasapi,push,Peru,PE,standard +samoa,Samoa,control,Samoa,WS,"standard,stadium" +shambali-monastery,Shambali Monastery,escort,Nepal,NP,standard +suravasa,Suravasa,flashpoint,India,IN,standard +thames-district,Thames District,payload-race,"London, United Kingdom",UK,stadium +throne-of-anubis,Throne of Anubis,clash,"Giza Plateau, Egypt",EG,arcade +volskaya,Volskaya Industries,assault,"St. Petersburg, Russia",RU,arcade +watchpoint-gibraltar,Watchpoint: Gibraltar,escort,Gibraltar,GI,standard +workshop-chamber,Workshop Chamber,workshop,Earth,,workshop +workshop-expanse,Workshop Expanse,workshop,Earth,,workshop +workshop-green-screen,Workshop Green Screen,workshop,Earth,,workshop +workshop-island,Workshop Island,workshop,Earth,,workshop +wuxing-university,Wuxing University,control,"Chengdu, Sichuan, China",CN,stadium diff --git a/tests/maps/parsers/test_maps_parser.py b/tests/maps/parsers/test_maps_parser.py index 468b4440..83046f96 100644 --- a/tests/maps/parsers/test_maps_parser.py +++ b/tests/maps/parsers/test_maps_parser.py @@ -19,6 +19,7 @@ def test_parse_maps_csv_entry_format(): "name", "screenshot", "gamemodes", + "uses", "location", "country_code", } diff --git a/tests/maps/test_maps_route.py b/tests/maps/test_maps_route.py index efee0591..66a3fdbf 100644 --- a/tests/maps/test_maps_route.py +++ b/tests/maps/test_maps_route.py @@ -35,3 +35,88 @@ def test_get_maps_filter_by_gamemode(client: TestClient, gamemode: MapGamemode): def test_get_maps_invalid_gamemode(client: TestClient): response = client.get("/maps", params={"gamemode": "invalid"}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT + + +@pytest.mark.parametrize( + ("key", "expected_uses"), + [ + ("aatlis", ["standard"]), + ("antarctic-peninsula", ["standard"]), + ("blizzard-world", ["standard"]), + ("circuit-royal", ["standard"]), + ("dorado", ["standard"]), + ("eichenwalde", ["standard"]), + ("grimsvotn", ["standard"]), + ("havana", ["standard"]), + ("hollywood", ["standard"]), + ("junkertown", ["standard"]), + ("kings-row", ["standard"]), + ("midtown", ["standard"]), + ("neon-junction", ["standard"]), + ("new-junk-city", ["standard"]), + ("new-queen-street", ["standard"]), + ("numbani", ["standard"]), + ("paraiso", ["standard"]), + ("rialto", ["standard"]), + ("route-66", ["standard"]), + ("runasapi", ["standard"]), + ("shambali-monastery", ["standard"]), + ("suravasa", ["standard"]), + ("watchpoint-gibraltar", ["standard"]), + ("anubis", ["arcade"]), + ("hanamura", ["arcade"]), + ("horizon", ["arcade"]), + ("paris", ["arcade"]), + ("volskaya", ["arcade"]), + ("practice-range", ["training"]), + ("ayutthaya", ["arcade"]), + ("black-forest", ["arcade"]), + ("castillo", ["arcade"]), + ("chateau-guillard", ["arcade"]), + ("ecopoint-antarctica", ["arcade"]), + ("kanezaka", ["arcade"]), + ("malevento", ["arcade"]), + ("necropolis", ["arcade"]), + ("petra", ["arcade"]), + ("workshop-chamber", ["workshop"]), + ("workshop-expanse", ["workshop"]), + ("workshop-green-screen", ["workshop"]), + ("workshop-island", ["workshop"]), + ("arena-victoriae", ["stadium"]), + ("gogadoro", ["stadium"]), + ("place-lacroix", ["stadium"]), + ("redwood-dam", ["stadium"]), + ("powder-keg-mine", ["stadium"]), + ("thames-district", ["stadium"]), + ("busan", ["standard", "stadium"]), + ("ilios", ["standard", "stadium"]), + ("lijiang-tower", ["standard", "stadium"]), + ("nepal", ["standard", "stadium"]), + ("oasis", ["standard", "stadium"]), + ("samoa", ["standard", "stadium"]), + ("wuxing-university", ["stadium"]), + ("colosseo", ["standard", "stadium"]), + ("esperanca", ["standard", "stadium"]), + ("hanaoka", ["arcade"]), + ("throne-of-anubis", ["arcade"]), + ], +) +def test_get_maps_uses(client: TestClient, key: str, expected_uses: list[str]) -> None: + url = "/maps" + + response = client.get(url) + + assert response.status_code == status.HTTP_200_OK + assert next(m for m in response.json() if m["key"] == key)["uses"] == expected_uses + assert all( + m["uses"] + in ( + ["standard"], + ["stadium"], + ["standard", "stadium"], + ["workshop"], + ["arcade"], + ["training"], + ) + for m in response.json() + ) From a86bcf92f3e4c556c2112f2e0485f976158198de Mon Sep 17 00:00:00 2001 From: Gatt Date: Sat, 17 Oct 2026 23:13:13 +1100 Subject: [PATCH 2/2] rename grimsvotn image --- .../{watchpoint-grimsvotn.jpg => grimsvotn.jpg} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename static/maps/{watchpoint-grimsvotn.jpg => grimsvotn.jpg} (100%) diff --git a/static/maps/watchpoint-grimsvotn.jpg b/static/maps/grimsvotn.jpg similarity index 100% rename from static/maps/watchpoint-grimsvotn.jpg rename to static/maps/grimsvotn.jpg