#!/usr/bin/env python3 """ Generiert Migration 023 direkt aus der Fähigkeitsmatrix (ohne CSV-Zwischenschritt). """ import sys import httpx from bs4 import BeautifulSoup from collections import defaultdict # Force UTF-8 output sys.stdout.reconfigure(encoding='utf-8') def to_slug(name): """Wandelt Namen in URL-friendly Slugs um.""" return name.lower().replace(' ', '_').replace('ä', 'ae').replace('ö', 'oe').replace('ü', 'ue').replace('ß', 'ss') # Wiki-Login und Matrix-Abruf api_url = 'https://karatetrainer.net/api.php' username = 'Jinkendo' password = 'Jinkendo6970' with httpx.Client(timeout=30) as client: # Login r1 = client.get(api_url, params={'action': 'query', 'meta': 'tokens', 'type': 'login', 'format': 'json'}) r1.raise_for_status() token = r1.json().get('query', {}).get('tokens', {}).get('logintoken', '') r2 = client.post(api_url, data={'action': 'login', 'lgname': username, 'lgpassword': password, 'lgtoken': token, 'format': 'json'}) r2.raise_for_status() # Hole Matrix r3 = client.get('https://karatetrainer.net/index.php?title=Fähigkeitsmatrix') r3.raise_for_status() html = r3.text # Parse HTML soup = BeautifulSoup(html, 'html.parser') table = soup.find('table', {'class': 'wikitable'}) if not table: print("ERROR: Tabelle nicht gefunden", file=sys.stderr) sys.exit(1) rows = table.find_all('tr') # Extrahiere Skills 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: text = cells[0].get_text(strip=True) if 'Inhaltsverzeichnis' in text: continue if 'ALLGEMEINE' in text and 'sportliche' in text: current_main_cat = 'allgemeine' current_sub_cat = None else: current_sub_cat = text.replace('­', '').strip() elif len(cells) == 6: skill_name = cells[0].get_text(strip=True).replace('­', '') if skill_name and current_main_cat and current_sub_cat: 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 }) # Duplikat-Handling duplicates_to_remove = { ('Anaerobe Ausdauer', 'Kumite'), ('Bewegungsschnelligkeit', 'Kumite'), ('Flexibilität', 'Kumite'), ('Reaktionsschnelligkeit', 'Kumite'), ('Schnelligkeitsausdauer', 'Kumite'), ('Antizipation', 'Koordination'), ('Timing', 'Koordination'), } filtered_skills = [] for s in skills_data: if (s['skill'], s['sub_cat']) not in duplicates_to_remove: filtered_skills.append(s) # Gruppiere nach Kategorien by_main_cat = defaultdict(lambda: defaultdict(list)) sub_categories = {} for s in filtered_skills: main = s['main_cat'] sub = s['sub_cat'] by_main_cat[main][sub].append(s) if sub not in sub_categories: sub_categories[sub] = (to_slug(sub), main) # Generiere SQL print("""-- Migration 023: Vollständiger Skills-Import -- Purpose: Produktionsreifer Import aller 69 Skills mit vollständiger Kategorisierung -- Source: Fähigkeitsmatrix https://karatetrainer.net/index.php?title=Fähigkeitsmatrix -- Date: 2026-04-27 -- ====================================================================== -- CLEANUP: Alte Daten löschen -- ====================================================================== -- Erst M:N-Beziehungen löschen DELETE FROM exercise_skills; -- Skills löschen (Cascades zu skill_level_definitions) DELETE FROM skills; -- Kategorien löschen DELETE FROM skill_categories; DELETE FROM skill_main_categories WHERE id > 0; -- Falls Tabelle existiert -- ====================================================================== -- 1. HAUPT-KATEGORIEN -- ====================================================================== INSERT INTO skill_main_categories (name, slug, description, sort_order) VALUES ('KARATE Fähigkeiten', 'karate', 'Karate-spezifische Techniken und Fähigkeiten', 1), ('ALLGEMEINE sportliche Fähigkeiten', 'allgemeine', 'Universelle sportliche und mentale Fähigkeiten', 2); -- ====================================================================== -- 2. UNTERKATEGORIEN -- ====================================================================== """) # Sortiere Unterkategorien karate_subs = sorted([(name, slug, main) for name, (slug, main) in sub_categories.items() if main == 'karate']) allgemeine_subs = sorted([(name, slug, main) for name, (slug, main) in sub_categories.items() if main == 'allgemeine']) sort_order = 1 print("INSERT INTO skill_categories (name, slug, main_category_id, description, sort_order) VALUES") for idx, (name, slug, main_cat) in enumerate(karate_subs + allgemeine_subs): comma = "," if idx < len(karate_subs) + len(allgemeine_subs) - 1 else ";" print(f"('{name}', '{slug}', (SELECT id FROM skill_main_categories WHERE slug='{main_cat}'), '', {sort_order}){comma}") sort_order += 1 print(""" -- ====================================================================== -- 3. SKILLS -- ====================================================================== INSERT INTO skills (name, description, category_id, main_category_id, focus_areas) VALUES""") # Sortiere Skills karate_skills = [] for sub in [name for name, slug, main in karate_subs]: karate_skills.extend(by_main_cat['karate'][sub]) allgemeine_skills = [] for sub in [name for name, slug, main in allgemeine_subs]: allgemeine_skills.extend(by_main_cat['allgemeine'][sub]) all_skills = karate_skills + allgemeine_skills for idx, s in enumerate(all_skills): name = s['skill'].replace("'", "''") # SQL-Escape sub_cat_slug = to_slug(s['sub_cat']) main_cat_slug = s['main_cat'] focus = s['focus'] comma = "," if idx < len(all_skills) - 1 else ";" print(f"('{name}', '', (SELECT id FROM skill_categories WHERE slug='{sub_cat_slug}'), (SELECT id FROM skill_main_categories WHERE slug='{main_cat_slug}'), '[\"{focus}\"]'::jsonb){comma}") print(""" -- ====================================================================== -- 4. VERIFIKATION -- ====================================================================== -- Sollte 69 Skills ergeben DO $$ DECLARE skill_count INT; BEGIN SELECT COUNT(*) INTO skill_count FROM skills; IF skill_count != 69 THEN RAISE WARNING 'FEHLER: % Skills gefunden, erwartet 69', skill_count; ELSE RAISE NOTICE 'OK: 69 Skills importiert'; END IF; END $$; -- Zeige Verteilung SELECT mc.name AS hauptkategorie, sc.name AS unterkategorie, COUNT(s.id) AS anzahl_skills FROM skills s JOIN skill_categories sc ON s.category_id = sc.id JOIN skill_main_categories mc ON s.main_category_id = mc.id GROUP BY mc.name, sc.name, mc.sort_order, sc.sort_order ORDER BY mc.sort_order, sc.sort_order; """) print(f"-- Total: {len(all_skills)} Skills", file=sys.stderr)