Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
sed replacement left orphaned hostname references in story page and empty lines in getApiBase functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
235 lines
6.4 KiB
Go
235 lines
6.4 KiB
Go
package seed
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// Seeder generates demo data for the school service
|
|
type Seeder struct {
|
|
pool *pgxpool.Pool
|
|
teacherID uuid.UUID
|
|
rng *rand.Rand
|
|
}
|
|
|
|
// NewSeeder creates a new seeder instance
|
|
func NewSeeder(pool *pgxpool.Pool, teacherID uuid.UUID) *Seeder {
|
|
return &Seeder{
|
|
pool: pool,
|
|
teacherID: teacherID,
|
|
rng: rand.New(rand.NewSource(time.Now().UnixNano())),
|
|
}
|
|
}
|
|
|
|
// German first names for realistic demo data
|
|
var firstNames = []string{
|
|
"Max", "Paul", "Leon", "Felix", "Jonas", "Lukas", "Tim", "Ben", "Finn", "Elias",
|
|
"Emma", "Mia", "Hannah", "Sophia", "Lena", "Anna", "Marie", "Leonie", "Lara", "Laura",
|
|
"Noah", "Luis", "David", "Moritz", "Jan", "Niklas", "Tom", "Simon", "Erik", "Jannik",
|
|
"Lea", "Julia", "Lisa", "Sarah", "Clara", "Amelie", "Emily", "Maja", "Zoe", "Lina",
|
|
"Alexander", "Maximilian", "Sebastian", "Philipp", "Julian", "Fabian", "Tobias", "Christian",
|
|
"Katharina", "Christina", "Johanna", "Franziska", "Antonia", "Victoria", "Helena", "Charlotte",
|
|
}
|
|
|
|
// German last names for realistic demo data
|
|
var lastNames = []string{
|
|
"Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker",
|
|
"Schulz", "Hoffmann", "Koch", "Bauer", "Richter", "Klein", "Wolf", "Schroeder",
|
|
"Neumann", "Schwarz", "Zimmermann", "Braun", "Krueger", "Hofmann", "Hartmann", "Lange",
|
|
"Schmitt", "Werner", "Schmitz", "Krause", "Meier", "Lehmann", "Schmid", "Schulze",
|
|
"Maier", "Koehler", "Herrmann", "Koenig", "Walter", "Mayer", "Huber", "Kaiser",
|
|
"Peters", "Lang", "Scholz", "Moeller", "Gross", "Jung", "Friedrich", "Keller",
|
|
}
|
|
|
|
// Subjects with exam data
|
|
type SubjectConfig struct {
|
|
Name string
|
|
ShortName string
|
|
IsMain bool
|
|
ExamsPerYear int
|
|
WrittenWeight int
|
|
}
|
|
|
|
var subjects = []SubjectConfig{
|
|
{"Deutsch", "De", true, 4, 60},
|
|
{"Mathematik", "Ma", true, 4, 60},
|
|
{"Englisch", "En", true, 4, 60},
|
|
{"Biologie", "Bio", false, 3, 50},
|
|
{"Geschichte", "Ge", false, 2, 50},
|
|
{"Physik", "Ph", false, 3, 50},
|
|
{"Chemie", "Ch", false, 2, 50},
|
|
{"Sport", "Sp", false, 0, 30},
|
|
{"Kunst", "Ku", false, 1, 40},
|
|
{"Musik", "Mu", false, 1, 40},
|
|
}
|
|
|
|
// SeedAll generates all demo data
|
|
func (s *Seeder) SeedAll(ctx context.Context) error {
|
|
log.Println("Starting seed data generation...")
|
|
|
|
// 1. Create school years
|
|
schoolYears, err := s.seedSchoolYears(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding school years: %w", err)
|
|
}
|
|
log.Printf("Created %d school years", len(schoolYears))
|
|
|
|
// 2. Create subjects
|
|
subjectIDs, err := s.seedSubjects(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding subjects: %w", err)
|
|
}
|
|
log.Printf("Created %d subjects", len(subjectIDs))
|
|
|
|
// 3. Create classes with students
|
|
for _, sy := range schoolYears {
|
|
classes, err := s.seedClassesForYear(ctx, sy)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding classes for year %s: %w", sy.Name, err)
|
|
}
|
|
|
|
for _, class := range classes {
|
|
// Create students
|
|
students, err := s.seedStudentsForClass(ctx, class.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding students for class %s: %w", class.Name, err)
|
|
}
|
|
log.Printf("Created %d students for class %s", len(students), class.Name)
|
|
|
|
// Create exams and results
|
|
for _, subj := range subjects {
|
|
subjectID := subjectIDs[subj.Name]
|
|
err := s.seedExamsAndResults(ctx, class.ID, subjectID, students, sy.ID, subj)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding exams for class %s, subject %s: %w", class.Name, subj.Name, err)
|
|
}
|
|
}
|
|
|
|
// Create attendance records
|
|
err = s.seedAttendance(ctx, students, sy)
|
|
if err != nil {
|
|
return fmt.Errorf("seeding attendance for class %s: %w", class.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Println("Seed data generation completed successfully!")
|
|
return nil
|
|
}
|
|
|
|
// SchoolYearResult holds created school year info
|
|
type SchoolYearResult struct {
|
|
ID uuid.UUID
|
|
Name string
|
|
StartDate time.Time
|
|
EndDate time.Time
|
|
IsCurrent bool
|
|
}
|
|
|
|
func (s *Seeder) seedSchoolYears(ctx context.Context) ([]SchoolYearResult, error) {
|
|
years := []struct {
|
|
name string
|
|
startYear int
|
|
isCurrent bool
|
|
}{
|
|
{"2022/23", 2022, false},
|
|
{"2023/24", 2023, false},
|
|
{"2024/25", 2024, true},
|
|
}
|
|
|
|
var results []SchoolYearResult
|
|
for _, y := range years {
|
|
id := uuid.New()
|
|
startDate := time.Date(y.startYear, time.August, 1, 0, 0, 0, 0, time.UTC)
|
|
endDate := time.Date(y.startYear+1, time.July, 31, 0, 0, 0, 0, time.UTC)
|
|
|
|
_, err := s.pool.Exec(ctx, `
|
|
INSERT INTO school_years (id, name, start_date, end_date, is_current, teacher_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
ON CONFLICT DO NOTHING
|
|
`, id, y.name, startDate, endDate, y.isCurrent, s.teacherID)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results = append(results, SchoolYearResult{
|
|
ID: id,
|
|
Name: y.name,
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
IsCurrent: y.isCurrent,
|
|
})
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (s *Seeder) seedSubjects(ctx context.Context) (map[string]uuid.UUID, error) {
|
|
subjectIDs := make(map[string]uuid.UUID)
|
|
|
|
for _, subj := range subjects {
|
|
id := uuid.New()
|
|
_, err := s.pool.Exec(ctx, `
|
|
INSERT INTO subjects (id, teacher_id, name, short_name, is_main_subject)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
ON CONFLICT (teacher_id, name) DO UPDATE SET id = subjects.id
|
|
RETURNING id
|
|
`, id, s.teacherID, subj.Name, subj.ShortName, subj.IsMain)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subjectIDs[subj.Name] = id
|
|
}
|
|
|
|
return subjectIDs, nil
|
|
}
|
|
|
|
// ClassResult holds created class info
|
|
type ClassResult struct {
|
|
ID uuid.UUID
|
|
Name string
|
|
GradeLevel int
|
|
}
|
|
|
|
func (s *Seeder) seedClassesForYear(ctx context.Context, sy SchoolYearResult) ([]ClassResult, error) {
|
|
// Classes: 5a, 5b, 6a, 6b, 7a, 7b
|
|
classConfigs := []struct {
|
|
name string
|
|
gradeLevel int
|
|
}{
|
|
{"5a", 5}, {"5b", 5},
|
|
{"6a", 6}, {"6b", 6},
|
|
{"7a", 7}, {"7b", 7},
|
|
}
|
|
|
|
var results []ClassResult
|
|
for _, c := range classConfigs {
|
|
id := uuid.New()
|
|
_, err := s.pool.Exec(ctx, `
|
|
INSERT INTO classes (id, teacher_id, school_year_id, name, grade_level, school_type, federal_state)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
ON CONFLICT (teacher_id, school_year_id, name) DO UPDATE SET id = classes.id
|
|
`, id, s.teacherID, sy.ID, c.name, c.gradeLevel, "gymnasium", "niedersachsen")
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results = append(results, ClassResult{
|
|
ID: id,
|
|
Name: c.name,
|
|
GradeLevel: c.gradeLevel,
|
|
})
|
|
}
|
|
|
|
return results, nil
|
|
}
|