shinkan-jinkendo/.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md
Lars 5626be792f
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 1m54s
docs: Dokumentation aktualisiert für Skills-Import Complete
Updates:
- DATABASE_SCHEMA.md v0.4.0 (Migration 022+023, Skills-Hierarchie)
- DOMAIN_MODEL.md v0.4.0 (Fähigkeiten-Domäne, Fokusbereich-Zuordnung)
- MEDIAWIKI_IMPORT_SPEC.md v1.1 (Skills via Migration statt API)
- PROJECT_STATUS.md (NEU - Projekt-Übersicht & Deployment-Status)

Änderungen:
- 69 Skills mit Kategorisierung dokumentiert
- Haupt-/Unterkategorien (KARATE/ALLGEMEINE)
- Fokusbereich-Zuordnung (karate/universal)
- Level-Definitionen-Schema
- Migration 021 als DEPRECATED markiert
- Deployment-Status aktualisiert
- Lessons Learned dokumentiert
2026-04-27 11:18:49 +02:00

14 KiB
Raw Blame History

MediaWiki Import Specification

Version: 1.1 Datum: 2026-04-27 Status: DEPLOYED - Skills via Migration, Exercises via API Autor: Claude Code


1. Übersicht

Ziel: Übungen aus dem Jinkendo MediaWiki-System direkt via API importieren (NICHT über Export/XML-Dump siehe Architektur-Entscheidung in EXERCISES_ARCHITECTURE.md §8).

Importrichtung: Einseitig (MediaWiki → Shinkan). Kein bidirektionaler Sync.

Import-Reihenfolge: Skills → Methods → Exercises (Abhängigkeiten zuerst)

Import-Tracking: Jeder Import wird geloggt, Duplikate werden via wiki_page_title erkannt und nicht doppelt angelegt.

⚠️ WICHTIG - Skills-Import (ab 2026-04-27): Skills werden NICHT mehr über die API importiert, sondern über Migration 023 (vollständiger Import aus Fähigkeitsmatrix mit Kategorisierung). Siehe .claude/handover/session-2026-04-27-skills-complete.md für Details.


2. MediaWiki API-Zugriff

2.1 API-Endpoint

GET https://karatetrainer.net/api.php

Benötigte Parameter:

  • action=query Seiteninhalte abfragen
  • action=parse Einzelseite parsen (mit Semantic MediaWiki Properties)
  • prop=text|properties|categories Relevante Felder
  • format=json

2.2 Authentifizierung

Aktuell: Kein Auth (wenn Wiki öffentlich zugänglich). Bei privatem Wiki: lgtoken via /api.php?action=login.

2.3 Relevante API-Calls

Alle Übungen einer Kategorie auflisten:

GET /api.php?action=query&list=categorymembers&cmtitle=Kategorie:Übungen&cmlimit=500&format=json

Einzelne Übung abrufen (mit SMW Properties):

GET /api.php?action=parse&page=Übungsname&prop=text|properties&format=json

SMW-Properties einer Seite:

GET /api.php?action=browsebysubject&subject=Übungsname&format=json

3. Field-Mapping (MediaWiki → Exercises)

Echte Property-Namen von karatetrainer.net (via discover_properties() ermittelt, 2026-04-24)

3.1 Kern-Felder

Wiki-Property (SMW) Exercises-Feld Transformation
Übungsbezeichnung title Bevorzugt über Seitentitel
Summary summary Direkt
Ziel goal Direkt
Durchführung execution Wikitext → Plaintext
Hinweise trainer_notes Wikitext → Plaintext
Plandauer duration_min = duration_max Zahl in Minuten z.B. "10"
Gruppengröße group_size_min Zahl z.B. "2"
Hilfsmittel equipment (JSONB) Array von Strings

3.2 Katalog-Mapping

Wiki-Property Exercises-Tabelle Transformation
Übungstyp exercise_focus_areas z.B. "Karate" → focus_area Name → ID
Zielgruppe exercise_target_groups Name → ID Lookup
Altersgruppe exercise_age_groups Name → ID Lookup
Trainingsmethode exercise method ref Wiki-Seitenname → Label (Unterstriche → Leerzeichen)
PrimaryCapability exercise_skills (Name) Skill-Name → ID Lookup
CapabilityLevel exercise_skills (target_level) Integer → Named Level (1=einsteiger…5=experte)
Schlüsselworte Keywords (für zukünftige Tag-Funktion)

3.3 Skill-Level-Mapping (CapabilityLevel → INTEGER)

🆕 ÄNDERUNG (2026-04-27): target_level ist jetzt INTEGER (1-5), nicht mehr String.

Wiki-Wert Shinkan DB-Wert Bedeutung
"1" 1 (INTEGER) Einsteiger
"2" 2 (INTEGER) Grundlagen
"3" 3 (INTEGER) Aufbau
"4" 4 (INTEGER) Fortgeschritten
"5" 5 (INTEGER) Experte

Implementierung: smw_mapper.py Zeile 271-299 build_skill_assignments() konvertiert String → INTEGER via int(level_str.strip()).

3.4 Kategorien (karatetrainer.net)

Import-Typ Wiki-Kategorie Status Import-Methode
exercises Übungen Produktiv API (/api/import-wiki/execute)
skills Fähigkeitsbeschreibung ⚠️ DEPRECATED APIMigration 023
methods Methodenbeschreibung 🔲 Geplant API (noch nicht implementiert)

⚠️ Skills-Import-Änderung (2026-04-27):

Skills werden NICHT mehr über import_type: skill importiert, sondern über:

  • Migration 022: Schema-Erweiterung (skill_main_categories, focus_areas, level_definitions)
  • Migration 023: Vollständiger Import (69 Skills mit Kategorisierung aus Fähigkeitsmatrix)

Grund: API-Import lieferte nur skill_name, aber KEINE Kategorisierung (Haupt-/Unterkategorie, Fokusbereich). Migration 023 importiert alle Skills mit vollständiger Metadaten-Struktur.

Quelle: https://karatetrainer.net/index.php?title=Fähigkeitsmatrix

Details: .claude/handover/session-2026-04-27-skills-complete.md

Unbekannte Katalog-Werte: Warnmeldung im Import-Log, Übung wird ohne diese Zuordnung importiert (kein Abbruch).

3.3 Import-Tracking-Felder

Feld Wert
import_source 'mediawiki'
import_id Wiki-Seitenname (URL-kodiert)
visibility 'private' (Trainer prüft vor Veröffentlichung)
status 'draft'

3.4 Wikitext-Bereinigung

Vor dem Speichern werden folgende Wikitext-Elemente in Plaintext umgewandelt:

  • [[Link|Text]]Text
  • [[Link]]Link
  • {{Template}} → (entfernt)
  • '''Bold'''Bold
  • ''Italic''Italic
  • ==Überschrift==\nÜberschrift\n
  • Wikitables → (entfernt, zu komplex für automatischen Import)

4. Tracking-Tabellen

-- Migration: wiki_import_tracking
-- (In eigene Migration auslagern: 018_wiki_import_tracking.sql)

CREATE TABLE IF NOT EXISTS wiki_import_log (
    id SERIAL PRIMARY KEY,
    import_type VARCHAR(50) NOT NULL,    -- 'exercise', 'skill', 'method'
    import_status VARCHAR(20) CHECK (import_status IN ('running', 'completed', 'failed')),
    items_total INT DEFAULT 0,
    items_imported INT DEFAULT 0,
    items_skipped INT DEFAULT 0,
    items_failed INT DEFAULT 0,
    error_log JSONB DEFAULT '[]'::jsonb, -- Array von {item, error} Objekten
    imported_by INT REFERENCES profiles(id),
    started_at TIMESTAMP DEFAULT NOW(),
    finished_at TIMESTAMP
);

CREATE TABLE IF NOT EXISTS wiki_import_references (
    id SERIAL PRIMARY KEY,
    wiki_page_title VARCHAR(500) NOT NULL,
    wiki_page_id INT,               -- MediaWiki interne ID
    content_type VARCHAR(50) NOT NULL, -- 'exercise', 'skill', 'method'
    local_id INT NOT NULL,          -- ID in der lokalen DB
    last_imported TIMESTAMP DEFAULT NOW(),
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(wiki_page_title, content_type)
);

CREATE INDEX IF NOT EXISTS idx_wiki_refs_title ON wiki_import_references(wiki_page_title);
CREATE INDEX IF NOT EXISTS idx_wiki_refs_type ON wiki_import_references(content_type);

5. API-Endpoints

5.1 Endpoints Overview

Method Endpoint Beschreibung
GET /import/mediawiki/preview Vorschau: Was wird importiert?
POST /import/mediawiki/execute Import ausführen
GET /import/mediawiki/status/{log_id} Import-Status abrufen
GET /import/mediawiki/logs Alle Import-Logs
DELETE /import/mediawiki/references/{id} Referenz löschen (für Re-Import)

5.2 GET /import/mediawiki/preview

Query Parameters:

  • category (string, required) Wiki-Kategorie z.B. Übungen
  • limit (int, optional, default: 10) Maximale Anzahl zum Voranschauen

Response: 200 OK

{
  "category": "Übungen",
  "total_found": 47,
  "preview": [
    {
      "wiki_page_title": "Maai - Distanzübung",
      "wiki_page_id": 1234,
      "already_imported": false,
      "last_imported_at": null,
      "mapped_fields": {
        "title": "Maai - Distanzübung",
        "summary": "Distanzgefühl entwickeln...",
        "goal": "Ziel...",
        "focus_area": "Karate",
        "focus_area_found": true
      },
      "warnings": [],
      "errors": []
    },
    {
      "wiki_page_title": "Kumite Grundtechniken",
      "already_imported": true,
      "last_imported_at": "2026-04-10T10:00:00Z",
      "mapped_fields": {...},
      "warnings": ["Feld 'Zielgruppe' hat unbekannten Wert 'Fortgeschrittene' - wird ignoriert"],
      "errors": []
    }
  ]
}

5.3 POST /import/mediawiki/execute

Request Body:

{
  "category": "Übungen",
  "reimport_existing": false,   // true: Bereits importierte Übungen überschreiben
  "dry_run": false,             // true: Nur simulieren, nichts speichern
  "limit": null                 // null: alle importieren
}

Response: 202 Accepted (Import läuft asynchron)

{
  "log_id": 5,
  "status": "running",
  "message": "Import gestartet. Verwende GET /import/mediawiki/status/5 für Status."
}

5.4 GET /import/mediawiki/status/{log_id}

Response: 200 OK

{
  "id": 5,
  "import_type": "exercise",
  "import_status": "completed",
  "items_total": 47,
  "items_imported": 44,
  "items_skipped": 2,
  "items_failed": 1,
  "error_log": [
    {
      "item": "Unvollständige Übung",
      "error": "Pflichtfeld 'Durchführung' fehlt in Wiki-Seite"
    }
  ],
  "imported_by": 1,
  "started_at": "2026-04-24T10:00:00Z",
  "finished_at": "2026-04-24T10:02:30Z"
}

5.5 GET /import/mediawiki/logs

Response: 200 OK Array von Import-Log Objekten (ohne error_log Details)


6. Backend-Implementation

6.1 Router-Datei

backend/routers/import_wiki.py

6.2 Import-Prozess (Pseudocode)

async def execute_wiki_import(category: str, reimport: bool, dry_run: bool):
    # 1. Alle Seiten der Kategorie via MediaWiki API abrufen
    pages = wiki_api.get_category_members(category)

    for page_title in pages:
        # 2. Duplikat-Check
        existing_ref = db.get_wiki_ref(page_title, 'exercise')
        if existing_ref and not reimport:
            log.skip(page_title, "bereits importiert")
            continue

        # 3. Seite parsen
        wiki_data = wiki_api.parse_page(page_title)

        # 4. Field-Mapping
        exercise_data = map_wiki_to_exercise(wiki_data)

        # 5. Validierung
        errors = validate_exercise(exercise_data)
        if errors:
            log.fail(page_title, errors)
            continue

        # 6. Katalog-IDs auflösen
        exercise_data = resolve_catalog_ids(exercise_data)

        if not dry_run:
            # 7. Speichern
            exercise_id = db.upsert_exercise(exercise_data)

            # 8. Import-Referenz aktualisieren
            db.upsert_wiki_ref(page_title, 'exercise', exercise_id)

        log.success(page_title)

6.3 Wikitext Parser

import re

def wikitext_to_plaintext(wikitext: str) -> str:
    """Konvertiert Wikitext in lesbares Plaintext."""
    text = wikitext

    # Links: [[Link|Text]] → Text, [[Link]] → Link
    text = re.sub(r'\[\[([^|]+)\|([^\]]+)\]\]', r'\2', text)
    text = re.sub(r'\[\[([^\]]+)\]\]', r'\1', text)

    # Templates entfernen
    text = re.sub(r'\{\{[^}]+\}\}', '', text)

    # Formatierung
    text = re.sub(r"'''(.+?)'''", r'\1', text)
    text = re.sub(r"''(.+?)''", r'\1', text)

    # Überschriften
    text = re.sub(r'={2,6}\s*(.+?)\s*={2,6}', r'\n\1\n', text)

    # Mehrfache Leerzeilen normalisieren
    text = re.sub(r'\n{3,}', '\n\n', text)

    return text.strip()

7. Frontend-Import-UI

7.1 Route

/admin/import/mediawiki   → AdminWikiImportPage

7.2 Layout

┌─────────────────────────────────────────┐
│ MediaWiki Import                        │
│ ─────────────────────────────────────── │
│ Wiki-Kategorie: [Übungen          ]     │
│ [+ Vorschau anzeigen]                   │
│ ─────────────────────────────────────── │
│ Vorschau (10 von 47):                   │
│                                         │
│ ✅ Maai - Distanzübung (neu)            │
│ ✅ Kizami-Zuki (neu)                    │
│ ⚠️ Kumite XY (bereits importiert)      │
│ ❌ Defekte Seite (Pflichtfeld fehlt)    │
│ ...                                     │
│ ─────────────────────────────────────── │
│ ☐ Bereits importierte überschreiben    │
│ ☐ Nur simulieren (dry run)             │
│ ─────────────────────────────────────── │
│ [47 Übungen importieren]                │
│ ─────────────────────────────────────── │
│ Letzter Import: 44/47 · 2026-04-10     │
│ [Import-Logs anzeigen]                  │
└─────────────────────────────────────────┘

8. Fehlerbehandlung

Fehler Verhalten
Wiki-Seite nicht erreichbar Import-Prozess abbricht, Log-Eintrag
Pflichtfeld (goal, execution) fehlt Item wird übersprungen, Warnung im Log
Unbekannter Katalog-Wert (Fokusbereich etc.) Item wird ohne diese Zuordnung importiert
Duplikat-Titel im selben Club Item wird übersprungen (außer reimport=true)
Wiki-API Timeout Retry 3x, dann fail mit Log-Eintrag

9. Sicherheit

  • Nur Super-Admin darf Import ausführen (require_role('superadmin'))
  • MediaWiki-URL ist konfigurierbar via .env (kein Hardcoding)
  • Rate-Limiting: max. 1 Import parallel, max. 10/Tag
  • Importierte Übungen starten immer als visibility='private', status='draft'

10. Konfiguration (.env)

MEDIAWIKI_API_URL=https://karatetrainer.net/api.php
MEDIAWIKI_USER=import-bot       # optional, für privates Wiki
MEDIAWIKI_PASSWORD=...          # optional

Version: 1.0 Datum: 2026-04-24 Status: DRAFT - Ready for Review