Inhalt

Aktueller Ordner: duesseldorfer-schuelerinventar-swift-client
⬅ Übergeordnet
📖 Readme-Datei automatisch erkannt

DÜSK - Düsseldorfer Schülerinventar




DÜSK ist eine professionelle Desktop- und Mobile-Anwendung zur Erfassung und Auswertung von Selbst- und Fremdeinschätzungen von Schülerkompetenzen. Die Anwendung kommuniziert mit einer PHP-API und ist sowohl als Swift/iOS-App als auch als Java/Swing-Desktop-Version verfügbar.

📋 Inhaltsverzeichnis



- Überblick
- Features
- Technologie-Stack
- API-Dokumentation
- Datenbankstruktur
- Installation
- Projektstruktur
- Berechnungslogik
- Konfiguration
- Fehlerbehandlung
- Entwicklung
- Lizenz

---

🎯 Überblick



DÜSK (Düsseldorfer Schülerinventar) ist ein diagnostisches Instrument zur Erfassung von Schülerkompetenzen in sechs Bereichen:

1. Arbeitsverhalten - Zuverlässigkeit, Arbeitstempo, Planung
2. Lernverhalten - Selbstständigkeit, Belastbarkeit, Konzentration
3. Sozialverhalten - Teamfähigkeit, Hilfsbereitschaft, Kommunikation
4. Fachkompetenz - Schreiben, Lesen, Mathematik, Naturwissenschaften
5. Personale Kompetenz - Kreativität, Problemlösung, Abstraktion
6. Methodenkompetenz - Präsentation, PC-Kenntnisse, fächerübergreifendes Denken

Die Anwendung ermöglicht den Vergleich von Selbst- (SE) und Fremdeinschätzung (FE) mit Normtabellen für Hauptschulen (HS) und Förderschulen (FS).

---

✨ Features



Kernfunktionen


- ✅ Login/Logout mit Session-Verwaltung
- ✅ CRUD-Operationen für Schülerprofile
- ✅ 36 Items pro Einschätzung (4-stufige Likert-Skala)
- ✅ Automatische Kompetenzberechnung aus 72 Items
- ✅ Normwertvergleich (HS/FS Normtabellen)
- ✅ Profilansicht mit Tabellen, Diagrammen und Interpretation

Erweiterte Funktionen


- 📊 Zeitreihenanalyse für Gruppenentwicklung
- 📈 Vergleichsdiagramme (SE vs. FE)
- 📐 Korrelationsberechnung (Pearson)
- 📑 Exportfunktion (PDF/CSV/Text)
- 🔍 Such- und Filterfunktionen
- 📁 Gruppenverwaltung (CRUD)
- 🌙 Dark Mode Support
- 💾 Offline-Cache (Core Data)

---

🛠 Technologie-Stack



Swift/iOS Version


| Komponente | Technologie | Version |
|------------|-------------|---------|
| Sprache | Swift | 5.7+ |
| UI-Framework | SwiftUI | - |
| Netzwerk | URLSession (async/await) | - |
| Diagramme | Swift Charts | iOS 16+ |
| Persistenz | Core Data (optional) | - |
| Mindest-iOS | iOS 16.0 | - |
| Mindest-macOS | macOS 13.0 | - |


Server (PHP API)


| Komponente | Technologie |
|------------|-------------|
| Backend | PHP 7.4+ |
| Datenbank | MySQL 5.7+ |
| Webserver | Apache/Nginx |
| Format | JSON |

---

📡 API-Dokumentation



Die REST-API ist unter https://paul-koop.org/api/ verfügbar.

Authentifizierung



#### POST /api_login.php
`json
// Request
{
"username": "gast",
"password": "gast"
}

// Response (Success)
{
"success": true,
"userID": "12345",
"session": "abc123def456"
}

// Response (Error)
{
"success": false,
"error": "Invalid credentials"
}
`

Profile-Endpunkte



Alle Profile-Endpunkte benötigen die Header:
- X-User-ID: Benutzer-ID aus Login
- X-Session: Session-Token aus Login

#### GET /api_profiles.php
Response: Array aller Profile
`json
[
{
"profilID": "1",
"name": "Max Mustermann",
"gruppename": "Klasse 8a",
"gruppeID": "5",
"item1": 4, "item2": 3, ..., "item36": 2,
"feitem1": 3, "feitem2": 4, ..., "feitem36": 3
}
]
`

#### GET /api_profiles.php?id={profileId}
Response: Einzelnes Profil

#### POST /api_profiles.php
Request Body: Vollständiges Profil-Objekt
Response: 200 OK bei Erfolg

#### PUT /api_profiles.php
Request Body: Aktualisiertes Profil-Objekt
Response: 200 OK bei Erfolg

#### DELETE /api_profiles.php?id={profileId}
Response: 200 OK bei Erfolg

Gruppen-Endpunkte



#### GET /api_groups.php
Response:
`json
[
{"gruppeID": 1, "name": "Klasse 8a"},
{"gruppeID": 2, "name": "Klasse 8b"}
]
`

#### POST /api_groups.php
Request Body: {"name": "Neue Gruppe"}
Response: 200 OK bei Erfolg

#### DELETE /api_groups.php?id={groupId}
Response: 200 OK bei Erfolg

---

🗄 Datenbankstruktur



Tabelle: profiles



| Feld | Typ | Beschreibung |
|------|-----|---------------|
| profilID | INT AUTO_INCREMENT | Primärschlüssel |
| userID | INT | Fremdschlüssel zu users |
| name | VARCHAR(255) | Profilname |
| gruppeID | INT | Fremdschlüssel zu groups |
| created_at | TIMESTAMP | Erstellungsdatum |
| updated_at | TIMESTAMP | Letzte Änderung |
| item1 - item36 | TINYINT | SE-Items (1-4) |
| feitem1 - feitem36 | TINYINT | FE-Items (1-4) |

Tabelle: groups



| Feld | Typ | Beschreibung |
|------|-----|---------------|
| gruppeID | INT AUTO_INCREMENT | Primärschlüssel |
| userID | INT | Fremdschlüssel zu users |
| name | VARCHAR(255) | Gruppenname |
| created_at | TIMESTAMP | Erstellungsdatum |

Tabelle: users



| Feld | Typ | Beschreibung |
|------|-----|---------------|
| userID | INT AUTO_INCREMENT | Primärschlüssel |
| username | VARCHAR(50) | Benutzername (unique) |
| password_hash | VARCHAR(255) | Gehashter Passwort |
| session_token | VARCHAR(255) | Aktueller Session-Token |
| created_at | TIMESTAMP | Registrierungsdatum |

SQL-Schema



`sql
-- Users Tabelle
CREATE TABLE users (
userID INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
session_token VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Groups Tabelle
CREATE TABLE groups (
gruppeID INT AUTO_INCREMENT PRIMARY KEY,
userID INT NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (userID) REFERENCES users(userID) ON DELETE CASCADE,
UNIQUE KEY unique_group_per_user (userID, name)
);

-- Profiles Tabelle
CREATE TABLE profiles (
profilID INT AUTO_INCREMENT PRIMARY KEY,
userID INT NOT NULL,
name VARCHAR(255) NOT NULL,
gruppeID INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 36 SE-Items
item1 TINYINT DEFAULT 2, item2 TINYINT DEFAULT 2, ..., item36 TINYINT DEFAULT 2,
-- 36 FE-Items
feitem1 TINYINT DEFAULT 2, feitem2 TINYINT DEFAULT 2, ..., feitem36 TINYINT DEFAULT 2,
FOREIGN KEY (userID) REFERENCES users(userID) ON DELETE CASCADE,
FOREIGN KEY (gruppeID) REFERENCES groups(gruppeID) ON DELETE SET NULL
);
`

---

💻 Installation



Swift (iOS/macOS) Version



`bash

Repository klonen


git clone https://github.com/yourusername/duesk-swift.git
cd duesk-swift

Xcode-Projekt öffnen


open Duesk.xcodeproj

Build und Run (⌘R)


`

Oder mit Swift Package Manager:
`bash
swift build
swift run
`

Server (PHP API) Setup



`bash

1. Dateien auf Webserver kopieren


scp -r api/* user@server:/var/www/html/api/

2. Datenbank einrichten


mysql -u root -p < database/schema.sql

3. Konfiguration anpassen


cp config.sample.php config.php
vim config.php

4. Verzeichnisberechtigungen setzen


chmod 755 /var/www/html/api/
chmod 644 /var/www/html/api/*.php
`

---

📁 Projektstruktur



Swift Version


`
duesk-swift/
├── Duesk.xcodeproj/ # Xcode Projektdatei
├── Duesk/
│ ├── App/
│ │ ├── DueskApp.swift # App-Einstiegspunkt
│ │ └── Info.plist # App-Konfiguration
│ ├── Models/
│ │ ├── LoginResponse.swift # API Response Model
│ │ ├── Profile.swift # Profil Datenmodell
│ │ ├── Group.swift # Gruppen Datenmodell
│ │ └── Norms.swift # Normtabellen & Konstanten
│ ├── Views/
│ │ ├── LoginView.swift # Login-Screen
│ │ ├── MainView.swift # Hauptansicht mit Sidebar
│ │ ├── ProfileDetailView.swift # Profildetails mit Charts
│ │ ├── ProfileEditView.swift # Profil bearbeiten/erstellen
│ │ ├── GroupManagerView.swift # Gruppenverwaltung
│ │ └── TimeSeriesView.swift # Zeitreihenanalyse
│ ├── ViewModels/
│ │ ├── LoginViewModel.swift
│ │ ├── ProfileListViewModel.swift
│ │ ├── ProfileDetailViewModel.swift
│ │ ├── ProfileEditViewModel.swift
│ │ ├── GroupManagerViewModel.swift
│ │ └── TimeSeriesViewModel.swift
│ ├── Services/
│ │ ├── APIService.swift # Netzwerk-API Calls
│ │ ├── SessionManager.swift # Session & Auth
│ │ └── Calculator.swift # Berechnungslogik
│ └── Assets.xcassets/ # App Icons & Assets
└── README.md
`


Server API Struktur


`
/var/www/html/api/
├── api_login.php # Login-Endpunkt
├── api_profiles.php # Profile CRUD
├── api_groups.php # Gruppen CRUD
├── config.php # Datenbankkonfiguration
├── database/
│ ├── schema.sql # DB-Schema
│ └── seed.sql # Testdaten
├── includes/
│ ├── db_connection.php # DB-Verbindungsklasse
│ └── auth.php # Authentifizierungs-Middleware
└── .htaccess # Apache Rewrite Rules
`

---

🧮 Berechnungslogik



Item-zu-Kompetenz-Zuordnung



`swift
// Kompetenz 1: Arbeitsverhalten (Items 1-10)
let kompetenz1 = items[0...9]

// Kompetenz 2: Lernverhalten (Items 11-20)
let kompetenz2 = items[10...19]

// Kompetenz 3: Sozialverhalten (Items 21-28 + 9-10)
let kompetenz3 = items[20...27] + [items[8], items[9]]

// Kompetenz 4: Fachkompetenz (Items 29-36)
let kompetenz4 = items[28...35]

// Kompetenz 5: Personale Kompetenz
let kompetenz5 = [items[0], items[1], items[5], items[6], items[7],
items[8], items[9], items[11], items[12], items[13], items[14]]

// Kompetenz 6: Methodenkompetenz
let kompetenz6 = [items[2], items[3], items[4], items[8], items[9],
items[10], items[16], items[17]]
`

Profilwertberechnung



`swift
func calculateProfileValue(rawSum: Int, normTable: [Double]) -> Int {
// 1 = weit unterdurchschnittlich
// 2 = unterdurchschnittlich
// 3 = durchschnittlich
// 4 = überdurchschnittlich
// 5 = weit überdurchschnittlich

for (index, threshold) in normTable.enumerated() {
if Double(rawSum) < threshold {
return index + 1
}
}
return 5
}
`

Korrelationsberechnung (Pearson)



`swift
func pearsonCorrelation(x: [Double], y: [Double]) -> Double {
let n = Double(x.count)
let sumX = x.reduce(0, +)
let sumY = y.reduce(0, +)
let sumXY = zip(x, y).map(*).reduce(0, +)
let sumX2 = x.map { $0 * $0 }.reduce(0, +)
let sumY2 = y.map { $0 * $0 }.reduce(0, +)

let numerator = sumXY - (sumX * sumY / n)
let denominator = sqrt((sumX2 - (sumX sumX / n)) (sumY2 - (sumY * sumY / n)))

return denominator == 0 ? 0 : numerator / denominator
}
`

Normtabellen (Auszug)



| Kompetenz | HS SE (Perzentile) | HS FE (Perzentile) |
|-----------|-------------------|-------------------|
| Arbeitsverhalten | 21.3, 25.3, 29.3, 33.3, 37.3 | 12.7, 18.2, 23.7, 29.2, 34.7 |
| Lernverhalten | 20.9, 24.9, 29.0, 33.1, 37.2 | 13.3, 18.4, 23.5, 28.6, 33.7 |
| Sozialverhalten | 17.9, 21.4, 24.8, 28.2, 31.7 | 10.8, 15.4, 20.1, 24.7, 29.4 |
| Fachkompetenz | 14.0, 17.7, 21.4, 25.2, 28.9 | 14.2, 15.3, 16.4, 17.5, 18.5 |
| Personale Kompetenz | 24.6, 28.6, 33.0, 37.5, 42.0 | 14.1, 20.2, 26.3, 32.4, 38.5 |
| Methodenkompetenz | 15.5, 19.0, 22.4, 25.8, 29.3 | 10.5, 14.5, 18.5, 22.5, 26.5 |

---

⚙️ Konfiguration



API Base URL ändern



Swift:
`swift
// APIService.swift
private let baseURL = "https://your-server.com/api/"
`


PHP (Server):
`php
// config.php
define('DB_HOST', 'localhost');
define('DB_NAME', 'duesk');
define('DB_USER', 'username');
define('DB_PASS', 'password');
define('API_SECRET', 'your-secret-key');
`

Umgebungsvariablen



`bash

Entwicklung


export API_URL="https://dev.paul-koop.org/api/"
export DEBUG_MODE=true

Produktion


export API_URL="https://paul-koop.org/api/"
export DEBUG_MODE=false
`

---

🐛 Fehlerbehandlung



HTTP-Statuscodes



| Code | Bedeutung | Behandlung |
|------|-----------|------------|
| 200 | Erfolg | Daten verarbeiten |
| 400 | Bad Request | Validierungsfehler prüfen |
| 401 | Unauthorized | Neu anmelden |
| 403 | Forbidden | Berechtigung prüfen |
| 404 | Not Found | Ressource existiert nicht |
| 500 | Server Error | Erneut versuchen, Support kontaktieren |

Typische Fehler und Lösungen



`swift
// Netzwerkfehler
do {
let profiles = try await APIService.shared.getProfiles()
} catch let error as URLError {
switch error.code {
case .notConnectedToInternet:
showAlert("Keine Internetverbindung")
case .timedOut:
showAlert("Verbindung zeitüberschritten")
default:
showAlert("Netzwerkfehler: \(error.localizedDescription)")
}
}
`

---

👨‍💻 Entwicklung



Voraussetzungen



Swift Entwicklung:
- macOS 12.0+
- Xcode 14.0+
- Swift 5.7+


Tests ausführen



`bash

Swift Tests


cd duesk-swift
swift test


Build für Produktion



`bash

iOS App (IPA)


xcodebuild -scheme Duesk -configuration Release archive

macOS App (APP)


xcodebuild -scheme Duesk -configuration Release build

Java JAR


mvn clean package
`