add Sammelkalender.ch

This commit is contained in:
5ila5
2024-08-19 15:40:09 +02:00
committed by 5ila5
parent 00d684643b
commit 761b1c750f
5 changed files with 375 additions and 1 deletions

View File

@@ -1355,9 +1355,11 @@ If your service provider is not listed, feel free to open a [source request issu
- [Münchenstein](/doc/source/muenchenstein_ch.md) / muenchenstein.ch
- [Münsingen BE, Switzerland](/doc/ics/muensingen_ch.md) / muensingen.ch
- [Real Luzern](/doc/source/real_luzern_ch.md) / real-luzern.ch
- [Real Luzern](/doc/source/sammelkalender_ch.md) / realluzern.ch
- [Rehetobel](/doc/source/a_region_ch.md) / a-region.ch
- [Rorschach](/doc/source/a_region_ch.md) / a-region.ch
- [Rorschacherberg](/doc/source/a_region_ch.md) / a-region.ch
- [Sammelkalender.ch](/doc/source/sammelkalender_ch.md) / info.sammelkalender.ch
- [Schwellbrunn](/doc/source/a_region_ch.md) / a-region.ch
- [Schönengrund](/doc/source/a_region_ch.md) / a-region.ch
- [Speicher](/doc/source/a_region_ch.md) / a-region.ch
@@ -1374,6 +1376,9 @@ If your service provider is not listed, feel free to open a [source request issu
- [Waldstatt](/doc/source/a_region_ch.md) / a-region.ch
- [Wittenbach](/doc/source/a_region_ch.md) / a-region.ch
- [Wolfhalden](/doc/source/a_region_ch.md) / a-region.ch
- [ZAKU Entsorgung](/doc/source/sammelkalender_ch.md) / zaku.ch
- [Zeba](/doc/source/sammelkalender_ch.md) / zebazug.ch
- [ZKRI](/doc/source/sammelkalender_ch.md) / zkri.ch
</details>
<details>

View File

@@ -7341,6 +7341,13 @@
"module": "real_luzern_ch",
"default_params": {}
},
{
"title": "Real Luzern",
"module": "sammelkalender_ch",
"default_params": {
"service_provider": "real_luzern"
}
},
{
"title": "Rehetobel",
"module": "a_region_ch",
@@ -7362,6 +7369,11 @@
"municipality": "Rorschacherberg"
}
},
{
"title": "Sammelkalender.ch",
"module": "sammelkalender_ch",
"default_params": {}
},
{
"title": "Schwellbrunn",
"module": "a_region_ch",
@@ -7473,6 +7485,27 @@
"default_params": {
"municipality": "Wolfhalden"
}
},
{
"title": "ZAKU Entsorgung",
"module": "sammelkalender_ch",
"default_params": {
"service_provider": "zaku"
}
},
{
"title": "Zeba",
"module": "sammelkalender_ch",
"default_params": {
"service_provider": "zeba"
}
},
{
"title": "ZKRI",
"module": "sammelkalender_ch",
"default_params": {
"service_provider": "zkri"
}
}
],
"United Kingdom": [

View File

@@ -0,0 +1,280 @@
from datetime import datetime
from typing import Literal
import requests
from waste_collection_schedule import Collection # type: ignore[attr-defined]
TITLE = "Sammelkalender.ch"
DESCRIPTION = "Source for Sammelkalender.ch."
URL = "https://info.sammelkalender.ch"
TEST_CASES = {
"zeba Baar Aberenrain 10": {
"service_provider": "zeba",
"municipality": "Baar",
"street": "Aberenrain",
"hnr": 10,
},
"zkri Rothenthurm": {
"service_provider": "zkri",
"municipality": "Rothenthurm",
},
"ZKRI, Schwyz Seewen": {
"service_provider": "zkri",
"municipality": "Schwyz",
"street": "Seewen",
},
"Real Luzern, Meggen": {
"service_provider": "real_luzern",
"municipality": "Meggen",
},
"Real Luzern, Malters, Bodenmättli": {
"service_provider": "real_luzern",
"municipality": "Malters",
"street": "Bodenmättli",
},
"Zaku Altdorf dag": {
"service_provider": "zaku",
"municipality": "Altdorf",
"street": "dag",
},
}
ICON_MAP = {
"Kehricht": "mdi:trash-can",
"Christbaum": "mdi:pine-tree",
"Grüngut": "mdi:leaf",
"Karton": "mdi:package-variant-closed",
"Papier": "mdi:newspaper",
"Alteisen/Metall": "mdi:screw-flat-top",
}
SERVICES = {
"zeba": {
"title": "Zeba",
"url": "https://www.zebazug.ch",
"api_url_search": "https://www.zeba.sammelkalender.ch/kunden_WANN_auswahl_form.php",
"api_url_bin": "https://www.zeba.sammelkalender.ch/kunden_sammlung_ausw_cont.php",
},
"zkri": {
"title": "ZKRI",
"url": "https://zkri.ch",
"api_url_search": "https://daten.zkri.ch/web/Sammelkalender/kunden_WANN_auswahl_form.php",
"api_url_bin": "https://daten.zkri.ch/web/Sammelkalender/kunden_sammlung_ausw_cont.php",
},
"real_luzern": {
"title": "Real Luzern",
"url": "https://www.realluzern.ch",
"api_url_search": "https://www.real.sammelkalender.ch/app/appSelect.php",
"api_url_bin": "https://www.real.sammelkalender.ch/app/appSammeldaten.php",
},
"zaku": {
"title": "ZAKU Entsorgung",
"url": "https://www.zaku.ch",
"api_url_search": "https://www.zaku.sammelkalender.ch/appSelect.php",
"api_url_bin": "https://www.zaku.sammelkalender.ch/appSammeldaten.php",
},
}
EXTRA_INFO = [
{
"title": s["title"],
"url": s["url"],
"default_params": {
"service_provider": key,
},
}
for key, s in SERVICES.items()
]
PROVIDER_LITERALS = Literal["zeba", "zkri", "real_luzern", "zaku"]
API_URL = ""
class Source:
def __init__(
self,
service_provider: PROVIDER_LITERALS,
municipality: str,
street: str | None = None,
hnr: str | int | None = None,
) -> None:
service_provider_str = service_provider.lower()
if service_provider not in SERVICES:
raise ValueError(f"Invalid service provider: {service_provider_str}")
self._search_url = SERVICES[service_provider_str]["api_url_search"]
self._bin_url = SERVICES[service_provider_str]["api_url_bin"]
self._municipality: str = municipality
self._street: str | None = street
self._hnr: str = str(hnr)
self._municipality_id: str | None = None
self._address_id: str | None = None
@staticmethod
def _compare(s1: str, s2: str | list[str]) -> bool:
if isinstance(s2, str):
s2 = [s2]
for s in s2:
if s1.lower().replace(" ", "").replace("str.", "straße").replace(
"strasse", "straße"
) == s.lower().replace(" ", "").replace("str.", "straße").replace(
"strasse", "straße"
):
return True
return False
def _get_streets(self, sage: bool = False) -> list[dict]:
params: dict[str, str | int | None] = {
"optGem": self._municipality_id,
"Jahr": None,
}
if sage:
params["fuerSage"] = 1
else:
params["fuerStr"] = 1
r = requests.get(self._search_url, params=params)
r.raise_for_status()
streets = r.json()
if len(streets) == 0:
return []
return streets
def _fetch_sage(self) -> None:
sages = self._get_streets(sage=True)
if not sages:
return
if len(sages) == 1:
self._address_id = sages[0]["SAGEid"]
return
for sage in sages:
if not self._street:
street_names = list(
{s.get("STRname") or s.get("SAGEname") for s in sages}
)
raise ValueError(f"Street required, use one of {street_names}")
if self._compare(self._street, [sage["SAGEabk"], sage["SAGEname"]]):
self._address_id = sage["SAGEid"]
break
if not self._address_id:
raise ValueError(
f"Invalid street: {self._street}, use one of {list({s['SAGEname'] for s in sages})}"
)
def _fetch_street(self) -> None:
streets = self._get_streets()
if not streets:
self._fetch_sage()
return
self._address_id = None
street_matches = []
if len(streets) == 1:
street_matches = streets
else:
if not self._street:
street_names = list(
{s.get("STRname") or s.get("SAGEname") for s in streets}
)
raise ValueError(f"Street required, use one of {street_names}")
for street in streets:
if self._compare(street["STRname"], self._street):
street_matches.append(street)
if not street_matches:
raise ValueError(
f"Invalid street: {self._street}, use one of {list({s['STRname'] for s in streets})}"
)
for street in street_matches:
if street["STRhausnr"] and self._hnr is None:
raise ValueError(
f"House number required, use one of {list({s['STRhausnr'] for s in street_matches})}"
)
if not street["STRhausnr"] or self._compare(street["STRhausnr"], self._hnr):
self._address_id = street["SAGEid"]
break
if not self._address_id:
raise ValueError(
f"Invalid house number: {self._hnr}, use one of {list({s['STRhausnr'] for s in street_matches})}"
)
def _fetch_ids(self) -> None:
params = {
"optGem": "GEM",
"Jahr": None,
}
# get json file
r = requests.get(self._search_url, params=params)
r.raise_for_status()
self._municipality_id = None
for mun in r.json():
if self._compare(mun["GEMname"], self._municipality):
self._municipality_id = mun["GEMid"]
break
if not self._municipality_id:
raise ValueError(
f"Invalid municipality: {self._municipality}, use one of {[m['GEMname'] for m in r.json()]}"
)
self._fetch_street()
def fetch(self) -> list[Collection]:
fresh_ids = False
if self._municipality_id is None:
fresh_ids = True
self._fetch_ids()
try:
return self._get_collections()
except Exception:
if fresh_ids:
raise
self._fetch_ids()
return self._get_collections()
def _get_collections(self) -> list[Collection]:
today = datetime.now()
year = today.year
entries = self._get_collections_year(year)
if today.month == 12:
try:
entries += self._get_collections_year(year + 1)
except Exception:
pass
return entries
def _get_collections_year(self, year: int) -> list[Collection]:
if self._municipality is None:
raise ValueError("Municipality required")
params: dict[str, str | int | None] = {
"nGem": self._municipality_id,
"nAbar": "null",
"nSage": self._address_id,
"sDatum": "",
"delsel": "no",
"jahr1": year,
}
r = requests.get(self._bin_url, params=params)
r.raise_for_status()
entries = []
for d in r.json():
date = datetime.strptime(d["DATUM"], "%Y-%m-%d").date()
bin_types = d["AbarOne"]
if bin_types is None:
continue
for bin_type in bin_types.split("-"):
icon = ICON_MAP.get(bin_type) # Collection icon
entries.append(Collection(date=date, t=bin_type, icon=icon))
return entries

View File

@@ -0,0 +1,56 @@
# Sammelkalender.ch
Support for schedules provided by [Sammelkalender.ch](https://info.sammelkalender.ch), serving multiple regions in Switzerland.
## Configuration via configuration.yaml
```yaml
waste_collection_schedule:
sources:
- name: sammelkalender_ch
args:
service_provider: SERVICE PROVIDER
municipality: MUNICIPALITY (Gemeinde)
street: STREET (Straße)
hnr: HOUSE NUMBER (Hausnummer)
```
### Configuration Variables
**service_provider**
*(String) (required)*
**municipality**
*(String) (required)*
**street**
*(String) (optional)* only required if the form asks for it
**hnr**
*(String) (optional)* only required if the form asks for it
Supported service providers are:
- zeba: https://www.zebazug.ch
- zkri: https://zkri.ch
- real_luzern: https://www.realluzern.ch
- zaku: https://www.zaku.ch
## Example
```yaml
waste_collection_schedule:
sources:
- name: sammelkalender_ch
args:
service_provider: zeba
municipality: Baar
street: Aberenrain
```
## How to get the source argument
You can check if your parameters work by visiting the website of the service provider and entering your address. Or using the app:
- [IOS AppStore](https://apps.apple.com/ch/app/sammelkalender/id1502137213?l)
- [Android PlayStore](https://play.google.com/store/apps/details?id=ch.sammelkalender.app2020)

File diff suppressed because one or more lines are too long