Lint: strict mode on; tune detekt thresholds; more decompositions
This commit is contained in:
@@ -94,6 +94,34 @@ class ProgressService(
|
||||
request: DistributeRequest,
|
||||
token: String,
|
||||
): DistributeResult {
|
||||
val labIds = resolveLabIds(request)
|
||||
val force = request.force == true
|
||||
val allStudents = collectStudentIds(request, token)
|
||||
var assignedTotal = 0
|
||||
var totalStudents = 0
|
||||
var lastLabId: String? = null
|
||||
var lastLabTitle: String? = null
|
||||
for (labIdStr in labIds) {
|
||||
val labId = UUID.fromString(labIdStr)
|
||||
val lab = academic.getLab(labIdStr, token)
|
||||
if (force) clearAutoAssignments(labId, allStudents)
|
||||
val kept = keepExisting(labId, allStudents)
|
||||
assignedTotal += kept.size
|
||||
assignedTotal += assignNew(lab, labId, allStudents.filterNot { it in kept })
|
||||
totalStudents += allStudents.size
|
||||
lastLabId = labIdStr
|
||||
lastLabTitle = lab.title
|
||||
}
|
||||
return DistributeResult(
|
||||
labs = labIds.size,
|
||||
assigned = assignedTotal,
|
||||
total = totalStudents,
|
||||
labId = if (labIds.size == 1) lastLabId else null,
|
||||
labTitle = if (labIds.size == 1) lastLabTitle else null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveLabIds(request: DistributeRequest): List<String> {
|
||||
val labIds =
|
||||
request.labIds
|
||||
.orEmpty()
|
||||
@@ -102,8 +130,13 @@ class ProgressService(
|
||||
.filter { it.isNotBlank() }
|
||||
.distinct()
|
||||
if (labIds.isEmpty()) throw BadRequestException("Не указана ни одна лабораторная")
|
||||
return labIds
|
||||
}
|
||||
|
||||
val force = request.force == true
|
||||
private fun collectStudentIds(
|
||||
request: DistributeRequest,
|
||||
token: String,
|
||||
): List<UUID> {
|
||||
val groupFilter =
|
||||
request.groupIds
|
||||
.orEmpty()
|
||||
@@ -113,94 +146,75 @@ class ProgressService(
|
||||
for (g in identity.listGroups(token)) {
|
||||
val gid = g["id"] as String
|
||||
if (groupFilter.isNotEmpty() && gid !in groupFilter) continue
|
||||
for (s in identity.getGroup(gid, token).students) {
|
||||
studentIds.add(UUID.fromString(s.id))
|
||||
}
|
||||
for (s in identity.getGroup(gid, token).students) studentIds.add(UUID.fromString(s.id))
|
||||
}
|
||||
val allStudents = studentIds.distinct()
|
||||
return studentIds.distinct()
|
||||
}
|
||||
|
||||
var assignedTotal = 0
|
||||
var totalStudents = 0
|
||||
var lastLabId: String? = null
|
||||
var lastLabTitle: String? = null
|
||||
private fun clearAutoAssignments(
|
||||
labId: UUID,
|
||||
allStudents: List<UUID>,
|
||||
) {
|
||||
assignments
|
||||
.findByLabId(labId)
|
||||
.filter { it.studentId in allStudents && !it.manual }
|
||||
.forEach { assignments.delete(it) }
|
||||
}
|
||||
|
||||
for (labIdStr in labIds) {
|
||||
val labId = UUID.fromString(labIdStr)
|
||||
val lab = academic.getLab(labIdStr, token)
|
||||
private fun keepExisting(
|
||||
labId: UUID,
|
||||
allStudents: List<UUID>,
|
||||
): Map<UUID, VariantAssignment> {
|
||||
val kept = mutableMapOf<UUID, VariantAssignment>()
|
||||
assignments
|
||||
.findByLabId(labId)
|
||||
.filter { it.studentId in allStudents }
|
||||
.forEach { kept[it.studentId] = it }
|
||||
return kept
|
||||
}
|
||||
|
||||
// force сбрасывает авто-назначения только у выбранных студентов;
|
||||
// закреплённые вручную варианты остаются как есть.
|
||||
if (force) {
|
||||
assignments
|
||||
.findByLabId(labId)
|
||||
.filter { it.studentId in allStudents && !it.manual }
|
||||
.forEach { assignments.delete(it) }
|
||||
}
|
||||
|
||||
val kept = mutableMapOf<UUID, VariantAssignment>()
|
||||
assignedTotal += assignments.findByLabId(labId).count { it.studentId in allStudents }
|
||||
/** Выдаёт варианты студентам без назначения; очередь общая, повторы — только когда пул исчерпан. */
|
||||
private fun assignNew(
|
||||
lab: LabInfo,
|
||||
labId: UUID,
|
||||
toAssign: List<UUID>,
|
||||
): Int {
|
||||
val poolsByLevel = lab.levels.associate { level -> level.id to level.variants.map { it.id } }
|
||||
val defaultLevel = lab.levels.firstOrNull()?.id
|
||||
if (toAssign.isEmpty() || poolsByLevel.isEmpty()) return 0
|
||||
val usage =
|
||||
assignments
|
||||
.findByLabId(labId)
|
||||
.filter { it.studentId in allStudents }
|
||||
.forEach { kept[it.studentId] = it }
|
||||
|
||||
val toAssign = allStudents.filterNot { kept.containsKey(it) }
|
||||
|
||||
// Уровень ограничивает пул: студент получает вариант только СВОЕГО уровня.
|
||||
// При этом очередь одна общая для всех выбранных групп: круг не
|
||||
// «перезапускается» на каждой группе, а продолжается по наименее
|
||||
// занятым вариантам уровня (пока хоть один не выдан — повторов нет).
|
||||
val poolsByLevel = lab.levels.associate { lv -> lv.id to lv.variants.map { it.id } }
|
||||
val defaultLevel = lab.levels.firstOrNull()?.id
|
||||
|
||||
if (toAssign.isNotEmpty() && poolsByLevel.isNotEmpty()) {
|
||||
val usage =
|
||||
assignments
|
||||
.findByLabId(labId)
|
||||
.groupingBy { it.variantId.toString() }
|
||||
.eachCount()
|
||||
.toMutableMap()
|
||||
|
||||
for (sid in toAssign.shuffled()) {
|
||||
val levelId =
|
||||
levels.findByStudentIdAndLabId(sid, labId)?.labLevelId?.toString()
|
||||
?: defaultLevel
|
||||
val pool =
|
||||
levelId
|
||||
?.let { poolsByLevel[it] }
|
||||
.orEmpty()
|
||||
.ifEmpty { defaultLevel?.let { poolsByLevel[it] }.orEmpty() }
|
||||
.ifEmpty { poolsByLevel.values.firstOrNull { it.isNotEmpty() }.orEmpty() }
|
||||
if (pool.isEmpty()) continue
|
||||
val minCount = pool.minOf { usage[it] ?: 0 }
|
||||
val chosen = pool.filter { (usage[it] ?: 0) == minCount }.random()
|
||||
assignments.save(
|
||||
VariantAssignment(
|
||||
studentId = sid,
|
||||
labId = labId,
|
||||
variantId = UUID.fromString(chosen),
|
||||
manual = false,
|
||||
),
|
||||
)
|
||||
usage[chosen] = (usage[chosen] ?: 0) + 1
|
||||
assignedTotal++
|
||||
}
|
||||
}
|
||||
|
||||
totalStudents += allStudents.size
|
||||
lastLabId = labIdStr
|
||||
lastLabTitle = lab.title
|
||||
.groupingBy { it.variantId.toString() }
|
||||
.eachCount()
|
||||
.toMutableMap()
|
||||
var assigned = 0
|
||||
for (sid in toAssign.shuffled()) {
|
||||
val levelId = levels.findByStudentIdAndLabId(sid, labId)?.labLevelId?.toString() ?: defaultLevel
|
||||
val pool = pickPool(levelId, poolsByLevel, defaultLevel)
|
||||
if (pool.isEmpty()) continue
|
||||
val minCount = pool.minOf { id -> usage[id] ?: 0 }
|
||||
val chosen = pool.filter { id -> (usage[id] ?: 0) == minCount }.random()
|
||||
assignments.save(
|
||||
VariantAssignment(studentId = sid, labId = labId, variantId = UUID.fromString(chosen), manual = false),
|
||||
)
|
||||
usage[chosen] = (usage[chosen] ?: 0) + 1
|
||||
assigned++
|
||||
}
|
||||
|
||||
return DistributeResult(
|
||||
labs = labIds.size,
|
||||
assigned = assignedTotal,
|
||||
total = totalStudents,
|
||||
labId = if (labIds.size == 1) lastLabId else null,
|
||||
labTitle = if (labIds.size == 1) lastLabTitle else null,
|
||||
)
|
||||
return assigned
|
||||
}
|
||||
|
||||
private fun pickPool(
|
||||
levelId: String?,
|
||||
poolsByLevel: Map<String, List<String>>,
|
||||
defaultLevel: String?,
|
||||
): List<String> =
|
||||
levelId
|
||||
?.let { poolsByLevel[it] }
|
||||
.orEmpty()
|
||||
.ifEmpty { defaultLevel?.let { poolsByLevel[it] }.orEmpty() }
|
||||
.ifEmpty { poolsByLevel.values.firstOrNull { it.isNotEmpty() }.orEmpty() }
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getAssignmentsForLab(labId: UUID): List<VariantAssignment> = assignments.findByLabId(labId)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user