{
  "openapi": "3.1.0",
  "info": {
    "title": "Franzenhof Wetterstation API",
    "version": "1.0.0",
    "description": "Oeffentliche, unauthentifizierte Lese-API fuer die LoRaWAN-Wetterstation und den Temperatursensor auf dem Franzenhof (Grosskonreuth, Bayern). Liefert Live-Messwerte (Temperatur, Windgeschwindigkeit, Windboee, Windrichtung, Niederschlag, Luftdruck) sowie historische Zeitreihen und Jahresvergleiche. Alle Endpunkte sind GET-Anfragen ohne Authentifizierung; Antworten sind JSON mit der Form {\"ok\": true, \"data\": {...}} bzw. {\"ok\": false, \"error\": \"...\"} bei Fehlern.",
    "contact": { "email": "info@franzenhof.com" }
  },
  "servers": [
    { "url": "https://franzenhof.com", "description": "Produktion" }
  ],
  "paths": {
    "/api/weather.php": {
      "get": {
        "operationId": "getWeather",
        "summary": "Wind, Windboee, Windrichtung, Niederschlag und Luftdruck der LoRaWAN-Wetterstation",
        "description": "Ein Endpunkt mit mehreren Aktionen ueber den Query-Parameter `action`. windDirDeg ist die meteorologische 'kommt von'-Richtung (0=Nord, 90=Ost, 180=Sued, 270=West). Fuer hour/day-Buckets wird die Windrichtung als zirkulaerer Mittelwert (Vektor-/Sinus-Kosinus-Mittel) berechnet, nicht als naiver Durchschnitt - ein naiver Durchschnitt waere am 0/360-Grad-Uebergang falsch (z.B. 350 und 10 Grad wuerden faelschlich zu 180 Grad gemittelt).",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "enum": ["latest", "series", "windrose", "extremes"] },
            "description": "latest: aktuellster unverdichteter Messwert je Sensor (aktualisiert bei jeder Knoten-Nachricht, ca. alle 10s); rain_24h ist zusaetzlich die gleitende 24h-Regensumme aus der Historie, da ein einzelner Live-Regenwert nur das letzte ~60s-Fenster zeigt. series: historische Zeitreihe. windrose: Haeufigkeitsverteilung der Windrichtung. extremes: Rekordwerte (hoechste Windgeschwindigkeit/Windboee, meiste Regenmenge an einem Tag, Anzahl Sturm- und Regentage) seit Aufzeichnungsbeginn - bewusst ohne Jahresvergleich, da Wind/Niederschlag erst seit kurzer Zeit erfasst werden."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 3650, "default": 7 },
            "description": "Nur fuer action=series und action=windrose: Anzahl Tage rueckwirkend ab jetzt."
          },
          {
            "name": "bucket",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["raw", "hour", "day"], "default": "hour" },
            "description": "Nur fuer action=series: raw = Einzelwerte alle 60s, hour = Stundenmittel, day = Tagesmittel."
          }
        ],
        "responses": {
          "200": {
            "description": "Erfolgreiche Antwort. Die Form von `data` haengt von `action` ab, siehe Beispiele.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "data": { "type": "object" }
                  },
                  "required": ["ok"]
                },
                "examples": {
                  "latest": {
                    "summary": "action=latest",
                    "value": {
                      "ok": true,
                      "data": {
                        "wind_speed": 12.4,
                        "wind_gust": 18.9,
                        "rain": 0.2,
                        "rain_24h": 4.6,
                        "wind_dir": 235,
                        "pressure": 1013.2,
                        "measured_at": "2026-08-11 14:32:10"
                      }
                    }
                  },
                  "series": {
                    "summary": "action=series&days=7&bucket=hour",
                    "value": {
                      "ok": true,
                      "data": {
                        "days": 7,
                        "bucket": "hour",
                        "points": [
                          {
                            "label": "2026-08-10 14:00:00",
                            "wind_speed": 10.1,
                            "wind_gust": 15.3,
                            "rain": 0,
                            "wind_dir": 210,
                            "pressure": 1015.4
                          }
                        ]
                      }
                    }
                  },
                  "windrose": {
                    "summary": "action=windrose&days=7",
                    "value": {
                      "ok": true,
                      "data": {
                        "days": 7,
                        "total": 2016,
                        "sectors": [
                          { "sector": 0, "label": "N", "count": 120, "pct": 6.0, "avg_speed": 8.2 },
                          { "sector": 1, "label": "NO", "count": 90, "pct": 4.5, "avg_speed": 7.1 }
                        ]
                      }
                    }
                  },
                  "extremes": {
                    "summary": "action=extremes",
                    "value": {
                      "ok": true,
                      "data": {
                        "recording_since": "2026-08-10 21:15:00",
                        "max_wind_speed": { "value": 32.4, "measured_at": "2026-08-11 15:02:00" },
                        "max_wind_gust": { "value": 41.7, "measured_at": "2026-08-11 15:03:00" },
                        "max_rain_day": { "value": 12.6, "date": "2026-08-11" },
                        "storm_days": 0,
                        "rain_days_0_1mm": 3,
                        "rain_days_1_0mm": 1
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/temperature.php": {
      "get": {
        "operationId": "getTemperature",
        "summary": "Aussentemperatur-Sensor: aktueller Wert, Zeitreihen, Jahresvergleiche, Erntesummen",
        "description": "Ein Endpunkt mit mehreren Aktionen ueber den Query-Parameter `action`. Werte vor November 2004 existieren nicht; die Erfassung vor 2013 war ereignisbasiert (neue Zeile nur bei Wertaenderung statt in festen Intervallen) - Tagesmittelwerte werden serverseitig zeitgewichtet berechnet, um das korrekt zu beruecksichtigen.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["latest", "series", "annual_compare", "frost_trend", "sum_mais", "sum_raps", "summary", "extremes"]
            },
            "description": "latest: aktuellster Messwert. series: Zeitreihe (siehe days/bucket). annual_compare: mehrere Jahre im Tagesvergleich (Tag-im-Jahr vs. Temperatur). frost_trend: erster Frosttag (Tagesminimum <= 0 Grad C) pro Jahr, August-Dezember. sum_mais/sum_raps: kumulierte Temperatursumme je Vegetationsperiode (Mais: Tagesmittel - 6 Grad; Raps: max(Tagesmittel, 0), August-August). summary: Datenbestand (erster/letzter Tag, Gesamtwerte, Extremwerte). extremes: waermstes/kaeltestes Jahr, Sommer und Winter, Gruenlandtemperatursumme (Vegetationsbeginn/-ende je Jahr per zeitgewichtetem Tagesmittel), absolute Rekorde, Sommer-/Hitze-/Wuesten-/Eistage, Tropennaechte (Nachttiefstwert 18-6 Uhr nicht unter 20 Grad), erster Herbstfrost und linearer Klimatrend - Jahre mit lueckenhafter Aufzeichnung (u.a. 2004, nur November/Dezember) werden ausgeschlossen."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 3650, "default": 7 },
            "description": "Nur fuer action=series."
          },
          {
            "name": "bucket",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["raw", "hour", "day"], "default": "hour" },
            "description": "Nur fuer action=series."
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Nur fuer action=annual_compare/sum_mais/sum_raps: Komma-getrennte Liste von Jahren, z.B. '2022,2023,2024'. Ohne Angabe werden sinnvolle Standardjahre verwendet."
          }
        ],
        "responses": {
          "200": {
            "description": "Erfolgreiche Antwort. Die Form von `data` haengt von `action` ab, siehe Beispiele.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "data": { "type": "object" }
                  },
                  "required": ["ok"]
                },
                "examples": {
                  "latest": {
                    "summary": "action=latest",
                    "value": { "ok": true, "data": { "temperature": 21.3, "measured_at": "2026-08-11 14:32:10" } }
                  },
                  "series": {
                    "summary": "action=series&days=7&bucket=day",
                    "value": {
                      "ok": true,
                      "data": {
                        "days": 7,
                        "bucket": "day",
                        "points": [
                          { "label": "2026-08-10", "value": 19.4, "min_value": 12.1, "max_value": 26.8, "sample_count": 288 }
                        ]
                      }
                    }
                  },
                  "annual_compare": {
                    "summary": "action=annual_compare&years=2024,2025",
                    "value": {
                      "ok": true,
                      "data": {
                        "years": [2024, 2025],
                        "series": {
                          "2024": [{ "year": 2024, "doy": 1, "mmdd": "01-01", "avg_temp": 2.1, "min_temp": -1.5, "max_temp": 5.0 }]
                        }
                      }
                    }
                  },
                  "summary": {
                    "summary": "action=summary",
                    "value": {
                      "ok": true,
                      "data": {
                        "first_day": "2004-11-15",
                        "last_day": "2026-08-11",
                        "total_samples": 512340,
                        "abs_min": -21.4,
                        "abs_max": 38.2,
                        "abs_avg": 9.1
                      }
                    }
                  },
                  "extremes": {
                    "summary": "action=extremes",
                    "value": {
                      "ok": true,
                      "data": {
                        "available": true,
                        "min_year": 2004,
                        "max_year": 2026,
                        "warmest_year": { "year": 2018, "avg": 10.4 },
                        "coldest_year": { "year": 2010, "avg": 7.2 },
                        "warmest_summer": { "year": 2022, "avg": 19.8 },
                        "coldest_summer": { "year": 2006, "avg": 15.9 },
                        "warmest_winter": { "season": "2019/20", "avg": 3.1 },
                        "coldest_winter": { "season": "2009/10", "avg": -3.8 },
                        "veg_start_earliest": { "year": 2020, "date": "2020-02-28", "doy": 59 },
                        "veg_start_latest": { "year": 2013, "date": "2013-04-15", "doy": 105 },
                        "veg_end_earliest": { "year": 2010, "date": "2010-10-02", "doy": 275 },
                        "veg_end_latest": { "year": 2022, "date": "2022-11-20", "doy": 324 },
                        "veg_duration_longest": { "year": 2020, "days": 260 },
                        "veg_duration_shortest": { "year": 2010, "days": 205 },
                        "abs_max": { "value": 38.2, "measured_at": "2015-08-07 16:40:00" },
                        "abs_min": { "value": -21.4, "measured_at": "2012-02-04 06:10:00" },
                        "most_summer_days": { "year": 2018, "count": 42 },
                        "most_heat_days": { "year": 2018, "count": 12 },
                        "most_desert_days": { "year": 2015, "count": 2 },
                        "most_ice_days": { "year": 2010, "count": 31 },
                        "most_tropical_nights": null,
                        "earliest_autumn_frost": { "year": 2012, "date": "2012-09-28", "doy": 272 },
                        "latest_autumn_frost": { "year": 2019, "date": "2019-11-25", "doy": 329 },
                        "climate_trend_c_per_year": 0.045
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
