feat:编写AI报告的接口、添加鼓励语句、修改举一反三从题库中获取相似题目的接口
This commit is contained in:
parent
068f16ffaa
commit
3e863506aa
@ -25,5 +25,9 @@
|
||||
<artifactId>common-validation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,36 @@
|
||||
package com.seer.teach.common.enums.teacher;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ReportTypeEnum {
|
||||
|
||||
DAILY(0, "今天"),
|
||||
WEEKLY(1, "本周"),
|
||||
MONTHLY(2, "本月");
|
||||
|
||||
private final Integer type;
|
||||
|
||||
private final String desc;
|
||||
|
||||
@JsonCreator
|
||||
public static ReportTypeEnum fromType(Integer type) {
|
||||
for (ReportTypeEnum e : values()) {
|
||||
if (e.type.equals(type)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("非法报告类型type:" + type);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public Integer getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.seer.teach.common.utils;
|
||||
|
||||
import com.seer.teach.common.enums.teacher.ReportTypeEnum;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@ -8,7 +10,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 将符合RFC3339标准格式的字符串转换为LocalDateTime对象
|
||||
* 格式:yyyy-MM-DDTHH:mm:ss+TIMEZONE
|
||||
@ -23,12 +25,13 @@ public class DateUtils {
|
||||
}
|
||||
// 使用ISO_OFFSET_DATE_TIME解析带时区的日期时间字符串
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||
|
||||
|
||||
return offsetDateTime.toLocalDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据创建时间计算距离当前时间相差多少天
|
||||
*
|
||||
* @param createTime 创建时间(LocalDateTime)
|
||||
* @return 相差的天数,例如:5
|
||||
*/
|
||||
@ -43,46 +46,54 @@ public class DateUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Ai报告获取开始时间
|
||||
* @param type 1-本周,2-本天
|
||||
* @return 开始时间
|
||||
* 获取开始时间
|
||||
*
|
||||
* @param type 类型
|
||||
* @return LocalDateTime对象
|
||||
*/
|
||||
public static LocalDateTime getStartTime(Integer type) {
|
||||
if (type == 1) {
|
||||
// 本天
|
||||
public static LocalDateTime getStartTime(ReportTypeEnum type) {
|
||||
if (type.getType().equals(0)) {
|
||||
// 今天
|
||||
return LocalDate.now().atStartOfDay();
|
||||
} else if(type == 0){
|
||||
} else if (type.getType().equals(1)) {
|
||||
// 本周
|
||||
return LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay();
|
||||
} else if (type.getType().equals(2)) {
|
||||
// 本月
|
||||
return LocalDate.now().withDayOfMonth(1).atStartOfDay();
|
||||
} else {
|
||||
throw new IllegalArgumentException("type只能为0或1");
|
||||
throw new IllegalArgumentException("type只能为0、1或2");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将秒数转换为小时或者天
|
||||
*
|
||||
* @param seconds 秒数
|
||||
* @param type 1-本周,2-本天
|
||||
* @param type 0-今天,1-本周,2-本月
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String formatSecondsToHourMinute(Integer seconds, Integer type) {
|
||||
if (type == 1) {
|
||||
// 本天转换为小时
|
||||
int hours = seconds / 3600;
|
||||
return hours + "小时";
|
||||
} else if (type == 0){
|
||||
// 本周转换为天
|
||||
public static String formatSecondsToHourMinute(Integer seconds, ReportTypeEnum type) {
|
||||
if (type.getType().equals(0)) {
|
||||
// 今天转换为小时和分钟
|
||||
return formatSecondsToHourAndMinute(seconds);
|
||||
} else if (type.getType().equals(1) || type.getType().equals(2)) {
|
||||
// 本周和本月转换为天
|
||||
int days = seconds / 86400;
|
||||
return days + "天";
|
||||
} else {
|
||||
throw new IllegalArgumentException("type只能为0或1");
|
||||
throw new IllegalArgumentException("type只能为0、1或2");
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatSecondsToHourAndMinute(Integer seconds) {
|
||||
int hours = seconds / 3600;
|
||||
int minutes = (seconds % 3600) / 60;
|
||||
return hours + "小时" + minutes + "分";
|
||||
if (hours > 0) {
|
||||
return hours + "小时" + minutes + "分钟";
|
||||
} else {
|
||||
return minutes + "分钟";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
package com.seer.teach.teacher.common.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import com.seer.teach.common.enums.teacher.ReportTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ -16,7 +16,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
@Schema(description = "Ai报告请求实体类")
|
||||
public class AiReportReq {
|
||||
|
||||
@Schema(description = "报告类型(0-本周,1-本天)")
|
||||
@Schema(description ="报告类型(0-今天,1-本周,2-本月)")
|
||||
@NotNull(message = "报告类型不能为空")
|
||||
private Integer type;
|
||||
private ReportTypeEnum type;
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.seer.teach.teacher.common.resp;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@ -32,6 +31,9 @@ public class AiReportResp {
|
||||
@Schema(description = "作业正确率")
|
||||
private String homeworkCorrectRate;
|
||||
|
||||
@Schema(description = "鼓励语句")
|
||||
private String encourage;
|
||||
|
||||
@Schema(description = "科目正确率")
|
||||
private List<SubjectCorrectRateResp> subjectCorrectRate;
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.seer.teach.teacher.common.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@Schema(description = "举一反三类似题目返回实体类")
|
||||
public class SimilarQuestionResp {
|
||||
|
||||
@Schema(description = "题目Id")
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "做题富文本数据")
|
||||
private String questionHtml;
|
||||
|
||||
@Schema(description = "题型(1单选选择题 2多选选择题 4判断题 6填空题)")
|
||||
private Integer type;
|
||||
}
|
||||
@ -7,8 +7,8 @@ import com.seer.teach.common.annotation.LogPrint;
|
||||
import com.seer.teach.teacher.common.req.AiReportReq;
|
||||
import com.seer.teach.teacher.common.resp.AiReportResp;
|
||||
import com.seer.teach.teacher.service.ReportService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
@ -5,19 +5,22 @@ import com.seer.teach.common.ResultBean;
|
||||
import com.seer.teach.common.annotation.DecryptionAnnotation;
|
||||
import com.seer.teach.common.annotation.EncryptionAnnotation;
|
||||
import com.seer.teach.common.annotation.LogPrint;
|
||||
import com.seer.teach.common.enums.AiScenarioCodeEnum;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionBookReq;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionPageListReq;
|
||||
import com.seer.teach.teacher.common.resp.SimilarQuestionResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionPageListResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionResp;
|
||||
import com.seer.teach.teacher.module.entity.IdEntity;
|
||||
import com.seer.teach.teacher.service.WrongQuestionService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -37,7 +40,7 @@ public class WrongQuestionController {
|
||||
@LogPrint
|
||||
@Operation(summary = "获取各科目的错题数量")
|
||||
public ResultBean<WrongQuestionResp> getWrongQuestionBook(@RequestBody @Validated WrongQuestionBookReq params) {
|
||||
WrongQuestionResp wrongQuestion = wrongQuestionService.getWrongQuestion(params);
|
||||
WrongQuestionResp wrongQuestion = wrongQuestionService.getWrongQuestionCount(params);
|
||||
return ResultBean.success(wrongQuestion);
|
||||
}
|
||||
|
||||
@ -57,13 +60,12 @@ public class WrongQuestionController {
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/generateQuestionsByQuestionId")
|
||||
@PostMapping("/getSimilarQuestions")
|
||||
@LogPrint
|
||||
@Operation(summary = "举一反三")
|
||||
public ResultBean<List<String>> generateSimilarQuestions(@RequestBody @Validated IdEntity params) {
|
||||
List<String> questions = wrongQuestionService.generateSimilarQuestions(params.getId(),
|
||||
AiScenarioCodeEnum.LEARN_BY_ANALOGY_GENERATE_QUESTIONS, 3);
|
||||
return ResultBean.success(questions);
|
||||
@Operation(summary = "举一反三获取相似题目")
|
||||
public ResultBean<List<SimilarQuestionResp>> getSimilarQuestions(@RequestBody @Validated IdEntity params) {
|
||||
List<SimilarQuestionResp> result = wrongQuestionService.getSimilarQuestionsByQuestionId(params.getId());
|
||||
return ResultBean.success(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ spring:
|
||||
import:
|
||||
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
|
||||
- optional:nacos:shared-database.yaml
|
||||
- optional:nacos:shared-minio.yaml
|
||||
- optional:nacos:shared-redis.yaml
|
||||
cloud:
|
||||
nacos:
|
||||
|
||||
@ -12,5 +12,6 @@ public interface AiReportConvert {
|
||||
|
||||
AiReportConvert INSTANCE = Mappers.getMapper(AiReportConvert.class);
|
||||
|
||||
AiReportResp toAiReportResp(String studyTime, String readingTime, String practiceCount, String dailyStudyTime, String homeworkCorrectRate, List<SubjectCorrectRateResp> subjectCorrectRate);
|
||||
AiReportResp toAiReportResp(String studyTime, String readingTime, String practiceCount, String encourage,
|
||||
String dailyStudyTime, String homeworkCorrectRate, List<SubjectCorrectRateResp> subjectCorrectRate);
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
package com.seer.teach.teacher.convert;
|
||||
|
||||
import com.seer.teach.teacher.common.resp.SimilarQuestionResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionResp;
|
||||
import com.seer.teach.teacher.module.entity.BankQuestionsEntity;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface WrongQuestionBookConvert {
|
||||
@ -13,4 +17,8 @@ public interface WrongQuestionBookConvert {
|
||||
|
||||
@Mapping(source = "totalWrongCount",target = "count")
|
||||
WrongQuestionResp toWrongQuestion(String totalWrongCount);
|
||||
|
||||
SimilarQuestionResp toSimilarQuestion(BankQuestionsEntity bankQuestionsEntity);
|
||||
|
||||
List<SimilarQuestionResp> toSimilarQuestionList(List<BankQuestionsEntity> bankQuestionsEntity);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import com.seer.teach.common.PageListBean;
|
||||
import com.seer.teach.common.enums.AiScenarioCodeEnum;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionBookReq;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionPageListReq;
|
||||
import com.seer.teach.teacher.common.resp.SimilarQuestionResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionPageListResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionResp;
|
||||
import com.seer.teach.teacher.module.entity.IdEntity;
|
||||
@ -24,7 +25,7 @@ public interface WrongQuestionService {
|
||||
* @param params 请求参数
|
||||
* @return 错题本
|
||||
*/
|
||||
WrongQuestionResp getWrongQuestion(WrongQuestionBookReq params);
|
||||
WrongQuestionResp getWrongQuestionCount(WrongQuestionBookReq params);
|
||||
|
||||
/**
|
||||
* 获取错题列表
|
||||
@ -51,4 +52,11 @@ public interface WrongQuestionService {
|
||||
* @return 举一反三
|
||||
*/
|
||||
List<String> generateSimilarQuestions(Integer id, AiScenarioCodeEnum scenarioCode, Integer count);
|
||||
|
||||
/**
|
||||
* 举一反三获取相似题目
|
||||
* @param id 题目Id
|
||||
* @return 相似题目
|
||||
*/
|
||||
List<SimilarQuestionResp> getSimilarQuestionsByQuestionId(Integer id);
|
||||
}
|
||||
@ -4,6 +4,7 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.seer.teach.common.entity.BaseEntity;
|
||||
import com.seer.teach.common.enums.MakeQuestionTypeEnums;
|
||||
import com.seer.teach.common.enums.teacher.ReportTypeEnum;
|
||||
import com.seer.teach.common.utils.DateUtils;
|
||||
import com.seer.teach.iot.api.UserDeviceServiceApi;
|
||||
import com.seer.teach.teacher.common.dto.ReportQuestionDTO;
|
||||
@ -18,18 +19,18 @@ import com.seer.teach.teacher.convert.QuestionConvert;
|
||||
import com.seer.teach.teacher.convert.ReportConvert;
|
||||
import com.seer.teach.teacher.module.entity.*;
|
||||
import com.seer.teach.teacher.service.*;
|
||||
import com.seer.teach.teacher.util.RateUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.*;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -47,6 +48,20 @@ public class ReportServiceImpl implements ReportService {
|
||||
|
||||
private static final String PERCENT_SIGN = "%";
|
||||
|
||||
private static final String FIRST_STUDY = "你近期首次学习,期待你的持续进步!";
|
||||
|
||||
private static final String NOT_STUDY = "你%s还没有学习哦! 快去学习吧~";
|
||||
|
||||
private static final String VERY_GREAT = "很棒哦! %s你的作业正确率提升了%d%%,继续保持优秀表现~";
|
||||
|
||||
private static final String NOT_BAD = "不错哦! %s你的作业正确率提升了%d%%,请继续加油~";
|
||||
|
||||
private static final String COME_ON = "加油哦! %s你的作业正确率有提升,再接再厉~";
|
||||
|
||||
private static final String CONTINUE_STUDYING = "继续努力哦! %s你的作业正确率还有提升空间~";
|
||||
|
||||
private static final String DON_LOSE_HEART = "别灰心哦!%s你的作业正确率有所下降,坚持学习一定会进步的~";
|
||||
|
||||
private final EvaluationTasksService evaluationTasksService;
|
||||
|
||||
private final UserDeviceServiceApi userDeviceServiceApi;
|
||||
@ -161,16 +176,144 @@ public class ReportServiceImpl implements ReportService {
|
||||
.reduce(0, Integer::sum);
|
||||
String readingTime = DateUtils.formatSecondsToHourAndMinute(readingSeconds);
|
||||
|
||||
// 鼓励语句
|
||||
String encourage = generateEncouragement(params.getType(), homeworkCorrectRate, userId);
|
||||
|
||||
// 日均学习时间
|
||||
long days = ChronoUnit.DAYS.between(startTime.toLocalDate(), endTime.toLocalDate()) + 1;
|
||||
String dailyStudyTime = (studySeconds / 60 / days) + MINUTE;
|
||||
|
||||
// 返回最终AI报告结果
|
||||
return AiReportConvert.INSTANCE.toAiReportResp(
|
||||
studyTime, readingTime, practiceCount, dailyStudyTime, homeworkCorrectRate, subjectRates
|
||||
);
|
||||
return AiReportConvert.INSTANCE.toAiReportResp(studyTime, readingTime, practiceCount, encourage,
|
||||
dailyStudyTime, homeworkCorrectRate, subjectRates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼓励语句
|
||||
*
|
||||
* @param type 时间类型
|
||||
* @param correctRate 当前正确率
|
||||
* @return 鼓励语
|
||||
*/
|
||||
private String generateEncouragement(ReportTypeEnum type, String correctRate, Integer userId) {
|
||||
// 时间段
|
||||
String timePeriod = type.getDesc();
|
||||
// 当前正确率
|
||||
BigDecimal currentRate = RateUtils.percentStrToRate(correctRate);
|
||||
|
||||
// 如果正确率为0%,说明没有做题,返回鼓励语
|
||||
if (currentRate.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return String.format(NOT_STUDY, timePeriod);
|
||||
}
|
||||
// 获取历史正确率进行对比
|
||||
Optional<BigDecimal> historyRateOpt = getHistoricalCorrectRate(userId, type);
|
||||
// 如果没有历史学习记录,返回提示语
|
||||
if (!historyRateOpt.isPresent()) {
|
||||
return FIRST_STUDY;
|
||||
}
|
||||
// 获取提升百分比
|
||||
BigDecimal historyRate = historyRateOpt.get();
|
||||
BigDecimal improvementPercent = RateUtils.calculateImprovementPercent(currentRate, historyRate);
|
||||
int improve = improvementPercent.intValue();
|
||||
if (improve >= 20) {
|
||||
return String.format(VERY_GREAT, timePeriod, improve);
|
||||
} else if (improve >= 10) {
|
||||
return String.format(NOT_BAD, timePeriod, improve);
|
||||
} else if (improve >= 5) {
|
||||
return String.format(COME_ON, timePeriod);
|
||||
} else if (improve > -5) {
|
||||
return String.format(CONTINUE_STUDYING, timePeriod);
|
||||
} else {
|
||||
return String.format(DON_LOSE_HEART, timePeriod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史作业正确率
|
||||
*
|
||||
* @param userId 用户Id
|
||||
* @param type 时间类型
|
||||
* @return 历史作业正确率
|
||||
*/
|
||||
private Optional<BigDecimal> getHistoricalCorrectRate(Integer userId, ReportTypeEnum type) {
|
||||
// 获取当前查询时间段的开始时间
|
||||
LocalDateTime currentTimeStart = DateUtils.getStartTime(type);
|
||||
|
||||
// 查询当前时间段之前的学习记录,按时间倒序排列
|
||||
List<LessonsEntity> previousLessons = lessonsService.lambdaQuery()
|
||||
.eq(LessonsEntity::getUserId, userId)
|
||||
.lt(LessonsEntity::getCreateTime, currentTimeStart)
|
||||
.orderByDesc(LessonsEntity::getCreateTime)
|
||||
.list();
|
||||
|
||||
List<EvaluationTasksEntity> previousEvaluations = evaluationTasksService.lambdaQuery()
|
||||
.eq(EvaluationTasksEntity::getUserId, userId)
|
||||
.lt(EvaluationTasksEntity::getCreateTime, currentTimeStart)
|
||||
.orderByDesc(EvaluationTasksEntity::getCreateTime)
|
||||
.list();
|
||||
|
||||
// 合并并按时间倒序排序,找出最近一次学习会话的时间范围
|
||||
List<LocalDateTime> allCreateTimes = Stream.concat(
|
||||
previousLessons.stream().map(LessonsEntity::getCreateTime),
|
||||
previousEvaluations.stream().map(EvaluationTasksEntity::getCreateTime))
|
||||
.sorted(Collections.reverseOrder()).collect(Collectors.toList());
|
||||
if (allCreateTimes.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// 获取最近一次学习的时间
|
||||
LocalDateTime latestLearningTime = allCreateTimes.get(0);
|
||||
|
||||
// 根据报告类型确定历史时间段
|
||||
LocalDateTime periodStart, periodEnd;
|
||||
|
||||
switch (type) {
|
||||
case DAILY:
|
||||
// 查找最近一次学习的那一天的所有学习记录
|
||||
LocalDate learningDate = latestLearningTime.toLocalDate();
|
||||
periodStart = learningDate.atStartOfDay();
|
||||
periodEnd = learningDate.atTime(LocalTime.MAX);
|
||||
break;
|
||||
case WEEKLY:
|
||||
// 查找最近一次学习的那周
|
||||
LocalDate learningWeekStart = latestLearningTime.toLocalDate().with(DayOfWeek.MONDAY);
|
||||
LocalDate learningWeekEnd = learningWeekStart.plusDays(6);
|
||||
periodStart = learningWeekStart.atStartOfDay();
|
||||
periodEnd = learningWeekEnd.atTime(LocalTime.MAX);
|
||||
break;
|
||||
case MONTHLY:
|
||||
// 查找最近一次学习的那个月
|
||||
YearMonth learningMonth = YearMonth.from(latestLearningTime);
|
||||
periodStart = learningMonth.atDay(1).atStartOfDay();
|
||||
periodEnd = learningMonth.atEndOfMonth().atTime(LocalTime.MAX);
|
||||
break;
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// 获取该历史时期的完整学习数据
|
||||
List<LessonsEntity> lessons = lessonsService.lambdaQuery()
|
||||
.eq(LessonsEntity::getUserId, userId)
|
||||
.ge(LessonsEntity::getCreateTime, periodStart)
|
||||
.le(LessonsEntity::getCreateTime, periodEnd)
|
||||
.list();
|
||||
|
||||
List<EvaluationTasksEntity> evaluations = evaluationTasksService.lambdaQuery()
|
||||
.eq(EvaluationTasksEntity::getUserId, userId)
|
||||
.ge(EvaluationTasksEntity::getCreateTime, periodStart)
|
||||
.le(EvaluationTasksEntity::getCreateTime, periodEnd)
|
||||
.list();
|
||||
|
||||
int totalQ = getTotalQuestions(lessons) + getTotalQuestions(evaluations);
|
||||
if (totalQ == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
BigDecimal totalCorrect = calculateTotalCorrect(lessons).add(calculateTotalCorrect(evaluations));
|
||||
return Optional.of(totalCorrect.divide(BigDecimal.valueOf(totalQ), 2, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定用户的全部题目数据(包括测评和计划中的题目)。
|
||||
*
|
||||
@ -535,6 +678,11 @@ public class ReportServiceImpl implements ReportService {
|
||||
* @return 科目ID到名称的映射
|
||||
*/
|
||||
private Map<Integer, String> getSubjectsByIds(Set<Integer> ids) {
|
||||
log.info("获取科目名称,ID集合: {}", ids);
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
log.info("ID集合为空,返回空map");
|
||||
return new HashMap<>();
|
||||
}
|
||||
Map<Integer, String> map = new HashMap<>();
|
||||
for (SubjectsEntity s : subjectsService.listByIds(new ArrayList<>(ids))) {
|
||||
map.put(s.getId(), s.getSubject());
|
||||
|
||||
@ -7,11 +7,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.seer.teach.common.PageListBean;
|
||||
import com.seer.teach.common.enums.*;
|
||||
import com.seer.teach.common.enums.AiScenarioCodeEnum;
|
||||
import com.seer.teach.common.enums.BankQuestionStatusEnum;
|
||||
import com.seer.teach.common.enums.QuestionResultEnums;
|
||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||
import com.seer.teach.common.utils.AssertUtils;
|
||||
import com.seer.teach.common.utils.PageConverterUtils;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionBookReq;
|
||||
import com.seer.teach.teacher.common.req.WrongQuestionPageListReq;
|
||||
import com.seer.teach.teacher.common.resp.SimilarQuestionResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionPageListResp;
|
||||
import com.seer.teach.teacher.common.resp.WrongQuestionResp;
|
||||
import com.seer.teach.teacher.convert.WrongQuestionBookConvert;
|
||||
@ -63,7 +67,7 @@ public class WrongQuestionServiceImpl implements WrongQuestionService {
|
||||
* @return 返回封装了总错题数的响应对象
|
||||
*/
|
||||
@Override
|
||||
public WrongQuestionResp getWrongQuestion(WrongQuestionBookReq params) {
|
||||
public WrongQuestionResp getWrongQuestionCount(WrongQuestionBookReq params) {
|
||||
Integer userId = StpUtil.getLoginIdAsInt();
|
||||
log.info("开始获取用户[{}]测评错题本数据", userId);
|
||||
|
||||
@ -243,6 +247,40 @@ public class WrongQuestionServiceImpl implements WrongQuestionService {
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 举一反三获取相似题目
|
||||
*
|
||||
* @param id 题目Id
|
||||
* @return 相似题目列表
|
||||
*/
|
||||
|
||||
@Override
|
||||
public List<SimilarQuestionResp> getSimilarQuestionsByQuestionId(Integer id) {
|
||||
// 根据题目Id获取题目
|
||||
BankQuestionsEntity questionBank = bankQuestionsService.getById(id);
|
||||
AssertUtils.notNull(questionBank, ResultCodeEnum.QUESTION_NOT_EXIST);
|
||||
// 获取知识点Id
|
||||
Integer knowId = questionBank.getKnowId();
|
||||
// 查询相同知识点的题目,排除原题目
|
||||
List<BankQuestionsEntity> questionsList = bankQuestionsService.lambdaQuery()
|
||||
.eq(BankQuestionsEntity::getKnowId, knowId)
|
||||
.ne(BankQuestionsEntity::getId, id)
|
||||
.list();
|
||||
List<SimilarQuestionResp> similarQuestionList = WrongQuestionBookConvert.INSTANCE.toSimilarQuestionList(questionsList);
|
||||
// 过滤掉questionHtml为空的题目
|
||||
List<SimilarQuestionResp> result = similarQuestionList.stream()
|
||||
.filter(question -> question.getQuestionHtml() != null && !question.getQuestionHtml().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
// 将相似题目打乱顺序排序
|
||||
Collections.shuffle(result);
|
||||
// 返回10道知识点相同的题目
|
||||
return result.stream()
|
||||
.limit(10)
|
||||
// 重新编号(序号1-10)
|
||||
.peek((question) -> question.setId(result.indexOf(question) + 1))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步批量插入生成的相似题目到题库
|
||||
*
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package com.seer.teach.teacher.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RateUtils {
|
||||
|
||||
/**
|
||||
* 百分比字符串转正确率("62%" -> 0.62)
|
||||
*
|
||||
* @param percent 百分比字符串
|
||||
* @return 正确率
|
||||
*/
|
||||
public static BigDecimal percentStrToRate(String percent) {
|
||||
if (percent == null || percent.trim().isEmpty() || "0%".equals(percent)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return new BigDecimal(percent.replace("%", ""))
|
||||
.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算提升百分比
|
||||
*
|
||||
* @param current 当前正确率
|
||||
* @param history 历史正确率
|
||||
* @return 提升百分比
|
||||
*/
|
||||
public static BigDecimal calculateImprovementPercent(BigDecimal current, BigDecimal history) {
|
||||
if (history.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
// 历史为 0,认为是首次学习
|
||||
return BigDecimal.valueOf(100);
|
||||
}
|
||||
BigDecimal improve = current.subtract(history)
|
||||
.divide(history, 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.setScale(0, RoundingMode.HALF_UP);
|
||||
// 限制范围 [-100, 100]
|
||||
return improve.max(BigDecimal.valueOf(-100))
|
||||
.min(BigDecimal.valueOf(100));
|
||||
}
|
||||
}
|
||||
@ -188,11 +188,10 @@ public class WrongQuestionUtils {
|
||||
String processed = originalHtml
|
||||
.replaceAll("<!--\\s*题目解析\\s*-->\\s*<p><b>解析:</b>", "<p>解析:")
|
||||
.replaceAll("<p>\\s*<b>答案:</b>\\s*", "<p>【正确答案】 ");
|
||||
String redX = " ❌";
|
||||
// String redX = " ❌";
|
||||
// 构建学生答案段
|
||||
String studentSegment = "<p>【你的答案】 "
|
||||
+ (safeStudentAnswer.isEmpty() ? "" : safeStudentAnswer)
|
||||
+ redX
|
||||
+ "</p>\n\n";
|
||||
// 插入到正确答案之前
|
||||
processed = processed.replaceFirst("<p>【正确答案】", studentSegment + "<p>【正确答案】");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user