# Anleitung für KI-Agenten: Daten an die Datenkarte anbinden {#navod-pro-ai-agenty-pripojeni-dat-do-datovemapy}

Dieses Dokument richtet sich an KI-Agenten, die Daten für das Portal Datenkarte aufbereiten. Datenanbieter kann jede und jeder sein, die oder der Daten mit Ortsbezug hat. Es beschreibt Ziel, Vorgehen, Regeln und Abnahmekriterien. Ein Mensch, der den Agenten steuert, findet eine leichter verständliche Fassung im [Leitfaden für Datenanbieter](https://datenkarte.at/dokumentation/fuer-datenanbieter.md).

## Ziel {#cil}

Über HTTPS veröffentlichen:

1. das **Manifest** – JSON gemäß [Manifest-Schema](https://datenkarte.at/schema/0.1/manifest.json),
2. für jeden Datensatz eine **Datenquelle** – eine GeoJSON-Datei oder einen Endpunkt gemäß [Datenschema](https://datenkarte.at/schema/0.1/data.json) und [OpenAPI-Vertrag für Anbieter](https://datenkarte.at/schema/0.1/provider-openapi.json), gegebenenfalls bestehende Rasterkacheln / WMS,

und zwar so, dass der [Validator](https://datenkarte.at/dokumentation/api.md#validator) `"valid": true` und möglichst wenige Warnungen zurückgibt.

## Was Sie lesen sollten {#co-si-nacist}

| Quelle | Wozu |
|---|---|
| [llms.txt](https://datenkarte.at/llms.txt) | Übersicht über die gesamte Dokumentation |
| [Protokoll 0.1](https://datenkarte.at/dokumentation/protokoll.md) | normative Spezifikation – im Zweifel gilt sie |
| [JSON Schema des Manifests](https://datenkarte.at/schema/0.1/manifest.json), [der Daten](https://datenkarte.at/schema/0.1/data.json) | genaue Typen, Grenzwerte und Feldbeschreibungen |
| [OpenAPI für Anbieter](https://datenkarte.at/schema/0.1/provider-openapi.json) | Vertrag des Endpunkts, daraus lässt sich ein Server generieren |
| [manifest-complete.json](https://datenkarte.at/beispiele/manifest-complete.json), [manifest-minimal.json](https://datenkarte.at/beispiele/manifest-minimal.json) | gültige Vorlagen |
| [data-points.json](https://datenkarte.at/beispiele/data-points.json), [data-polygons.json](https://datenkarte.at/beispiele/data-polygons.json) | gültige Vorlagen für Antworten |
| [Katalog](https://datenkarte.at/quellen.md) | was das Portal bereits enthält – vermeiden Sie Duplikate, lassen Sie sich von den Schlüsselwörtern inspirieren |

Wenn Sie MCP nutzen können, verbinden Sie sich mit dem Server `https://datenkarte.at/mcp` (Streamable HTTP, ohne Authentifizierung). Die Werkzeuge `documentation`, `schema`, `example`, `validate_manifest`, `validate_data` und `catalog` decken alles in dieser Anleitung ab. Siehe [MCP-Server](https://datenkarte.at/dokumentation/api.md#mcp-server).

## Vorgehen {#postup}

### 1. Bestandsaufnahme der Daten {#1-inventura-dat}

Ermitteln Sie, welche räumlichen Daten der Anbieter hat: Datenbanken (PostGIS, MS SQL), GIS-Dateien (SHP, GPKG, KML, CSV mit Koordinaten), bestehende APIs, WMS/WMTS-Dienste. Klären Sie für jede Quelle:

- den Geometrietyp (Punkte / Linien / Flächen),
- das Koordinatenreferenzsystem (in Österreich häufig **MGI / Austria Lambert, EPSG:31287** oder **Gauß-Krüger M28/M31/M34, EPSG:31257/31258/31259** – muss umgerechnet werden),
- die Anzahl der Objekte und wie oft sie sich ändern,
- die Lizenz und wer sie freigeben darf,
- ob sie **personenbezogene Daten** enthält (Namen, Sozialversicherungsnummern, Adressen natürlicher Personen, Kfz-Kennzeichen …).

### 2. Entwurf der Datensätze {#2-navrh-datovych-sad}

- Ein Datensatz = eine Art von Objekten mit einer Darstellungsweise. Müllcontainer und eine Lärmkarte gehören in zwei Datensätze.
- `id` des Datensatzes und des Anbieters: Kleinbuchstaben ohne Umlaute und Sonderzeichen, Ziffern, Bindestriche (`muellcontainer`). Ändern Sie sie nach der Veröffentlichung nicht – sie ist Teil von Links.
- Wahl der Quelle:

| Situation | `source` |
|---|---|
| bis ca. 2 000 Objekte, Änderungen höchstens täglich | statische Datei, `"type": "geojson", "mode": "full"` |
| mehr Objekte oder Live-Daten | Endpunkt, `"mode": "bbox"`, setzen Sie `coverage.bbox` und bei großen Datensätzen `coverage.minZoom` |
| es gibt bereits WMS / XYZ-Kacheln | `"type": "raster"` mit der Vorlage `{bbox-epsg-3857}` oder `{z}/{x}/{y}`; ergänzen Sie `presentation.legend` (Farben und Beschriftungen aus der Legende des Dienstes, z. B. ArcGIS `…/MapServer/legend?f=json` oder WMS `GetLegendGraphic`) |

### 3. Umwandlung der Daten {#3-prevod-dat}

- Koordinaten in **WGS84 (EPSG:4326), Reihenfolge `[lon, lat]`**. Für Österreich ist das etwa `[14, 47.5]`, nicht `[47.5, 14]`.
- Runden Sie auf 6 Dezimalstellen (≈ 10 cm), alles Weitere vergrößert nur die Antwort.
- Polygone müssen geschlossene Ringe haben (erster Punkt = letzter Punkt).
- Jedes Objekt sollte eine stabile `id` haben.
- Eigenschaften (`properties`) als einfache Werte – Zeichenketten, Zahlen, Booleans, Datumsangaben in ISO 8601. Kein HTML.
- Zahlen als Zahlen (`1100`), nicht als Zeichenketten (`"1100 l"`); die Einheit gehört in `fields[].unit`.

Umwandlung einer GIS-Datei in GeoJSON:

```bash
ogr2ogr -f GeoJSON -t_srs EPSG:4326 -lco RFC7946=YES -lco COORDINATE_PRECISION=6 lavicky.geojson vstup.shp
```

Umrechnung von Koordinaten in Python:

```python
from pyproj import Transformer

to_wgs84 = Transformer.from_crs("EPSG:31287", "EPSG:4326", always_xy=True)
lon, lat = to_wgs84.transform(x_lambert, y_lambert)  # always_xy=True liefert [lon, lat]
```

### 4. Manifest {#4-manifest}

Füllen Sie nicht nur die Pflichtfelder aus, sondern auch jene, die darüber entscheiden, ob die Daten gefunden werden:

- `description` – 1–2 Sätze: was die Daten enthalten, woher sie stammen, wie aktuell sie sind.
- `keywords` – 5–15 Wörter und Synonyme auf Deutsch, einschließlich umgangssprachlicher und regionaler Formen (`Mistkübel` neben `Müllcontainer`).
- `useCases` – wobei die Daten helfen: `construction`, `housing`, `land-purchase`, `agriculture`, `investment`, `transport`, `tourism`, `business`, `public-administration`. Danach wählt der Assistent des Portals die Daten zu Anfragen wie „Ich möchte in der Gemeinde X bauen“ aus.
- `category` – eine von: `territory`, `nature`, `water`, `risks`, `energy`, `transport`, `society`, `economy`, `infrastructure`.
- `fields` – jede Eigenschaft, die Nutzerinnen und Nutzer sehen sollen: `label`, `type`, `unit`. Markieren Sie eine als `primary` (Überschrift im Detail).
- `license` – Name und Attributionstext. **Erfinden Sie keine Lizenz**; wenn Sie sie nicht kennen, fragen Sie den Menschen.
- `temporal.updated`, `temporal.frequency` – Aktualität der Daten.
- `temporal.param` – bei zeitbezogenen Daten (Unfälle, Messungen, Ereignisse): der Schlüssel eines Parameters vom Typ `range` mit Jahren (`min`/`max` = erstes und letztes Jahr), mit dem der Zeitraum gewählt wird. Die Antwort sollte dazu `dm.timeline` mit Anzahlen pro Jahr senden (bei Werten im Zeitverlauf wie der Einwohnerzahl stattdessen ein zusammenfassendes `dm.chart`), siehe [Zeitfilter](https://datenkarte.at/dokumentation/protokoll.md#casovy-filtr).
- **Entwicklung im Zeitverlauf** – prüfen Sie, ob Objekte Werte für mehrere Zeiträume haben (Stände nach Jahren, Messungen, Preise). Wenn ja, senden Sie diese bei jedem Objekt immer in `dm.chart`; geht es im Datensatz um die Veränderung, färben Sie nach Trend. Siehe [Entwicklung im Zeitverlauf und Trend](https://datenkarte.at/dokumentation/protokoll.md#vyvoj-v-case-a-trend).

### 5. Visualisierung {#5-vizualizace}

| Geometrie | Geeigneter Typ | Hinweis |
|---|---|---|
| Punkte, Dutzende bis Hunderte | `circle` | Farbe nach Kategorie (`categorical`), Beschriftung über `label` |
| Punkte, Tausende | `circle` mit `cluster` oder `heatmap` | Heatmap nur für die Dichte, sie zeigt keine einzelnen Objekte |
| Linien | `line` | die Breite kann von einem Wert abhängen |
| Flächen mit Wert | `fill` + `interpolate` oder `step` | Choroplethenkarte; `opacity` 0.4–0.6, damit die Grundkarte sichtbar bleibt |
| Punkte oder Flächen, deren Wert sich im Zeitverlauf ändert | `circle` / `fill` + `color: { "type": "trend" }` | wenn es um die Veränderung geht (steigt / sinkt); die Wertereihe in `dm.chart` bei jedem Objekt, den Trend berechnet das Portal |
| Flächen ohne Wert | `fill` mit einer Farbe | |

Regeln: Farben als Hex; Kategorien max. ~8 Farben; für Werte eine sequenzielle Skala von hell nach dunkel; `field` in der Darstellung muss einer Eigenschaft in den Daten entsprechen (und in `fields`, mit numerischem Typ bei `interpolate`/`step`). Hat der Wert eines Objekts eine Historie, senden Sie diese bei jeder Darstellung in `dm.chart` – auch bei einer Choroplethenkarte mit Jahresauswahl (das gewählte Jahr in `highlight`).

### 6. Endpunkt (nur bei `mode: "bbox"` oder Live-Daten) {#6-endpoint-jen-u-mode-bbox-nebo-zivych-dat}

Implementieren Sie `GET` gemäß [OpenAPI-Vertrag](https://datenkarte.at/schema/0.1/provider-openapi.json):

- Query `bbox=minLon,minLat,maxLon,maxLat` – geben Sie die Objekte zurück, die den Ausschnitt schneiden,
- `zoom` – ganze Zahl; bei geringer Zoomstufe dürfen Sie Aggregationen zurückgeben,
- `limit` – geben Sie nicht mehr Objekte zurück; wenn Sie kürzen, setzen Sie `dm.truncated: true` und `dm.total`,
- eigene Parameter aus `params` (Bereich `von,bis`, mehrere Werte `a,b,c`),
- `Content-Type: application/geo+json` oder `application/json`, HTTPS, Antwort innerhalb von 1 s,
- unbekannte Query-Parameter ignorieren (`lang`, `protocol`, `cursor` können Sie übergehen).

CORS ist nicht nötig – das Portal fragt vom Server aus ab.

Python (FastAPI + PostGIS):

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/datovamapa/kontejnery")
def kontejnery(bbox: str | None = None, zoom: int | None = None, limit: int = 5000, druh: str | None = None):
    west, south, east, north = map(float, bbox.split(",")) if bbox else (-180, -90, 180, 90)
    druhy = druh.split(",") if druh else None
    rows = db.fetch_all(
        """
        SELECT id, druh, objem, vyvoz::text, ST_AsGeoJSON(geom, 6)::json AS geometry
        FROM kontejnery
        WHERE geom && ST_MakeEnvelope(%s, %s, %s, %s, 4326)
          AND (%s::text[] IS NULL OR druh = ANY(%s))
        LIMIT %s
        """,
        (west, south, east, north, druhy, druhy, limit + 1),
    )
    features = [
        {"type": "Feature", "id": r["id"], "geometry": r["geometry"],
         "properties": {"druh": r["druh"], "objem": r["objem"], "vyvoz": r["vyvoz"]}}
        for r in rows[:limit]
    ]
    return {"type": "FeatureCollection", "features": features, "dm": {"truncated": len(rows) > limit, "ttl": 3600}}
```

TypeScript (Cloudflare Worker / Node, Daten im Speicher):

```ts
// KONTEJNERY: Array von GeoJSON-Features mit Punkten, beim Start geladen (aus der Datenbank oder einer Datei).
declare const KONTEJNERY: { type: "Feature"; id: number; geometry: { type: "Point"; coordinates: [number, number] }; properties: Record<string, unknown> }[];

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const [west, south, east, north] = (url.searchParams.get("bbox") ?? "-180,-90,180,90").split(",").map(Number);
    const limit = Math.min(Number(url.searchParams.get("limit")) || 5000, 5000);
    const inside = KONTEJNERY.filter(({ geometry: { coordinates: [lon, lat] } }) =>
      lon >= west && lon <= east && lat >= south && lat <= north);
    return Response.json(
      { type: "FeatureCollection", features: inside.slice(0, limit), dm: { total: inside.length, truncated: inside.length > limit } },
      { headers: { "Content-Type": "application/geo+json", "Cache-Control": "public, max-age=300" } },
    );
  },
};
```

### 7. Validierung {#7-validace}

Wiederholen Sie, bis keine Fehler mehr auftreten, und gehen Sie jede Warnung durch (beheben oder bewusst belassen und begründen):

```bash
# Manifest (ohne Veröffentlichung)
curl -sX POST https://datenkarte.at/api/validate -H 'Content-Type: application/json' --data @manifest.json
# Daten gegen einen bestimmten Datensatz
curl -sX POST https://datenkarte.at/api/validate -H 'Content-Type: application/json' \
  --data "{\"data\": $(cat lavicky.geojson), \"dataset\": $(jq '.datasets[0]' manifest.json)}"
# Live-Test des veröffentlichten Manifests und aller Quellen
curl -s 'https://datenkarte.at/api/validate?manifestUrl=https://www.gemeinde-beispiel.at/datenkarte/manifest.json'
```

Antwort: `{ valid, summary, errors: [{ path, code, message, hint? }], warnings: [...] }`, beim Live-Test zusätzlich `datasets[]` mit Abfrage, Antwortzeit und Anzahl der Objekte. Die Bedeutung der Codes steht in der [API des Portals](https://datenkarte.at/dokumentation/api.md#kody-nalezu).

### 8. Übergabe an den Menschen {#8-predani-cloveku}

Fassen Sie zum Schluss zusammen:

- URL des Manifests und Liste der Datensätze (id, Titel, Quelltyp, Anzahl der Objekte),
- Ergebnis des Live-Tests (`summary`, verbleibende Warnungen und warum sie bleiben),
- was der Mensch bestätigen muss: **Lizenz und Attribution**, dass die Daten keine personenbezogenen Daten enthalten, Kontakt für das Portal,
- wie die Daten aktualisiert werden (Skript, Cron, maßgebliche Quelle).

Sobald der Mensch Lizenz und Inhalt bestätigt hat, registrieren Sie das Manifest: `POST https://datenkarte.at/api/registration` mit `{ "manifestUrl": "…" }` oder über das MCP-Werkzeug `register_provider`. Die Daten sind dann als ungeprüft verfügbar (mit Warnung, außerhalb des Katalogs), bis der Betreiber sie freigibt. Nach jeder Änderung des Manifests warten sie auf eine neue Freigabe. Einzelheiten: [Registrierung](https://datenkarte.at/dokumentation/fuer-datenanbieter.md#5-registrace).

## Regeln {#pravidla}

- **Veröffentlichen Sie keine personenbezogenen Daten** (DSGVO). Im Zweifel aggregieren oder weglassen und nachfragen.
- Erfinden Sie weder Lizenz noch Quelle noch Aktualität der Daten.
- Fügen Sie in `properties` und `dm` kein HTML ein; das Portal zeigt alles als Text an.
- Keine geheimen Schlüssel in URLs – das Manifest ist öffentlich.
- Ändern Sie `provider.id` und `dataset.id` nach der Veröffentlichung nicht.
- Verwenden Sie die Schlüssel `bbox`, `zoom`, `limit`, `cursor`, `lang`, `protocol` nicht für eigene Parameter.
- Max. 5 000 Objekte und 15 MB pro Antwort, Antwort innerhalb von 10 s (Ziel ist unter 1 s).

## Abnahmekriterien {#kriteria-hotove-prace}

- [ ] Das Manifest ist über HTTPS erreichbar und der Live-Test des Validators liefert `"valid": true`.
- [ ] Jeder Datensatz hat `description`, `keywords`, `useCases`, `category` und (bei GeoJSON) `fields`.
- [ ] Lizenz und Attribution sind vom Menschen bestätigt.
- [ ] Die Koordinaten sind WGS84 `[lon, lat]`, keine Warnung `swapped_coordinates` oder `outside_coverage`.
- [ ] Kein Datensatz liefert im Test 0 Objekte (`empty_response`), sofern das nicht beabsichtigt ist.
- [ ] Objekte mit Werten für mehrere Zeiträume senden `dm.chart` (kein `missing_trend_chart` und kein `trend_without_chart`).
- [ ] Die Antwortzeit der Endpunkte liegt unter 3 s (`slow_response`).
- [ ] Die Daten enthalten keine personenbezogenen Daten.
- [ ] Es ist beschrieben, wie die Daten aktualisiert werden.

## Auftrag für den Agenten (zum Kopieren) {#zadani-pro-agenta-ke-zkopirovani}

Ein Mensch kann seinem Agenten diesen Text geben:

```text
Bereite unsere räumlichen Daten für das Portal Datenkarte (https://datenkarte.at) auf.
Gehe nach der Anleitung https://datenkarte.at/dokumentation/fuer-ki-agenten.md vor
(Übersicht der Dokumentation: https://datenkarte.at/llms.txt, MCP-Server: https://datenkarte.at/mcp).
Datenquellen: <Datenbanken, Dateien, Dienste beschreiben>.
Wo veröffentlichen: <Website / Server / Repository>.
Lizenz: <z. B. CC BY 4.0, Attribution „© Gemeinde Beispiel“> – erfinde keine andere.
Fertig ist es, wenn der Live-Test des Validators valid: true liefert und du die Abnahmekriterien erfüllt hast.
Übergib mir am Ende eine Zusammenfassung gemäß Schritt 8.
```
