Update
This commit is contained in:
parent
76ab5cbc87
commit
e7b370b40f
|
@ -14,9 +14,6 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# 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=
|
||||
|
|
4
Makefile
4
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,7 +125,11 @@ 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|
|
||||
|
@ -138,12 +154,16 @@ module Backend
|
|||
teacher_score: teacher_score,
|
||||
}
|
||||
end
|
||||
|
||||
valid_count += 1
|
||||
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,
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de-DE">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Zuordnungen</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=jetbrains-mono:400" rel="stylesheet" />
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
.column {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.padded {
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
float: left;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
background: #f4f4f4;
|
||||
word-wrap: break-word;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.1rem 0.3rem 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.dashed {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.dashed > li {
|
||||
text-indent: -5px;
|
||||
}
|
||||
|
||||
ul.dashed > li:before {
|
||||
content: "-";
|
||||
text-indent: -5px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.link {
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
font-weight: lighter;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Mentorenwahl: Zuordnungen</h1>
|
||||
<table width="100%" cellspacing="0" border="0" class="border-table">
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="75%" />
|
||||
</colgroup>
|
||||
|
||||
<tr>
|
||||
<th class="border">Zeitpunkt</th>
|
||||
<%- time_unix = @time.to_unix -%>
|
||||
<td class="border padded"><%= @time %> (<%= time_unix.negative? ? "-" : "+" %><%= time_unix %>)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th class="border">Anzahl Lehrer</th>
|
||||
<td class="border padded"><%= @assignments.size %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Zuordnungen</h2>
|
||||
<table width="100%" cellspacing="0" border="0">
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="75%" />
|
||||
</colgroup>
|
||||
|
||||
<tr>
|
||||
<th class="border">Lehrer</th>
|
||||
<th class="border">Schüler</th>
|
||||
</tr>
|
||||
|
||||
<%- @assignments.each do |a| -%>
|
||||
<tr>
|
||||
<td class="border padded"><%= a.teacher.last_name %>, <%= a.teacher.first_name %></td>
|
||||
<td class="border padded">
|
||||
<ul class="dashed">
|
||||
<%- a.students.each do |s| -%>
|
||||
<li><%= s[:user].last_name %>, <%= s[:user].first_name %> (<%= s[:class] %>)</li>
|
||||
<%- end -%>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="de-DE">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="de-DE">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<script src="https://kit.fontawesome.com/7efbac0aa5.js" crossorigin="anonymous"></script>
|
||||
<script
|
||||
src="https://kit.fontawesome.com/7efbac0aa5.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<link data-trunk rel="css" href="assets/css/bulma.css" />
|
||||
<link data-trunk rel="css" href="assets/css/styles.css" />
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>Diese Website funktioniert leider nicht ohne JavaScript.</strong>
|
||||
</noscript>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue