feat(复习): 节点级 focusPath + findNode,回退 session 维度

- 移除 MindMapNode.sessionNum、filterBySession、getTaskSessions
- 新增 MindMapTreeTool.extractSubtree() 按路径提取子树作为对比基准
- 新增 findClosestNode/getPath/similarityScore 支持节点匹配
- 新增 POST /review/standard-mind-map/{taskNum}/find-node
- recallCompare 使用 focusPath 替代 sessionNum
- ReviewRecallRecordEntity.focusPath 替代 sessionNum
- Flyway V20260706_1: review_recall_records 加 focus_path 列
This commit is contained in:
2026-07-05 18:12:32 +08:00
parent 3c3f682b2b
commit b18f8b4d1c
13 changed files with 197 additions and 129 deletions
@@ -152,7 +152,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
@Override
@Transactional
public ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline, String sessionNum)
public ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline, String focusPath)
throws NotFindEntitiesException, OperationFailedException {
ensureTaskExists(taskNum);
@@ -163,9 +163,14 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
MindMapNode standardRoot = MindMapTreeTool.fromJson(standard.getContent(), objectMapper);
MindMapNode recallRoot = MindMapTreeTool.parseOutline(recallOutline);
// 2b. 若指定了会话维度,过滤标准导图只保留该会话相关节点
if (sessionNum != null && !sessionNum.isBlank()) {
standardRoot = MindMapTreeTool.filterBySession(standardRoot, sessionNum);
// 2b. 若指定了起点节点路径,提取该子树作为对比基准
MindMapNode compareRoot = standardRoot;
if (focusPath != null && !focusPath.isBlank()) {
compareRoot = MindMapTreeTool.extractSubtree(standardRoot, focusPath);
if (compareRoot == null) {
log.warn("focusPath 未匹配到节点,使用全量标准导图: path={}", focusPath);
compareRoot = standardRoot;
}
}
// 3. 执行对比(优先 AI 语义对比,失败时降级为内置算法)
@@ -176,19 +181,19 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
.getTaskName();
var optJson = aiServiceClient.compareRecall(
taskName,
MindMapTreeTool.toFullOutline(standardRoot),
MindMapTreeTool.toFullOutline(compareRoot),
recallOutline
);
if (optJson.isPresent()) {
result = buildCompareResultFromAI(optJson.get(), standardRoot);
result = buildCompareResultFromAI(optJson.get(), compareRoot);
log.info("AI 回忆对比: taskNum={}, recallRatio={}, evaluation={}",
taskNum, result.getRecallRatio(),
result.getEvaluation() != null ? result.getEvaluation().substring(0, Math.min(50, result.getEvaluation().length())) : "");
} else {
result = compareTrees(standardRoot, recallRoot);
result = compareTrees(compareRoot, recallRoot);
}
} else {
result = compareTrees(standardRoot, recallRoot);
result = compareTrees(compareRoot, recallRoot);
}
// 4. 序列化对比结果
@@ -203,7 +208,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
ReviewRecallRecordEntity record = new ReviewRecallRecordEntity();
record.setTaskNum(taskNum);
record.setStandardMapId(standard.getId());
record.setSessionNum(sessionNum != null && !sessionNum.isBlank() ? sessionNum : null);
record.setFocusPath(focusPath != null && !focusPath.isBlank() ? focusPath : null);
record.setRecallContent(recallOutline);
record.setCompareResult(resultJson);
record.setRecallRatio(result.getRecallRatio());
@@ -219,6 +224,29 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
return standard;
}
// ============ 节点查找 ============
@Override
public Map<String, Object> findNode(String taskNum, String content) throws NotFindEntitiesException, OperationFailedException {
ensureTaskExists(taskNum);
ReviewStandardMindMapEntity standard = getOrGenerate(taskNum);
MindMapNode root = MindMapTreeTool.fromJson(standard.getContent(), objectMapper);
MindMapNode closest = MindMapTreeTool.findClosestNode(root, content);
Map<String, Object> result = new LinkedHashMap<>();
if (closest != null) {
String path = MindMapTreeTool.getPath(root, closest.getTitle());
result.put("path", path);
result.put("nodeTitle", closest.getTitle());
result.put("score", MindMapTreeTool.similarityScore(closest.getTitle(), content));
} else {
result.put("path", "");
result.put("nodeTitle", "");
result.put("score", 0.0);
}
return result;
}
// ============ 对比算法核心 ============
/**