shinkan-jinkendo/backend/scripts/parse_matrix.py
Lars e8eba57b3a
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 1m55s
feat: Migration 022+023 - Vollständiger Skills-Import
- Migration 022: Schema-Erweiterung
  - skill_main_categories (KARATE / ALLGEMEINE)
  - skills.main_category_id + focus_areas JSONB
  - skill_level_definitions Tabelle
  - Indizes für Performance

- Migration 023: Produktionsreifer Skills-Import
  - 69 Skills mit vollständiger Kategorisierung
  - 2 Haupt-Kategorien, 9 Unterkategorien
  - KARATE: 32 Skills (karate focus)
  - ALLGEMEINE: 37 Skills (universal focus)
  - Duplikat-Bereinigung (Timing, Antizipation, etc.)

- Scripts:
  - parse_matrix.py: Extrahiert Skills aus Fähigkeitsmatrix
  - generate_migration_023_direct.py: Generiert Migration direkt aus Matrix

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

Verified auf Dev: 69 Skills importiert, korrekte Kategorisierung
2026-04-27 10:58:33 +02:00

150 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Parse Fähigkeitsmatrix und extrahiere vollständige Kategorisierung.
"""
import sys
import httpx
from bs4 import BeautifulSoup
def parse_matrix():
"""Parse die Fähigkeitsmatrix und gib CSV-Mapping aus."""
# Wiki-Login
api_url = 'https://karatetrainer.net/api.php'
username = 'Jinkendo'
password = 'Jinkendo6970'
# Synchroner Client
with httpx.Client(timeout=30) as client:
# Schritt 1: Login-Token holen
r1 = client.get(api_url, params={
'action': 'query',
'meta': 'tokens',
'type': 'login',
'format': 'json',
})
r1.raise_for_status()
data1 = r1.json()
token = data1.get('query', {}).get('tokens', {}).get('logintoken', '')
if not token:
print("ERROR: Kein Login-Token erhalten", file=sys.stderr)
return
# Schritt 2: Login durchführen
r2 = client.post(api_url, data={
'action': 'login',
'lgname': username,
'lgpassword': password,
'lgtoken': token,
'format': 'json',
})
r2.raise_for_status()
data2 = r2.json()
if data2.get('login', {}).get('result') != 'Success':
print(f"ERROR: Login fehlgeschlagen: {data2}", file=sys.stderr)
return
# Hole Matrix-Seite als HTML
page_url = 'https://karatetrainer.net/index.php?title=Fähigkeitsmatrix'
r3 = client.get(page_url)
r3.raise_for_status()
html = r3.text
if not html:
print("ERROR: Konnte Matrix-HTML nicht abrufen", file=sys.stderr)
return
# Parse HTML
soup = BeautifulSoup(html, 'html.parser')
table = soup.find('table', {'class': 'wikitable'})
if not table:
print("ERROR: Tabelle nicht gefunden", file=sys.stderr)
return
rows = table.find_all('tr')
# Extrahiere Struktur
# Default: KARATE Fähigkeiten (bis Row 49 wo ALLGEMEINE beginnt)
current_main_cat = 'karate'
current_sub_cat = None
skills_data = []
for idx, row in enumerate(rows):
cells = row.find_all(['td', 'th'])
if len(cells) == 1:
# Kategorie-Header (1 Zelle)
text = cells[0].get_text(strip=True)
# Überspringe Inhaltsverzeichnis (Row 4)
if 'Inhaltsverzeichnis' in text:
continue
# Haupt-Kategorie Wechsel erkennen (nur bei "ALLGEMEINE sportliche Fähigkeiten")
if 'ALLGEMEINE' in text and 'sportliche' in text:
current_main_cat = 'allgemeine'
current_sub_cat = None
# print(f"DEBUG: Row {idx} - Wechsel zu allgemeine", file=sys.stderr)
else:
# Alle anderen 1-Zellen-Rows sind Unterkategorien
# Bereinige Text (entferne Sonderzeichen, Leerzeichen)
current_sub_cat = text.replace('­', '').strip() # soft hyphen entfernen
# print(f"DEBUG: Row {idx} - Unterkategorie '{current_sub_cat}' (main_cat={current_main_cat})", file=sys.stderr)
elif len(cells) == 6:
# Skill-Zeile (6 Zellen: Name + 5 Level-Beschreibungen)
skill_name = cells[0].get_text(strip=True).replace('­', '') # soft hyphen entfernen
# Nur Skills mit Hauptkategorie UND Unterkategorie erfassen
if skill_name and current_main_cat and current_sub_cat:
# Fokusbereich bestimmen
focus = 'karate' if current_main_cat == 'karate' else 'universal'
skills_data.append({
'skill': skill_name,
'sub_cat': current_sub_cat,
'main_cat': current_main_cat,
'focus': focus,
'row': idx
})
# Duplikat-Handling: Bevorzuge spezifischere Kategorien
# Bei Duplikaten: Kondition/Koordination > Kumite (für allgemeine Fähigkeiten)
# Kumite > Rest (für Kampf-spezifische wie "Timing", "Antizipation")
duplicates_to_remove = {
('Anaerobe Ausdauer', 'Kumite'), # Behalte Kondition
('Bewegungsschnelligkeit', 'Kumite'), # Behalte Kondition
('Flexibilität', 'Kumite'), # Behalte Kondition
('Reaktionsschnelligkeit', 'Kumite'), # Behalte Kondition
('Schnelligkeitsausdauer', 'Kumite'), # Behalte Kondition
('Antizipation', 'Koordination'), # Behalte Kumite (kampfspezifisch)
('Timing', 'Koordination'), # Behalte Kumite (kampfspezifisch)
}
# Filtere Duplikate
filtered_skills = []
for s in skills_data:
if (s['skill'], s['sub_cat']) not in duplicates_to_remove:
filtered_skills.append(s)
# Ausgabe als CSV (UTF-8)
import sys
# Force UTF-8 output
sys.stdout.reconfigure(encoding='utf-8')
print('skill_name,sub_category,main_category,focus_areas')
for s in filtered_skills:
# Escape Kommas in Namen
skill = s['skill'].replace(',', ';')
sub_cat = s['sub_cat'].replace(',', ';')
print(f"{skill},{sub_cat},{s['main_cat']},{s['focus']}")
print(f"\n# Total: {len(filtered_skills)} Skills (von {len(skills_data)} Zeilen, {len(skills_data) - len(filtered_skills)} Duplikate entfernt)", file=sys.stderr)
if __name__ == '__main__':
parse_matrix()