代码加入保存数据库功能, 创建DLS语句来创建调查问卷
This commit is contained in:
parent
2347120cd3
commit
cebfde04b7
|
@ -12,5 +12,6 @@
|
||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="offlineMode" value="true" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.0.10" />
|
<option name="version" value="2.0.20" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,9 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jdk-17.0.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,5 +1,9 @@
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "2.0.10"
|
kotlin("jvm") version "2.0.20"
|
||||||
|
kotlin("plugin.spring") version "2.0.20"
|
||||||
|
kotlin("plugin.serialization") version "2.0.20"
|
||||||
|
id("org.springframework.boot") version "3.3.3"
|
||||||
|
id("io.spring.dependency-management") version "1.1.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.echo"
|
group = "org.echo"
|
||||||
|
@ -26,3 +30,23 @@ repositories {
|
||||||
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
|
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
|
|
||||||
|
implementation("org.apache.httpcomponents:httpclient:4.5.14")
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
runtimeOnly("com.mysql:mysql-connector-j")
|
||||||
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform() // 确保使用 JUnit 平台
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
package org.echo
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
println("Hello World!")
|
|
||||||
}
|
|
|
@ -0,0 +1,737 @@
|
||||||
|
package org.echo.org.echo.questionnaire
|
||||||
|
|
||||||
|
import org.springframework.data.annotation.Id
|
||||||
|
import org.springframework.data.annotation.Transient
|
||||||
|
import org.springframework.data.relational.core.mapping.MappedCollection
|
||||||
|
import org.springframework.data.relational.core.mapping.Table
|
||||||
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.util.Optional
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Table("questionnaire")
|
||||||
|
class Questionnaire(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val title: String, // 问卷的标题
|
||||||
|
val description: String, // 问卷的描述或说明
|
||||||
|
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
|
||||||
|
val questions: List<SurveyQuestion>,
|
||||||
|
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
|
||||||
|
val questionGroup: List<QuestionGroup>,
|
||||||
|
@MappedCollection(idColumn = "questionnaire_id")
|
||||||
|
val surveyBackground: SurveyBackground, // 问卷中的所有问题
|
||||||
|
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
|
||||||
|
val procedures: List<SurveyProcedure>,
|
||||||
|
) {
|
||||||
|
@Transient
|
||||||
|
private val selectedOptions: MutableMap<Long, MutableSet<Long>> = mutableMapOf()// Map of questionId to optionId
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
questionGroup: List<QuestionGroup>,
|
||||||
|
questions: List<SurveyQuestion>,
|
||||||
|
surveyBackground: SurveyBackground,
|
||||||
|
procedures: List<SurveyProcedure>
|
||||||
|
): Questionnaire {
|
||||||
|
return Questionnaire(null, title, description, questions, questionGroup, surveyBackground, procedures)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun questionnaire(block: QuestionnaireBuilder.() -> Unit): Questionnaire {
|
||||||
|
val builder = QuestionnaireBuilder()
|
||||||
|
builder.block()
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, optionId, targetQuestionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
sourceOptionId: Long,
|
||||||
|
targetQuestionId: Long,
|
||||||
|
) {
|
||||||
|
val influencedOptionSet = questionnaire.getSelectedOption(sourceQuestionId)
|
||||||
|
val targetQuestion = questionnaire.questions.map { it.question }.find { it.id == targetQuestionId }
|
||||||
|
|
||||||
|
if (influencedOptionSet == null || targetQuestion == null) return
|
||||||
|
|
||||||
|
for (influencedOption in influencedOptionSet) {
|
||||||
|
targetQuestion.optionList.forEach { targetOption ->
|
||||||
|
// Remove existing dependencies from the same source question
|
||||||
|
val dependency = OptionDependency(
|
||||||
|
sourceQuestionId = sourceQuestionId,
|
||||||
|
sourceOptionId = sourceOptionId,
|
||||||
|
influencedOptionId = influencedOption.id!!,
|
||||||
|
targetOptionId = targetQuestion.id!!,
|
||||||
|
condition = { q ->
|
||||||
|
q.isOptionSelected(sourceQuestionId, sourceOptionId)
|
||||||
|
},
|
||||||
|
effect = { _ ->
|
||||||
|
// 使用枚举类的 calculateEffectBasedOnOrder 方法
|
||||||
|
val effectValue = ComputeMethod.BaseComputeMethod
|
||||||
|
.calculateEffectBasedOnOrder(influencedOption, targetOption)
|
||||||
|
// 根据计算结果进行相应处理
|
||||||
|
println("Effect calculated: $effectValue")
|
||||||
|
effectValue // 返回 effect 值,这个可以用于后续的逻辑
|
||||||
|
}
|
||||||
|
)
|
||||||
|
targetOption.addDependency(dependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateEffectBasedOnOrder(
|
||||||
|
influencedOption: QuestionOption,
|
||||||
|
targetOption: QuestionOption, //是他自己
|
||||||
|
): Double {
|
||||||
|
val orderDifference = abs(influencedOption.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table("question")
|
||||||
|
open class Question(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val wrapperQuestionId: Long?,
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
@MappedCollection(idColumn = "question_id", keyColumn = "question_key")
|
||||||
|
val optionList: List<QuestionOption>,
|
||||||
|
val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
|
||||||
|
val order: Int, // 表示问题顺序
|
||||||
|
val isRequired: Int = 1, // 是否必答,默认为必答
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
optionList: List<QuestionOption>,
|
||||||
|
questionType: QuestionType,
|
||||||
|
order: Int,
|
||||||
|
isRequired: Int = 1
|
||||||
|
): Question {
|
||||||
|
return Question(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
wrapperQuestionId = null,
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
optionList = optionList,
|
||||||
|
questionType = questionType,
|
||||||
|
order = order,
|
||||||
|
isRequired = isRequired
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOptionListData(): List<QuestionOption> {
|
||||||
|
return optionList
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table("question_group")
|
||||||
|
class QuestionGroup(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val questionnaireId: Long?,
|
||||||
|
val parentId: Long?,
|
||||||
|
val groupName: String,
|
||||||
|
@MappedCollection(idColumn = "question_group_id", keyColumn = "question_group_key")
|
||||||
|
val surveyQuestionList: List<SurveyQuestion>
|
||||||
|
) {
|
||||||
|
@Transient
|
||||||
|
private val answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
parentId: Long?,
|
||||||
|
groupName: String,
|
||||||
|
surveyQuestionList: List<SurveyQuestion>
|
||||||
|
): QuestionGroup {
|
||||||
|
return QuestionGroup(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
questionnaireId = null, //外键由数据库生成
|
||||||
|
parentId = parentId,
|
||||||
|
groupName = groupName,
|
||||||
|
surveyQuestionList = surveyQuestionList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkIfInGroup(questionId: Long): Boolean {
|
||||||
|
return surveyQuestionList.map { it.question }.any { it.id == questionId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table("question_option")
|
||||||
|
class QuestionOption(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val questionId: Long?, //外键
|
||||||
|
val label: String,
|
||||||
|
val content: String,
|
||||||
|
val mark: String,
|
||||||
|
val markOrder: Int,
|
||||||
|
val score: Int? = null, // 可选的分数字段
|
||||||
|
) {
|
||||||
|
@Transient
|
||||||
|
private val dependencies: MutableList<OptionDependency> = mutableListOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
label: String,
|
||||||
|
content: String,
|
||||||
|
mark: String,
|
||||||
|
markOrder: Int,
|
||||||
|
score: Int? = null
|
||||||
|
): QuestionOption {
|
||||||
|
return QuestionOption(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
questionId = null, //外键
|
||||||
|
label = label,
|
||||||
|
content = content,
|
||||||
|
mark = mark,
|
||||||
|
markOrder = markOrder,
|
||||||
|
score = score
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算依赖关系对权重的影响
|
||||||
|
fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double {
|
||||||
|
var baseWeight = 1.0
|
||||||
|
dependencies.forEach { dependency ->
|
||||||
|
if (dependency.condition(questionnaire)) {
|
||||||
|
baseWeight += dependency.effect(questionnaire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baseWeight.coerceAtLeast(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateAndMapToWeight(questionnaire: Questionnaire): QuestionOptionWeight {
|
||||||
|
return QuestionOptionWeight(id!!, calculateWeightWithDependencies(questionnaire))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDependency(dependency: OptionDependency) {
|
||||||
|
//添加选项依赖,添加前会删除所有之前 问题的产生的依赖项,然后再进行添加
|
||||||
|
val sourceQuestionId = dependency.sourceQuestionId
|
||||||
|
this.dependencies.removeAll { it.sourceQuestionId == sourceQuestionId }
|
||||||
|
this.dependencies.add(dependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuestionOptionWeight(
|
||||||
|
val optionId: Long,
|
||||||
|
val weight: Double,
|
||||||
|
)
|
||||||
|
|
||||||
|
class OptionDependency(
|
||||||
|
val sourceQuestionId: Long,
|
||||||
|
val sourceOptionId: Long,
|
||||||
|
val influencedOptionId: Long,
|
||||||
|
val targetOptionId: Long,
|
||||||
|
val condition: (Questionnaire) -> Boolean,
|
||||||
|
val effect: (Questionnaire) -> Double,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class QuestionType {
|
||||||
|
SINGLE_CHOICE, // 单选
|
||||||
|
MULTIPLE_CHOICE, // 多选
|
||||||
|
OPEN_ENDED // 开放式问题
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table("survey_question")
|
||||||
|
class SurveyQuestion(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val questionnaireId: Long?,
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
@MappedCollection(idColumn = "wrapper_question_id", keyColumn = "wrapper_question_key")
|
||||||
|
val question: Question,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
question: Question
|
||||||
|
): SurveyQuestion {
|
||||||
|
return SurveyQuestion(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
questionnaireId = null, //外键值由数据库生成
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
question = question
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQuestionData(): Question {
|
||||||
|
return this.question
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SurveyBackgroundSample(
|
||||||
|
val surveyRespondent: SurveyRespondent,
|
||||||
|
val surveyBackgroundList: List<SurveyBackground>,
|
||||||
|
)
|
||||||
|
|
||||||
|
class SurveyRespondent(
|
||||||
|
val name: String,
|
||||||
|
val age: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Table("survey_background")
|
||||||
|
class SurveyBackground(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val questionnaireId: Long?,
|
||||||
|
val title: String,
|
||||||
|
@MappedCollection(idColumn = "wrapper_question_id", keyColumn = "wrapper_question_key")
|
||||||
|
val backgroundQuestion: List<Question>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
title: String,
|
||||||
|
backgroundQuestion: List<Question>
|
||||||
|
): SurveyBackground {
|
||||||
|
return SurveyBackground(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
questionnaireId = null,
|
||||||
|
title = title,
|
||||||
|
backgroundQuestion = backgroundQuestion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table("survey_procedure")
|
||||||
|
class SurveyProcedure(
|
||||||
|
@Id val id: Long?,
|
||||||
|
val questionnaireId: Long?,
|
||||||
|
val sourceQuestionId: Long,
|
||||||
|
val sourceOptionId: Long,
|
||||||
|
val influencedQuestionId: Long,
|
||||||
|
val influencedOptionId: Long,
|
||||||
|
var computeMethodName: String?,
|
||||||
|
@Transient val computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
sourceQuestionId: Long,
|
||||||
|
sourceOptionId: Long,
|
||||||
|
influencedQuestionId: Long,
|
||||||
|
influencedOptionId: Long,
|
||||||
|
computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
|
||||||
|
): SurveyProcedure {
|
||||||
|
val surveyProcedure = SurveyProcedure(
|
||||||
|
id = null, // `id` 由数据库生成
|
||||||
|
questionnaireId = null,
|
||||||
|
sourceQuestionId = sourceQuestionId,
|
||||||
|
sourceOptionId = sourceOptionId,
|
||||||
|
influencedQuestionId = influencedQuestionId,
|
||||||
|
influencedOptionId = influencedOptionId,
|
||||||
|
computeMethodName = null,
|
||||||
|
computeMethod = computeMethod
|
||||||
|
)
|
||||||
|
surveyProcedure.computeMethodName = computeMethod.name
|
||||||
|
return surveyProcedure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ifProcess(sourceOptionalId: Long, questionnaire: Questionnaire) {
|
||||||
|
if (sourceOptionalId == sourceOptionId) generateOptionDependency(questionnaire)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateOptionDependency(questionnaire: Questionnaire) {
|
||||||
|
val influencedQuestionOptions = questionnaire.questions
|
||||||
|
.filter { it.question.id == influencedQuestionId }
|
||||||
|
.map { it.question }
|
||||||
|
.flatMap { it.optionList }
|
||||||
|
val influencedOption = influencedQuestionOptions
|
||||||
|
.filter { option -> option.id == influencedOptionId }
|
||||||
|
.last()
|
||||||
|
influencedQuestionOptions.forEach {
|
||||||
|
//遍历所有的选项,向其中加入选项依赖
|
||||||
|
val optionDependency = OptionDependency(
|
||||||
|
sourceOptionId, sourceOptionId,
|
||||||
|
influencedOption.id!!,
|
||||||
|
it.id!!,
|
||||||
|
condition = { q ->
|
||||||
|
q.isOptionSelected(sourceQuestionId, sourceOptionId)
|
||||||
|
},
|
||||||
|
effect = { _ ->
|
||||||
|
// Define your effect based on source and target options
|
||||||
|
computeMethod.calculateEffectBasedOnOrder(influencedOption, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it.addDependency(optionDependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ComputeMethod(
|
||||||
|
val computeMethodName: String,
|
||||||
|
) {
|
||||||
|
BaseComputeMethod("Default") {
|
||||||
|
override fun calculateEffectBasedOnOrder(
|
||||||
|
influencedOption: QuestionOption,
|
||||||
|
targetOption: QuestionOption,
|
||||||
|
): Double {
|
||||||
|
val orderDifference = abs(influencedOption.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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface QuestionnaireRepository {
|
||||||
|
fun save(questionnaire: Questionnaire): Questionnaire
|
||||||
|
fun findById(id: Long): Optional<Questionnaire>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class QuestionnaireRepositoryImpl(private val jdbcQuestionnaireRepository: JdbcQuestionnaireRepository) :
|
||||||
|
QuestionnaireRepository {
|
||||||
|
override fun save(questionnaire: Questionnaire): Questionnaire {
|
||||||
|
return jdbcQuestionnaireRepository.save(questionnaire)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findById(id: Long): Optional<Questionnaire> {
|
||||||
|
return jdbcQuestionnaireRepository.findById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface JdbcQuestionnaireRepository : CrudRepository<Questionnaire, Long>
|
||||||
|
|
||||||
|
@DslMarker
|
||||||
|
annotation class QuestionnaireDsl
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class QuestionnaireBuilder {
|
||||||
|
var title: String = ""
|
||||||
|
var description: String = ""
|
||||||
|
private val questions = mutableListOf<SurveyQuestion>()
|
||||||
|
private val questionGroups = mutableListOf<QuestionGroup>()
|
||||||
|
private val procedures = mutableListOf<SurveyProcedure>()
|
||||||
|
lateinit var surveyBackground: SurveyBackground
|
||||||
|
|
||||||
|
fun questionGroup(block: QuestionGroupBuilder.() -> Unit) {
|
||||||
|
val groupBuilder = QuestionGroupBuilder()
|
||||||
|
groupBuilder.block()
|
||||||
|
val group = groupBuilder.build()
|
||||||
|
questionGroups.add(group)
|
||||||
|
questions.addAll(group.surveyQuestionList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun procedures(block: ProceduresBuilder.() -> Unit) {
|
||||||
|
val proceduresBuilder = ProceduresBuilder()
|
||||||
|
proceduresBuilder.block()
|
||||||
|
procedures.addAll(proceduresBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun background(block: BackgroundBuilder.() -> Unit) {
|
||||||
|
val backgroundBuilder = BackgroundBuilder()
|
||||||
|
backgroundBuilder.block()
|
||||||
|
surveyBackground = backgroundBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Questionnaire {
|
||||||
|
return Questionnaire.create(
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
questionGroup = questionGroups,
|
||||||
|
questions = questions,
|
||||||
|
surveyBackground = surveyBackground,
|
||||||
|
procedures = procedures
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class QuestionGroupBuilder {
|
||||||
|
var parentId: Long? = null
|
||||||
|
var groupName: String = ""
|
||||||
|
private val surveyQuestions = mutableListOf<SurveyQuestion>()
|
||||||
|
|
||||||
|
fun surveyQuestion(block: SurveyQuestionBuilder.() -> Unit) {
|
||||||
|
val questionBuilder = SurveyQuestionBuilder()
|
||||||
|
questionBuilder.block()
|
||||||
|
surveyQuestions.add(questionBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): QuestionGroup {
|
||||||
|
return QuestionGroup.create(
|
||||||
|
parentId = parentId,
|
||||||
|
groupName = groupName,
|
||||||
|
surveyQuestionList = surveyQuestions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class SurveyQuestionBuilder {
|
||||||
|
var title: String = ""
|
||||||
|
var content: String = ""
|
||||||
|
lateinit var question: Question
|
||||||
|
|
||||||
|
fun question(block: QuestionBuilder.() -> Unit) {
|
||||||
|
val questionBuilder = QuestionBuilder()
|
||||||
|
questionBuilder.block()
|
||||||
|
question = questionBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): SurveyQuestion {
|
||||||
|
return SurveyQuestion.create(
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
question = question
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class QuestionBuilder {
|
||||||
|
var title: String = ""
|
||||||
|
var content: String = ""
|
||||||
|
var questionType: QuestionType = QuestionType.SINGLE_CHOICE
|
||||||
|
var order: Int = 0
|
||||||
|
var isRequired: Int = 1
|
||||||
|
private val options = mutableListOf<QuestionOption>()
|
||||||
|
|
||||||
|
fun options(block: OptionsBuilder.() -> Unit) {
|
||||||
|
val optionsBuilder = OptionsBuilder()
|
||||||
|
optionsBuilder.block()
|
||||||
|
options.addAll(optionsBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Question {
|
||||||
|
return Question.create(
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
optionList = options,
|
||||||
|
questionType = questionType,
|
||||||
|
order = order,
|
||||||
|
isRequired = isRequired
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class OptionsBuilder {
|
||||||
|
private val options = mutableListOf<QuestionOption>()
|
||||||
|
|
||||||
|
fun option(block: OptionBuilder.() -> Unit) {
|
||||||
|
val optionBuilder = OptionBuilder()
|
||||||
|
optionBuilder.block()
|
||||||
|
options.add(optionBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): List<QuestionOption> = options
|
||||||
|
}
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class OptionBuilder {
|
||||||
|
var label: String = ""
|
||||||
|
var content: String = ""
|
||||||
|
var mark: String = ""
|
||||||
|
var markOrder: Int = 0
|
||||||
|
var score: Int? = null
|
||||||
|
|
||||||
|
fun build(): QuestionOption {
|
||||||
|
return QuestionOption.create(
|
||||||
|
label = label,
|
||||||
|
content = content,
|
||||||
|
mark = mark,
|
||||||
|
markOrder = markOrder,
|
||||||
|
score = score
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class ProceduresBuilder {
|
||||||
|
private val procedures = mutableListOf<SurveyProcedure>()
|
||||||
|
|
||||||
|
fun procedure(block: ProcedureBuilder.() -> Unit) {
|
||||||
|
val procedureBuilder = ProcedureBuilder()
|
||||||
|
procedureBuilder.block()
|
||||||
|
procedures.add(procedureBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): List<SurveyProcedure> = procedures
|
||||||
|
}
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class ProcedureBuilder {
|
||||||
|
var sourceQuestionId: Long = 0L
|
||||||
|
var sourceOptionId: Long = 0L
|
||||||
|
var influencedQuestionId: Long = 0L
|
||||||
|
var influencedOptionId: Long = 0L
|
||||||
|
var computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
|
||||||
|
|
||||||
|
fun build(): SurveyProcedure {
|
||||||
|
return SurveyProcedure.create(
|
||||||
|
sourceQuestionId = sourceQuestionId,
|
||||||
|
sourceOptionId = sourceOptionId,
|
||||||
|
influencedQuestionId = influencedQuestionId,
|
||||||
|
influencedOptionId = influencedOptionId,
|
||||||
|
computeMethod = computeMethod
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class SampleBuilder {
|
||||||
|
lateinit var surveyRespondent: SurveyRespondent
|
||||||
|
private val backgroundList = mutableListOf<SurveyBackground>()
|
||||||
|
|
||||||
|
fun respondent(block: RespondentBuilder.() -> Unit) {
|
||||||
|
val respondentBuilder = RespondentBuilder()
|
||||||
|
respondentBuilder.block()
|
||||||
|
surveyRespondent = respondentBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun background(block: BackgroundBuilder.() -> Unit) {
|
||||||
|
val backgroundBuilder = BackgroundBuilder()
|
||||||
|
backgroundBuilder.block()
|
||||||
|
backgroundList.add(backgroundBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): SurveyBackgroundSample {
|
||||||
|
return SurveyBackgroundSample(
|
||||||
|
surveyRespondent = surveyRespondent,
|
||||||
|
surveyBackgroundList = backgroundList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class RespondentBuilder {
|
||||||
|
var name: String = ""
|
||||||
|
var age: Int = 0
|
||||||
|
|
||||||
|
fun build(): SurveyRespondent {
|
||||||
|
return SurveyRespondent(name = name, age = age)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@QuestionnaireDsl
|
||||||
|
class BackgroundBuilder {
|
||||||
|
var title: String = ""
|
||||||
|
private val backgroundQuestions = mutableListOf<Question>()
|
||||||
|
|
||||||
|
fun question(block: QuestionBuilder.() -> Unit) {
|
||||||
|
val questionBuilder = QuestionBuilder()
|
||||||
|
questionBuilder.block()
|
||||||
|
backgroundQuestions.add(questionBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): SurveyBackground {
|
||||||
|
return SurveyBackground.create(
|
||||||
|
title = title,
|
||||||
|
backgroundQuestion = backgroundQuestions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,323 +0,0 @@
|
||||||
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<SurveyQuestion>,
|
|
||||||
val questionGroup: List<QuestionGroup>,
|
|
||||||
val sample: SurveyBackgroundSample, // 问卷中的所有问题
|
|
||||||
val procedures: List<SurveyProcedure>,
|
|
||||||
) {
|
|
||||||
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, optionId, 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,
|
|
||||||
sourceOptionId: Long,
|
|
||||||
targetQuestionId: Long,
|
|
||||||
) {
|
|
||||||
val influencedOptionSet = questionnaire.getSelectedOption(sourceQuestionId)
|
|
||||||
val targetQuestion = questionnaire.questions.map { it.question }.find { it.id == targetQuestionId }
|
|
||||||
|
|
||||||
if (influencedOptionSet == null || targetQuestion == null) return
|
|
||||||
|
|
||||||
for (influencedOption in influencedOptionSet) {
|
|
||||||
targetQuestion.optionList.forEach { targetOption ->
|
|
||||||
// Remove existing dependencies from the same source question
|
|
||||||
val dependency = OptionDependency(
|
|
||||||
sourceQuestionId = sourceQuestionId,
|
|
||||||
sourceOptionId = sourceOptionId,
|
|
||||||
influencedOptionId = influencedOption.id,
|
|
||||||
targetOptionId = targetQuestion.id,
|
|
||||||
condition = { q ->
|
|
||||||
q.isOptionSelected(sourceQuestionId, sourceOptionId)
|
|
||||||
},
|
|
||||||
effect = { _ ->
|
|
||||||
// 使用枚举类的 calculateEffectBasedOnOrder 方法
|
|
||||||
val effectValue = ComputeMethod.BaseComputeMethod
|
|
||||||
.calculateEffectBasedOnOrder(influencedOption, targetOption)
|
|
||||||
// 根据计算结果进行相应处理
|
|
||||||
println("Effect calculated: $effectValue")
|
|
||||||
effectValue // 返回 effect 值,这个可以用于后续的逻辑
|
|
||||||
}
|
|
||||||
)
|
|
||||||
targetOption.addDependency(dependency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calculateEffectBasedOnOrder(
|
|
||||||
influencedOption: QuestionOption,
|
|
||||||
targetOption: QuestionOption, //是他自己
|
|
||||||
): Double {
|
|
||||||
val orderDifference = abs(influencedOption.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(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val content: String,
|
|
||||||
val optionList: List<QuestionOption>,
|
|
||||||
val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
|
|
||||||
val order: Int, // 表示问题顺序
|
|
||||||
val isRequired: Boolean = true, // 是否必答,默认为必答
|
|
||||||
) {
|
|
||||||
fun getOptionListData(): List<QuestionOption> {
|
|
||||||
return optionList
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuestionGroup(
|
|
||||||
val id: Long,
|
|
||||||
val parentId: Long,
|
|
||||||
val groupName: String,
|
|
||||||
val surveyQuestionList: List<SurveyQuestion>,
|
|
||||||
var answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf(), // 已回答的问题及其分数
|
|
||||||
) {
|
|
||||||
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,
|
|
||||||
val markOrder: Int,
|
|
||||||
val score: Int? = null, // 可选的分数字段
|
|
||||||
var dependencies: MutableList<OptionDependency> = mutableListOf(), // 与其他选项的依赖关系
|
|
||||||
) {
|
|
||||||
// 计算依赖关系对权重的影响
|
|
||||||
fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double {
|
|
||||||
var baseWeight = 1.0
|
|
||||||
dependencies.forEach { dependency ->
|
|
||||||
if (dependency.condition(questionnaire)) {
|
|
||||||
baseWeight += dependency.effect(questionnaire)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return baseWeight.coerceAtLeast(0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calculateAndMapToWeight(questionnaire: Questionnaire): QuestionOptionWeight {
|
|
||||||
return QuestionOptionWeight(id, calculateWeightWithDependencies(questionnaire))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addDependency(dependency: OptionDependency) {
|
|
||||||
//添加选项依赖,添加前会删除所有之前 问题的产生的依赖项,然后再进行添加
|
|
||||||
val sourceQuestionId = dependency.sourceQuestionId
|
|
||||||
this.dependencies.removeAll { it.sourceQuestionId == sourceQuestionId }
|
|
||||||
this.dependencies.add(dependency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuestionOptionWeight(
|
|
||||||
val optionId: Long,
|
|
||||||
val weight: Double,
|
|
||||||
)
|
|
||||||
|
|
||||||
class OptionDependency(
|
|
||||||
val sourceQuestionId: Long,
|
|
||||||
val sourceOptionId: Long,
|
|
||||||
val influencedOptionId: Long,
|
|
||||||
val targetOptionId: Long,
|
|
||||||
val condition: (Questionnaire) -> Boolean,
|
|
||||||
val effect: (Questionnaire) -> Double,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
enum class QuestionType {
|
|
||||||
SINGLE_CHOICE, // 单选
|
|
||||||
MULTIPLE_CHOICE, // 多选
|
|
||||||
OPEN_ENDED // 开放式问题
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestion(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val content: String,
|
|
||||||
val questionId: Long,
|
|
||||||
val question: Question,
|
|
||||||
) {
|
|
||||||
fun getQuestionData(): Question {
|
|
||||||
return this.question
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SurveyBackgroundSample(
|
|
||||||
val surveyRespondent: SurveyRespondent,
|
|
||||||
val surveyBackgroundList: List<SurveyBackground>,
|
|
||||||
)
|
|
||||||
|
|
||||||
class SurveyRespondent(
|
|
||||||
val name: String,
|
|
||||||
val age: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
class SurveyBackground(
|
|
||||||
val title: String,
|
|
||||||
val backgroundQuestion: Question,
|
|
||||||
)
|
|
||||||
|
|
||||||
class SurveyProcedure(
|
|
||||||
val id: Long,
|
|
||||||
val sourceQuestionId: Long,
|
|
||||||
val sourceOptionId: Long,
|
|
||||||
val influencedQuestionId: Long,
|
|
||||||
val influencedOptionId: Long,
|
|
||||||
val computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun ifProcess(sourceOptionalId: Long, questionnaire: Questionnaire) {
|
|
||||||
if (sourceOptionalId == sourceOptionId) generateOptionDependency(questionnaire)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateOptionDependency(questionnaire: Questionnaire) {
|
|
||||||
val influencedQuestionOptions = questionnaire.questions
|
|
||||||
.filter { it.questionId == influencedQuestionId }
|
|
||||||
.map { it.question }
|
|
||||||
.flatMap { it.optionList }
|
|
||||||
val influencedOption = influencedQuestionOptions
|
|
||||||
.filter { option -> option.id == influencedOptionId }
|
|
||||||
.last()
|
|
||||||
influencedQuestionOptions.forEach {
|
|
||||||
//遍历所有的选项,向其中加入选项依赖
|
|
||||||
val optionDependency = OptionDependency(
|
|
||||||
sourceOptionId, sourceOptionId,
|
|
||||||
influencedOption.id,
|
|
||||||
it.id,
|
|
||||||
condition = { q ->
|
|
||||||
q.isOptionSelected(sourceQuestionId, sourceOptionId)
|
|
||||||
},
|
|
||||||
effect = { _ ->
|
|
||||||
// Define your effect based on source and target options
|
|
||||||
computeMethod.calculateEffectBasedOnOrder(influencedOption, it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
it.addDependency(optionDependency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ComputeMethod(
|
|
||||||
val computeMethodName: String,
|
|
||||||
) {
|
|
||||||
BaseComputeMethod("Default") {
|
|
||||||
override fun calculateEffectBasedOnOrder(
|
|
||||||
influencedOption: QuestionOption,
|
|
||||||
targetOption: QuestionOption,
|
|
||||||
): Double {
|
|
||||||
val orderDifference = abs(influencedOption.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
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
server:
|
||||||
|
port: 8083
|
||||||
|
echo:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
host: 47.97.21.20
|
||||||
|
port: 3306
|
||||||
|
database: questionnaire
|
||||||
|
username: root
|
||||||
|
password: kerowsqw34
|
||||||
|
redis:
|
||||||
|
host: 10.10.103.131
|
||||||
|
port: 6379
|
||||||
|
password: Worktask@Redis2023
|
||||||
|
database: 8
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.jdbc.core.JdbcTemplate: info
|
||||||
|
org.springframework.jdbc.core.StatementCreatorUtils: info
|
||||||
|
root: info
|
||||||
|
org.springframework.cache: info
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: test
|
||||||
|
datasource:
|
||||||
|
driver-class-name: ${echo.datasource.driver-class-name}
|
||||||
|
url: jdbc:mysql://${echo.datasource.host}:${echo.datasource.port}/${echo.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
|
username: ${echo.datasource.username}
|
||||||
|
password: ${echo.datasource.password}
|
||||||
|
hikari:
|
||||||
|
minimum-idle: 20
|
||||||
|
maximum-pool-size: 60 # 连接池最大连接数
|
||||||
|
idle-timeout: 300000 # 空闲连接超时时间(毫秒)
|
||||||
|
max-lifetime: 1800000 # 连接的最长生命周期(毫秒)
|
||||||
|
connection-timeout: 60000 # 连接超时时间(毫秒)
|
||||||
|
pool-name: 'SpringBootHikariCP' # 连接池名字
|
||||||
|
leak-detection-threshold: 30000 # 连接泄露检测阈值(毫秒)
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${echo.redis.host}
|
||||||
|
port: ${echo.redis.port}
|
||||||
|
password: ${echo.redis.password}
|
||||||
|
database: ${echo.redis.database}
|
||||||
|
aop:
|
||||||
|
auto: true
|
||||||
|
proxy-target-class: true
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.echo
|
||||||
|
|
||||||
|
|
||||||
|
import org.echo.org.echo.questionnaire.ComputeMethod
|
||||||
|
import org.echo.org.echo.questionnaire.QuestionType
|
||||||
|
import org.echo.org.echo.questionnaire.Questionnaire
|
||||||
|
import org.echo.org.echo.questionnaire.Questionnaire.Companion.questionnaire
|
||||||
|
import org.echo.org.echo.questionnaire.QuestionnaireRepository
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class QuestionnaireApplicationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var questionnaireRepository: QuestionnaireRepository
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun contextLoads() {
|
||||||
|
var exampleQuestionnaire = exampleQuestionnaire()
|
||||||
|
questionnaireRepository.save(exampleQuestionnaire)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun findQuestionnaire(){
|
||||||
|
var findById = questionnaireRepository.findById(16L)
|
||||||
|
println(findById)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exampleQuestionnaire(): Questionnaire {
|
||||||
|
return questionnaire {
|
||||||
|
title = "客户满意度调查"
|
||||||
|
description = "一份用于测量客户满意度的调查问卷。"
|
||||||
|
|
||||||
|
background {
|
||||||
|
title = "背景信息"
|
||||||
|
question {
|
||||||
|
title = "您的年龄是多少?"
|
||||||
|
content = "请选择您的年龄范围。"
|
||||||
|
questionType = QuestionType.SINGLE_CHOICE
|
||||||
|
options {
|
||||||
|
option {
|
||||||
|
label = "18-25"
|
||||||
|
content = "18-25 岁"
|
||||||
|
mark = "A"
|
||||||
|
markOrder = 1
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
label = "26-35"
|
||||||
|
content = "26-35 岁"
|
||||||
|
mark = "B"
|
||||||
|
markOrder = 2
|
||||||
|
}
|
||||||
|
// 更多选项...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
questionGroup {
|
||||||
|
groupName = "服务满意度"
|
||||||
|
|
||||||
|
surveyQuestion {
|
||||||
|
title = "您对我们的服务满意吗?"
|
||||||
|
content = "请选择一个选项。"
|
||||||
|
question {
|
||||||
|
title = "您对我们的服务满意吗?"
|
||||||
|
content = "请选择一个选项。"
|
||||||
|
questionType = QuestionType.SINGLE_CHOICE
|
||||||
|
options {
|
||||||
|
option {
|
||||||
|
label = "非常满意"
|
||||||
|
content = "非常满意"
|
||||||
|
mark = "A"
|
||||||
|
markOrder = 1
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
label = "满意"
|
||||||
|
content = "满意"
|
||||||
|
mark = "B"
|
||||||
|
markOrder = 2
|
||||||
|
}
|
||||||
|
// 更多选项...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue