tijiao
This commit is contained in:
parent
2d3036ca6c
commit
813ac0a228
|
@ -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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue