Zum Inhalt

Umsetzungsvorschläge zur API-Spezifikation

Konkrete Erweiterungsvorschläge zur TBA3 API-Spec v1.1, basierend auf den offenen Fragen (api_fragen.md) und dem Audit der Frontend-Komponenten.

Stand: 2026-03-26


1. Schulamt-/Bezirks-Hierarchie

Empfehlung: ?type=state,district nutzen + groupType-Feld

Die Spec kennt bereits den Example-Wert state,district für den type-Parameter auf /states/{id}/*. Value-Groups im Response repräsentieren dann sowohl Landes- als auch Bezirksdaten. Problem: Es fehlt ein Diskriminator-Feld, um die Value-Groups zu unterscheiden.

Vorgeschlagene Spec-Änderung

Neues optionales Feld groupType im value-group-Schema:

value-group:
  type: object
  required:
    - name
  properties:
    id:
      type: string
    name:
      type: string
    groupType:
      type: string
      description: >
        Typ der Value-Group zur Unterscheidung bei kombinierten type-Abfragen.
        Ermöglicht dem Client, Landes- von Bezirksdaten zu trennen.
      enum:
        - state
        - district
        - school
        - group
        - student
    covariates:
      # ... (bestehend)
    properties:
      # ... (bestehend)

Beispiel-Response: /states/nrw/competence-levels?type=state,district

[
  {
    "id": "nrw",
    "name": "Nordrhein-Westfalen",
    "groupType": "state",
    "competenceLevels": [
      { "nameShort": "Ib", "descriptiveStatistics": { "frequency": 312, "mean": 0.05 } }
    ]
  },
  {
    "id": "sa-1",
    "name": "Stadt Bottrop",
    "groupType": "district",
    "competenceLevels": [
      { "nameShort": "Ib", "descriptiveStatistics": { "frequency": 42, "mean": 0.06 } }
    ]
  },
  {
    "id": "sa-2",
    "name": "Stadt Gelsenkirchen",
    "groupType": "district",
    "competenceLevels": [
      { "nameShort": "Ib", "descriptiveStatistics": { "frequency": 38, "mean": 0.04 } }
    ]
  }
]

Frontend-Mapping

Das Frontend filtert nach groupType:

const allGroups = await statesService.getCompetenceLevels(stateId, 'state,district');
const stateData = allGroups.filter((g) => g.groupType === 'state');
const districtData = allGroups.filter((g) => g.groupType === 'district');

Langfristiger Ausblick

Wenn die Schulamt-Ebene komplexer wird (eigene Aggregationen, Detailansichten), wäre ein dediziertes Endpoint-Level /authorities/{id}/* die sauberere Lösung — analog zu den bestehenden /groups, /schools, /states-Levels.

Stand im Prototyp (Mai 2026)

Die Detail-Sichten verlangen mittlerweile zwei zusätzliche Aufsichts-Ebenen mit eigener Identität (Bezirk + Schulamt), beide mit Aggregationen analog zu /states/{id}/*. Daher wurde im Prototyp das langfristig empfohlene Pattern bereits umgesetzt:

Ebene Pfad Status in der Spec
Bezirk /districts/{id}/* fehlt — lokal über DistrictsApiService gemockt
Schulamt /authorities/{id}/* vorhanden in der Spec (generierter AuthoritiesService)

Empfehlung an die Spec: /districts/{id}/* analog zu /authorities/{id}/* ergänzen (competence-levels / aggregations / items). Der lokale DistrictsApiService ist signaturgleich zum generierten StatesService/AuthoritiesService aufgebaut, sodass ein reiner Import-Swap ausreicht, sobald die Spec nachzieht.


2. Schulliste / Schulübersicht

Empfehlung: ?type=schools auf States-Endpoints + Organisations-Metadaten via properties

Bestehendes Muster: Klassen einer Schule

Die Spec löst die Beziehung Schule→Klassen bereits implizit: Beim Abruf von /schools/{id}/competence-levels kommen Klassen als Einträge im comparison-Array zurück. Es gibt keinen dedizierten /schools/{id}/groups-Endpoint.

Analog sollten Schulen eines Bezirks/Landes funktionieren: Über ?type=schools kommen Schulen als eigene Value-Groups im Response zurück. Organisatorische Metadaten (Schulamt-Zuordnung, Klassenanzahl, Teilnahmequote) werden über das properties-Feld transportiert.

Vorgeschlagene Spec-Änderung

Neuer type-Example-Wert schools auf /states/{id}/*-Endpoints:

# Ergänzung der type-Examples auf /states/{id}/*
parameters:
  - name: type
    in: query
    schema:
      type: string
    examples:
      Lerngruppe:
        value: ''
      Schulen:
        value: 'schools'
        description: 'Eine Value-Group pro Schule mit Leistungsdaten'
      Land und Schulamtsbezirke:
        value: 'state,district'

Beispiel-Response: /states/nrw/aggregations?type=schools

[
  {
    "id": "s-1",
    "name": "Grundschule Am Stadtpark",
    "groupType": "school",
    "properties": [
      { "key": "authorityId", "value": "sa-1" },
      { "key": "authorityName", "value": "Stadt Bottrop" },
      { "key": "klassen", "value": "4" },
      { "key": "teilnehmende", "value": "87" },
      { "key": "teilnahmequote", "value": "95.6" }
    ],
    "aggregations": [
      {
        "type": "competence",
        "value": "Deutsch",
        "descriptiveStatistics": { "mean": 0.78 }
      },
      {
        "type": "competence",
        "value": "Mathematik",
        "descriptiveStatistics": { "mean": 0.72 }
      }
    ]
  }
]

Frontend-Mapping

interface SchoolOverviewRow {
  id: string;
  name: string;
  authorityName: string; // aus properties["authorityName"]
  klassen: number; // aus properties["klassen"]
  teilnehmende: number; // aus properties["teilnehmende"]
  teilnahmequote: number; // aus properties["teilnahmequote"]
  minStDeutsch: number; // aus aggregations[type=competences, value=Deutsch].mean * 100
  minStMathematik: number; // aus aggregations[type=competences, value=Mathematik].mean * 100
}

Hinweis

Schulmetadaten (Adresse, Schultyp, Schulamt-Zuordnung) kommen über properties — analog zu Klassen, die implizit über comparison der Schuldaten gelöst sind.


3. Übersichts-Metadaten (Schulaufsicht)

Empfehlung: properties-Feld auf der Landes-Value-Group

Das properties-Feld ist laut Spec für "zusätzliche Informationen, z.B. Testdauer, andere systemspezifische Attribute" vorgesehen — Teilnahme-Metadaten passen hier gut.

Vorgeschlagene Konvention für properties-Keys

Key Typ Beschreibung
schulenSoll number Erwartete Anzahl Schulen (angemeldet)
schulenIst number Tatsächlich teilgenommene Schulen
angemeldeteTeilnehmende number Registrierte Schüler:innen
teilgenommenTeilnehmende number Tatsächlich teilgenommene Schüler:innen

Hinweis: properties.value ist laut Spec ein string. Numerische Werte werden als String transportiert und im Frontend geparst.

Beispiel-Response: /states/nrw/aggregations?type=state

[
  {
    "id": "nrw",
    "name": "Nordrhein-Westfalen",
    "groupType": "state",
    "properties": [
      { "key": "schulenSoll", "value": "125" },
      { "key": "schulenIst", "value": "118" },
      { "key": "angemeldeteTeilnehmende", "value": "4250" },
      { "key": "teilgenommenTeilnehmende", "value": "3990" }
    ],
    "covariates": [
      { "type": "gender", "value": "female", "descriptiveStatistics": { "frequency": 1950 } },
      { "type": "gender", "value": "male", "descriptiveStatistics": { "frequency": 1980 } },
      { "type": "gender", "value": "diverse", "descriptiveStatistics": { "frequency": 60 } },
      {
        "type": "languageAtHome",
        "value": "german",
        "descriptiveStatistics": { "frequency": 2790 }
      },
      { "type": "languageAtHome", "value": "other", "descriptiveStatistics": { "frequency": 1200 } }
    ]
  }
]

Frontend-Mapping

function parseOverviewMetadata(group: ValueGroup): OverviewMetadata {
  const props = new Map(group.properties?.map((p) => [p.key, p.value]));
  return {
    schulenSoll: Number(props.get('schulenSoll')),
    schulenIst: Number(props.get('schulenIst')),
    angemeldeteTeilnehmende: Number(props.get('angemeldeteTeilnehmende')),
    teilgenommenTeilnehmende: Number(props.get('teilgenommenTeilnehmende')),
  };
}

4. type-Parameter: Definierte Werte und Response-Varianten

Empfehlung: type als Enum definieren, pro Endpoint-Level dokumentieren

Vorgeschlagene Spec-Änderung

parameters:
  type-groups:
    name: type
    in: query
    schema:
      type: string
      enum:
        - ''
        - students
        - group,students
    description: 'Bestimmt die Granularität der Value-Groups im Response.'

  type-schools:
    name: type
    in: query
    schema:
      type: string
      enum:
        - ''
        - students
    description: 'Bestimmt die Granularität der Value-Groups im Response.'

  type-states:
    name: type
    in: query
    schema:
      type: string
      enum:
        - ''
        - district
        - schools
        - state,district
    description: 'Bestimmt die Granularität der Value-Groups im Response.'

Response-Varianten Übersicht

Endpoint-Level type-Wert Value-Groups repräsentieren groupType-Wert
/groups "" (default) Die Lerngruppe selbst group
/groups students Einzelne Schüler:innen student
/groups group,students Gruppe + Schüler:innen kombiniert group/student
/schools "" (default) Die Schule selbst school
/schools students Aggregiert pro Schüler:in student
/states "" (default) Das Land selbst state
/states district Einzelne Schulamtsbezirke district
/states schools Einzelne Schulen school
/states state,district Land + Bezirke kombiniert state/district

5. aggregation-Parameter: Erlaubte Werte dokumentieren

Problem

Die Spec dokumentiert nur competence und gender als Beispielwerte für den aggregation-Parameter auf den /aggregations-Endpoints. Das Frontend nutzt aber weitere Werte, die vom Backend unterstützt werden müssen. Ohne explizite Dokumentation ist unklar, welche Werte das Backend akzeptiert.

Ist-Zustand

aggregation-Wert In Spec dokumentiert? Genutzt von
competence Ja (Beispiel) Class-Teacher, School-Manager, Student-Detail
gender Ja (Beispiel) (nicht aktiv genutzt)
exercise Nein Class-Teacher, School-Manager, Student-Detail
competenceLevel Nein Class-Teacher (Lösungsquoten)
domain Nein Class-Teacher (Schüler-Aggregation)

Vorgeschlagene Spec-Änderung

Den aggregation-Parameter als Enum definieren mit allen unterstützten Werten:

parameters:
  aggregation:
    name: aggregation
    in: query
    description: >
      Filtert, welche Aggregationen berechnet und zurückgegeben werden.
      Bestimmt die Gruppierung der Lösungshäufigkeiten.
    schema:
      type: string
      enum:
        - competence
        - exercise
        - competenceLevel
        - domain

Bedeutung der Werte

Wert Beschreibung Beispiel-Response
competence Lösungshäufigkeiten gruppiert nach Kompetenzbereich (Domäne) Leseverstehen: mean 0.72, Sprechen: mean 0.65
exercise Lösungshäufigkeiten gruppiert nach Aufgabe/Testlet "Im Ferienlager": mean 0.68, "Am Strand": mean 0.55
competenceLevel Lösungshäufigkeiten gruppiert nach Kompetenzstufe (I–V) Stufe I: mean 0.25, Stufe III: mean 0.65, Stufe V: mean 0.92
domain Lösungshäufigkeiten pro Domäne (ähnlich competence, für studentenbezogene Sicht) Wird bei type=students genutzt für individuelle Domänen-Werte

Beispiel-Response: /groups/1/aggregations?aggregation=exercise

[
  {
    "type": "exercise",
    "value": "Im Ferienlager",
    "description": "Aufgabe 1",
    "descriptiveStatistics": { "mean": 0.68, "total": 5 },
    "comparison": [{ "name": "Landesmittelwert", "descriptiveStatistics": { "mean": 0.62 } }]
  },
  {
    "type": "exercise",
    "value": "Am Strand",
    "description": "Aufgabe 2",
    "descriptiveStatistics": { "mean": 0.55, "total": 4 },
    "comparison": [{ "name": "Landesmittelwert", "descriptiveStatistics": { "mean": 0.58 } }]
  }
]

Beispiel-Response: /groups/1/aggregations?aggregation=competenceLevel

[
  {
    "type": "competenceLevel",
    "value": "Ia",
    "descriptiveStatistics": { "mean": 0.15, "frequency": 2 }
  },
  {
    "type": "competenceLevel",
    "value": "II",
    "descriptiveStatistics": { "mean": 0.45, "frequency": 5 }
  },
  {
    "type": "competenceLevel",
    "value": "III",
    "descriptiveStatistics": { "mean": 0.65, "frequency": 8 }
  },
  {
    "type": "competenceLevel",
    "value": "V",
    "descriptiveStatistics": { "mean": 0.92, "frequency": 3 }
  }
]

Hinweis: aggregation vs. Covariaten

Aggregationswerte beschreiben die Gruppierung der Lösungshäufigkeiten — sie definieren, wonach die Ergebnisse aufgeschlüsselt werden. Covariaten (gender, ses, languageAtHome) sind dagegen Merkmale der Value-Group und werden über das covariates-Array transportiert, nicht über den aggregation-Parameter.


6. SES-Stufen AE: Wertebereich und Bedeutung dokumentieren

Problem

Im Schema characteristic (Spec-Zeilen 434–457) ist value: string ohne Enum definiert. Das einzige SES-Beispiel verwendet den Wert niedrig (Zeile 456). Im Projekt nutzen wir aber Buchstaben-Codes AE und mappen diese intern auf:

Code Bedeutung
A sehr niedrig
B niedrig
C mittel
D hoch
E sehr hoch

Diese Konvention ist nirgends spec-seitig festgehalten. Verschiedene Backends könnten abweichende Werte (niedrig/mittel/hoch als Volltext oder andere Codes) liefern und damit die Komponentenbibliothek brechen.

Vorgeschlagene Spec-Änderung

Das characteristic-Schema um eine optionale, wohldefinierte SES-Codierung erweitern, oder ein ergänzendes Schema ses-characteristic mit Enum:

characteristic:
  # ... bestehend ...
  example:
    type: SES
    label: Sozioökonomischer Status
    value: D
  description: >
    Für SES gelten die Werte A (sehr niedrig), B (niedrig), C (mittel), D (hoch),
    E (sehr hoch). Andere Kovariate-Typen können beliebige Werte führen.

Frontend-Mapping

SES_LABELS und SES_ORDER in group-summary.ts und school-summary.ts setzen die obige Konvention um. Sobald die Spec den Wertebereich verbindlich dokumentiert, kann das Mapping zentralisiert (z.B. in shared/config/ses.config.ts) werden.


7. descriptiveStatistics.frequency für demografische Aggregationen klären

Problem

Spec definiert frequency (Zeilen 583–584) im Kontext von Lösungs-/Items-Statistiken — also als „Anzahl richtig gelöster Items". Für demografische Aggregationen wie type=ses (oder gender, languageAtHome) wird das Feld jedoch zweckentfremdet als „Anzahl Personen pro Kategorie" genutzt.

Beispiel aus /Users/yannick/workspaces/tba3-evaluation/src/assets/mock-api/schools/aggregations.json:73–116:

{ "type": "ses", "value": "A", "descriptiveStatistics": { "frequency": 18 } }

Hier ist frequency = 18 die Anzahl Schüler:innen mit SES=A, nicht eine Lösungshäufigkeit. Ein generisches Backend könnte frequency für demografische Aggregationen anders interpretieren.

Vorgeschlagene Spec-Änderung

Entweder:

Option A: descriptive-statistics-Schema um eine konsumentenneutrale Beschreibung erweitern:

descriptive-statistics:
  properties:
    frequency:
      type: integer
      description: >
        Häufigkeit des betreffenden Werts. Im Kontext von Lösungsstatistiken: Anzahl
        richtig gelöster Items. Im Kontext demografischer Aggregationen: Anzahl Personen
        in der jeweiligen Kategorie.

Option B: Eigenes Schema count-statistics für demografische Aggregationen einführen, das explizit count (statt frequency) trägt.

Option A ist abwärtskompatibel, Option B sauberer. Empfehlung: Option A zuerst, Option B als spätere Migration.


8. competence-level: Natürliche Sortierung mitliefern

Problem

Stufen werden im Frontend regelmäßig schwach→stark sortiert dargestellt (z.B. Bucket-Reihen, Heatmap-Farben, Stufen-Verteilungen). Die Spec liefert heute nur nameShort ("I", "Ia", "A2.1", …) und name — keine Sortierinformation. nameShort-Strings lassen sich nicht zuverlässig in eine Reihenfolge bringen (römische Zahlen, gemischte Schemata pro Fach, GER-Stufen für Sprachen).

Folge: Jede konsumierende Anwendung muss die Reihenfolge je Fach manuell kuratieren (siehe src/assets/competence-level-order.json im Frontend).

Vorgeschlagene Spec-Änderung

competence-level-Schema um ein optionales Feld level ergänzen, das die natürliche Sortierung trägt (1 = schwächste Stufe, aufsteigend):

competence-level:
  type: object
  required:
    - nameShort
  properties:
    id: { type: string }
    name: { type: string }
    nameShort: { type: string }
    description: { type: string }
    level:
      type: integer
      minimum: 1
      description: >
        Natürliche Reihenfolge der Stufe innerhalb des Fachs (1 = schwächste).
        Erlaubt es Konsumenten, Stufen ohne fachspezifische Sortierregel
        aufsteigend zu ordnen.

Optional, abwärtskompatibel: Konsumenten ohne Sortier-Anforderung ignorieren das Feld; Konsumenten mit Sortier-Anforderung fallen bei fehlendem Feld auf eine lokale Konfig zurück.

Frontend-Mapping

Sobald S8 umgesetzt ist, kann competence-level-order.json im Frontend schrumpfen (nur noch Farben statt Reihenfolge) oder ganz entfallen, wenn auch Farben über Konvention abgeleitet werden.


9. Performance-Aggregation nach Covariate (SES, Gender, …)

Problem

Die Schulleitungs-Sicht "Schulentwicklung → Bildungsgerechtigkeit" zeigt einen SES-Gradienten: Lösungsquote pro SES-Quartil A–E. Die Spec kennt aktuell zwei Use Cases für demografische Information:

  1. Verteilung pro Kategorie (über ?aggregation=...-Pattern via Covariate): Wie viele Kinder sind in jeder SES-Gruppe? — heute im Mock als type=ses, value=A, descriptiveStatistics.frequency=18. Siehe Vorschlag S6/S7.
  2. Performance pro Kategorie: Wie gut schneiden Kinder mit SES=A ab vs. SES=E? — fehlt heute komplett.

Ohne (2) lässt sich Bildungsgerechtigkeit innerhalb einer Schule nicht aus den Standard-Endpoints ableiten. Stattdessen müsste auf Schüler:innen-Ebene aggregiert werden, was a) datenschutzkritisch ist und b) auf Schul-Endpoint-Ebene heute keinen Pfad hat.

Empfehlung: zweiter Aggregations-Achsen-Parameter byCovariate

Den aggregation-Parameter (S5) um einen Wert ergänzen, der eine Performance-Aggregation anhand einer Covariate auslöst. Die Covariate wird über einen zusätzlichen Parameter spezifiziert:

parameters:
  aggregation:
    schema:
      type: string
      enum:
        - competence
        - exercise
        - competenceLevel
        - domain
        - byCovariate # NEU
  byCovariate:
    name: byCovariate
    in: query
    description: >
      Bei `aggregation=byCovariate` aktiv: Name der Covariate, nach der die
      Lösungshäufigkeiten gruppiert werden. Folgt der Covariaten-Konvention
      (siehe S6).
    schema:
      type: string
      enum:
        - ses
        - gender
        - languageAtHome

Alternative (kürzer, weniger explizit): Die Covariaten-Werte direkt als zulässige aggregation-Werte zulassen — aggregation=ses liefert dann Performance pro SES-Quartil. Frontend nutzt heute diese Variante (Mock-Datei aggregations-ses.json, Aufruf schoolsIdAggregationsGet('1', 'ses')).

Empfehlung: explizite Variante mit byCovariate, weil sie Performance-Aggregation klar von Verteilungs-Aggregation trennt und die zulässigen Covariaten-Werte zentral pflegbar sind.

Beispiel-Response: /schools/{id}/aggregations?aggregation=byCovariate&byCovariate=ses

[
  {
    "type": "ses",
    "value": "A",
    "description": "sehr niedrig",
    "descriptiveStatistics": {
      "total": 18,
      "frequency": 18,
      "mean": 0.412
    },
    "comparison": [
      {
        "name": "Vergleichsschule",
        "descriptiveStatistics": { "mean": 0.548, "frequency": 7 }
      },
      {
        "name": "Landesmittelwert",
        "descriptiveStatistics": { "mean": 0.451, "frequency": 1342 }
      }
    ]
  },
  {
    "type": "ses",
    "value": "E",
    "description": "sehr hoch",
    "descriptiveStatistics": { "total": 33, "frequency": 33, "mean": 0.724 },
    "comparison": [
      {
        "name": "Vergleichsschule",
        "descriptiveStatistics": { "mean": 0.694, "frequency": 14 }
      }
    ]
  }
]

mean = Lösungsquote der Schüler:innen in dieser Covariaten-Gruppe. frequency = Anzahl Schüler:innen in dieser Gruppe (vgl. S7).

Frontend-Mapping

Aktuell genutzt im Schulentwicklungs-Dashboard (src/app/school-manager/school-development/) im Block "Bildungsgerechtigkeit". Datenquelle: Mock-Datei src/assets/mock-api/schools/aggregations-ses.json. Sobald S9 ausgeliefert ist, muss der Mock-Aufruf umgestellt werden auf ?aggregation=byCovariate&byCovariate=ses (heute: ?aggregation=ses).

Auswirkung auf andere Sichten

Dieselbe Mechanik kann für Gender-Gaps (byCovariate=gender) und Sprache-zu-Hause-Gaps (byCovariate=languageAtHome) genutzt werden — relevant für die Schul- und Schulaufsichts-Sichten. Die Authority-/District-Endpoints (S1) erben das Pattern automatisch, sobald sie Aggregations-Endpoints bereitstellen.


Zusammenfassung: Benötigte Spec-Änderungen

# Änderung Aufwand Priorität
S1 groupType-Feld im value-group-Schema Klein Hoch
S2 type-Parameter als Enum (pro Endpoint-Level) Klein Hoch
S3 type=schools Example für /states-Endpoints Klein Mittel
S4 properties-Keys für Teilnahme-Metadaten Doku Mittel
S5 aggregation-Werte als Enum dokumentieren Doku Mittel
S6 SES-Stufen AE als Konvention spezifizieren Doku Hoch
S7 frequency-Semantik für demografische Aggregationen klären Doku Mittel
S8 competence-level.level für natürliche Sortierung Klein Mittel
S9 Performance-Aggregation nach Covariate (byCovariate=ses/gender/…) Klein Hoch