- 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
150 lines
5.3 KiB
Python
150 lines
5.3 KiB
Python
#!/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()
|