commit 70ba4a0279f44aafb044c0c2328b94a2c91a6402 Author: Eduard Iten Date: Sat Feb 21 11:45:58 2026 +0100 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..400bf61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +.venv/ +env/ +venv/ +.env + +# Configuration / Secrets +# Falls die YAML sensible Daten enthält, hier aufnehmen: +# config.yaml + +# OS +.DS_Store +Thumbs.db diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..2765f62 --- /dev/null +++ b/config.yaml @@ -0,0 +1,12 @@ +google_sheet: + sheet_id: "15_SuNjWruwHcX4_R5OZPYb8Zwa__DrQ56ZcAodPj7QQ" + gid_times: "0" # Meistens 0 für das erste Blatt + gid_remarks: "92621051" # Beispiel-ID deines Bemerkungen-Blatts + date_column: "Datum" + +processing: + days_to_show: 14 + +server: + host: "0.0.0.0" + port: 8000 diff --git a/console.py b/console.py new file mode 100644 index 0000000..b13b4e3 --- /dev/null +++ b/console.py @@ -0,0 +1,28 @@ +from core.data_processor import get_upcoming_events, get_remarks + +def main(): + print("Starte Datenabruf...") + try: + # 1. Termine abrufen + events = get_upcoming_events() + if events: + for event in events: + datum_formatiert = event['Datum'].strftime('%d.%m.%Y') + morgen = event.get('Morgen', '-') + nachmittag = event.get('Nachmittag', '-') + wochentag = event.get('Wochentag', '-') + "." + print(f"{datum_formatiert} ({wochentag}) | Morgen: {morgen} | Nachmittag: {nachmittag}") + + # 2. Bemerkungen abrufen + remarks = get_remarks() + if remarks: + print("\nBemerkungen:") + print("-" * 20) + for r in remarks: + print(f"• {r}") + + except Exception as e: + print(f"Fehler: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/core/config_loader.py b/core/config_loader.py new file mode 100644 index 0000000..7f260fd --- /dev/null +++ b/core/config_loader.py @@ -0,0 +1,23 @@ +import yaml +from pathlib import Path + +CONFIG_PATH = Path(__file__).parent.parent / "config.yaml" + +def load_config(): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: + cfg = yaml.safe_load(f) + + s_id = cfg['google_sheet']['sheet_id'] + gid_t = cfg['google_sheet']['gid_times'] + gid_r = cfg['google_sheet']['gid_remarks'] + + # Wir nutzen exakt das Format, das bei deinem ersten Test funktionierte + cfg['links'] = { + 'times_csv': f"https://docs.google.com/spreadsheets/d/{s_id}/export?format=csv&gid={gid_t}", + 'remarks_csv': f"https://docs.google.com/spreadsheets/d/{s_id}/export?format=csv&gid={gid_r}", + 'times_edit': f"https://docs.google.com/spreadsheets/d/{s_id}/edit#gid={gid_t}", + 'remarks_edit': f"https://docs.google.com/spreadsheets/d/{s_id}/edit#gid={gid_r}" + } + return cfg + +config = load_config() \ No newline at end of file diff --git a/core/data_processor.py b/core/data_processor.py new file mode 100644 index 0000000..2eab065 --- /dev/null +++ b/core/data_processor.py @@ -0,0 +1,67 @@ +import pandas as pd +import requests +from io import StringIO +from datetime import datetime, timedelta +from .config_loader import config + +_cache = {"events": None, "remarks": None} +HEADERS = {"User-Agent": "Mozilla/5.0"} + +def invalidate_cache(): + global _cache + _cache = {"events": None, "remarks": None} + print("DEBUG: Cache wurde manuell geleert.") + return "Cache gelöscht" + +def get_upcoming_events(): + if _cache["events"] is not None: + return _cache["events"] + + url = config['links']['times_csv'] + print(f"DEBUG: Lade Zeiten von URL: {url}") + + response = requests.get(url, headers=HEADERS) + df = pd.read_csv(StringIO(response.text)) + + # Debug: Spaltennamen prüfen + print(f"DEBUG: Spalten in Zeiten-Sheet gefunden: {df.columns.tolist()}") + + date_col = config['google_sheet']['date_column'] + df = df.dropna(subset=[date_col]) + df[date_col] = pd.to_datetime(df[date_col], dayfirst=True, errors='coerce') + df = df.dropna(subset=[date_col]).fillna('') + + heute = pd.Timestamp(datetime.now().date()) + ende = heute + timedelta(days=config['processing']['days_to_show']) + + mask = (df[date_col] >= heute) & (df[date_col] <= ende) + gefilterte = df.loc[mask].sort_values(by=date_col).copy() + + wt_map = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'} + gefilterte['Wochentag'] = gefilterte[date_col].dt.weekday.map(wt_map) + + _cache["events"] = gefilterte.to_dict(orient='records') + return _cache["events"] + +def get_remarks(): + if _cache["remarks"] is not None: + return _cache["remarks"] + + url = config['links']['remarks_csv'] + print(f"DEBUG: Lade Bemerkungen von URL: {url}") + + response = requests.get(url, headers=HEADERS) + + # Debug: Die ersten 100 Zeichen der Antwort sehen + print(f"DEBUG: Raw Data Bemerkungen (Anfang): {response.text[:100]}...") + + df = pd.read_csv(StringIO(response.text), skiprows=2, header=None) + + if not df.empty: + print(f"DEBUG: Bemerkungen-DF Head:\n{df.head(3)}") + remarks = df[0].dropna().astype(str).tolist() + _cache["remarks"] = [r.strip() for r in remarks if r.strip()] + else: + _cache["remarks"] = [] + + return _cache["remarks"] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..2c24f1c --- /dev/null +++ b/main.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from routers.web_routes import router as web_router +import uvicorn +from core.config_loader import config + +app = FastAPI() + +# Router einbinden +app.include_router(web_router) + +if __name__ == "__main__": + uvicorn.run(app, host=config['server']['host'], port=config['server']['port']) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..39862c1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +pandas +requests +pyyaml +fastapi +uvicorn +jinja2 +python-multipart \ No newline at end of file diff --git a/routers/web_routes.py b/routers/web_routes.py new file mode 100644 index 0000000..a889ce0 --- /dev/null +++ b/routers/web_routes.py @@ -0,0 +1,32 @@ +from fastapi import APIRouter, Request +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.templating import Jinja2Templates +from core.data_processor import get_upcoming_events, get_remarks, invalidate_cache +from pathlib import Path + +router = APIRouter() +BASE_DIR = Path(__file__).parent.parent +templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) + +# Admin auf Root +@router.get("/", response_class=HTMLResponse) +async def admin_page(request: Request): + from core.config_loader import config + return templates.TemplateResponse("admin.html", { + "request": request, "links": config['links'] + }) + +# Tabelle unter /zeiten +@router.get("/zeiten", response_class=HTMLResponse) +async def times_table(request: Request): + events = get_upcoming_events() + remarks = get_remarks() + return templates.TemplateResponse("index.html", { + "request": request, "events": events, "remarks": remarks + }) + +@router.get("/cache-clear") +async def clear_cache(): + invalidate_cache() + # Relativer Redirect zurück zum Admin-Interface + return RedirectResponse(url="./") \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..396e1af --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,28 @@ + + + + Admin - Öffnungszeiten + + + +

Administration

+ +
+

Google Sheets Links

+

➔ Tabelle: Öffnungszeiten bearbeiten

+

➔ Tabelle: Bemerkungen bearbeiten

+
+ +
+

Cache Management

+

Nach Änderungen in Google Sheets muss der Cache gelöscht werden, damit die Website sofort aktualisiert wird.

+ Invalidate Cache (Cache löschen) +
+ +

← Zurück zur Ansicht

+ + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f804998 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,45 @@ + + + + + + + + + {% for event in events %} + + + + + {% if not event.Morgen and not event.Nachmittag %} + + {% else %} + + + {% endif %} + + {% endfor %} +
{{ event.Wochentag }}.{{ event.Datum.strftime('%d.%m.') }}geschlossen{{ event.Morgen }}{{ event.Nachmittag }}
+ + {% if remarks %} +
+ {% for r in remarks %} +
• {{ r }}
+ {% endfor %} +
+ {% endif %} + + \ No newline at end of file