diff --git a/build.gradle.kts b/build.gradle.kts index af9024a..7dfacb7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,6 @@ buildscript { } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.10") - classpath("io.ktor.plugin:plugin:2.3.12") } } diff --git a/src/main/kotlin/questionnaire/Questionnaire.kt b/src/main/kotlin/questionnaire/Questionnaire.kt index 4d04861..851dc3f 100644 --- a/src/main/kotlin/questionnaire/Questionnaire.kt +++ b/src/main/kotlin/questionnaire/Questionnaire.kt @@ -1,16 +1,148 @@ package org.echo.questionnaire import kotlin.math.abs +import kotlin.random.Random class Questionnaire( + val id: Long, val title: String, // 问卷的标题 val description: String, // 问卷的描述或说明 val questions: List, + val questionGroup: List, val sample: SurveyBackgroundSample// 问卷中的所有问题 -) +) { + private val selectedOptions: MutableMap> = 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? { + 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 { + 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 { + 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( - val questionId: String, + val id: Long, val title: String, val content: String, val optionList: List, @@ -18,51 +150,27 @@ open class Question( val order: Int, // 表示问题顺序 val isRequired: Boolean = true // 是否必答,默认为必答 ) { - fun getOptionList(): List { + fun getOptionListData(): List { return optionList } + + } class QuestionGroup( + val id: Long, + val parentId: Long, val groupName: String, val surveyQuestionList: List, var answeredQuestions: MutableMap = mutableMapOf() // 已回答的问题及其分数 ) { - // 当组内的问题被回答时,更新其他问题的依赖关系 - fun updateDependenciesOnAnswer(answeredQuestion: SurveyQuestion, selectedOption: QuestionOption) { - // 将已回答的问题和选项添加到记录中 - 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 - } + fun checkIfInGroup(questionId: Long): Boolean { + return surveyQuestionList.map { it.question }.any { it.id == questionId } } } class QuestionOption( + val id: Long, val label: String, val content: String, val mark: String, @@ -71,22 +179,32 @@ class QuestionOption( var dependencies: MutableList = mutableListOf() // 与其他选项的依赖关系 ) { // 计算依赖关系对权重的影响 - fun calculateWeightWithDependencies(): Double { - var baseWeight = 1.0 // 基础权重 + fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double { + var baseWeight = 1.0 dependencies.forEach { dependency -> - if (dependency.condition(dependency.sourceOption)) { - baseWeight += dependency.effect(dependency.sourceOption) + if (dependency.condition(questionnaire)) { + 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( - val sourceOption: QuestionOption, // 依赖的来源选项 - val targetOption: QuestionOption, // 目标选项,受影响的选项 - val condition: (QuestionOption) -> Boolean, // 判断条件,当 sourceOption 满足某个条件时影响 targetOption - val effect: (QuestionOption) -> Double // 定义当条件满足时对目标选项的权重影响 + val sourceQuestionId: Long, + val sourceOptionId: Long, + val targetOption: QuestionOption, + val condition: (Questionnaire) -> Boolean, + val effect: (Questionnaire) -> Double ) @@ -96,16 +214,15 @@ enum class QuestionType { OPEN_ENDED // 开放式问题 } -enum class Gender { - MALE, FEMALE -} class SurveyQuestion( + val id: Long, val title: String, val content: String, + val questionId: Long, val question: Question, ) { - fun getQuestion(): Question { + fun getQuestionData(): Question { return this.question } } @@ -117,8 +234,7 @@ class SurveyBackgroundSample( class SurveyRespondent( val name: String, - val age: Int, - val gender: Gender + val age: Int ) class SurveyBackground( @@ -126,3 +242,35 @@ class SurveyBackground( 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 +} \ No newline at end of file