Update
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Dominic Grimm 2022-12-29 22:24:29 +01:00
parent 125bbf7ff1
commit 6620dea812
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
10 changed files with 92 additions and 71 deletions

View file

@ -84,6 +84,9 @@ module Backend
true true
end end
user = Db::User.create!(username: input.username, role: input.role.to_db, admin: input.admin) user = Db::User.create!(username: input.username, role: input.role.to_db, admin: input.admin)
if input.role.student?
Db::Student.create!(user_id: user.id)
end
Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue
User.new(user) User.new(user)
@ -105,7 +108,7 @@ module Backend
def start_assignment(context : Context) : Bool? def start_assignment(context : Context) : Bool?
context.admin! context.admin!
Worker::Jobs::AssignStudentsJob.new.enqueue Worker::Jobs::AssignmentJob.new.enqueue
true true
end end

View file

@ -62,7 +62,7 @@ module Backend
getter minimum_teacher_selection_count : Int32 getter minimum_teacher_selection_count : Int32
# Assignment possibility count # Assignment possibility count
getter assignment_possibility_count : Int32 getter assignment_possibility_count : UInt32
@[EnvConfig::Setting(key: "api")] @[EnvConfig::Setting(key: "api")]
# Configuration for `Api` # Configuration for `Api`

View file

@ -24,11 +24,9 @@ module Backend
alias Raw = Hash(String, Array(String)) alias Raw = Hash(String, Array(String))
@[JSON::Field(key: "givenName")]
# First name # First name
getter first_name : String getter first_name : String
@[JSON::Field(key: "sn")]
# Last name # Last name
getter last_name : String getter last_name : String

View file

@ -18,13 +18,9 @@ module Backend
module Worker module Worker
module Jobs module Jobs
# Assigns students to teachers when all students voted # Assigns students to teachers when all students voted
class AssignStudentsJob < Mosquito::QueuedJob class AssignmentJob < Mosquito::QueuedJob
# run_every 1.minute # run_every 1.minute
def rescheduleable? : Bool
false
end
alias TeacherVote = {student: Int32, priority: Int32} alias TeacherVote = {student: Int32, priority: Int32}
alias Assignment = {teacher: Int32, priority: Int32} alias Assignment = {teacher: Int32, priority: Int32}
@ -33,7 +29,6 @@ module Backend
teacher_count = Db::Teacher.query.count teacher_count = Db::Teacher.query.count
student_count = Db::Student.query.count student_count = Db::Student.query.count
vote_count = Db::Vote.query.count vote_count = Db::Vote.query.count
if teacher_count == 0 if teacher_count == 0
log "No teachers found, skipping assignment" log "No teachers found, skipping assignment"
fail fail
@ -58,13 +53,12 @@ module Backend
end end
.with_teacher_votes .with_teacher_votes
.to_a .to_a
vote_index = Hash.zip(teachers.map(&.id), [0] * teachers.size) teacher_ids = teachers.map(&.id)
teacher_max_students = Hash.zip(teacher_ids, teachers.map(&.max_students))
teacher_votes : Hash(Int32, Array(TeacherVote)) = Hash.zip( teacher_votes : Hash(Int32, Array(TeacherVote)) = Hash.zip(
teachers.map(&.id), teacher_ids,
teachers.map do |t| teachers.map do |t|
t.teacher_votes.map do |tv| t.teacher_votes.map do |tv|
vote_index[t.id] += 1
{ {
student: tv.vote.student.id, student: tv.vote.student.id,
priority: tv.priority, priority: tv.priority,
@ -72,78 +66,75 @@ module Backend
end end
end end
) )
teachers.sort_by! { |t| vote_index[t.id] }
students = Db::Student.query students = Db::Student.query
.with_vote(&.with_teacher_votes(&.order_by(priority: :desc))) .with_vote(&.with_teacher_votes(&.order_by(priority: :desc)))
.to_a .to_a
students.each do |s|
p! s
pp! s.vote.not_nil!.teacher_votes
end
student_ids = students.map(&.id) student_ids = students.map(&.id)
votes = Hash.zip( votes = Hash.zip(
student_ids, student_ids,
students.map do |s| students.map do |s|
s.vote.not_nil!.teacher_votes s.vote.not_nil!.teacher_votes
.to_a .to_a
.select { |tv| teacher_votes.has_key?(tv.teacher.id) } .select! { |tv| teacher_votes.has_key?(tv.teacher.id) }
.map do |tv| .map do |tv|
{ {
teacher: tv.teacher.id, teacher: tv.teacher.id,
teacher_max_students: tv.teacher.max_students, priority: tv.priority,
} }
end end
end end
) )
votes_a = votes.to_a votes_a = votes.to_a
best : {assignment: Hash(Int32, Assignment), score: UInt32}? = nil best : {assignment: Hash(Int32, Assignment), priority_score: UInt32, teacher_score: UInt32}? = nil
empty_assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size)
Backend.config.assignment_possibility_count.times.each do Backend.config.assignment_possibility_count.times.each do
assignment = {} of Int32 => Assignment assignment = {} of Int32 => Assignment
assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size) assignment_count = empty_assignment_count.clone
votes_a.shuffle(Random::Secure).each do |s, tvs| votes_a.shuffle(Random::Secure).each do |s, tvs|
tvs.each_with_index do |tv, i| tvs.each do |tv|
if assignment[s]?.nil? if assignment_count[tv[:teacher]] < teacher_max_students[tv[:teacher]]
assignment_count[tv[:teacher]] += 1 if assignment[s]?.nil?
assignment[s] = {teacher: tv[:teacher], priority: i} assignment_count[tv[:teacher]] += 1
elsif assignment_count[tv[:teacher]] < tv[:teacher_max_students] assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]}
assignment_count[assignment[s][:teacher]] -= 1 else
assignment_count[tv[:teacher]] += 1 assignment_count[assignment[s][:teacher]] -= 1
assignment[s] = {teacher: tv[:teacher], priority: i} assignment_count[tv[:teacher]] += 1
assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]}
end
end end
end end
end end
score = 0_u32 priority_score = 0_u32
assignment.each do |_, a| assignment.each do |_, a|
score += a[:priority] ** 2 priority_score += a[:priority] ** 2
end
teacher_score = 0_u32
assignment_count.each do |t, c|
teacher_score += (teacher_max_students[t] ** c) * teacher_max_students[t]
end end
if best.nil? if best.nil?
best = { best = {
assignment: assignment, assignment: assignment,
score: score, priority_score: priority_score,
teacher_score: teacher_score,
} }
elsif score < best.not_nil![:score] elsif priority_score < best.not_nil![:priority_score] && teacher_score < best.not_nil![:teacher_score]
best = { best = {
assignment: assignment, assignment: assignment,
score: score, priority_score: priority_score,
teacher_score: teacher_score,
} }
end end
end end
pp! best pp! best
# str = String.build do |str| Db::Assignment.import(best.not_nil![:assignment].map { |s, a| Db::Assignment.new({student_id: s, teacher_id: a[:teacher]}) })
# str << "===========================\n"
# best.not_nil![:assignment].each do |s, a|
# pp! a
# str << "#{Db::Student.query.find!(s).user.username} #{s} : #{Db::Teacher.query.find!(a[:teacher]).user.username} #{a[:priority]} (#{votes[s].size - a[:priority]} / #{votes[s].size})\n"
# end
# str << "===========================\n"
# end
# print str
end end
end end
end end

View file

@ -1,4 +1,5 @@
body { body {
// bulma fix
overflow-x: hidden; overflow-x: hidden;
display: flex; display: flex;

View file

@ -0,0 +1,36 @@
use yew::prelude::*;
use crate::components;
#[derive(Properties, PartialEq)]
pub struct BaseProps {
pub token: Option<String>,
pub logged_in: bool,
#[prop_or_default]
pub children: Children,
}
pub struct Base;
impl Component for Base {
type Message = ();
type Properties = BaseProps;
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<>
<components::logged_in_handler::LoggedInHandler logged_in={ctx.props().logged_in} />
<div id="wrapper">
<components::navbar::Navbar token={ctx.props().token.to_owned()} logged_in={ctx.props().logged_in} />
{ for ctx.props().children.iter() }
</div>
<components::footer::Footer />
</>
}
}
}

View file

@ -46,7 +46,6 @@ impl Component for LoggedIn {
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
if !self.logged_in { if !self.logged_in {
log::info!("Viewing logged in required site while not logged in!");
ctx.link().history().unwrap().push(routes::Route::Login); ctx.link().history().unwrap().push(routes::Route::Login);
} }

View file

@ -1,11 +1,7 @@
use yew::prelude::*; use yew::prelude::*;
use crate::components; // use crate::components;
use crate::layouts;
pub enum Msg {
LogOutClicked,
LogOut(Option<Vec<String>>),
}
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct MainProps { pub struct MainProps {
@ -18,7 +14,7 @@ pub struct MainProps {
pub struct Main; pub struct Main;
impl Component for Main { impl Component for Main {
type Message = Msg; type Message = ();
type Properties = MainProps; type Properties = MainProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
@ -27,17 +23,13 @@ impl Component for Main {
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<> <layouts::base::Base token={ctx.props().token.to_owned()} logged_in={ctx.props().logged_in}>
<components::logged_in_handler::LoggedInHandler logged_in={ctx.props().logged_in} /> <div class={classes!("columns")}>
<main class={classes!("column", "is-four-fifths", "mx-auto")}>
<div id="wrapper">
<components::navbar::Navbar token={ctx.props().token.to_owned()} logged_in={ctx.props().logged_in} />
<main>
{ for ctx.props().children.iter() } { for ctx.props().children.iter() }
</main> </main>
</div> </div>
<components::footer::Footer /> </layouts::base::Base>
</>
} }
} }
} }

View file

@ -1,2 +1,3 @@
pub mod base;
pub mod logged_in; pub mod logged_in;
pub mod main; pub mod main;

View file

@ -24,14 +24,14 @@ impl Component for Settings {
html! { html! {
<> <>
<Title value="Settings" /> <Title value="Settings" />
<section> <section>
<tokens::Tokens token={ctx.props().token.as_ref().unwrap().to_owned()} /> <tokens::Tokens token={ctx.props().token.as_ref().unwrap().to_owned()} />
</section>
if ctx.props().admin {
<section>
<assignments::Assignments token={ctx.props().token.as_ref().unwrap().to_owned()} />
</section> </section>
} if ctx.props().admin {
<section>
<assignments::Assignments token={ctx.props().token.as_ref().unwrap().to_owned()} />
</section>
}
</> </>
} }
} }