Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New sica lu api #2766

Merged
merged 4 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,88 +1,138 @@
import datetime
from typing import TypedDict

import requests
from waste_collection_schedule import Collection
from waste_collection_schedule.exceptions import SourceArgumentNotFoundWithSuggestions

TITLE = "SICA"
DESCRIPTION = "Source script for sica.lu served municipalities"
URL = "https://sica.lu"
TEST_CASES = {
"Bertrange": {"municipality": "Bertrange"},
"Capellen": {"municipality": "capellen"},
"Garnich": {"municipality": "Garnich"},
"Holzem": {"municipality": "holzem"},
# For testing purposes:
# "Kehlen": {"municipality": "Kehlen"},
# "Koerich": {"municipality": "Koerich"},
# "Kopstal": {"municipality": "Kopstal"},
# "Mamer": {"municipality": "Mamer"},
# "Septfontaines": {"municipality": "Septfontaines"},
# "Steinfort": {"municipality": "Steinfort"},
"Habscht": {"municipality": "habscht"},
"Steinfort": {"municipality": "Steinfort"},
}

API_URL = "http://sicaapp.lu/wp-json/wp/v2"
BASE_URL = "https://dashboard.sicaapp.lu"
API_URL = f"{BASE_URL}/api/api/app"
HEADERS = {"User-Agent": "SicaAPP", "Accept": "application/json"}
ICON_MAP = {
"Residual waste": "mdi:trash-can",
"Valorlux packaging": "mdi:recycle",
"Valorlux - blue bag": "mdi:recycle",
"Organic waste": "mdi:leaf",
"Glass": "mdi:bottle-wine-outline",
"Paper - Cartons": "mdi:newspaper",
"Paper /Carton": "mdi:newspaper",
"Scrap and electrical appliances": "mdi:washing-machine",
"Clothing and Shoes": "mdi:tshirt-crew",
"Trees, shrubs and hedges": "mdi:tree",
"Hedges, Shrubs and Trees": "mdi:tree",
"Bulky waste": "mdi:sofa",
}

MUNICIPALITIES = {
"bertrange": 28,
"capellen": 138,
"garnich": 29,
"holzem": 139,
"kehlen": 30,
"koerich": 31,
"kopstal": 24,
"mamer": 137,
"septfontaines": 26,
"steinfort": 27,
}

class LanguageDict(TypedDict):
en: str
fr: str
de: str
lb: str

class Source:
def __init__(self, municipality: str):
# https://sicaapp.lu/wp-json/wp/v2/locations/

self._municipality = MUNICIPALITIES.get(municipality.lower())
if not self._municipality:
raise ValueError(
f"Unknown municipality: {municipality}, use one of {list(MUNICIPALITIES.keys())}"
)
class MunicipalityData(TypedDict):
id: int
name: LanguageDict
feeData: list[dict[str, LanguageDict]]
isDisabled: bool
parentCommunity_id: int | None
children: list["MunicipalityData"]


class MunicipalitiesResult(TypedDict):
data: list[MunicipalityData]


class PickupType(TypedDict):
id: int
name: LanguageDict
description: str | None
collectionInfo: LanguageDict
color: str
isBookable: bool
isDisabled: bool
icon_id: int
created_at: str
updated_at: str
icon: dict[str, str | None | int]


def fetch(self):
headers = {"User-Agent": "SicaAPP", "Accept": "application/json"}
year = datetime.datetime.now().year
url = f"{API_URL}/calendaryear/{self._municipality}/{year}"
class CollectionEntry(TypedDict):
id: int
date: str
note: None | str
description: LanguageDict | None
isDisabled: bool
status: str
community_id: int
comunity_id: int
creator_id: int
pickupType_id: int
created_at: str
updated_at: str
pickup_type: PickupType

# Retrieve specified municipality from API as JSON

class Source:
def __init__(self, municipality: str) -> None:
self._municipality = municipality.lower()
self._municipality_id: int | None = None

@staticmethod
def _fetch_json(
url: str, headers: dict
) -> MunicipalitiesResult | list[CollectionEntry]:
r = requests.get(url, headers)
if r.status_code != 200:
r.raise_for_status()
try:
data = r.json()
except ValueError as e:
raise ValueError(f"Error decoding JSON from SICA API: {e} - {r.text}")

# Extract collection dates
entries = []
for month in data:
for day in month["schedule"]:
pickup_date = datetime.datetime.strptime(day["date"], "%Y%m%d").date()
for pickup in day["pickupTypes"]:
entries.append(
Collection(
date=pickup_date,
t=pickup["name"],
icon=ICON_MAP.get(pickup["name"]),
picture=pickup["img"],
)
)
raise ValueError(f"Error decoding JSON from API: {e} - {r.text}")
return data

def _get_municipality_id(self) -> None:
url = f"{API_URL}/community"
data = self._fetch_json(url, HEADERS)
if not isinstance(data, dict):
raise ValueError(f"Unexpected data type: {type(data)}")
_municipalities: dict[str, int] = {}
_municipalities.update(
{
item["name"]["en"].lower(): item["id"]
for m in data.get("data", [])
for item in [m] + m.get("children", [])
if not item["isDisabled"] and not item.get("children", [])
}
)
self._municipality_id = _municipalities.get(self._municipality)
if not self._municipality_id:
raise SourceArgumentNotFoundWithSuggestions(
"municipality", self._municipality, list(_municipalities.keys())
)

def fetch(self) -> list[Collection]:
if not self._municipality_id:
self._get_municipality_id()

url = f"{API_URL}/pickup-date"
data = self._fetch_json(url, HEADERS)
if not isinstance(data, list):
raise ValueError(f"Unexpected data type: {type(data)}")
entries = [
Collection(
date=datetime.date.fromisoformat(e["date"].split(" ")[0]),
t=e["pickup_type"]["name"]["en"],
picture=f"{BASE_URL}{e['pickup_type']['icon']['url']}",
icon=ICON_MAP.get(e["pickup_type"]["name"]["en"]),
)
for e in data
if not e["isDisabled"] and e["community_id"] == self._municipality_id
]
return entries
9 changes: 5 additions & 4 deletions doc/source/sica_lu.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ Valid values is one of the following:
- Bertrange
- Capellen
- Garnich
- Habscht
- Holzem
- Kehlen
- Koerich
- Kopstal
- Mamer
- Septfontaines
- Steinfort

## Included collection types

- Bulky waste
- Clothing and Shoes
- Glass
- Organic waste
- Paper - Cartons
- Paper /Carton
- Residual waste
- Scrap and electrical appliances
- Trees, shrubs and hedges
- Valorlux packaging
- Hedges, Shrubs and Trees
- Valorlux - blue bag