Criteria for assignment grading
This commit is contained in:
parent
a7aafccd19
commit
c88d0d2e58
|
@ -17,4 +17,5 @@ captures
|
|||
!*.xcodeproj/project.xcworkspace/
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
**/grader.db
|
||||
**/grader.db
|
||||
**/*.backup
|
|
@ -85,34 +85,34 @@ object PeerEvaluations : UUIDTable("peerEvals") {
|
|||
}
|
||||
|
||||
object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
||||
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||
val criterionId = reference("criterion_id", GroupAssignments.id).nullable()
|
||||
val assignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||
val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable()
|
||||
val groupId = reference("group_id", Groups.id)
|
||||
val feedback = text("feedback")
|
||||
val grade = varchar("grade", 32)
|
||||
|
||||
override val primaryKey = PrimaryKey(groupAssignmentId, groupId)
|
||||
override val primaryKey = PrimaryKey(groupId, criterionId)
|
||||
}
|
||||
|
||||
object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
|
||||
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||
val criterionId = reference("criterion_id", GroupAssignments.id).nullable()
|
||||
val assignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||
val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable()
|
||||
val groupId = reference("group_id", Groups.id)
|
||||
val studentId = reference("student_id", Students.id)
|
||||
val feedback = text("feedback")
|
||||
val grade = varchar("grade", 32)
|
||||
|
||||
override val primaryKey = PrimaryKey(groupAssignmentId, studentId)
|
||||
override val primaryKey = PrimaryKey(studentId, criterionId)
|
||||
}
|
||||
|
||||
object SoloFeedbacks : CompositeIdTable("soloFdbks") {
|
||||
val soloAssignmentId = reference("solo_assignment_id", SoloAssignments.id)
|
||||
val criterionId = reference("criterion_id", SoloAssignments.id).nullable()
|
||||
val assignmentId = reference("solo_assignment_id", SoloAssignments.id)
|
||||
val criterionId = reference("criterion_id", SoloAssignmentCriteria.id).nullable()
|
||||
val studentId = reference("student_id", Students.id)
|
||||
val feedback = text("feedback")
|
||||
val grade = varchar("grade", 32)
|
||||
|
||||
override val primaryKey = PrimaryKey(soloAssignmentId, studentId)
|
||||
override val primaryKey = PrimaryKey(studentId, criterionId)
|
||||
}
|
||||
|
||||
object PeerEvaluationContents : CompositeIdTable("peerEvalCnts") {
|
||||
|
|
|
@ -70,6 +70,24 @@ class GroupAssignmentCriterion(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
var assignment by GroupAssignment referencedOn GroupAssignmentCriteria.assignmentId
|
||||
var name by GroupAssignmentCriteria.name
|
||||
var description by GroupAssignmentCriteria.desc
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as GroupAssignmentCriterion
|
||||
|
||||
if (name != other.name) return false
|
||||
if (description != other.description) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + description.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
|
@ -104,8 +122,7 @@ class SoloAssignmentCriterion(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = assignment.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + description.hashCode()
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -255,7 +255,8 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
|
|||
groupFeedbackPane(
|
||||
criteria, critIdx, { critIdx = it }, feedback.global,
|
||||
if(critIdx == 0) feedback.global else feedback.byCriterion[critIdx - 1].entry,
|
||||
suggestions, updateGrade, updateFeedback, Modifier.weight(0.75f).padding(10.dp)
|
||||
suggestions, updateGrade, updateFeedback, Modifier.weight(0.75f).padding(10.dp),
|
||||
key = idx to critIdx
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -270,10 +271,11 @@ fun groupFeedbackPane(
|
|||
autofill: List<String>,
|
||||
onSetGrade: (String) -> Unit,
|
||||
onSetFeedback: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
key: Any? = null
|
||||
) {
|
||||
var grade by remember(globFeedback) { mutableStateOf(globFeedback?.grade ?: "") }
|
||||
var feedback by remember(currentCriterion, criteria) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) }
|
||||
var grade by remember(globFeedback, key) { mutableStateOf(globFeedback?.grade ?: "") }
|
||||
var feedback by remember(currentCriterion, criteria, criterionFeedback, key) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) }
|
||||
Column(modifier) {
|
||||
Row {
|
||||
Text("Overall grade: ", Modifier.align(Alignment.CenterVertically))
|
||||
|
|
|
@ -310,15 +310,23 @@ class EditionState(val edition: Edition) {
|
|||
}
|
||||
fun delete(sa: SoloAssignment) {
|
||||
transaction {
|
||||
SoloFeedbacks.deleteWhere { soloAssignmentId eq sa.id }
|
||||
SoloAssignmentCriteria.selectAll().where { SoloAssignmentCriteria.assignmentId eq sa.id }.forEach { it ->
|
||||
val id = it[SoloAssignmentCriteria.assignmentId]
|
||||
SoloFeedbacks.deleteWhere { criterionId eq id }
|
||||
}
|
||||
SoloAssignmentCriteria.deleteWhere { assignmentId eq sa.id }
|
||||
sa.delete()
|
||||
}
|
||||
solo.refresh()
|
||||
}
|
||||
fun delete(ga: GroupAssignment) {
|
||||
transaction {
|
||||
GroupFeedbacks.deleteWhere { groupAssignmentId eq ga.id }
|
||||
IndividualFeedbacks.deleteWhere { groupAssignmentId eq ga.id }
|
||||
GroupAssignmentCriteria.selectAll().where { GroupAssignmentCriteria.assignmentId eq ga.id }.forEach { it ->
|
||||
val id = it[GroupAssignmentCriteria.assignmentId]
|
||||
GroupFeedbacks.deleteWhere { criterionId eq id }
|
||||
IndividualFeedbacks.deleteWhere { criterionId eq id }
|
||||
}
|
||||
GroupAssignmentCriteria.deleteWhere { assignmentId eq ga.id }
|
||||
ga.delete()
|
||||
}
|
||||
groupAs.refresh()
|
||||
|
@ -363,13 +371,15 @@ class StudentState(val student: Student, edition: Edition) {
|
|||
(Groups.editionId eq edition.id) and (Groups.id inList student.groups.map { it.id })
|
||||
}.associate { it.id to it.name }
|
||||
|
||||
val asGroup = (GroupAssignments innerJoin GroupFeedbacks innerJoin Groups).selectAll().where {
|
||||
GroupFeedbacks.groupId inList groupsForEdition.keys.toList()
|
||||
}.map { it[GroupFeedbacks.groupAssignmentId] to it }
|
||||
val asGroup = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin GroupFeedbacks innerJoin Groups).selectAll().where {
|
||||
(GroupFeedbacks.groupId inList groupsForEdition.keys.toList()) and
|
||||
(GroupAssignmentCriteria.name eq "")
|
||||
}.map { it[GroupAssignments.id] to it }
|
||||
|
||||
val asIndividual = (GroupAssignments innerJoin IndividualFeedbacks innerJoin Groups).selectAll().where {
|
||||
IndividualFeedbacks.studentId eq student.id
|
||||
}.map { it[IndividualFeedbacks.groupAssignmentId] to it }
|
||||
val asIndividual = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin IndividualFeedbacks innerJoin Groups).selectAll().where {
|
||||
(IndividualFeedbacks.studentId eq student.id) and
|
||||
(GroupAssignmentCriteria.name eq "")
|
||||
}.map { it[GroupAssignments.id] to it }
|
||||
|
||||
val res = mutableMapOf<EntityID<UUID>, LocalGroupGrade>()
|
||||
asGroup.forEach {
|
||||
|
@ -391,8 +401,9 @@ class StudentState(val student: Student, edition: Edition) {
|
|||
}
|
||||
|
||||
val soloGrades = RawDbState {
|
||||
(SoloAssignments innerJoin SoloFeedbacks).selectAll().where {
|
||||
SoloFeedbacks.studentId eq student.id
|
||||
(SoloAssignments innerJoin SoloAssignmentCriteria innerJoin SoloFeedbacks).selectAll().where {
|
||||
(SoloFeedbacks.studentId eq student.id) and
|
||||
(SoloAssignmentCriteria.name eq "")
|
||||
}.map { LocalSoloGrade(it[SoloAssignments.name], it[SoloFeedbacks.grade]) }.toList()
|
||||
}
|
||||
|
||||
|
@ -456,7 +467,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
data class LocalGFeedback(
|
||||
val group: Group,
|
||||
val feedback: LocalFeedback,
|
||||
val individuals: List<Pair<Student, Pair<String?, LocalFeedback>>>
|
||||
val individuals: List<Pair<Student, Pair<String?, LocalFeedback>>> // Student -> (Role, Feedback)
|
||||
)
|
||||
|
||||
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
||||
|
@ -469,11 +480,11 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
val feedback = RawDbState { loadFeedback() }
|
||||
|
||||
val autofill = RawDbState {
|
||||
val forGroups = GroupFeedbacks.selectAll().where { GroupFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
||||
val forGroups = (GroupFeedbacks innerJoin GroupAssignmentCriteria).selectAll().where { GroupAssignmentCriteria.assignmentId eq assignment.id }.flatMap {
|
||||
it[GroupFeedbacks.feedback].split('\n')
|
||||
}
|
||||
|
||||
val forIndividuals = IndividualFeedbacks.selectAll().where { IndividualFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
||||
val forIndividuals = (IndividualFeedbacks innerJoin GroupAssignmentCriteria).selectAll().where { GroupAssignmentCriteria.assignmentId eq assignment.id }.flatMap {
|
||||
it[IndividualFeedbacks.feedback].split('\n')
|
||||
}
|
||||
|
||||
|
@ -481,53 +492,67 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
}
|
||||
|
||||
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
|
||||
val allCrit = GroupAssignmentCriterion.find {
|
||||
GroupAssignmentCriteria.assignmentId eq assignment.id
|
||||
}
|
||||
|
||||
return Group.find {
|
||||
(Groups.editionId eq assignment.edition.id)
|
||||
}.sortAsc(Groups.name).map { group ->
|
||||
// step 1: group-level feedback, including criteria
|
||||
val forGroup = GroupFeedbacks.selectAll().where {
|
||||
(GroupFeedbacks.groupAssignmentId eq assignment.id) and
|
||||
(GroupFeedbacks.groupId eq group.id)
|
||||
}.associate {
|
||||
val criterion = it[GroupFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] }
|
||||
val fe = FeedbackEntry(it[GroupFeedbacks.feedback], it[GroupFeedbacks.grade])
|
||||
criterion to fe
|
||||
val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where {
|
||||
(GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id)
|
||||
}.map { row ->
|
||||
val crit = row[GroupFeedbacks.criterionId]?.let { GroupAssignmentCriterion[it] }
|
||||
val fdbk = row[GroupFeedbacks.feedback]
|
||||
val grade = row[GroupFeedbacks.grade]
|
||||
|
||||
crit to FeedbackEntry(fdbk, grade)
|
||||
}
|
||||
val feedback = LocalFeedback(
|
||||
global = forGroup[null],
|
||||
byCriterion = criteria.entities.value.map { c -> LocalCriterionFeedback(c, forGroup[c]) }
|
||||
)
|
||||
|
||||
// step 2: individual feedback
|
||||
val individuals = group.studentRoles.map { sr ->
|
||||
val student = sr.student
|
||||
val role = sr.role
|
||||
val global = forGroup.firstOrNull { it.first == null }?.second
|
||||
val byCrit_ = forGroup.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } }
|
||||
.filterNotNull().associateBy { it.criterion.id }
|
||||
val byCrit = allCrit.map { c ->
|
||||
byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
|
||||
}
|
||||
|
||||
val forStudent = IndividualFeedbacks.selectAll().where {
|
||||
(IndividualFeedbacks.groupAssignmentId eq assignment.id) and
|
||||
(IndividualFeedbacks.groupId eq group.id) and
|
||||
(IndividualFeedbacks.studentId eq student.id)
|
||||
}.associate {
|
||||
val criterion = it[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] }
|
||||
val fe = FeedbackEntry(it[IndividualFeedbacks.feedback], it[IndividualFeedbacks.grade])
|
||||
criterion to fe
|
||||
val byGroup = LocalFeedback(global, byCrit)
|
||||
|
||||
val indiv = group.studentRoles.map {
|
||||
val student = it.student
|
||||
val role = it.role
|
||||
|
||||
val forSt = (IndividualFeedbacks innerJoin Groups innerJoin GroupStudents)
|
||||
.selectAll().where {
|
||||
(IndividualFeedbacks.assignmentId eq assignment.id) and
|
||||
(GroupStudents.studentId eq student.id) and (Groups.id eq group.id)
|
||||
}.map { row ->
|
||||
val crit = row[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] }
|
||||
val fdbk = row[IndividualFeedbacks.feedback]
|
||||
val grade = row[IndividualFeedbacks.grade]
|
||||
|
||||
crit to FeedbackEntry(fdbk, grade)
|
||||
}
|
||||
|
||||
val global = forSt.firstOrNull { it.first == null }?.second
|
||||
val byCrit_ = forSt.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } }
|
||||
.filterNotNull().associateBy { it.criterion.id }
|
||||
val byCrit = allCrit.map { c ->
|
||||
byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
|
||||
}
|
||||
val studentFeedback = LocalFeedback(
|
||||
global = forStudent[null],
|
||||
byCriterion = criteria.entities.value.map { c -> LocalCriterionFeedback(c, forStudent[c]) }
|
||||
)
|
||||
val byStudent = LocalFeedback(global, byCrit)
|
||||
|
||||
student to (role to studentFeedback)
|
||||
}.sortedBy { it.first.name }
|
||||
student to (role to byStudent)
|
||||
}
|
||||
|
||||
group to LocalGFeedback(group, feedback, individuals)
|
||||
group to LocalGFeedback(group, byGroup, indiv)
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertGroupFeedback(group: Group, msg: String, grd: String, criterion: GroupAssignmentCriterion? = null) {
|
||||
transaction {
|
||||
GroupFeedbacks.upsert {
|
||||
it[groupAssignmentId] = assignment.id
|
||||
it[assignmentId] = assignment.id
|
||||
it[groupId] = group.id
|
||||
it[this.feedback] = msg
|
||||
it[this.grade] = grd
|
||||
|
@ -540,7 +565,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
fun upsertIndividualFeedback(student: Student, group: Group, msg: String, grd: String, criterion: GroupAssignmentCriterion? = null) {
|
||||
transaction {
|
||||
IndividualFeedbacks.upsert {
|
||||
it[groupAssignmentId] = assignment.id
|
||||
it[assignmentId] = assignment.id
|
||||
it[groupId] = group.id
|
||||
it[studentId] = student.id
|
||||
it[this.feedback] = msg
|
||||
|
@ -608,34 +633,42 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
|
|||
val feedback = RawDbState { loadFeedback() }
|
||||
|
||||
val autofill = RawDbState {
|
||||
SoloFeedbacks.selectAll().where { SoloFeedbacks.soloAssignmentId eq assignment.id }.map {
|
||||
SoloFeedbacks.selectAll().where { SoloFeedbacks.assignmentId eq assignment.id }.map {
|
||||
it[SoloFeedbacks.feedback].split('\n')
|
||||
}.flatten().distinct().sorted()
|
||||
}
|
||||
|
||||
private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> {
|
||||
return editionCourse.second.soloStudents.sortAsc(Students.name).map { student ->
|
||||
val each = SoloFeedbacks.selectAll().where {
|
||||
(SoloFeedbacks.soloAssignmentId eq assignment.id) and
|
||||
(SoloFeedbacks.studentId eq student.id)
|
||||
}.associate {
|
||||
val criterion = it[SoloFeedbacks.criterionId]?.let { id -> SoloAssignmentCriterion[id] }
|
||||
val fe = LocalFeedback(it[SoloFeedbacks.feedback], it[SoloFeedbacks.grade])
|
||||
criterion to fe
|
||||
}
|
||||
val feedback = FullFeedback(
|
||||
global = each[null],
|
||||
byCriterion = criteria.entities.value.map { c -> c to each[c] }
|
||||
)
|
||||
val allCrit = SoloAssignmentCriterion.find {
|
||||
SoloAssignmentCriteria.assignmentId eq assignment.id
|
||||
}
|
||||
|
||||
student to feedback
|
||||
return editionCourse.second.soloStudents.sortAsc(Students.name).map { student ->
|
||||
val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where {
|
||||
(IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id)
|
||||
}.map { row ->
|
||||
val crit = row[IndividualFeedbacks.criterionId]?.let { SoloAssignmentCriterion[it] }
|
||||
val fdbk = row[IndividualFeedbacks.feedback]
|
||||
val grade = row[IndividualFeedbacks.grade]
|
||||
|
||||
crit to LocalFeedback(fdbk, grade)
|
||||
}
|
||||
|
||||
val global = forStudent.firstOrNull { it.first == null }?.second
|
||||
val byCrit_ = forStudent.map { it.first?.let { k -> Pair(k, it.second) } }
|
||||
.filterNotNull().associateBy { it.first.id }
|
||||
val byCrit = allCrit.map { c ->
|
||||
byCrit_[c.id] ?: Pair(c, null)
|
||||
}
|
||||
|
||||
student to FullFeedback(global, byCrit)
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertFeedback(student: Student, msg: String?, grd: String?, criterion: SoloAssignmentCriterion? = null) {
|
||||
transaction {
|
||||
SoloFeedbacks.upsert {
|
||||
it[soloAssignmentId] = assignment.id
|
||||
it[assignmentId] = assignment.id
|
||||
it[studentId] = student.id
|
||||
it[this.feedback] = msg ?: ""
|
||||
it[this.grade] = grd ?: ""
|
||||
|
|
Loading…
Reference in New Issue