diff --git a/.example.env b/.example.env
index a766173..ad3afe6 100644
--- a/.example.env
+++ b/.example.env
@@ -14,9 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-# General
-URL=
-
# Db
POSTGRES_USER="mw"
POSTGRES_PASSWORD=
@@ -30,7 +27,7 @@ AUTH_UNTIS_PASSWORD=
# Backend
BACKEND_MINIMUM_TEACHER_SELECTION_COUNT=6
-BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=16777216
+BACKEND_ASSIGNMENT_RUN_TIME=60
BACKEND_URL=URL
# Backend - API
BACKEND_API_JWT_SECRET=
diff --git a/Makefile b/Makefile
index 1e9d9b7..e2a7a73 100644
--- a/Makefile
+++ b/Makefile
@@ -19,10 +19,10 @@
all: prod
dev:
- BUILDKIT_PROGRESS=plain docker compose build --build-arg BUILD_ENV=development
+ docker compose build --build-arg BUILD_ENV=development
prod:
- BUILDKIT_PROGRESS=plain docker compose build
+ docker compose build
docs:
cd docs && mdbook build
diff --git a/backend/src/backend/cli.cr b/backend/src/backend/cli.cr
index 035dcd4..560848e 100644
--- a/backend/src/backend/cli.cr
+++ b/backend/src/backend/cli.cr
@@ -250,5 +250,41 @@ module Backend
end
end
end
+
+ # ameba:disable Lint/ShadowingOuterLocalVar
+ cmd.commands.add do |cmd|
+ cmd.use = "assignments:export"
+ cmd.short = "Generates report for all assignments"
+ cmd.long = cmd.short
+
+ cmd.run do
+ time = Time.local
+
+ a_id = Db::Assignment.query.select(:id).where { active }.first!.id
+
+ html = Templates::Assignments.new(
+ time,
+ Db::Teacher.query
+ .with_student_assignments(&.where { assignment_id == a_id }.with_student(&.with_user.with_class_model))
+ .to_a
+ .select(&.student_assignments.count.positive?)
+ .map do |t|
+ Templates::Assignments::Assignment.new(
+ Templates::Assignments::User.new(t.user.first_name, t.user.last_name),
+ t.student_assignments
+ .map do |sa|
+ {
+ user: Templates::Assignments::User.new(sa.student.user.first_name, sa.student.user.last_name),
+ class: sa.student.class_model.name,
+ }
+ end
+ .sort_by! { |sa| {sa[:class], sa[:user].last_name, sa[:user].first_name} }
+ )
+ end
+ .sort_by! { |a| {a.teacher.last_name, a.teacher.first_name} }
+ ).to_s
+ puts "Filepath: #{Auth.generate_pdf(html).filename}"
+ end
+ end
end
end
diff --git a/backend/src/backend/config.cr b/backend/src/backend/config.cr
index 84ff3f1..7172f0f 100644
--- a/backend/src/backend/config.cr
+++ b/backend/src/backend/config.cr
@@ -55,14 +55,11 @@ module Backend
build_env.development?
end
- # Base URL of application
- getter url : String
-
# Minimum teacher selection count
getter minimum_teacher_selection_count : Int32
- # Assignment possibility count
- getter assignment_possibility_count : UInt32
+ # Assignment max run time for algorithm
+ getter assignment_run_time : UInt32
@[EnvConfig::Setting(key: "api")]
# Configuration for `Api`
diff --git a/backend/src/backend/db/teacher.cr b/backend/src/backend/db/teacher.cr
index dd863f3..54ed062 100644
--- a/backend/src/backend/db/teacher.cr
+++ b/backend/src/backend/db/teacher.cr
@@ -10,6 +10,6 @@ module Backend::Db
column max_students : Int32
has_many teacher_votes : TeacherVote, foreign_key: :teacher_id
- has_many assignments : Assignment, foreign_key: :teacher_id
+ has_many student_assignments : StudentAssignment, foreign_key: :teacher_id
end
end
diff --git a/backend/src/backend/templates/assignments.cr b/backend/src/backend/templates/assignments.cr
new file mode 100644
index 0000000..3a80147
--- /dev/null
+++ b/backend/src/backend/templates/assignments.cr
@@ -0,0 +1,27 @@
+class Backend::Templates::Assignments
+ struct User
+ property first_name
+ property last_name
+
+ def initialize(@first_name : String, @last_name : String)
+ end
+ end
+
+ alias Student = {user: User, class: String}
+
+ struct Assignment
+ property teacher
+ property students
+
+ def initialize(@teacher : User, @students : Array(Student))
+ end
+ end
+
+ def initialize(
+ @time : Time,
+ @assignments : Array(Assignment)
+ )
+ end
+
+ ECR.def_to_s "templates/html/assignments.min.html.ecr"
+end
diff --git a/backend/src/backend/worker/jobs/assignment_job.cr b/backend/src/backend/worker/jobs/assignment_job.cr
index c3f100e..5a626b1 100644
--- a/backend/src/backend/worker/jobs/assignment_job.cr
+++ b/backend/src/backend/worker/jobs/assignment_job.cr
@@ -28,6 +28,11 @@ module Backend
# :ditto:
def perform : Nil
+ if Db::Config.query.where { active }.first!.can_vote
+ log "Voting still allowed, skipping assignment"
+ fail
+ end
+
teacher_count = Db::Teacher.query.count
student_count = Db::Student.query.count
vote_count = Db::Vote.query.count
@@ -43,8 +48,10 @@ module Backend
elsif vote_count < student_count
log "Not all students voted, skipping assignment"
fail
- elsif Db::Assignment.query.count > 0
- log "Assignment has already run, skipping another assignment"
+ end
+
+ if Db::Teacher.query.sum(:max_students, Int64) < student_count
+ log "Capacity too low, teachers need to adopt more students, skipping assignment"
fail
end
@@ -68,7 +75,7 @@ module Backend
end
end
)
- p! "Got teachers"
+ log "Got teachers"
students = Db::Student.query
.with_vote(&.with_teacher_votes(&.order_by(priority: :desc)))
@@ -89,13 +96,18 @@ module Backend
end
)
votes_a = votes.to_a
- p! "Got students' votes"
+ log "Got students' votes"
best : {assignment: Hash(Int32, Assignment), priority_score: Int64, teacher_score: Int64}? = nil
empty_assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size)
- p! "Starting assignment"
- i = 0
- while i < Backend.config.assignment_possibility_count
+ log "Starting assignment"
+ valid_count = 0_u64
+ invalid_count = 0_u64
+
+ max_span = Time::Span.new(minutes: Backend.config.assignment_run_time)
+ start_time = Time.utc
+
+ loop do
assignment = {} of Int32 => Assignment
assignment_count = empty_assignment_count.clone
votes_a.shuffle(Random::Secure).each do |s, tvs|
@@ -113,37 +125,45 @@ module Backend
end
end
- next if assignment.size != student_count
+ if assignment.size != student_count
+ invalid_count += 1
+ log "Invalid combination, next (#{assignment.size} != #{student_count}, valid: #{valid_count} invalid: #{invalid_count})"
+ else
+ log "Valid combination (valid: #{valid_count} invalid: #{invalid_count})!"
- priority_score = 0_i64
- assignment.each do |_, a|
- priority_score += a[:priority] ** 2
+ priority_score = 0_i64
+ assignment.each do |_, a|
+ priority_score += a[:priority] ** 2
+ end
+
+ teacher_score = 0_i64
+ assignment_count.each do |t, c|
+ teacher_score += (teacher_max_students[t] ** c) * teacher_max_students[t]
+ end
+
+ if best.nil?
+ best = {
+ assignment: assignment,
+ priority_score: priority_score,
+ teacher_score: teacher_score,
+ }
+ elsif priority_score < best.not_nil![:priority_score] && teacher_score < best.not_nil![:teacher_score]
+ best = {
+ assignment: assignment,
+ priority_score: priority_score,
+ teacher_score: teacher_score,
+ }
+ end
+
+ valid_count += 1
end
- teacher_score = 0_i64
- assignment_count.each do |t, c|
- teacher_score += (teacher_max_students[t] ** c) * teacher_max_students[t]
- end
-
- if best.nil?
- best = {
- assignment: assignment,
- priority_score: priority_score,
- teacher_score: teacher_score,
- }
- elsif priority_score < best.not_nil![:priority_score] && teacher_score < best.not_nil![:teacher_score]
- best = {
- assignment: assignment,
- priority_score: priority_score,
- teacher_score: teacher_score,
- }
- end
+ break if (Time.utc - start_time) > max_span && valid_count > 0
end
pp! best
- p! "Saving best assignment into database"
- # Db::Assignment.import(best.not_nil![:assignment].map { |s, a| Db::Assignment.new({student_id: s, teacher_id: a[:teacher]}) })
+ log "Saving best assignment into database"
Db::Assignment.query.where { active }.to_update.set(active: false).execute
assignment_id = Db::Assignment.create!({
active: true,
diff --git a/backend/templates/html/assignments.html.ecr b/backend/templates/html/assignments.html.ecr
new file mode 100644
index 0000000..0b46b87
--- /dev/null
+++ b/backend/templates/html/assignments.html.ecr
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+ Zuordnungen
+
+
+
+
+
+
+
+
+
Mentorenwahl: Zuordnungen
+
+
+
+
+
+
+
+ Zeitpunkt |
+ <%- time_unix = @time.to_unix -%>
+ <%= @time %> (<%= time_unix.negative? ? "-" : "+" %><%= time_unix %>) |
+
+
+
+ Anzahl Lehrer |
+ <%= @assignments.size %> |
+
+
+
+
+
+
Zuordnungen
+
+
+
+
+
+
+
+ Lehrer |
+ Schüler |
+
+
+ <%- @assignments.each do |a| -%>
+
+ <%= a.teacher.last_name %>, <%= a.teacher.first_name %> |
+
+
+ <%- a.students.each do |s| -%>
+ - <%= s[:user].last_name %>, <%= s[:user].first_name %> (<%= s[:class] %>)
+ <%- end -%>
+
+ |
+
+ <%- end -%>
+
+
+
+
diff --git a/backend/templates/html/users.html.ecr b/backend/templates/html/users.html.ecr
index 6d22e6b..c1c7ada 100644
--- a/backend/templates/html/users.html.ecr
+++ b/backend/templates/html/users.html.ecr
@@ -1,5 +1,5 @@
-
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 4ea42c6..04edded 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -78,9 +78,8 @@ services:
- redis
- auth
environment:
- BACKEND_URL: ${URL}
BACKEND_MINIMUM_TEACHER_SELECTION_COUNT: ${BACKEND_MINIMUM_TEACHER_SELECTION_COUNT}
- BACKEND_ASSIGNMENT_POSSIBILITY_COUNT: ${BACKEND_ASSIGNMENT_POSSIBILITY_COUNT}
+ BACKEND_ASSIGNMENT_RUN_TIME: ${BACKEND_ASSIGNMENT_RUN_TIME}
BACKEND_API_JWT_SECRET: ${BACKEND_API_JWT_SECRET}
BACKEND_API_JWT_EXPIRATION: ${BACKEND_API_JWT_EXPIRATION}
BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER}
diff --git a/frontend/index.html b/frontend/index.html
index 22f6574..d34a799 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,21 +1,22 @@
-
+
+
+
+
+
-
-
-
-
+
-
+
+
+
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+