# Datenprodukt implementieren

Rote Tests grün machen, mit einem KI-Coding-Agenten oder von Hand.

Source: https://learn.datacontract.com/de/implement/

In der [vorigen Übung](https://learn.datacontract.com/de/contract-first/) hast du den Kontrakt entworfen. Jetzt erfüllst du ihn: eine SQL-View `analytics.sku_sales_per_year` auf den `orders_v2`-Tabellen, mit der deine Kontrakt-Tests grün werden.

Eine perfekte Aufgabe für einen **KI-Coding-Agenten**: Der Kontrakt legt genau fest, was gebaut wird, und mit `datacontract test` prüft der Agent seine Arbeit selbst.

_Scenario: the Orders data product (output ports orders_v1 and orders_v2) is consumed by the SKU Sales data product, which the purchasing team uses._

**You will learn:**
- Ein Datenprodukt aus seinem Kontrakt heraus implementieren
- Einen KI-Coding-Agenten gegen Kontrakt und Test-Feedback-Schleife arbeiten lassen
- Verstehen, warum Datentypen Teil des Kontrakts sind
- Ein Verified Statement aus dem KI-Kontext des Kontrakts prüfen
- Ein Datenprodukt live schalten

> **Warum Kontrakte und KI-Agenten zusammenpassen**
>
> Ein Agent ist nur so gut wie seine Spezifikation und sein Feedback. Ein Datenkontrakt liefert beides:
> das **Ziel** (Spalten, Typen, Semantik, Qualitätsregeln) und die **Quelle** (den `orders_v2`-Kontrakt) als präzisen Input, und `datacontract test` als objektive Prüfung, ob er fertig ist.
> Kein Raten, kein „sieht für mich richtig aus“.

## Implementieren

Die View-Definition kommt nach `sql/sku_sales_per_year.sql`. So ist sie mit dem Kontrakt versioniert, und die CI-Pipeline in [Teil C](https://learn.datacontract.com/de/ci-cd/) kann sie automatisch anwenden.

### Mit einem KI-Coding-Agenten implementieren

Starte deinen KI-Coding-Agenten (Claude Code, Codex, Copilot, …) im Repository und gib ihm zum Beispiel diesen Prompt:

> Implement a PostgreSQL view that fulfills the data contract in `sku_sales_per_year.odcs.yaml`. The source data is described by the contract `orders_v2.odcs.yaml`. Write the SQL to `sql/sku_sales_per_year.sql`. It must be idempotent (`CREATE SCHEMA IF NOT EXISTS`, `CREATE OR REPLACE VIEW`). Apply it to the database via `docker compose exec -T postgres psql -U workshop -d workshop`. Then verify with `datacontract test sku_sales_per_year.odcs.yaml` and iterate until all tests pass.

Beobachte, was der Agent tut:

- Liest er beide Kontrakte (deinen für das Ziel, `orders_v2` für die Quelle)?
- Nutzt er ihren `context`? Die Instructions sagen, wie die Tabellen zu joinen sind, die Verified Statements zeigen funktionierende Abfragen.
- Führt er die Tests aus und reagiert er auf Fehler?
- Das Repository weist Agenten an, nicht in `solutions/` zu schauen. Er muss aus dem Kontrakt heraus arbeiten, wie ein Engineer.

Kein KI-Agent zur Hand? Dann mach es im nächsten Schritt von Hand.

### …oder von Hand implementieren

Lege den Ordner `sql` und darin die Datei `sku_sales_per_year.sql` an und ergänze die Transformation:

```sql title=sql/sku_sales_per_year.sql
CREATE SCHEMA IF NOT EXISTS analytics;

CREATE OR REPLACE VIEW analytics.sku_sales_per_year AS
SELECT
    -- your transformation here
FROM orders_v2.line_items li
JOIN orders_v2.orders o ON li.order_id = o.order_id
GROUP BY ...;
```

Wende sie auf die Datenbank an:

macOS / Linux:

```bash
docker compose exec -T postgres psql -U workshop -d workshop < sql/sku_sales_per_year.sql
```

Windows (PowerShell):

```powershell
Get-Content sql/sku_sales_per_year.sql | docker compose exec -T postgres psql -U workshop -d workshop
```

Output:

```text
CREATE SCHEMA
CREATE VIEW
```

> **Datentypen sind Teil deines Kontrakts**
>
> `EXTRACT(YEAR FROM order_timestamp)` liefert in PostgreSQL `numeric`: caste es mit `::int`.
> Auch `SUM(quantity)` liefert `numeric`: caste es mit `::bigint`.
> Der Kontrakt sagt `INTEGER` und `BIGINT`, und genau das prüfen die Tests.

> **Tip**
>
> `CREATE OR REPLACE VIEW` kann Name oder Typ einer bestehenden Spalte nicht ändern. Wenn PostgreSQL meckert, setz `DROP VIEW IF EXISTS analytics.sku_sales_per_year;` davor.

## Live gehen

### Tests grün machen

Testen, SQL korrigieren, erneut anwenden, wiederholen, bis alles grün ist:

macOS / Linux:

```bash
datacontract test sku_sales_per_year.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract test sku_sales_per_year.odcs.yaml
```

Output:

```text
Testing sku_sales_per_year.odcs.yaml
Server: postgres (type=postgres, host=localhost, port=5433, database=workshop, schema=analytics)
╭────────┬──────────────────────────────────────────────────────────┬────────────────┬─────────╮
│ Result │ Check                                                    │ Field          │ Details │
├────────┼──────────────────────────────────────────────────────────┼────────────────┼─────────┤
│ passed │ Ensure the view has data                                 │                │         │
│ passed │ Check that field 'order_count' is present                │ order_count    │         │
│ passed │ Check that field order_count has physical type bigint    │ order_count    │         │
│ passed │ Ensure order_count is positive                           │ order_count    │         │
…
│ passed │ Check that field year has physical type integer          │ year           │         │
│ passed │ Check that field year has no missing values              │ year           │         │
│ passed │ Ensure year is plausible                                 │ year           │         │
╰────────┴──────────────────────────────────────────────────────────┴────────────────┴─────────╯
🟢 data contract is valid. Run 15 checks. Took 0.588051 seconds.
```

**Solution: sql/sku_sales_per_year.sql**

```sql title=sku_sales_per_year.sql
CREATE SCHEMA IF NOT EXISTS analytics;

CREATE OR REPLACE VIEW analytics.sku_sales_per_year AS
SELECT
    li.sku,
    EXTRACT(YEAR FROM o.order_timestamp)::int AS year,
    COUNT(*)::bigint                          AS order_count,
    SUM(li.quantity)::bigint                  AS total_quantity
FROM orders_v2.line_items li
JOIN orders_v2.orders o ON li.order_id = o.order_id
GROUP BY li.sku, EXTRACT(YEAR FROM o.order_timestamp);
```

### Verified Statement prüfen

Der `context` deines Kontrakts verspricht eine Antwort auf „Which three SKUs sold the most units in 2024?“. Führ sie aus:

macOS / Linux:

```bash
docker compose exec -T postgres psql -U workshop -d workshop -c "SELECT sku, total_quantity FROM analytics.sku_sales_per_year WHERE year = 2024 ORDER BY total_quantity DESC LIMIT 3;"
```

Windows (PowerShell):

```powershell
docker compose exec -T postgres psql -U workshop -d workshop -c "SELECT sku, total_quantity FROM analytics.sku_sales_per_year WHERE year = 2024 ORDER BY total_quantity DESC LIMIT 3;"
```

Output:

```text
      sku      | total_quantity
---------------+----------------
 D3KT74L5EV46T |            146
 IWMJ3ZX164    |             62
 TFH11HYOR     |             46
(3 rows)
```

Stell deinem KI-Agenten jetzt dieselbe Frage, nur mit dem Kontrakt als Input. Kommt er auf diese Abfrage?

### Datenprodukt auf active setzen

Dein Datenprodukt ist live. Setze `status` auf `active` in `sku_sales_per_year.odcs.yaml` und `sku_sales_per_year.odps.yaml` und prüfe beide:

macOS / Linux:

```bash
datacontract lint sku_sales_per_year.odcs.yaml
dataproduct lint sku_sales_per_year.odps.yaml
```

Windows (PowerShell):

```powershell
datacontract lint sku_sales_per_year.odcs.yaml
dataproduct lint sku_sales_per_year.odps.yaml
```

Output:

```text
╭────────┬────────────────────────────────────────────┬───────┬─────────╮
│ Result │ Check                                      │ Field │ Details │
├────────┼────────────────────────────────────────────┼───────┼─────────┤
│ passed │ Data contract is valid against ODCS v3.2.0 │       │         │
╰────────┴────────────────────────────────────────────┴───────┴─────────╯
🟢 data contract is valid. Run 1 checks. Took 0.12012 seconds.
✅ Data product is valid against ODPS v1.1.0
🟢 Data product is valid.
```

**Quick check:** Deine View liefert die richtigen Zahlen, aber der Check für den physischen Typ der Spalte year schlägt fehl. Wahrscheinlichster Grund?

- Der Kontrakt ist zu streng, entferne den physicalType
- EXTRACT liefert numeric, der Kontrakt verspricht aber INTEGER (correct)
- Spalten von PostgreSQL-Views haben keine Typen
- Die Jahreswerte liegen außerhalb des erlaubten Bereichs

Richtig. Der physische Typ ist Teil des Kontrakts. Caste `EXTRACT(YEAR FROM ...)` mit `::int`, damit Consumer genau den versprochenen Typ bekommen.
