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
This commit is contained in:
parent
97c1bb0c8b
commit
e8eba57b3a
90
backend/migrations/022_skills_schema_complete.sql
Normal file
90
backend/migrations/022_skills_schema_complete.sql
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
-- Migration 022: Skills Schema Complete - Hauptkategorien + Fokusbereich + Level-Definitionen
|
||||
-- Purpose: Erweitert Skills-Schema um vollständige Kategorisierung für Prod-Import
|
||||
-- Date: 2026-04-27
|
||||
|
||||
-- ======================================================================
|
||||
-- 1. Haupt-Kategorien (KARATE Fähigkeiten / ALLGEMEINE sportliche Fähigkeiten)
|
||||
-- ======================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS skill_main_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) UNIQUE NOT NULL,
|
||||
slug VARCHAR(50) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
sort_order INT,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ======================================================================
|
||||
-- 2. Unterkategorien erweitern (Beziehung zu Hauptkategorie)
|
||||
-- ======================================================================
|
||||
|
||||
-- Slug für URL-friendly Namen
|
||||
ALTER TABLE skill_categories
|
||||
ADD COLUMN IF NOT EXISTS slug VARCHAR(50);
|
||||
|
||||
-- Beziehung zu Hauptkategorie
|
||||
ALTER TABLE skill_categories
|
||||
ADD COLUMN IF NOT EXISTS main_category_id INT REFERENCES skill_main_categories(id);
|
||||
|
||||
-- Unique constraint für slug
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'skill_categories_slug_unique'
|
||||
) THEN
|
||||
ALTER TABLE skill_categories ADD CONSTRAINT skill_categories_slug_unique UNIQUE (slug);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ======================================================================
|
||||
-- 3. Skills erweitern (Hauptkategorie + Fokusbereich)
|
||||
-- ======================================================================
|
||||
|
||||
-- Beziehung zu Hauptkategorie (für direkte Queries)
|
||||
ALTER TABLE skills
|
||||
ADD COLUMN IF NOT EXISTS main_category_id INT REFERENCES skill_main_categories(id);
|
||||
|
||||
-- Fokusbereich als JSONB Array (z.B. ['karate'], ['universal'], ['karate', 'selbstverteidigung'])
|
||||
ALTER TABLE skills
|
||||
ADD COLUMN IF NOT EXISTS focus_areas JSONB DEFAULT '[]'::jsonb;
|
||||
|
||||
-- ======================================================================
|
||||
-- 4. Level-Definitionen (1-5 Beschreibungen aus Matrix)
|
||||
-- ======================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS skill_level_definitions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
skill_id INT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
|
||||
level INT NOT NULL CHECK (level BETWEEN 1 AND 5),
|
||||
description TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE (skill_id, level)
|
||||
);
|
||||
|
||||
-- Index für schnelle Abfragen
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_level_definitions_skill_id
|
||||
ON skill_level_definitions(skill_id);
|
||||
|
||||
-- ======================================================================
|
||||
-- 5. Indizes für Performance
|
||||
-- ======================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_main_category_id
|
||||
ON skills(main_category_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_category_id
|
||||
ON skills(category_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_categories_main_category_id
|
||||
ON skill_categories(main_category_id);
|
||||
|
||||
-- ======================================================================
|
||||
-- 6. Kommentare für Dokumentation
|
||||
-- ======================================================================
|
||||
|
||||
COMMENT ON TABLE skill_main_categories IS 'Hauptkategorien: KARATE Fähigkeiten / ALLGEMEINE sportliche Fähigkeiten';
|
||||
COMMENT ON COLUMN skills.focus_areas IS 'JSONB Array: [''karate''] = nur Karate, [''universal''] = alle Fokussbereiche';
|
||||
COMMENT ON TABLE skill_level_definitions IS 'Level 1-5 Beschreibungen aus Fähigkeitsmatrix für jede Fähigkeit';
|
||||
147
backend/migrations/023_skills_complete_import.sql
Normal file
147
backend/migrations/023_skills_complete_import.sql
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
-- Total: 69 Skills
|
||||
-- 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
|
||||
-- ======================================================================
|
||||
|
||||
INSERT INTO skill_categories (name, slug, main_category_id, description, sort_order) VALUES
|
||||
('Kata', 'kata', (SELECT id FROM skill_main_categories WHERE slug='karate'), '', 1),
|
||||
('Kihon', 'kihon', (SELECT id FROM skill_main_categories WHERE slug='karate'), '', 2),
|
||||
('Kumite', 'kumite', (SELECT id FROM skill_main_categories WHERE slug='karate'), '', 3),
|
||||
('Selbstverteidigung', 'selbstverteidigung', (SELECT id FROM skill_main_categories WHERE slug='karate'), '', 4),
|
||||
('Kognition', 'kognition', (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '', 5),
|
||||
('Kondition', 'kondition', (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '', 6),
|
||||
('Koordination', 'koordination', (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '', 7),
|
||||
('Psychische Fähigkeiten', 'psychische_faehigkeiten', (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '', 8),
|
||||
('Soziale Fähigkeiten', 'soziale_faehigkeiten', (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '', 9);
|
||||
|
||||
-- ======================================================================
|
||||
-- 3. SKILLS
|
||||
-- ======================================================================
|
||||
|
||||
INSERT INTO skills (name, description, category_id, main_category_id, focus_areas) VALUES
|
||||
('Technik Kombination', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Kata Ablauf', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Bunkai', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Oyo', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Henka', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Kakushi', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Kata Atmung', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Kata Rhythmus', '', (SELECT id FROM skill_categories WHERE slug='kata'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Dachi Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Uke Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Zuki Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Uchi Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Geri Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Nage Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Nukite Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Ken Waza', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Hüfteinsatz', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Kime', '', (SELECT id FROM skill_categories WHERE slug='kihon'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Beinarbeit', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Distanzkontrolle', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Angriff', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Abwehr Konter', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Präzision', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Antizipation', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Timing', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Taktik', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Fokus', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Mentale Stärke', '', (SELECT id FROM skill_categories WHERE slug='kumite'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Gefahrenbewustsein', '', (SELECT id FROM skill_categories WHERE slug='selbstverteidigung'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Selbstbehauptung', '', (SELECT id FROM skill_categories WHERE slug='selbstverteidigung'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Selbstschutz', '', (SELECT id FROM skill_categories WHERE slug='selbstverteidigung'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Gefahrenabwehr', '', (SELECT id FROM skill_categories WHERE slug='selbstverteidigung'), (SELECT id FROM skill_main_categories WHERE slug='karate'), '["karate"]'::jsonb),
|
||||
('Aufmerksamkeit', '', (SELECT id FROM skill_categories WHERE slug='kognition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Wahrnehmung', '', (SELECT id FROM skill_categories WHERE slug='kognition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Urteilsvermögen', '', (SELECT id FROM skill_categories WHERE slug='kognition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Merkfähigkeit', '', (SELECT id FROM skill_categories WHERE slug='kognition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Lernfähigkeit', '', (SELECT id FROM skill_categories WHERE slug='kognition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Maximalkraft', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Schnellkraft', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Reaktivkraft', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Kraftausdauer', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Muskelaufbau', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Reaktionsschnelligkeit', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Bewegungsschnelligkeit', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Handlungsschnelligkeit', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Schnelligkeitsausdauer', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Grundlagenausdauer', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Aerobe Ausdauer', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Anaerobe Ausdauer', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Regenerationsfähigkeit', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Ermüdungswiderstandsfähigkeit', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Flexibilität', '', (SELECT id FROM skill_categories WHERE slug='kondition'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Orientierung', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Differenzierung', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Kopplung', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Gleichgewicht', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Rhythmisierung', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Reaktion', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Umstellung', '', (SELECT id FROM skill_categories WHERE slug='koordination'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Selbstvertrauen', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Konzentration', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Emotionale Kontrolle', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Motivation', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Stressresistenz', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Stressregulation', '', (SELECT id FROM skill_categories WHERE slug='psychische_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Deeskalation', '', (SELECT id FROM skill_categories WHERE slug='soziale_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Selbstdisziplin', '', (SELECT id FROM skill_categories WHERE slug='soziale_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Toleranz', '', (SELECT id FROM skill_categories WHERE slug='soziale_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb),
|
||||
('Fairness', '', (SELECT id FROM skill_categories WHERE slug='soziale_faehigkeiten'), (SELECT id FROM skill_main_categories WHERE slug='allgemeine'), '["universal"]'::jsonb);
|
||||
|
||||
-- ======================================================================
|
||||
-- 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;
|
||||
|
||||
211
backend/scripts/generate_migration_023_direct.py
Normal file
211
backend/scripts/generate_migration_023_direct.py
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#!/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)
|
||||
149
backend/scripts/parse_matrix.py
Normal file
149
backend/scripts/parse_matrix.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#!/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()
|
||||
Loading…
Reference in New Issue
Block a user