This commit is contained in:
lulz1 2024-09-19 17:00:58 +08:00
parent 2d3036ca6c
commit 813ac0a228
2 changed files with 197 additions and 50 deletions

View File

@ -14,7 +14,6 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.10")
classpath("io.ktor.plugin:plugin:2.3.12")
} }
} }

View File

@ -1,16 +1,148 @@
package org.echo.questionnaire package org.echo.questionnaire
import kotlin.math.abs import kotlin.math.abs
import kotlin.random.Random
class Questionnaire( class Questionnaire(
val id: Long,
val title: String, // 问卷的标题 val title: String, // 问卷的标题
val description: String, // 问卷的描述或说明 val description: String, // 问卷的描述或说明
val questions: List<SurveyQuestion>, val questions: List<SurveyQuestion>,
val questionGroup: List<QuestionGroup>,
val sample: SurveyBackgroundSample// 问卷中的所有问题 val sample: SurveyBackgroundSample// 问卷中的所有问题
) {
private val selectedOptions: MutableMap<Long, MutableSet<Long>> = mutableMapOf()// Map of questionId to optionId
fun selectOption(questionId: Long, optionId: Long) {
val options = selectedOptions.getOrPut(questionId) { mutableSetOf() }
options.add(optionId)
onOptionSelected(questionId, optionId)
}
fun isOptionSelected(questionId: Long, optionId: Long): Boolean {
return selectedOptions[questionId]?.contains(optionId) == true
}
fun getSelectedOption(questionId: Long): Set<QuestionOption>? {
val optionIds = selectedOptions[questionId]
return optionIds?.let {
questions.map { it.question }
.find { it.id == questionId }
?.optionList
?.filter { it.id in optionIds }
?.toSet()
}
}
fun onOptionSelected(questionId: Long, optionId: Long) {
// Identify target questions influenced by the source question
val influencedQuestionIds = getInfluencedQuestionIdsInGroup(questionId)
for (targetQuestionId in influencedQuestionIds) {
addDependenciesAfterAnswer(this, questionId, targetQuestionId)
}
// Recalculate weights for all options that might be affected
// this.questions.map { it.question }.forEach { question ->
// question.optionList.forEach { option ->
// val weight = option.calculateWeightWithDependencies(this)
// // Use the weight as needed, e.g., adjust display order, filter out options, etc.
// }
// }
}
fun getInfluencedQuestionIdsInGroup(questionId: Long): Set<Long> {
return this.questionGroup.filter { it.checkIfInGroup(questionId) }
.flatMap { it.surveyQuestionList.map { it.question.id }.toSet() }
.toSet()
}
fun addDependenciesAfterAnswer(
questionnaire: Questionnaire,
sourceQuestionId: Long,
targetQuestionId: Long
) {
val sourceOptionSet = questionnaire.getSelectedOption(sourceQuestionId)
val targetQuestion = questionnaire.questions.map { it.question }.find { it.id == targetQuestionId }
if (sourceOptionSet == null || targetQuestion == null) return
for (sourceOption in sourceOptionSet) {
targetQuestion.optionList.forEach { targetOption ->
// Remove existing dependencies from the same source question
targetOption.dependencies.removeAll { it.sourceQuestionId == sourceQuestionId }
val dependency = OptionDependency(
sourceQuestionId = sourceQuestionId,
sourceOptionId = sourceOption.id,
targetOption = targetOption,
condition = { q ->
q.isOptionSelected(sourceQuestionId, sourceOption.id)
},
effect = { _ ->
// Define your effect based on source and target options
calculateEffectBasedOnOrder(sourceOption, targetOption)
}
) )
targetOption.dependencies.add(dependency)
}
}
}
fun calculateEffectBasedOnOrder(
sourceOption: QuestionOption,
targetOption: QuestionOption
): Double {
val orderDifference = abs(sourceOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
fun autoFillAnswers() {
for (group in questionGroup) {
group.surveyQuestionList
.map { it.question }
.forEach { question -> autoSelectAnswer(question) }
}
}
fun autoSelectAnswer(question: Question) {
val weightedOptions = question.optionList
.map { it.calculateAndMapToWeight(this) }
// 按照权重随机选择
val selectedOption = weightedRandomSelection(weightedOptions)
selectOption(question.id, selectedOption.optionId)
// 如果是多选题,随机选择两个选项(前提是至少有两个选项可选)
if (question.questionType == QuestionType.MULTIPLE_CHOICE &&
weightedOptions.size > 1
) {
val secondOption =
weightedRandomSelection(weightedOptions.filter { it.optionId != selectedOption.optionId })
selectOption(question.id, secondOption.optionId)
}
}
fun weightedRandomSelection(weightedOptions: List<QuestionOptionWeight>): QuestionOptionWeight {
val totalWeight = weightedOptions.sumOf { it.weight }
val randomValue = Random.nextDouble() * totalWeight
var cumulativeWeight = 0.0
for (option in weightedOptions) {
cumulativeWeight += option.weight
if (randomValue <= cumulativeWeight) {
return option
}
}
return weightedOptions.last()
}
}
open class Question( open class Question(
val questionId: String, val id: Long,
val title: String, val title: String,
val content: String, val content: String,
val optionList: List<QuestionOption>, val optionList: List<QuestionOption>,
@ -18,51 +150,27 @@ open class Question(
val order: Int, // 表示问题顺序 val order: Int, // 表示问题顺序
val isRequired: Boolean = true // 是否必答,默认为必答 val isRequired: Boolean = true // 是否必答,默认为必答
) { ) {
fun getOptionList(): List<QuestionOption> { fun getOptionListData(): List<QuestionOption> {
return optionList return optionList
} }
} }
class QuestionGroup( class QuestionGroup(
val id: Long,
val parentId: Long,
val groupName: String, val groupName: String,
val surveyQuestionList: List<SurveyQuestion>, val surveyQuestionList: List<SurveyQuestion>,
var answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf() // 已回答的问题及其分数 var answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf() // 已回答的问题及其分数
) { ) {
// 当组内的问题被回答时,更新其他问题的依赖关系 fun checkIfInGroup(questionId: Long): Boolean {
fun updateDependenciesOnAnswer(answeredQuestion: SurveyQuestion, selectedOption: QuestionOption) { return surveyQuestionList.map { it.question }.any { it.id == questionId }
// 将已回答的问题和选项添加到记录中
answeredQuestions[answeredQuestion] = selectedOption
// 遍历组内未回答的问题,并为每个未回答问题的选项更新依赖关系
surveyQuestionList
.filter { it != answeredQuestion && !answeredQuestions.containsKey(it) } // 过滤未回答的问题
.flatMap { it.getQuestion().getOptionList() } // 将每个问题的选项展开成一个列表
.forEach { option ->
// 创建一个新的 OptionDependency根据已回答的选项影响当前选项
val dependency = OptionDependency(
sourceOption = selectedOption,
targetOption = option,
condition = { _ ->
// 定义条件sourceOption 和 targetOption 必须同一问题组,且影响关系基于 markOrder 差值
true
},
effect = { targetOpt ->
val orderDifference = abs(selectedOption.markOrder - targetOpt.markOrder)
when (orderDifference) {
0 -> 1.0 // 相同 markOrder权重最大
1 -> 0.7 // 相邻 markOrder权重较高
2 -> 0.4 // 相差2权重中等
else -> 0.1 // 更远的,权重最低
}
}
)
// 将新的依赖关系添加到选项的 dependencies 列表中
option.dependencies += dependency
}
} }
} }
class QuestionOption( class QuestionOption(
val id: Long,
val label: String, val label: String,
val content: String, val content: String,
val mark: String, val mark: String,
@ -71,22 +179,32 @@ class QuestionOption(
var dependencies: MutableList<OptionDependency> = mutableListOf() // 与其他选项的依赖关系 var dependencies: MutableList<OptionDependency> = mutableListOf() // 与其他选项的依赖关系
) { ) {
// 计算依赖关系对权重的影响 // 计算依赖关系对权重的影响
fun calculateWeightWithDependencies(): Double { fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double {
var baseWeight = 1.0 // 基础权重 var baseWeight = 1.0
dependencies.forEach { dependency -> dependencies.forEach { dependency ->
if (dependency.condition(dependency.sourceOption)) { if (dependency.condition(questionnaire)) {
baseWeight += dependency.effect(dependency.sourceOption) baseWeight += dependency.effect(questionnaire)
} }
} }
return baseWeight.coerceAtLeast(0.0) // 确保权重不小于0 return baseWeight.coerceAtLeast(0.0)
}
fun calculateAndMapToWeight(questionnaire: Questionnaire): QuestionOptionWeight {
return QuestionOptionWeight(id, calculateWeightWithDependencies(questionnaire))
} }
} }
class QuestionOptionWeight(
val optionId: Long,
val weight: Double,
)
class OptionDependency( class OptionDependency(
val sourceOption: QuestionOption, // 依赖的来源选项 val sourceQuestionId: Long,
val targetOption: QuestionOption, // 目标选项,受影响的选项 val sourceOptionId: Long,
val condition: (QuestionOption) -> Boolean, // 判断条件,当 sourceOption 满足某个条件时影响 targetOption val targetOption: QuestionOption,
val effect: (QuestionOption) -> Double // 定义当条件满足时对目标选项的权重影响 val condition: (Questionnaire) -> Boolean,
val effect: (Questionnaire) -> Double
) )
@ -96,16 +214,15 @@ enum class QuestionType {
OPEN_ENDED // 开放式问题 OPEN_ENDED // 开放式问题
} }
enum class Gender {
MALE, FEMALE
}
class SurveyQuestion( class SurveyQuestion(
val id: Long,
val title: String, val title: String,
val content: String, val content: String,
val questionId: Long,
val question: Question, val question: Question,
) { ) {
fun getQuestion(): Question { fun getQuestionData(): Question {
return this.question return this.question
} }
} }
@ -117,8 +234,7 @@ class SurveyBackgroundSample(
class SurveyRespondent( class SurveyRespondent(
val name: String, val name: String,
val age: Int, val age: Int
val gender: Gender
) )
class SurveyBackground( class SurveyBackground(
@ -126,3 +242,35 @@ class SurveyBackground(
val backgroundQuestion: Question val backgroundQuestion: Question
) )
class SurveyProcedure(
val id: Long,
val sourceOptionId: Long,
val targetQuestionId: Long,
val targetOptionId: Long,
val computeMethod: ComputeMethod
)
enum class ComputeMethod(
val computeMethodName: String
) {
BaseComputeMethod("Default") {
override fun calculateEffectBasedOnOrder(
sourceOption: QuestionOption,
targetOption: QuestionOption
): Double {
val orderDifference = abs(sourceOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
};
// 定义抽象方法,子类必须实现
abstract fun calculateEffectBasedOnOrder(
sourceOption: QuestionOption,
targetOption: QuestionOption
): Double
}