This commit is contained in:
parent
125bbf7ff1
commit
6620dea812
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
body {
|
body {
|
||||||
|
// bulma fix
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
36
frontend/src/layouts/base.rs
Normal file
36
frontend/src/layouts/base.rs
Normal 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 />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod base;
|
||||||
pub mod logged_in;
|
pub mod logged_in;
|
||||||
pub mod main;
|
pub mod main;
|
||||||
|
|
|
@ -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>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue