1
0

4 Revīzijas fb02e06147 ... 36e1ed78c0

Autors SHA1 Ziņojums Datums
  zsh 36e1ed78c0 解决合并冲突:删除Python缓存文件 2 nedēļas atpakaļ
  zsh 38a925f4e5 fix: 解决缓存文件冲突,更新gitignore 2 nedēļas atpakaļ
  zsh 82830f10bb 完成报告生成流程 2 nedēļas atpakaļ
  zsh 1732cfb840 完成报告生成流程 2 nedēļas atpakaļ
100 mainītis faili ar 856 papildinājumiem un 8424 dzēšanām
  1. 4 0
      .gitignore
  2. 4 1
      algo/src/finrep_algo_agent.egg-info/SOURCES.txt
  3. BIN
      algo/src/finrep_algo_agent/__pycache__/__init__.cpython-312.pyc
  4. BIN
      algo/src/finrep_algo_agent/__pycache__/main.cpython-312.pyc
  5. BIN
      algo/src/finrep_algo_agent/api/__pycache__/__init__.cpython-312.pyc
  6. BIN
      algo/src/finrep_algo_agent/api/__pycache__/deps.cpython-312.pyc
  7. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/__init__.cpython-312.pyc
  8. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/debug.cpython-312.pyc
  9. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/health.cpython-312.pyc
  10. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/outline.cpython-312.pyc
  11. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/rag.cpython-312.pyc
  12. BIN
      algo/src/finrep_algo_agent/api/routers/__pycache__/section.cpython-312.pyc
  13. BIN
      algo/src/finrep_algo_agent/config/__pycache__/__init__.cpython-312.pyc
  14. BIN
      algo/src/finrep_algo_agent/config/__pycache__/settings.cpython-312.pyc
  15. BIN
      algo/src/finrep_algo_agent/llm/__pycache__/__init__.cpython-312.pyc
  16. BIN
      algo/src/finrep_algo_agent/llm/__pycache__/client.cpython-312.pyc
  17. BIN
      algo/src/finrep_algo_agent/llm/client/__pycache__/client.cpython-312.pyc
  18. BIN
      algo/src/finrep_algo_agent/prompts/__pycache__/__init__.cpython-312.pyc
  19. BIN
      algo/src/finrep_algo_agent/prompts/__pycache__/builders.cpython-312.pyc
  20. BIN
      algo/src/finrep_algo_agent/prompts/builders/__pycache__/__init__.cpython-312.pyc
  21. BIN
      algo/src/finrep_algo_agent/rag/__pycache__/__init__.cpython-312.pyc
  22. BIN
      algo/src/finrep_algo_agent/rag/ingestion/__pycache__/__init__.cpython-312.pyc
  23. BIN
      algo/src/finrep_algo_agent/rag/ingestion/__pycache__/chunking.cpython-312.pyc
  24. BIN
      algo/src/finrep_algo_agent/rag/ingestion/__pycache__/file_extract.cpython-312.pyc
  25. BIN
      algo/src/finrep_algo_agent/rag/retrieval/__pycache__/__init__.cpython-312.pyc
  26. BIN
      algo/src/finrep_algo_agent/rag/retrieval/__pycache__/formatting.cpython-312.pyc
  27. BIN
      algo/src/finrep_algo_agent/rag/retrieval/__pycache__/similarity.cpython-312.pyc
  28. BIN
      algo/src/finrep_algo_agent/rag/vectorstore/__pycache__/__init__.cpython-312.pyc
  29. BIN
      algo/src/finrep_algo_agent/rag/vectorstore/__pycache__/store.cpython-312.pyc
  30. BIN
      algo/src/finrep_algo_agent/schemas/__pycache__/__init__.cpython-312.pyc
  31. BIN
      algo/src/finrep_algo_agent/schemas/__pycache__/outline.cpython-312.pyc
  32. BIN
      algo/src/finrep_algo_agent/schemas/__pycache__/rag.cpython-312.pyc
  33. BIN
      algo/src/finrep_algo_agent/schemas/__pycache__/section.cpython-312.pyc
  34. BIN
      algo/src/finrep_algo_agent/skills/__pycache__/__init__.cpython-312.pyc
  35. BIN
      algo/src/finrep_algo_agent/skills/__pycache__/outline_l1.cpython-312.pyc
  36. BIN
      algo/src/finrep_algo_agent/skills/__pycache__/outline_l2.cpython-312.pyc
  37. BIN
      algo/src/finrep_algo_agent/skills/__pycache__/section_gen.cpython-312.pyc
  38. BIN
      algo/src/finrep_algo_agent/skills/outline_l1/__pycache__/__init__.cpython-312.pyc
  39. BIN
      algo/src/finrep_algo_agent/skills/outline_l1/__pycache__/outline_l1.cpython-312.pyc
  40. BIN
      algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/__init__.cpython-312.pyc
  41. BIN
      algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/batch.cpython-312.pyc
  42. BIN
      algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/outline_l2.cpython-312.pyc
  43. BIN
      algo/src/finrep_algo_agent/skills/rag_retrieve/__pycache__/__init__.cpython-312.pyc
  44. BIN
      algo/src/finrep_algo_agent/skills/rag_retrieve/__pycache__/rag_service.cpython-312.pyc
  45. BIN
      algo/src/finrep_algo_agent/skills/section_gen/__pycache__/__init__.cpython-312.pyc
  46. BIN
      algo/src/finrep_algo_agent/skills/section_gen/__pycache__/section_gen.cpython-312.pyc
  47. BIN
      algo/tests/__pycache__/__init__.cpython-312.pyc
  48. BIN
      algo/tests/__pycache__/test_health.cpython-312-pytest-9.0.2.pyc
  49. BIN
      algo/tests/__pycache__/test_outline_l2_batch.cpython-312-pytest-9.0.2.pyc
  50. BIN
      algo/tests/__pycache__/test_outline_l2_s2_skip_llm.cpython-312-pytest-9.0.2.pyc
  51. BIN
      algo/tests/__pycache__/test_prompts.cpython-312-pytest-9.0.2.pyc
  52. BIN
      algo/tests/__pycache__/test_rag.cpython-312-pytest-9.0.2.pyc
  53. BIN
      algo/tests/__pycache__/test_section_rag.cpython-312-pytest-9.0.2.pyc
  54. BIN
      backend/.DS_Store
  55. BIN
      backend/finrep-application/.DS_Store
  56. BIN
      backend/finrep-application/src/.DS_Store
  57. BIN
      backend/finrep-application/src/main/.DS_Store
  58. BIN
      backend/finrep-application/src/main/java/.DS_Store
  59. BIN
      backend/finrep-application/src/main/java/com/.DS_Store
  60. BIN
      backend/finrep-application/src/main/java/com/yuxin/.DS_Store
  61. BIN
      backend/finrep-application/src/main/java/com/yuxin/finrep/.DS_Store
  62. BIN
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/.DS_Store
  63. 4 4
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/DataPreparationOrchestrator.java
  64. 57 77
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/OutlineGenerationOrchestrator.java
  65. 350 26
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/ReportGenerationOrchestrator.java
  66. 4 3
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/port/in/DataPackageApplicationService.java
  67. 1 1
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/port/in/ReportApplicationService.java
  68. 32 26
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/DataPackageApplicationServiceImpl.java
  69. 62 4
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/OutlineApplicationServiceImpl.java
  70. 1 2
      backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/ReportApplicationServiceImpl.java
  71. 1 13
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/dataprep/DataPackage.java
  72. 10 2
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/generation/Report.java
  73. 10 0
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/knowledge/KnowledgeType.java
  74. 5 0
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/knowledge/KnowledgeUnit.java
  75. 38 0
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/outline/Outline.java
  76. 22 0
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/KnowledgeUnitRepositoryPort.java
  77. 2 0
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/OutlineRepositoryPort.java
  78. 1 1
      backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/PythonAgentClientPort.java
  79. 1 6
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/converter/KnowledgeUnitConverter.java
  80. 1 2
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/converter/ReportConverter.java
  81. 8 5
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/integration/pythonagent/PythonAgentClientImpl.java
  82. 0 12
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/DataPackageEntity.java
  83. 5 54
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/KnowledgeUnitEntity.java
  84. 2 8
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/ReportEntity.java
  85. 48 0
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/repository/KnowledgeUnitRepositoryImpl.java
  86. 4 4
      backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/repository/OutlineRepositoryImpl.java
  87. 8 10
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/DataPackageController.java
  88. 24 328
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/OutlineController.java
  89. 1 4
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/ReportController.java
  90. 0 1
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/TaskController.java
  91. 0 12
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/DataPackageRequest.java
  92. 0 10
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/DataPackageResponse.java
  93. 141 0
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/Level2ChapterSection.java
  94. 5 0
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/OutlineResponse.java
  95. 0 6
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/ReportRequest.java
  96. 0 5
      backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/ReportResponse.java
  97. BIN
      logs/finrep-web-all.2026-04-02.0.gz
  98. 0 4737
      logs/finrep-web-all.log
  99. BIN
      logs/finrep-web-error.2026-04-02.0.gz
  100. 0 3060
      logs/finrep-web-error.log

+ 4 - 0
.gitignore

@@ -3,3 +3,7 @@
 algo/.venv/
 
 **/target/
+/logs
+algo/py312
+__pycache__/
+*.pyc

+ 4 - 1
algo/src/finrep_algo_agent.egg-info/SOURCES.txt

@@ -20,7 +20,7 @@ src/finrep_algo_agent/config/settings.py
 src/finrep_algo_agent/llm/__init__.py
 src/finrep_algo_agent/llm/client/client.py
 src/finrep_algo_agent/prompts/__init__.py
-src/finrep_algo_agent/prompts/builders.py
+src/finrep_algo_agent/prompts/builders/__init__.py
 src/finrep_algo_agent/prompts/templates/outline_l1.j2
 src/finrep_algo_agent/prompts/templates/outline_l2.j2
 src/finrep_algo_agent/prompts/templates/section_analysis.j2
@@ -45,12 +45,15 @@ src/finrep_algo_agent/skills/__init__.py
 src/finrep_algo_agent/skills/outline_l1/__init__.py
 src/finrep_algo_agent/skills/outline_l1/outline_l1.py
 src/finrep_algo_agent/skills/outline_l2/__init__.py
+src/finrep_algo_agent/skills/outline_l2/batch.py
 src/finrep_algo_agent/skills/outline_l2/outline_l2.py
 src/finrep_algo_agent/skills/rag_retrieve/__init__.py
 src/finrep_algo_agent/skills/rag_retrieve/rag_service.py
 src/finrep_algo_agent/skills/section_gen/__init__.py
 src/finrep_algo_agent/skills/section_gen/section_gen.py
 tests/test_health.py
+tests/test_outline_l2_batch.py
+tests/test_outline_l2_s2_skip_llm.py
 tests/test_prompts.py
 tests/test_rag.py
 tests/test_section_rag.py

BIN
algo/src/finrep_algo_agent/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/__pycache__/main.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/__pycache__/deps.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/debug.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/health.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/outline.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/rag.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/api/routers/__pycache__/section.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/config/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/config/__pycache__/settings.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/llm/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/llm/__pycache__/client.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/llm/client/__pycache__/client.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/prompts/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/prompts/__pycache__/builders.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/prompts/builders/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/ingestion/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/ingestion/__pycache__/chunking.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/ingestion/__pycache__/file_extract.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/retrieval/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/retrieval/__pycache__/formatting.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/retrieval/__pycache__/similarity.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/vectorstore/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/rag/vectorstore/__pycache__/store.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/schemas/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/schemas/__pycache__/outline.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/schemas/__pycache__/rag.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/schemas/__pycache__/section.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/__pycache__/outline_l1.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/__pycache__/outline_l2.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/__pycache__/section_gen.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/outline_l1/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/outline_l1/__pycache__/outline_l1.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/batch.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/outline_l2/__pycache__/outline_l2.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/rag_retrieve/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/rag_retrieve/__pycache__/rag_service.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/section_gen/__pycache__/__init__.cpython-312.pyc


BIN
algo/src/finrep_algo_agent/skills/section_gen/__pycache__/section_gen.cpython-312.pyc


BIN
algo/tests/__pycache__/__init__.cpython-312.pyc


BIN
algo/tests/__pycache__/test_health.cpython-312-pytest-9.0.2.pyc


BIN
algo/tests/__pycache__/test_outline_l2_batch.cpython-312-pytest-9.0.2.pyc


BIN
algo/tests/__pycache__/test_outline_l2_s2_skip_llm.cpython-312-pytest-9.0.2.pyc


BIN
algo/tests/__pycache__/test_prompts.cpython-312-pytest-9.0.2.pyc


BIN
algo/tests/__pycache__/test_rag.cpython-312-pytest-9.0.2.pyc


BIN
algo/tests/__pycache__/test_section_rag.cpython-312-pytest-9.0.2.pyc


BIN
backend/.DS_Store


BIN
backend/finrep-application/.DS_Store


BIN
backend/finrep-application/src/.DS_Store


BIN
backend/finrep-application/src/main/.DS_Store


BIN
backend/finrep-application/src/main/java/.DS_Store


BIN
backend/finrep-application/src/main/java/com/.DS_Store


BIN
backend/finrep-application/src/main/java/com/yuxin/.DS_Store


BIN
backend/finrep-application/src/main/java/com/yuxin/finrep/.DS_Store


BIN
backend/finrep-application/src/main/java/com/yuxin/finrep/application/.DS_Store


+ 4 - 4
backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/DataPreparationOrchestrator.java

@@ -85,7 +85,7 @@ public class DataPreparationOrchestrator {
                                 taskId,
                                 task.getTenantId(),
                                 knowledgeUnitId,
-                                knowledgeUnitName,
+
                                 autoData
                         );
 
@@ -145,9 +145,9 @@ public class DataPreparationOrchestrator {
             // 获取任务的所有数据包
             List<DataPackage> dataPackages = dataPackageRepositoryPort.findByTaskId(taskId);
 
-            if (dataPackages.isEmpty()) {
-                throw new BusinessException(ErrorCode.DATA_PREPARE_FAILED, "没有找到数据包");
-            }
+//            if (dataPackages.isEmpty()) {
+//                throw new BusinessException(ErrorCode.DATA_PREPARE_FAILED, "没有找到数据包");
+//            }
 
             // 检查是否所有数据包都已确认
             boolean allConfirmed = dataPackages.stream().allMatch(DataPackage::isConfirmed);

+ 57 - 77
backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/OutlineGenerationOrchestrator.java

@@ -1,5 +1,6 @@
 package com.yuxin.finrep.application.orchestration;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.yuxin.finrep.domain.port.out.OutlineRepositoryPort;
@@ -123,78 +124,97 @@ public class OutlineGenerationOrchestrator {
 
     /**
      * 生成二级大纲
+     * 一次性生成所有章节的二级大纲
      *
      * @param taskId 任务ID
      * @param level1OutlineId 一级大纲ID
-     * @param parameters 额外参数(应包含 chapter_name, chapter_no 等必填字段)
+     * @param parameters 额外参数(应包含 l1_response, l1_task_snapshot 等字段)
      */
-    public void generateLevel2Outline(String taskId, String level1OutlineId, Map<String, Object> parameters) {
+    public Outline   generateLevel2Outline(String taskId, String level1OutlineId, Map<String, Object> parameters) {
         log.info("开始生成二级大纲: taskId={}, level1OutlineId={}", taskId, level1OutlineId);
 
         Task task = taskRepositoryPort.findById(taskId)
                 .orElseThrow(() -> new BusinessException(ErrorCode.TASK_NOT_FOUND));
 
-        // 获取一级大纲(用于后续可能的扩展)
-        outlineRepositoryPort.findById(level1OutlineId)
+        Outline level1Outline = outlineRepositoryPort.findById(level1OutlineId)
                 .orElseThrow(() -> new BusinessException(ErrorCode.OUTLINE_NOT_FOUND));
 
         try {
-            // 调用Python Agent生成二级大纲
-            // API 请求参数: chapter_name(必填), chapter_no(必填), l1_chapter_id,
-            // chapter_paragraph_count_enum, chapter_presentation_enum, chapter_reason, overall_logic,
-            // leaf_chapter_candidates, l1_task_snapshot, report_type, l1_context 等
+            // 调用Python Agent生成二级大纲(一次性生成所有章节)
+            // API 请求参数: l1_task_snapshot, l1_response, leaf_chapter_candidates_by_chapter_id, report_type 等
             JSONObject response = pythonAgentClientPort.generateLevel2Outline(
                     taskId,
                     task.getTenantId(),
                     level1OutlineId,
-                    parameters
+                    parameters != null ? parameters : Collections.emptyMap()
             );
 
-            // 解析响应
-            // API 响应结构: { chapter_name, chapter_no, chapter_structure: [...], structure_logic: "..." }
-            // chapter_structure 每项: { node_id, node_name, node_no, node_level, parent_node_id,
-            //                            source_type, source_candidate_name, is_selected }
             if (response == null) {
                 String errorMsg = "Python Agent返回空响应";
                 throw new BusinessException(ErrorCode.OUTLINE_GENERATE_FAILED, errorMsg);
             }
 
-            // 验证章节名称是否匹配请求
-            String requestChapterName = parameters != null ? (String) parameters.get("chapter_name") : null;
-            String responseChapterName = response.getString("chapter_name");
-            if (requestChapterName != null && !requestChapterName.equals(responseChapterName)) {
-                log.warn("二级大纲响应的 chapter_name 与请求不匹配: request={}, response={}",
-                        requestChapterName, responseChapterName);
-            }
-
-            // 解析章节结构
-            com.alibaba.fastjson2.JSONArray chapterStructure = response.getJSONArray("chapter_structure");
-            List<OutlineNode> nodes = parseChapterStructure(chapterStructure);
-
-            // 获取结构逻辑
-            String structureLogic = response.getString("structure_logic");
+            // 解析响应:API返回所有章节的二级大纲结构
+            // 响应结构: { overall_logic: "...", chapters: [{ chapter_id, chapter_name, presentation_enum, paragraph_count_enum, reason, l2: {...} }] }
             String rawContent = response.toJSONString();
 
-            // 创建二级大纲
+            // 从响应中获取 overall_logic 和 chapters
+            String overallLogic = response.getString("overall_logic");
+            com.alibaba.fastjson2.JSONArray chapters = response.getJSONArray("chapters");
+            if (chapters == null || chapters.isEmpty()) {
+                log.warn("二级大纲没有章节,直接更新任务状态");
+                task.updateStatus(TaskStatus.PENDING_DATA_PREPARE, "data_prepare");
+                task.updateProgress(40);
+                taskRepositoryPort.save(task);
+                return new Outline();
+            }
+
+            // 合并所有章节的节点
+//            List<OutlineNode> allNodes = new ArrayList<>();
+//            for (int i = 0; i < chapters.size(); i++) {
+//                com.alibaba.fastjson2.JSONObject chapterData = chapters.getJSONObject(i);
+//                String chapterName = chapterData.getString("chapter_name");
+//                String presentationEnum = chapterData.getString("presentation_enum");
+//
+//                // 如果 presentation_enum 为 S2(不呈现),则跳过该章节
+//                if ("S2".equals(presentationEnum)) {
+//                    log.info("跳过不呈现章节: chapter_name={}", chapterName);
+//                    continue;
+//                }
+//
+//                // 从 l2 字段获取二级大纲数据
+//                com.alibaba.fastjson2.JSONObject l2Data = chapterData.getJSONObject("l2");
+//                if (l2Data == null) {
+//                    log.warn("章节 {} 没有l2数据,跳过", chapterName);
+//                    continue;
+//                }
+//
+//                // 解析章节结构
+//                com.alibaba.fastjson2.JSONArray chapterStructure = l2Data.getJSONArray("chapter_structure");
+//                List<OutlineNode> nodes = parseChapterStructure(chapterStructure);
+//                allNodes.addAll(nodes);
+//            }
+            String chapters1 = response.getString("chapters");
+            // 创建二级大纲(保存所有章节为一个大纲)
             Outline outline = Outline.builder()
                     .outlineId(java.util.UUID.randomUUID().toString().replace("-", ""))
                     .taskId(taskId)
                     .tenantId(task.getTenantId())
-                    .title(responseChapterName)
+                    .title(task.getTaskName() + "二级大纲")
                     .type(OutlineType.LEVEL2)
                     .rawContent(rawContent)
-                    .writingLogic(structureLogic)
+                    .sections(response.getString("chapters"))
+                    .writingLogic(overallLogic)
                     .confirmed(false)
                     .version(1)
                     .createTime(java.time.LocalDateTime.now())
                     .updateTime(java.time.LocalDateTime.now())
                     .build();
 
-            // 设置根节点(将节点列表转换为树形结构)
-            if (!nodes.isEmpty()) {
-                OutlineNode rootNode = buildOutlineTree(nodes);
-                outline.setRootNode(rootNode);
-            }
+            // 设置节点列表(扁平结构)
+//            if (!allNodes.isEmpty()) {
+//                outline.setNodeList(allNodes);
+//            }
 
             outlineRepositoryPort.save(outline);
 
@@ -203,8 +223,8 @@ public class OutlineGenerationOrchestrator {
             task.updateProgress(40);
             taskRepositoryPort.save(task);
 
-            log.info("二级大纲生成成: taskId={}, outlineId={}", taskId, outline.getOutlineId());
-
+            log.info("二级大纲生成成: taskId={}, outlineId={}", taskId, outline.getOutlineId());
+            return outline;
         } catch (BusinessException e) {
             log.error("二级大纲生成失败: taskId={}, error={}", taskId, e.getMessage());
             taskOrchestrator.failTask(taskId, e.getMessage());
@@ -301,44 +321,4 @@ public class OutlineGenerationOrchestrator {
         }
         return result;
     }
-
-    /**
-     * 将节点列表构建为树形结构
-     * 返回根节点(level 最小的节点)
-     */
-    private OutlineNode buildOutlineTree(List<OutlineNode> nodes) {
-        if (nodes == null || nodes.isEmpty()) {
-            return null;
-        }
-
-        // 创建节点ID到节点的映射
-        java.util.Map<String, OutlineNode> nodeMap = new java.util.HashMap<>();
-        OutlineNode rootNode = null;
-
-        // 第一次遍历:构建映射
-        for (OutlineNode node : nodes) {
-            nodeMap.put(node.getNodeId(), node);
-            // 假设第一个节点为根节点
-            if (rootNode == null) {
-                rootNode = node;
-            }
-        }
-
-        // 第二次遍历:构建父子关系
-        for (OutlineNode node : nodes) {
-            String parentId = node.getParentId();
-            if (parentId != null && !parentId.isEmpty()) {
-                OutlineNode parent = nodeMap.get(parentId);
-                if (parent != null) {
-                    parent.addChild(node);
-                    // 有父节点的节点不可能是根节点
-                    if (rootNode == node) {
-                        rootNode = null;
-                    }
-                }
-            }
-        }
-
-        return rootNode != null ? rootNode : nodes.get(0);
-    }
 }

+ 350 - 26
backend/finrep-application/src/main/java/com/yuxin/finrep/application/orchestration/ReportGenerationOrchestrator.java

@@ -1,7 +1,10 @@
 package com.yuxin.finrep.application.orchestration;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+import com.yuxin.finrep.domain.knowledge.KnowledgeUnit;
 import com.yuxin.finrep.domain.port.out.DataPackageRepositoryPort;
+import com.yuxin.finrep.domain.port.out.KnowledgeUnitRepositoryPort;
 import com.yuxin.finrep.domain.port.out.OutlineRepositoryPort;
 import com.yuxin.finrep.domain.port.out.PythonAgentClientPort;
 import com.yuxin.finrep.domain.port.out.ReportRepositoryPort;
@@ -12,6 +15,7 @@ import com.yuxin.finrep.common.util.JsonUtil;
 import com.yuxin.finrep.domain.dataprep.DataPackage;
 import com.yuxin.finrep.domain.generation.Report;
 import com.yuxin.finrep.domain.outline.Outline;
+import com.yuxin.finrep.domain.outline.OutlineNode;
 import com.yuxin.finrep.domain.outline.OutlineSection;
 import com.yuxin.finrep.domain.task.Task;
 import com.yuxin.finrep.domain.task.TaskStatus;
@@ -38,24 +42,23 @@ public class ReportGenerationOrchestrator {
     private final ReportRepositoryPort reportRepositoryPort;
     private final PythonAgentClientPort pythonAgentClientPort;
     private final TaskOrchestrator taskOrchestrator;
+    private final KnowledgeUnitRepositoryPort knowledgeUnitRepositoryPort;
 
     private final ExecutorService executorService = Executors.newFixedThreadPool(5);
 
-    public void generateReport(String taskId, Map<String, Object> parameters) {
+    public Report generateReport(String taskId, Map<String, Object> parameters) {
         log.info("开始生成报告: taskId={}", taskId);
 
         Task task = taskRepositoryPort.findById(taskId)
                 .orElseThrow(() -> new BusinessException(ErrorCode.TASK_NOT_FOUND));
 
         try {
-            Outline outline = outlineRepositoryPort.findByTaskId(taskId)
-                    .orElseThrow(() -> new BusinessException(ErrorCode.OUTLINE_NOT_FOUND));
-
+            Outline outline = outlineRepositoryPort.findByTaskIdAndType(taskId,"LEVEL2");
             List<DataPackage> dataPackages = dataPackageRepositoryPort.findByTaskId(taskId);
 
-            if (dataPackages.isEmpty()) {
-                throw new BusinessException(ErrorCode.DATA_PREPARE_FAILED, "没有找到数据包");
-            }
+//            if (dataPackages.isEmpty()) {
+//                throw new BusinessException(ErrorCode.DATA_PREPARE_FAILED, "没有找到数据包");
+//            }
 
             Report report = Report.create(
                     taskId,
@@ -81,6 +84,7 @@ public class ReportGenerationOrchestrator {
             taskRepositoryPort.save(task);
 
             log.info("报告生成成功: taskId={}, reportId={}", taskId, report.getReportId());
+            return report;
 
         } catch (BusinessException e) {
             log.error("报告生成失败: taskId={}, error={}", taskId, e.getMessage());
@@ -93,39 +97,142 @@ public class ReportGenerationOrchestrator {
         }
     }
 
+    /**
+     * 组装完整文章:将生成的段落与二级大纲结构结合
+     * @param report 生成的报告
+     * @param outline 二级大纲
+     * @return 完整文章内容(JSON格式)
+     */
+    public String assembleFullArticle(Report report, Outline outline) {
+        log.info("开始组装完整文章: reportId={}, outlineId={}", report.getReportId(), outline.getOutlineId());
+
+        try {
+            String sectionsJson = outline.getSections();
+            if (sectionsJson == null || sectionsJson.isEmpty()) {
+                log.warn("二级大纲sections为空");
+                return "[]";
+            }
+
+            com.alibaba.fastjson2.JSONArray sectionsArray = JSON.parseArray(sectionsJson);
+            if (sectionsArray == null || sectionsArray.isEmpty()) {
+                log.warn("二级大纲sections解析为空数组");
+                return "[]";
+            }
+
+            // 创建node_id到段落内容的映射
+            Map<String, String> contentMap = new java.util.HashMap<>();
+            if (report.getSections() != null) {
+                for (Report.Section section : report.getSections()) {
+                    if (section.getContent() != null) {
+                        contentMap.put(section.getSectionId(), section.getContent());
+                    }
+                }
+            }
+
+            // 遍历二级大纲结构,组装文章
+            for (int i = 0; i < sectionsArray.size(); i++) {
+                com.alibaba.fastjson2.JSONObject  chapter = sectionsArray.getJSONObject(i);
+                if (chapter == null) {
+                    continue;
+                }
+
+                // 移除chapter级别的无用字段
+                chapter.remove("presentation_enum");
+                chapter.remove("paragraph_count_enum");
+                chapter.remove("reason");
+
+                // 获取l2字段
+                com.alibaba.fastjson2.JSONObject l2 = chapter.getJSONObject("l2");
+                if (l2 == null) {
+                    continue;
+                }
+                l2.remove("structure_logic");
+                // 获取chapter_structure
+                com.alibaba.fastjson2.JSONArray chapterStructure = l2.getJSONArray("chapter_structure");
+                if (chapterStructure == null || chapterStructure.isEmpty()) {
+                    continue;
+                }
+
+                // 遍历章节结构,添加生成的段落内容
+                for (int j = 0; j < chapterStructure.size(); j++) {
+                    com.alibaba.fastjson2.JSONObject node = chapterStructure.getJSONObject(j);
+                    if (node == null) {
+                        continue;
+                    }
+
+                    String nodeId = node.getString("node_id");
+                    if (nodeId != null && !nodeId.isEmpty()) {
+                        // 添加生成的段落内容到节点
+                        String content = contentMap.get(nodeId);
+                        node.put("content", content != null ? content : "");
+                    }
+
+                    // 移除node级别的无用字段
+                    node.remove("node_level");
+                    node.remove("node_id");
+                    node.remove("source_type");
+                    node.remove("is_selected");
+                    node.remove("source_candidate_name");
+                }
+            }
+
+            log.info("完整文章组装完成: reportId={}", report.getReportId());
+            return sectionsArray.toJSONString();
+        } catch (Exception ekt) {
+            log.error("组装完整文章失败: {}", ekt.getMessage());
+            return "[]";
+        }
+    }
+
     private void generateReportSections(Task task, Outline outline, List<DataPackage> dataPackages,
                                         Report report, Map<String, Object> parameters) {
         log.info("开始生成报告段落: taskId={}, outlineId={}", task.getTaskId(), outline.getOutlineId());
 
         List<Report.Section> sections = new ArrayList<>();
         List<CompletableFuture<Report.Section>> futures = new ArrayList<>();
+        String writingLogic = outline.getWritingLogic();
 
-        List<OutlineSection> outlineSections = outline.getSectionList();
+        // 从二级大纲的sections中提取node_id与node_name的映射
+        Map<String, String> nodeNameMap = extractNodeNameMappingFromLevel2Outline(outline);
+        log.info("找到待生成段落的节点数量: {}", nodeNameMap.size());
 
-        for (OutlineSection outlineSection : outlineSections) {
-            String knowledgeUnitId = outlineSection.getKnowledgeUnitId();
+        for (Map.Entry<String, String> entry : nodeNameMap.entrySet()) {
+            String nodeId = entry.getKey();
+            String nodeName = entry.getValue();
 
-            if (knowledgeUnitId == null) {
-                log.warn("章节缺少知识单元ID: sectionId={}", outlineSection.getSectionId());
+            // 根据node_id获取知识单元
+            KnowledgeUnit knowledgeUnit = knowledgeUnitRepositoryPort.findById(nodeId)
+                    .orElse(null);
+
+            if (knowledgeUnit == null) {
+                log.warn("未找到知识单元: nodeId={}", nodeId);
                 continue;
             }
 
+            // 查找对应的数据包
             DataPackage dataPackage = dataPackages.stream()
-                    .filter(dp -> dp.getKnowledgeUnitId().equals(knowledgeUnitId))
+                    .filter(dp -> dp.getKnowledgeUnitId().equals(nodeId))
                     .findFirst()
                     .orElse(null);
 
             if (dataPackage == null) {
-                log.warn("未找到知识单元对应的数据包: knowledgeUnitId={}", knowledgeUnitId);
-                continue;
+                log.warn("未找到知识单元对应的数据包: knowledgeUnitId={}", nodeId);
+                // 即使没有数据包,也可以生成段落(使用空数据)
+                dataPackage = createEmptyDataPackage(nodeId);
             }
 
+            final String finalNodeId = nodeId;
+            final String finalNodeName = nodeName;
+            final DataPackage finalDataPackage = dataPackage;
+            final KnowledgeUnit finalKnowledgeUnit = knowledgeUnit;
+
             CompletableFuture<Report.Section> future = CompletableFuture.supplyAsync(() -> {
                 try {
-                    return generateSection(task, outlineSection, dataPackage, parameters);
+                    return generateSectionFromKnowledgeUnit(task, finalNodeId, finalNodeName, finalKnowledgeUnit,
+                            finalDataPackage, parameters, writingLogic);
                 } catch (Exception e) {
-                    log.error("生成段落异常: sectionId={}, error={}",
-                              outlineSection.getSectionId(), e.getMessage(), e);
+                    log.error("生成段落异常: nodeId={}, error={}",
+                              finalNodeId, e.getMessage(), e);
                     throw new BusinessException(ErrorCode.SECTION_GENERATE_FAILED, e);
                 }
             }, executorService);
@@ -142,7 +249,7 @@ public class ReportGenerationOrchestrator {
                     sections.add(section);
                     report.addSection(section);
 
-                    int progress = 75 + (int) (25.0 * sections.size() / outlineSections.size());
+                    int progress = 75 + (int) (25.0 * sections.size() / nodeNameMap.size());
                     task.updateProgress(Math.min(progress, 95));
                     taskRepositoryPort.save(task);
                 }
@@ -154,16 +261,31 @@ public class ReportGenerationOrchestrator {
         log.info("报告段落生成完成: taskId={}, sectionCount={}", task.getTaskId(), sections.size());
     }
 
-    private Report.Section generateSection(Task task, OutlineSection outlineSection,
-                                          DataPackage dataPackage, Map<String, Object> parameters) {
-        log.debug("生成段落: taskId={}, sectionId={}", task.getTaskId(), outlineSection.getSectionId());
+
+
+    private Report.Section generateSection(Task task, OutlineNode outlineNode,
+                                          DataPackage dataPackage, Map<String, Object> parameters, String writingLogic) {
+        log.debug("生成段落: taskId={}, nodeId={}", task.getTaskId(), outlineNode.getNodeId());
+
+        // 根据知识单元ID查询知识单元
+        KnowledgeUnit knowledgeUnit = knowledgeUnitRepositoryPort.findById(dataPackage.getKnowledgeUnitId())
+                .orElseThrow(() -> new BusinessException(ErrorCode.SECTION_GENERATE_FAILED,
+                        "知识单元不存在: " + dataPackage.getKnowledgeUnitId()));
+        // 获取知识单元的template
+        String template = knowledgeUnit.getTemplate();
+        if (template == null || template.isEmpty()) {
+            throw new BusinessException(ErrorCode.SECTION_GENERATE_FAILED,
+                    "知识单元模板为空: " + dataPackage.getKnowledgeUnitId());
+        }
 
         JSONObject response = pythonAgentClientPort.generateSection(
                 task.getTaskId(),
                 task.getTenantId(),
                 dataPackage.getKnowledgeUnitId(),
                 mergeData(dataPackage),
-                parameters
+                template,
+                parameters,
+                writingLogic
         );
 
         if (response == null || !response.containsKey("success") || !response.getBoolean("success")) {
@@ -172,9 +294,9 @@ public class ReportGenerationOrchestrator {
         }
 
         Report.Section section = new Report.Section();
-        section.setSectionId(outlineSection.getSectionId());
-        section.setSectionNumber(outlineSection.getSortOrder() != null ? String.valueOf(outlineSection.getSortOrder()) : "1");
-        section.setTitle(outlineSection.getTitle());
+        section.setSectionId(outlineNode.getNodeId());
+        section.setSectionNumber(outlineNode.getChapterNumber() != null ? outlineNode.getChapterNumber() : "1");
+        section.setTitle(outlineNode.getTitle());
         section.setContent(response.getString("content"));
         section.setKnowledgeUnitId(dataPackage.getKnowledgeUnitId());
         section.setCreateTime(LocalDateTime.now());
@@ -285,4 +407,206 @@ public class ReportGenerationOrchestrator {
         }
         return result;
     }
+
+    /**
+     * 从二级大纲中提取node_id列表
+     * 二级大纲的sections结构示例:
+     * [
+     *   {
+     *     "chapter_id": "pf-l1-01",
+     *     "chapter_name": "企业基本情况分析",
+     *     "l2": {
+     *       "chapter_structure": [
+     *         {"node_id": "pf-01-ku-02", "node_name": "企业基本情况", ...}
+     *       ]
+     *     }
+'     *   }
+     * ]
+     */
+    private List<String> extractNodeIdsFromLevel2Outline(Outline outline) {
+        List<String> nodeIds = new ArrayList<>();
+        try {
+            String sectionsJson = outline.getSections();
+            if (sectionsJson == null || sectionsJson.isEmpty()) {
+                log.warn("二级大纲sections为空");
+                return nodeIds;
+            }
+
+            com.alibaba.fastjson2.JSONArray sectionsArray = JSON.parseArray(sectionsJson);
+            if (sectionsArray == null || sectionsArray.isEmpty()) {
+                log.warn("二级大纲sections解析为空数组");
+                return nodeIds;
+            }
+
+            for (int i = 0; i < sectionsArray.size(); i++) {
+                com.alibaba.fastjson2.JSONObject chapter = sectionsArray.getJSONObject(i);
+                if (chapter == null) {
+                    continue;
+                }
+
+                // 获取l2字段
+                com.alibaba.fastjson2.JSONObject l2 = chapter.getJSONObject("l2");
+                if (l2 == null) {
+                    continue;
+                }
+
+                // 获取chapter_structure
+                com.alibaba.fastjson2.JSONArray chapterStructure = l2.getJSONArray("chapter_structure");
+                if (chapterStructure == null || chapterStructure.isEmpty()) {
+                    continue;
+                }
+
+                // 提取所有node_id
+                for (int j = 0; j < chapterStructure.size(); j++) {
+                    com.alibaba.fastjson2.JSONObject node = chapterStructure.getJSONObject(j);
+                    if (node == null) {
+                        continue;
+                    }
+
+                    String nodeId = node.getString("node_id");
+                    if (nodeId != null && !nodeId.isEmpty()) {
+                        nodeIds.add(nodeId);
+                    }
+                }
+            }
+
+            log.info("从二级大纲提取到{}个node_id", nodeIds.size());
+        } catch (Exception e) {
+            log.error("提取node_id失败: {}", e.getMessage(), e);
+        }
+        return nodeIds;
+    }
+
+    /**
+     * 从二级大纲中提取node_id与node_name的映射
+     * 二级大纲的sections结构示例:
+     * [
+     *   {
+     *     "chapter_id": "pf-l1-01",
+     *     "chapter_name": "企业基本情况分析",
+     *     "l2": {
+     *       "chapter_structure": [
+     *         {"node_id": "pf-01-ku-02", "node_name": "企业基本情况", ...}
+     *       ]
+     *     }
+     *   }
+     * ]
+     * @return node_id -> node_name 的映射
+     */
+    private Map<String, String> extractNodeNameMappingFromLevel2Outline(Outline outline) {
+        Map<String, String> nodeNameMap = new java.util.HashMap<>();
+        try {
+            String sectionsJson = outline.getSections();
+            if (sectionsJson == null || sectionsJson.isEmpty()) {
+                log.warn("二级大纲sections为空");
+                return nodeNameMap;
+            }
+
+            com.alibaba.fastjson2.JSONArray sectionsArray = JSON.parseArray(sectionsJson);
+            if (sectionsArray == null || sectionsArray.isEmpty()) {
+                log.warn("二级大纲sections解析为空数组");
+                return nodeNameMap;
+            }
+
+            for (int i = 0; i < sectionsArray.size(); i++) {
+                com.alibaba.fastjson2.JSONObject chapter = sectionsArray.getJSONObject(i);
+                if (chapter == null) {
+                    continue;
+                }
+
+                // 获取l2字段
+                com.alibaba.fastjson2.JSONObject l2 = chapter.getJSONObject("l2");
+                if (l2 == null) {
+                    continue;
+                }
+
+                // 获取chapter_structure
+                com.alibaba.fastjson2.JSONArray chapterStructure = l2.getJSONArray("chapter_structure");
+                if (chapterStructure == null || chapterStructure.isEmpty()) {
+                    continue;
+                }
+
+                // 提取所有node_id和node_name的映射
+                for (int j = 0; j < chapterStructure.size(); j++) {
+                    com.alibaba.fastjson2.JSONObject node = chapterStructure.getJSONObject(j);
+                    if (node == null) {
+                        continue;
+                    }
+
+                    String nodeId = node.getString("node_id");
+                    String nodeName = node.getString("node_name");
+                    if (nodeId != null && !nodeId.isEmpty()) {
+                        // 如果node_name为空,使用node_id作为默认值
+                        nodeNameMap.put(nodeId, nodeName != null && !nodeName.isEmpty() ? nodeName : nodeId);
+                    }
+                }
+            }
+
+            log.info("从二级大纲提取到{}个node_id与node_name映射", nodeNameMap.size());
+        } catch (Exception e) {
+            log.error("提取node_id与node_name映射失败: {}", e.getMessage(), e);
+        }
+        return nodeNameMap;
+    }
+
+    /**
+     * 创建空的数据包
+     */
+    private DataPackage createEmptyDataPackage(String knowledgeUnitId) {
+        DataPackage dataPackage = new DataPackage();
+        dataPackage.setKnowledgeUnitId(knowledgeUnitId);
+        dataPackage.setAutoData(new java.util.HashMap<>());
+        dataPackage.setManualData(new java.util.HashMap<>());
+        return dataPackage;
+    }
+
+    /**
+     * 根据知识单元生成段落
+     */
+    private Report.Section generateSectionFromKnowledgeUnit(Task task, String nodeId, String nodeName,
+                                                         KnowledgeUnit knowledgeUnit,
+                                                         DataPackage dataPackage,
+                                                         Map<String, Object> parameters,
+                                                         String writingLogic) {
+        log.debug("生成段落: taskId={}, nodeId={}, nodeName={}", task.getTaskId(), nodeId, nodeName);
+
+        // 获取知识单元的template
+        String template = knowledgeUnit.getTemplate();
+        if (template == null || template.isEmpty()) {
+            throw new BusinessException(ErrorCode.SECTION_GENERATE_FAILED,
+                    "知识单元模板为空: " + nodeId);
+        }
+
+        JSONObject response = pythonAgentClientPort.generateSection(
+                task.getTaskId(),
+                task.getTenantId(),
+                nodeId,
+                mergeData(dataPackage),
+                template,
+                parameters,
+                writingLogic
+        );
+
+        if (response == null ) {
+            String errorMsg = "Python Agent返回空响应";
+            throw new BusinessException(ErrorCode.SECTION_GENERATE_FAILED, errorMsg);
+        }
+
+        Report.Section section = new Report.Section();
+        section.setSectionId(nodeId);
+        // 使用l2大纲中的node_name作为段落标题,如果为空则使用知识单元名称
+        section.setTitle(nodeName != null && !nodeName.isEmpty() ? nodeName : knowledgeUnit.getName());
+        section.setContent(response.getString("generated_text"));
+        section.setKnowledgeUnitId(nodeId);
+        section.setCreateTime(LocalDateTime.now());
+        section.setUpdateTime(LocalDateTime.now());
+
+        if (response.containsKey("references")) {
+            List<String> references = parseReferences(response.getJSONArray("references"));
+            section.setReferences(references);
+        }
+
+        log.debug("段落生成成功: sectionId={}", section.getSectionId());
+        return section;
+    }
 }

+ 4 - 3
backend/finrep-application/src/main/java/com/yuxin/finrep/application/port/in/DataPackageApplicationService.java

@@ -15,8 +15,8 @@ public interface DataPackageApplicationService {
      * 创建数据包
      */
     DataPackage createDataPackage(String taskId, String tenantId, String knowledgeUnitId,
-                                  String knowledgeUnitName, Map<String, Object> autoData,
-                                  Map<String, Object> manualData, String remark);
+                                  Map<String, Object> autoData,
+                                  Map<String, Object> manualData);
 
     /**
      * 获取数据包
@@ -65,6 +65,7 @@ public interface DataPackageApplicationService {
 
     /**
      * 批量确认任务的所有数据包并生成报告
+     * @return 生成的完整文章内容
      */
-    void confirmAllDataPackagesAndGenerateReport(String taskId, String confirmerId, String confirmerName);
+    String confirmAllDataPackagesAndGenerateReport(String taskId, String confirmerId, String confirmerName);
 }

+ 1 - 1
backend/finrep-application/src/main/java/com/yuxin/finrep/application/port/in/ReportApplicationService.java

@@ -11,7 +11,7 @@ public interface ReportApplicationService {
     /**
      * 创建报告
      */
-    Report createReport(String taskId, String tenantId, String title, String summary,
+    Report createReport(String taskId, String tenantId, String title,
                        java.util.List<Report.Section> sections, String creatorId, String creatorName);
 
     /**

+ 32 - 26
backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/DataPackageApplicationServiceImpl.java

@@ -5,10 +5,12 @@ import com.yuxin.finrep.application.orchestration.ReportGenerationOrchestrator;
 import com.yuxin.finrep.application.port.in.DataPackageApplicationService;
 import com.yuxin.finrep.domain.port.out.DataPackageRepositoryPort;
 import com.yuxin.finrep.domain.port.out.OutlineRepositoryPort;
+import com.yuxin.finrep.domain.port.out.ReportRepositoryPort;
 import com.yuxin.finrep.domain.port.out.TaskRepositoryPort;
 import com.yuxin.finrep.common.exception.BusinessException;
 import com.yuxin.finrep.common.exception.ErrorCode;
 import com.yuxin.finrep.domain.dataprep.DataPackage;
+import com.yuxin.finrep.domain.generation.Report;
 import com.yuxin.finrep.domain.outline.Outline;
 import com.yuxin.finrep.domain.task.Task;
 import com.yuxin.finrep.domain.task.TaskStatus;
@@ -33,6 +35,7 @@ public class DataPackageApplicationServiceImpl implements DataPackageApplication
     private final DataPackageRepositoryPort dataPackageRepository;
     private final TaskRepositoryPort taskRepositoryPort;
     private final OutlineRepositoryPort outlineRepositoryPort;
+    private final ReportRepositoryPort reportRepositoryPort;
     private final DataPreparationOrchestrator dataPreparationOrchestrator;
     private final ReportGenerationOrchestrator reportGenerationOrchestrator;
 
@@ -60,21 +63,18 @@ public class DataPackageApplicationServiceImpl implements DataPackageApplication
 
     @Override
     public DataPackage createDataPackage(String taskId, String tenantId, String knowledgeUnitId,
-                                         String knowledgeUnitName, Map<String, Object> autoData,
-                                         Map<String, Object> manualData, String remark) {
+                                         Map<String, Object> autoData,
+                                         Map<String, Object> manualData) {
         log.info("创建数据包: taskId={}, tenantId={}, knowledgeUnitId={}", taskId, tenantId, knowledgeUnitId);
 
         // 创建数据包
-        DataPackage dataPackage = DataPackage.create(taskId, tenantId, knowledgeUnitId, knowledgeUnitName, autoData);
+        DataPackage dataPackage = DataPackage.create(taskId, tenantId, knowledgeUnitId, autoData);
 
         // 如果有手动数据,则更新
         if (manualData != null && !manualData.isEmpty()) {
             dataPackage.updateManualData(manualData);
         }
 
-        // 设置备注
-        dataPackage.setRemark(remark);
-
         // 保存
         dataPackage = dataPackageRepository.save(dataPackage);
 
@@ -233,7 +233,7 @@ public class DataPackageApplicationServiceImpl implements DataPackageApplication
     }
 
     @Override
-    public void confirmAllDataPackagesAndGenerateReport(String taskId, String confirmerId, String confirmerName) {
+    public String confirmAllDataPackagesAndGenerateReport(String taskId, String confirmerId, String confirmerName) {
         log.info("批量确认任务的所有数据包并生成报告: taskId={}, confirmerId={}", taskId, confirmerId);
 
         // 获取任务
@@ -246,36 +246,42 @@ public class DataPackageApplicationServiceImpl implements DataPackageApplication
         // 获取所有数据包
         List<DataPackage> dataPackages = dataPackageRepository.findByTaskId(taskId);
 
-        if (dataPackages.isEmpty()) {
-            throw new BusinessException(ErrorCode.DATA_PACKAGE_NOT_FOUND, "没有找到数据包");
-        }
+//        if (dataPackages.isEmpty()) {
+//            throw new BusinessException(ErrorCode.DATA_PACKAGE_NOT_FOUND, "没有找到数据包");
+//        }
 
         // 批量确认数据包
-        for (DataPackage dataPackage : dataPackages) {
-            try {
-                dataPackage.confirm(confirmerId, confirmerName);
-                dataPackageRepository.save(dataPackage);
-            } catch (Exception e) {
-                log.error("确认数据包失败: packageId={}", dataPackage.getPackageId(), e);
-                throw new BusinessException(ErrorCode.DATA_CONFIRM_FAILED, "数据包确认失败: " + e.getMessage());
-            }
-        }
-
-        log.info("批量确认数据包完成: taskId={}", taskId);
+//        for (DataPackage dataPackage : dataPackages) {
+//            try {
+//                dataPackage.confirm(confirmerId, confirmerName);
+//                dataPackageRepository.save(dataPackage);
+//            } catch (Exception e) {
+//                log.error("确认数据包失败: packageId={}", dataPackage.getPackageId(), e);
+//                throw new BusinessException(ErrorCode.DATA_CONFIRM_FAILED, "数据包确认失败: " + e.getMessage());
+//            }
+//        }
+//
+//        log.info("批量确认数据包完成: taskId={}", taskId);
 
         // 调用数据准备编排器确认数据准备完成
         dataPreparationOrchestrator.confirmDataPreparation(taskId, confirmerId, confirmerName);
 
         // 获取大纲
-        Outline outline = outlineRepositoryPort.findByTaskId(taskId)
-                .orElseThrow(() -> new BusinessException(ErrorCode.OUTLINE_NOT_FOUND, "大纲不存在"));
-
+        Outline outline = outlineRepositoryPort.findByTaskIdAndType(taskId,"LEVEL2");
         // 调用报告生成编排器生成报告
-        reportGenerationOrchestrator.generateReport(
+        Report report = reportGenerationOrchestrator.generateReport(
                 taskId,
                 task.getParameters() != null ? task.getParameters() : Collections.emptyMap()
         );
 
-        log.info("报告生成已启动: taskId={}", taskId);
+        // 组装完整文章
+        String fullArticle = reportGenerationOrchestrator.assembleFullArticle(report, outline);
+
+        // 将组装的文章保存到Report的content字段
+        report.setFullText(fullArticle);
+        reportRepositoryPort.save(report);
+
+        log.info("报告生成完成,完整文章已保存: taskId={}", taskId);
+        return fullArticle;
     }
 }

+ 62 - 4
backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/OutlineApplicationServiceImpl.java

@@ -316,15 +316,18 @@ public class OutlineApplicationServiceImpl implements OutlineApplicationService
 
             log.info("大纲确认成功: outlineId={}", outlineId);
 
+            // 构建二级大纲生成参数,包含一级大纲结果
+            Map<String, Object> level2Parameters = buildLevel2OutlineParameters(task, outline);
+            log.info("构建二级大纲生成参数: taskId={}, outlineId={}", task.getTaskId(), outlineId);
+
             // 调用大纲生成编排器生成二级大纲
-            outlineGenerationOrchestrator.generateLevel2Outline(
+
+            return outlineGenerationOrchestrator.generateLevel2Outline(
                     outline.getTaskId(),
                     outlineId,
-                    task.getParameters() != null ? task.getParameters() : Collections.emptyMap()
+                    level2Parameters
             );
 
-            return outline;
-
         } catch (Exception e) {
             log.error("确认大纲并生成二级大纲失败: outlineId={}, error={}", outlineId, e.getMessage(), e);
             throw new BusinessException(ErrorCode.OUTLINE_GENERATE_FAILED, e);
@@ -372,4 +375,59 @@ public class OutlineApplicationServiceImpl implements OutlineApplicationService
             throw new BusinessException(ErrorCode.DATA_PREPARE_FAILED, e);
         }
     }
+
+    /**
+     * 构建二级大纲生成参数
+     * 将一级大纲结果转换为Python Agent API所需的格式
+     */
+    private Map<String, Object> buildLevel2OutlineParameters(Task task, Outline level1Outline) {
+        Map<String, Object> params = new java.util.HashMap<>();
+
+        // 添加task_id和tenant_id
+        params.put("task_id", task.getTaskId());
+        params.put("tenant_id", task.getTenantId());
+
+        // 解析一级大纲的rawContent获取chapter_results和overall_logic
+        if (level1Outline.getRawContent() != null) {
+            try {
+                com.alibaba.fastjson2.JSONObject rawContent = com.alibaba.fastjson2.JSON.parseObject(level1Outline.getRawContent());
+
+                // 构建l1_response
+                com.alibaba.fastjson2.JSONObject l1Response = new com.alibaba.fastjson2.JSONObject();
+                l1Response.put("chapter_results", rawContent.get("chapter_results"));
+                l1Response.put("overall_logic", rawContent.get("overall_logic"));
+                params.put("l1_response", l1Response);
+
+                // 构建l1_task_snapshot
+                com.alibaba.fastjson2.JSONObject l1TaskSnapshot = new com.alibaba.fastjson2.JSONObject();
+                l1TaskSnapshot.put("report_type", task.getParameters() != null ? task.getParameters().get("report_type") : null);
+                l1TaskSnapshot.put("agreement_amount", task.getParameters() != null ? task.getParameters().get("agreement_amount") : null);
+                l1TaskSnapshot.put("has_independent_report", task.getParameters() != null ? task.getParameters().get("has_independent_report") : null);
+                l1TaskSnapshot.put("independent_report_types", task.getParameters() != null ? task.getParameters().get("independent_report_types") : null);
+
+                // 从chapter_results提取chapter_candidates(仅包含chapter_id和chapter_name)
+                com.alibaba.fastjson2.JSONArray chapterResults = rawContent.getJSONArray("chapter_results");
+                com.alibaba.fastjson2.JSONArray chapterCandidates = new com.alibaba.fastjson2.JSONArray();
+                if (chapterResults != null) {
+                    for (int i = 0; i < chapterResults.size(); i++) {
+                        com.alibaba.fastjson2.JSONObject resultItem = chapterResults.getJSONObject(i);
+                        com.alibaba.fastjson2.JSONObject candidate = new com.alibaba.fastjson2.JSONObject();
+                        candidate.put("chapter_id", resultItem.get("chapter_id"));
+                        candidate.put("chapter_name", resultItem.get("chapter_name"));
+                        chapterCandidates.add(candidate);
+                    }
+                }
+                l1TaskSnapshot.put("chapter_candidates", chapterCandidates);
+                params.put("l1_task_snapshot", l1TaskSnapshot);
+
+                // 添加空字典
+                params.put("leaf_chapter_candidates_by_chapter_id", new java.util.HashMap<>());
+
+            } catch (Exception e) {
+                log.warn("解析一级大纲rawContent失败: {}", e.getMessage());
+            }
+        }
+
+        return params;
+    }
 }

+ 1 - 2
backend/finrep-application/src/main/java/com/yuxin/finrep/application/service/ReportApplicationServiceImpl.java

@@ -22,7 +22,7 @@ public class ReportApplicationServiceImpl implements ReportApplicationService {
     private final ReportRepositoryPort reportRepository;
 
     @Override
-    public Report createReport(String taskId, String tenantId, String title, String summary,
+    public Report createReport(String taskId, String tenantId, String title,
                                List<Report.Section> sections, String creatorId, String creatorName) {
         log.info("创建报告: taskId={}, tenantId={}, title={}", taskId, tenantId, title);
 
@@ -34,7 +34,6 @@ public class ReportApplicationServiceImpl implements ReportApplicationService {
                 .taskId(taskId)
                 .tenantId(tenantId)
                 .title(title)
-                .summary(summary)
                 .status(ReportStatus.DRAFT)
                 .creatorId(creatorId)
                 .creatorName(creatorName)

+ 1 - 13
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/dataprep/DataPackage.java

@@ -39,11 +39,6 @@ public class DataPackage {
     private String knowledgeUnitId;
 
     /**
-     * 知识单元名称
-     */
-    private String knowledgeUnitName;
-
-    /**
      * 自动获取的数据(JSON)
      */
     private Map<String, Object> autoData;
@@ -84,11 +79,6 @@ public class DataPackage {
     private LocalDateTime confirmTime;
 
     /**
-     * 备注信息
-     */
-    private String remark;
-
-    /**
      * 版本号
      */
     private Integer version;
@@ -107,7 +97,7 @@ public class DataPackage {
      * 创建数据包
      */
     public static DataPackage create(String taskId, String tenantId, String knowledgeUnitId,
-                                      String knowledgeUnitName, Map<String, Object> autoData) {
+                                      Map<String, Object> autoData) {
         String packageId = ValueObject.generateId();
         LocalDateTime now = LocalDateTime.now();
 
@@ -116,7 +106,6 @@ public class DataPackage {
                 .taskId(taskId)
                 .tenantId(tenantId)
                 .knowledgeUnitId(knowledgeUnitId)
-                .knowledgeUnitName(knowledgeUnitName)
                 .autoData(autoData)
                 .mergedData(autoData != null ? new java.util.HashMap<>(autoData) : new java.util.HashMap<>())
                 .status(DataStatus.PENDING)
@@ -176,7 +165,6 @@ public class DataPackage {
      */
     public void markFailed(String errorMessage) {
         this.status = DataStatus.FAILED;
-        this.remark = errorMessage;
         this.updateTime = LocalDateTime.now();
     }
 

+ 10 - 2
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/generation/Report.java

@@ -40,9 +40,9 @@ public class Report {
     private String title;
 
     /**
-     * 报告摘要
+     * 报告内容(JSON结构)
      */
-    private String summary;
+    private String content;
 
     /**
      * 报告状态
@@ -158,6 +158,14 @@ public class Report {
     }
 
     /**
+     * 设置报告完整内容
+     */
+    public void setContent(String content) {
+        this.content = content;
+        this.updateTime = LocalDateTime.now();
+    }
+
+    /**
      * 标记为失败
      */
     public void fail(String errorMessage) {

+ 10 - 0
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/knowledge/KnowledgeType.java

@@ -34,6 +34,16 @@ public enum KnowledgeType {
      * 法律知识
      */
     LEGAL("LEGAL", "法律知识"),
+    ANALYSIS("ANALYSIS",""),
+
+    JUDGMENT("JUDGMENT",""),
+
+    METRIC("METRIC",""),
+
+    INFO("INFO",""),
+
+
+
 
     /**
      * 其他

+ 5 - 0
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/knowledge/KnowledgeUnit.java

@@ -75,6 +75,11 @@ public class KnowledgeUnit {
     private Map<String, Object> dataBinding;
 
     /**
+     * 内容模板
+     */
+    private String template;
+
+    /**
      * 模板配置
      */
     private TemplateConfig templateConfig;

+ 38 - 0
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/outline/Outline.java

@@ -72,6 +72,18 @@ public class Outline {
     private transient List<OutlineSection> sectionsList;
 
     /**
+     * 节点列表(扁平结构,JSON字符串存储)
+     */
+    private String nodes;
+
+    /**
+     * 解析后的节点列表(扁平结构,运行时使用)
+     */
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
+    private transient List<OutlineNode> nodeList;
+
+    /**
      * 使用的知识单元ID列表
      */
     private List<String> knowledgeUnitIds;
@@ -254,4 +266,30 @@ public class Outline {
             this.rootNode = null;
         }
     }
+
+    /**
+     * 获取节点列表(扁平结构)
+     */
+    public List<OutlineNode> getNodeList() {
+        if (nodeList == null && nodes != null) {
+            try {
+                nodeList = JSON.parseObject(nodes, new TypeReference<List<OutlineNode>>() {});
+            } catch (Exception e) {
+                nodeList = new ArrayList<>();
+            }
+        }
+        return nodeList != null ? nodeList : new ArrayList<>();
+    }
+
+    /**
+     * 设置节点列表(扁平结构)
+     */
+    public void setNodeList(List<OutlineNode> nodeList) {
+        this.nodeList = nodeList;
+        if (nodeList != null) {
+            this.nodes = JSON.toJSONString(nodeList);
+        } else {
+            this.nodes = null;
+        }
+    }
 }

+ 22 - 0
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/KnowledgeUnitRepositoryPort.java

@@ -0,0 +1,22 @@
+package com.yuxin.finrep.domain.port.out;
+
+import com.yuxin.finrep.domain.knowledge.KnowledgeUnit;
+
+import java.util.Optional;
+
+/**
+ * 知识单元仓储端口
+ * 出站端口,由Infrastructure层实现
+ */
+public interface KnowledgeUnitRepositoryPort {
+
+    /**
+     * 根据ID查找知识单元
+     */
+    Optional<KnowledgeUnit> findById(String knowledgeUnitId);
+
+    /**
+     * 根据编码查找知识单元
+     */
+    Optional<KnowledgeUnit> findByCode(String code);
+}

+ 2 - 0
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/OutlineRepositoryPort.java

@@ -51,4 +51,6 @@ public interface OutlineRepositoryPort {
      * 查找过期大纲ID列表
      */
     List<String> findExpiredOutlines(LocalDateTime expireThreshold);
+
+    Outline findByTaskIdAndType(String taskId, String type);
 }

+ 1 - 1
backend/finrep-domain/src/main/java/com/yuxin/finrep/domain/port/out/PythonAgentClientPort.java

@@ -30,7 +30,7 @@ public interface PythonAgentClientPort {
     /**
      * 生成段落
      */
-    JSONObject generateSection(String taskId, String tenantId, String knowledgeUnitId, Map<String, Object> data, Map<String, Object> parameters);
+    JSONObject generateSection(String taskId, String tenantId, String knowledgeUnitId, Map<String, Object> data, String template, Map<String, Object> parameters,String writingLogic);
 
     /**
      * 批量生成段落

+ 1 - 6
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/converter/KnowledgeUnitConverter.java

@@ -18,9 +18,7 @@ public interface KnowledgeUnitConverter {
      * 领域对象转实体
      */
     @Mapping(target = "dataBinding", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getDataBinding()))")
-    @Mapping(target = "templateConfig", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getTemplateConfig()))")
-    @Mapping(target = "applicableScopes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getApplicableScopes()))")
-    @Mapping(target = "attributes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getAttributes()))")
+    @Mapping(target = "template", source = "template")
     @Mapping(target = "type", expression = "java(domain.getType() != null ? domain.getType().name() : null)")
     @Mapping(target = "deleted", ignore = true) // 修复:忽略逻辑删除字段
     KnowledgeUnitEntity toEntity(KnowledgeUnit domain);
@@ -29,9 +27,6 @@ public interface KnowledgeUnitConverter {
      * 实体转领域对象
      */
     @Mapping(target = "dataBinding", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseMap(entity.getDataBinding()))")
-    @Mapping(target = "templateConfig", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseJson(entity.getTemplateConfig(), com.yuxin.finrep.domain.knowledge.KnowledgeUnit.TemplateConfig.class))")
-    @Mapping(target = "applicableScopes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseListString(entity.getApplicableScopes()))")
-    @Mapping(target = "attributes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseMap(entity.getAttributes()))")
     @Mapping(target = "type", expression = "java(com.yuxin.finrep.domain.knowledge.KnowledgeType.valueOf(entity.getType()))")
     KnowledgeUnit toDomain(KnowledgeUnitEntity entity);
 

+ 1 - 2
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/converter/ReportConverter.java

@@ -17,7 +17,7 @@ public interface ReportConverter {
     /**
      * 领域对象转实体
      */
-    @Mapping(target = "sections", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getSections()))")
+    @Mapping(target = "content", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getSections()))")
     @Mapping(target = "metadata", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getMetadata()))")
     @Mapping(target = "attributes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.toJsonString(domain.getAttributes()))")
     @Mapping(target = "status", expression = "java(domain.getStatus() != null ? domain.getStatus().name() : null)")
@@ -26,7 +26,6 @@ public interface ReportConverter {
     /**
      * 实体转领域对象
      */
-    @Mapping(target = "sections", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseList(entity.getSections(), com.yuxin.finrep.domain.generation.Report.Section.class))")
     @Mapping(target = "metadata", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseJson(entity.getMetadata(), com.yuxin.finrep.domain.generation.Report.ReportMetadata.class))")
     @Mapping(target = "attributes", expression = "java(com.yuxin.finrep.common.util.JsonUtil.parseMap(entity.getAttributes()))")
     @Mapping(target = "status", expression = "java(com.yuxin.finrep.domain.generation.ReportStatus.valueOf(entity.getStatus()))")

+ 8 - 5
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/integration/pythonagent/PythonAgentClientImpl.java

@@ -83,7 +83,7 @@ public class PythonAgentClientImpl implements PythonAgentClientPort {
     @CircuitBreaker(name = "pythonAgent")
     @TimeLimiter(name = "pythonAgent")
     public JSONObject generateLevel2Outline(String taskId, String tenantId, String level1OutlineId, Map<String, Object> parameters) {
-        String url = baseUrl + "/v1/outline/l2";
+        String url = baseUrl + "/v1/outline/l2/batch";
 
         // 构建请求体,参数直接从 parameters 获取
         // API 请求参数: chapter_name(必填), chapter_no(必填), l1_chapter_id,
@@ -103,6 +103,7 @@ public class PythonAgentClientImpl implements PythonAgentClientPort {
             }
         }
 
+        log.info("请求体{}",requestBody);
         return doPost(url, requestBody);
     }
 
@@ -121,11 +122,13 @@ public class PythonAgentClientImpl implements PythonAgentClientPort {
     @Retry(name = "pythonAgent")
     @CircuitBreaker(name = "pythonAgent")
     @TimeLimiter(name = "pythonAgent")
-    public JSONObject generateSection(String taskId, String tenantId, String knowledgeUnitId, Map<String, Object> data, Map<String, Object> parameters) {
-        String url = baseUrl + "/v1/generate/section";
+    public JSONObject generateSection(String taskId, String tenantId, String knowledgeUnitId, Map<String, Object> data, String template, Map<String, Object> parameters,String writingLogic) {
+        String url = baseUrl + "/v1/section";
         JSONObject requestBody = buildRequest(taskId, tenantId, null, parameters);
-        requestBody.put("knowledgeUnitId", knowledgeUnitId);
-        requestBody.put("data", data);
+        JSONObject templateObject = JSONObject.parseObject(template);
+        requestBody.putAll(templateObject);
+        requestBody.put("overall_logic",writingLogic);
+
         return doPost(url, requestBody);
     }
 

+ 0 - 12
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/DataPackageEntity.java

@@ -37,12 +37,6 @@ public class DataPackageEntity {
     private String knowledgeUnitId;
 
     /**
-     * 知识单元名称
-     */
-    @TableField("knowledge_unit_name")
-    private String knowledgeUnitName;
-
-    /**
      * 自动获取的数据(JSON)
      */
     @TableField("auto_data")
@@ -91,12 +85,6 @@ public class DataPackageEntity {
     private LocalDateTime confirmTime;
 
     /**
-     * 备注信息
-     */
-    @TableField("remark")
-    private String remark;
-
-    /**
      * 版本号
      */
     @TableField("version")

+ 5 - 54
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/KnowledgeUnitEntity.java

@@ -48,29 +48,7 @@ public class KnowledgeUnitEntity {
     @TableField("type")
     private String type;
 
-    /**
-     * 所属知识库ID
-     */
-    @TableField("knowledge_base_id")
-    private String knowledgeBaseId;
 
-    /**
-     * 父知识单元ID
-     */
-    @TableField("parent_id")
-    private String parentId;
-
-    /**
-     * 层级
-     */
-    @TableField("level")
-    private Integer level;
-
-    /**
-     * 排序号
-     */
-    @TableField("sort_order")
-    private Integer sortOrder;
 
     /**
      * 数据绑定配置(JSON)
@@ -79,40 +57,18 @@ public class KnowledgeUnitEntity {
     private String dataBinding;
 
     /**
-     * 模板配置(JSON)
+     * 内容模板
      */
-    @TableField("template_config")
-    private String templateConfig;
+    @TableField("template")
+    private String template;
 
-    /**
-     * 适用范围(JSON)
-     */
-    @TableField("applicable_scopes")
-    private String applicableScopes;
 
     /**
      * 是否启用
      */
-    @TableField("enabled")
-    private Boolean enabled;
-
-    /**
-     * 扩展属性(JSON)
-     */
-    @TableField("attributes")
-    private String attributes;
+    @TableField("is_active")
+    private Integer isActive;
 
-    /**
-     * 创建人ID
-     */
-    @TableField("creator_id")
-    private String creatorId;
-
-    /**
-     * 创建人名称
-     */
-    @TableField("creator_name")
-    private String creatorName;
 
     /**
      * 创建时间
@@ -126,11 +82,6 @@ public class KnowledgeUnitEntity {
     @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
     private LocalDateTime updateTime;
 
-    /**
-     * 版本号
-     */
-    @TableField("version")
-    private Integer version;
 
     /**
      * 是否删除

+ 2 - 8
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/entity/ReportEntity.java

@@ -37,12 +37,6 @@ public class ReportEntity {
     private String title;
 
     /**
-     * 报告摘要
-     */
-    @TableField("summary")
-    private String summary;
-
-    /**
      * 报告状态
      */
     @TableField("status")
@@ -51,8 +45,8 @@ public class ReportEntity {
     /**
      * 报告内容(JSON结构)
      */
-    @TableField("sections")
-    private String sections;
+    @TableField("content")
+    private String content;
 
     /**
      * 完整报告文本

+ 48 - 0
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/repository/KnowledgeUnitRepositoryImpl.java

@@ -0,0 +1,48 @@
+package com.yuxin.finrep.infrastructure.persistence.repository;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yuxin.finrep.domain.knowledge.KnowledgeUnit;
+import com.yuxin.finrep.domain.port.out.KnowledgeUnitRepositoryPort;
+import com.yuxin.finrep.infrastructure.converter.KnowledgeUnitConverter;
+import com.yuxin.finrep.infrastructure.persistence.entity.KnowledgeUnitEntity;
+import com.yuxin.finrep.infrastructure.persistence.mapper.KnowledgeUnitMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * 知识单元仓储实现
+ */
+@Slf4j
+@Repository
+@RequiredArgsConstructor
+public class KnowledgeUnitRepositoryImpl implements KnowledgeUnitRepositoryPort {
+
+    private final KnowledgeUnitMapper knowledgeUnitMapper;
+    private final KnowledgeUnitConverter knowledgeUnitConverter;
+
+    @Override
+    public Optional<KnowledgeUnit> findById(String knowledgeUnitId) {
+        KnowledgeUnitEntity entity = knowledgeUnitMapper.selectById(knowledgeUnitId);
+        if (entity == null) {
+            return Optional.empty();
+        }
+        return Optional.of(knowledgeUnitConverter.toDomain(entity));
+    }
+
+    @Override
+    public Optional<KnowledgeUnit> findByCode(String code) {
+        LambdaQueryWrapper<KnowledgeUnitEntity> wrapper = new LambdaQueryWrapper<KnowledgeUnitEntity>()
+                .eq(KnowledgeUnitEntity::getCode, code)
+                .eq(KnowledgeUnitEntity::getIsActive, 1)
+                .last("LIMIT 1");
+
+        KnowledgeUnitEntity entity = knowledgeUnitMapper.selectOne(wrapper);
+        if (entity == null) {
+            return Optional.empty();
+        }
+        return Optional.of(knowledgeUnitConverter.toDomain(entity));
+    }
+}

+ 4 - 4
backend/finrep-infrastructure/src/main/java/com/yuxin/finrep/infrastructure/persistence/repository/OutlineRepositoryImpl.java

@@ -50,7 +50,7 @@ public class OutlineRepositoryImpl implements OutlineRepositoryPort {
     }
 
 
-    public Optional<Outline> findByTaskIdAndType(String taskId, String type) {
+    public Outline findByTaskIdAndType(String taskId, String type) {
         LambdaQueryWrapper<OutlineEntity> wrapper = new LambdaQueryWrapper<OutlineEntity>()
                 .eq(OutlineEntity::getTaskId, taskId)
                 .eq(OutlineEntity::getType, type)
@@ -58,9 +58,9 @@ public class OutlineRepositoryImpl implements OutlineRepositoryPort {
 
         OutlineEntity entity = outlineMapper.selectOne(wrapper);
         if (entity == null) {
-            return Optional.empty();
-        }
-        return Optional.of(outlineConverter.toDomain(entity));
+        return null;}
+
+        return outlineConverter.toDomain(entity);
     }
 
     @Override

+ 8 - 10
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/DataPackageController.java

@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
  */
 @Slf4j
 @RestController
-@RequestMapping("/api/v1/data-packages")
+@RequestMapping("/api/v1/data")
 @RequiredArgsConstructor
 @Tag(name = "数据包管理", description = "数据包相关接口")
 public class DataPackageController {
@@ -43,8 +43,8 @@ public class DataPackageController {
 
         DataPackage dataPackage = dataPackageApplicationService.createDataPackage(
                 request.getTaskId(), tenantId, request.getKnowledgeUnitId(),
-                request.getKnowledgeUnitName(), request.getAutoData(),
-                request.getManualData(), request.getRemark()
+                request.getAutoData(),
+                request.getManualData()
         );
 
         return CommonResponse.success(convertToResponse(dataPackage));
@@ -147,18 +147,18 @@ public class DataPackageController {
     /**
      * 批量确认数据包并生成报告
      */
-    @PostMapping("/task/{taskId}/confirm-and-generate-report")
+    @PostMapping("/{taskId}/generate")
     @Operation(summary = "批量确认数据包并生成报告", description = "批量确认任务的所有数据包并开始生成报告")
-    public CommonResponse<Void> confirmDataPackagesAndGenerateReport(
+    public CommonResponse<String> confirmDataPackagesAndGenerateReport(
             @Parameter(description = "任务ID") @PathVariable String taskId) {
         String confirmerId = UserContext.getUserId();
         String confirmerName = UserContext.getRealName();
 
         log.info("批量确认数据包并生成报告: taskId={}, confirmerId={}", taskId, confirmerId);
 
-        dataPackageApplicationService.confirmAllDataPackagesAndGenerateReport(taskId, confirmerId, confirmerName);
+        String fullArticle = dataPackageApplicationService.confirmAllDataPackagesAndGenerateReport(taskId, confirmerId, confirmerName);
 
-        return CommonResponse.success("数据包确认完成,报告生成已启动", null);
+        return CommonResponse.success("报告生成完成", fullArticle);
     }
 
     /**
@@ -175,7 +175,7 @@ public class DataPackageController {
     }
 
     /**
-     * 转换为响应DTO
+     * 转换为响应DTO
      */
     private DataPackageResponse convertToResponse(DataPackage dataPackage) {
         return DataPackageResponse.builder()
@@ -183,7 +183,6 @@ public class DataPackageController {
                 .taskId(dataPackage.getTaskId())
                 .tenantId(dataPackage.getTenantId())
                 .knowledgeUnitId(dataPackage.getKnowledgeUnitId())
-                .knowledgeUnitName(dataPackage.getKnowledgeUnitName())
                 .autoData(dataPackage.getAutoData())
                 .manualData(dataPackage.getManualData())
                 .mergedData(dataPackage.getMergedData())
@@ -192,7 +191,6 @@ public class DataPackageController {
                 .confirmerId(dataPackage.getConfirmerId())
                 .confirmerName(dataPackage.getConfirmerName())
                 .confirmTime(dataPackage.getConfirmTime())
-                .remark(dataPackage.getRemark())
                 .version(dataPackage.getVersion())
                 .createTime(dataPackage.getCreateTime())
                 .updateTime(dataPackage.getUpdateTime())

+ 24 - 328
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/OutlineController.java

@@ -1,5 +1,7 @@
 package com.yuxin.finrep.web.rest.controller;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.yuxin.finrep.application.port.in.OutlineApplicationService;
 import com.yuxin.finrep.application.port.in.dto.ChapterCandidateDto;
 import com.yuxin.finrep.common.context.TenantContext;
@@ -7,6 +9,7 @@ import com.yuxin.finrep.common.context.UserContext;
 import com.yuxin.finrep.domain.outline.Outline;
 import com.yuxin.finrep.domain.outline.OutlineNode;
 import com.yuxin.finrep.domain.outline.OutlineSection;
+import com.yuxin.finrep.domain.outline.OutlineType;
 import com.yuxin.finrep.domain.outline.SectionType;
 import com.yuxin.finrep.web.rest.dto.*;
 import io.swagger.v3.oas.annotations.Operation;
@@ -33,221 +36,8 @@ import java.util.stream.Collectors;
 public class OutlineController {
 
     private final OutlineApplicationService outlineApplicationService;
+    private final ObjectMapper objectMapper;
 
-    /**
-     * 创建大纲
-     */
-    @PostMapping
-    @Operation(summary = "创建大纲", description = "创建新的报告大纲")
-    public CommonResponse<OutlineResponse> createOutline(@Valid @RequestBody OutlineRequest request) {
-        String tenantId = TenantContext.getTenantId();
-        String creatorId = UserContext.getUserId();
-        String creatorName = UserContext.getRealName();
-
-        log.info("创建大纲: tenantId={}, taskId={}, type={}", tenantId, request.getTaskId(), request.getType());
-
-        Outline outline = outlineApplicationService.createOutline(
-                request.getTaskId(), tenantId, request.getTitle(), request.getType(),
-                convertToNode(request.getRootNode()), convertToSections(request.getSections()),
-                request.getKnowledgeUnitIds(), request.getModelConfigId(),
-                request.getRawContent(), request.getWritingLogic(),
-                creatorId, creatorName
-        );
-
-        return CommonResponse.success(convertToResponse(outline));
-    }
-
-    /**
-     * 创建一级大纲(基于章节候选项)
-     */
-    @PostMapping("/level1")
-    @Operation(summary = "创建一级大纲", description = "基于章节候选项创建一级大纲")
-    public CommonResponse<Level1OutlineResponse> createLevel1Outline(@Valid @RequestBody Level1OutlineRequest request) {
-        String creatorId = UserContext.getUserId();
-        String creatorName = UserContext.getRealName();
-
-        log.info("创建一级大纲: tenantId={}, taskId={}, reportType={}",
-                request.getTenantId(), request.getTaskId(), request.getReportType());
-
-        List<ChapterCandidateDto> chapterCandidateDtos = convertToChapterCandidateDtos(request.getChapterCandidates());
-
-        Outline outline = outlineApplicationService.createLevel1Outline(
-                request.getTaskId(), request.getTenantId(), request.getReportType(),
-                chapterCandidateDtos, creatorId, creatorName
-        );
-
-        return CommonResponse.success(convertToLevel1Response(request, outline));
-    }
-
-    /**
-     * 更新一级大纲章节选择
-     */
-    @PutMapping("/level1/{outlineId}")
-    @Operation(summary = "更新一级大纲", description = "更新一级大纲的章节选择")
-    public CommonResponse<Level1OutlineResponse> updateLevel1Outline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId,
-            @Valid @RequestBody Level1OutlineRequest request) {
-        String updaterId = UserContext.getUserId();
-        String updaterName = UserContext.getRealName();
-
-        log.info("更新一级大纲: outlineId={}", outlineId);
-
-        List<ChapterCandidateDto> chapterCandidateDtos = convertToChapterCandidateDtos(request.getChapterCandidates());
-
-        Outline outline = outlineApplicationService.updateLevel1Outline(
-                outlineId, chapterCandidateDtos, updaterId, updaterName
-        );
-
-        return CommonResponse.success(convertToLevel1Response(request, outline));
-    }
-
-    /**
-     * 获取一级大纲详情
-     */
-    @GetMapping("/level1/{outlineId}")
-    @Operation(summary = "获取一级大纲详情", description = "根据大纲ID获取一级大纲详情")
-    public CommonResponse<Level1OutlineResponse> getLevel1Outline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId) {
-        log.info("获取一级大纲详情: outlineId={}", outlineId);
-
-        Outline outline = outlineApplicationService.getOutline(outlineId);
-
-        // 从sections中解析章节候选项
-        Level1OutlineRequest request = new Level1OutlineRequest();
-        request.setTenantId(outline.getTenantId());
-        request.setTaskId(outline.getTaskId());
-
-        // 将sections转换为chapterCandidates
-        if (outline.getSections() != null) {
-            List<Level1OutlineRequest.ChapterCandidate> chapterCandidates =
-                    outline.getSectionList().stream()
-                            .map(section -> {
-                                Level1OutlineRequest.ChapterCandidate candidate =
-                                        Level1OutlineRequest.ChapterCandidate.builder()
-                                                .chapterId(section.getSectionId())
-                                                .chapterName(section.getTitle())
-                                                .applicableCondition(section.getDescription())
-                                                .build();
-                                if (section.getAttributes() != null) {
-                                    candidate.setImportanceNoReport((String) section.getAttributes().get("importanceNoReport"));
-                                    candidate.setImportanceWithReport((String) section.getAttributes().get("importanceWithReport"));
-                                    candidate.setKnowledgeBoundaryDescription((String) section.getAttributes().get("knowledgeBoundary"));
-                                }
-                                return candidate;
-                            })
-                            .collect(Collectors.toList());
-            request.setChapterCandidates(chapterCandidates);
-        }
-
-        return CommonResponse.success(convertToLevel1Response(request, outline));
-    }
-
-    /**
-     * 确认一级大纲
-     */
-    @PostMapping("/level1/{outlineId}/confirm")
-    @Operation(summary = "确认一级大纲", description = "确认一级大纲并生成二级大纲")
-    public CommonResponse<Level1OutlineResponse> confirmLevel1Outline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId) {
-        String confirmerId = UserContext.getUserId();
-        String confirmerName = UserContext.getRealName();
-
-        log.info("确认一级大纲: outlineId={}, confirmerId={}", outlineId, confirmerId);
-
-        Outline outline = outlineApplicationService.confirmOutline(outlineId, confirmerId, confirmerName);
-
-        // 从sections中解析章节候选项
-        Level1OutlineRequest request = new Level1OutlineRequest();
-        request.setTenantId(outline.getTenantId());
-        request.setTaskId(outline.getTaskId());
-
-        // 将sections转换为chapterCandidates
-        if (outline.getSections() != null) {
-            List<Level1OutlineRequest.ChapterCandidate> chapterCandidates =
-                    outline.getSectionList().stream()
-                            .map(section -> {
-                                Level1OutlineRequest.ChapterCandidate candidate =
-                                        Level1OutlineRequest.ChapterCandidate.builder()
-                                                .chapterId(section.getSectionId())
-                                                .chapterName(section.getTitle())
-                                                .applicableCondition(section.getDescription())
-                                                .build();
-                                if (section.getAttributes() != null) {
-                                    candidate.setImportanceNoReport((String) section.getAttributes().get("importanceNoReport"));
-                                    candidate.setImportanceWithReport((String) section.getAttributes().get("importanceWithReport"));
-                                    candidate.setKnowledgeBoundaryDescription((String) section.getAttributes().get("knowledgeBoundary"));
-                                }
-                                return candidate;
-                            })
-                            .collect(Collectors.toList());
-            request.setChapterCandidates(chapterCandidates);
-        }
-
-        return CommonResponse.success(convertToLevel1Response(request, outline));
-    }
-
-    /**
-     * 获取大纲详情
-     */
-    @GetMapping("/{outlineId}")
-    @Operation(summary = "获取大纲详情", description = "根据大纲ID获取大纲详情")
-    public CommonResponse<OutlineResponse> getOutline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId) {
-        log.info("获取大纲详情: outlineId={}", outlineId);
-
-        Outline outline = outlineApplicationService.getOutline(outlineId);
-        return CommonResponse.success(convertToResponse(outline));
-    }
-
-    /**
-     * 获取任务的大纲列表
-     */
-    @GetMapping("/task/{taskId}")
-    @Operation(summary = "获取任务的大纲", description = "根据任务ID获取大纲")
-    public CommonResponse<OutlineResponse> getOutlinesByTask(
-            @Parameter(description = "任务ID") @PathVariable String taskId) {
-        log.info("获取任务的大纲: taskId={}", taskId);
-
-        Optional<Outline> outline = outlineApplicationService.getOutlinesByTask(taskId);
-        if (outline.isEmpty()) {
-            return CommonResponse.success("大纲不存在", null);
-        }
-
-        return CommonResponse.success(convertToResponse(outline.get()));
-    }
-
-    /**
-     * 更新大纲内容
-     */
-    @PutMapping("/{outlineId}")
-    @Operation(summary = "更新大纲内容", description = "更新大纲内容")
-    public CommonResponse<OutlineResponse> updateOutline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId,
-            @Valid @RequestBody OutlineRequest request) {
-        log.info("更新大纲内容: outlineId={}", outlineId);
-
-        Outline outline = outlineApplicationService.updateOutline(
-                outlineId, convertToNode(request.getRootNode()), convertToSections(request.getSections())
-        );
-
-        return CommonResponse.success(convertToResponse(outline));
-    }
-
-    /**
-     * 确认大纲
-     */
-    @PostMapping("/{outlineId}/confirm")
-    @Operation(summary = "确认大纲", description = "确认大纲并推进任务状态")
-    public CommonResponse<OutlineResponse> confirmOutline(
-            @Parameter(description = "大纲ID") @PathVariable String outlineId) {
-        String confirmerId = UserContext.getUserId();
-        String confirmerName = UserContext.getRealName();
-
-        log.info("确认大纲: outlineId={}, confirmerId={}", outlineId, confirmerId);
-
-        Outline outline = outlineApplicationService.confirmOutline(outlineId, confirmerId, confirmerName);
-        return CommonResponse.success(convertToResponse(outline));
-    }
 
     /**
      * 确认大纲并生成二级大纲
@@ -294,50 +84,6 @@ public class OutlineController {
         return CommonResponse.success("删除成功",null);
     }
 
-    /**
-     * 转换为领域模型节点
-     */
-    private OutlineNode convertToNode(OutlineRequest.OutlineNodeRequest request) {
-        if (request == null) {
-            return null;
-        }
-        OutlineNode node = new com.yuxin.finrep.domain.outline.OutlineNode();
-        node.setNodeId(request.getNodeId());
-        node.setTitle(request.getTitle());
-        node.setDescription(request.getDescription());
-//        node.setNodeType(request.getNodeType());
-        node.setKnowledgeUnitId(request.getKnowledgeUnitId());
-        node.setSortOrder(request.getOrder());
-        node.setAttributes(request.getAttributes());
-        if (request.getChildren() != null) {
-            List<OutlineNode> children = request.getChildren().stream()
-                    .map(this::convertToNode)
-                    .collect(Collectors.toList());
-            node.setChildren(children);
-        }
-        return node;
-    }
-
-    /**
-     * 转换为领域模型章节列表
-     */
-    private List<OutlineSection> convertToSections(
-            List<OutlineRequest.OutlineSectionRequest> requests) {
-        if (requests == null) {
-            return null;
-        }
-        return requests.stream().map(req -> {
-            OutlineSection section = new com.yuxin.finrep.domain.outline.OutlineSection();
-            section.setSectionId(req.getSectionId());
-            section.setSectionNumber(req.getSectionNumber());
-            section.setTitle(req.getTitle());
-            section.setType(SectionType.valueOf(req.getSectionType()));
-            section.setKnowledgeUnitId(req.getKnowledgeUnitId());
-            section.setSortOrder(req.getOrder());
-            section.setAttributes(req.getAttributes());
-            return section;
-        }).collect(Collectors.toList());
-    }
 
     /**
      * 转换为响应DTO
@@ -348,6 +94,12 @@ public class OutlineController {
                 ? outline.getSectionList().stream().map(this::convertSectionToResponse).collect(Collectors.toList())
                 : null;
 
+        // 解析二级大纲的sections JSON字符串为结构化数据
+        List<Level2ChapterSection> level2Sections = null;
+        if (outline.getType() == OutlineType.LEVEL2 && outline.getSections() != null) {
+            level2Sections = parseLevel2Sections(outline.getSections());
+        }
+
         return OutlineResponse.builder()
                 .outlineId(outline.getOutlineId())
                 .taskId(outline.getTaskId())
@@ -356,6 +108,7 @@ public class OutlineController {
                 .type(outline.getType() != null ? outline.getType().getCode() : null)
                 .rootNode(rootNodeResponse)
                 .sections(sectionResponses)
+                .level2Sections(level2Sections)
                 .knowledgeUnitIds(outline.getKnowledgeUnitIds())
                 .modelConfigId(outline.getModelConfigId())
                 .rawContent(outline.getRawContent())
@@ -371,6 +124,18 @@ public class OutlineController {
     }
 
     /**
+     * 解析二级大纲sections JSON字符串
+     */
+    private List<Level2ChapterSection> parseLevel2Sections(String sectionsJson) {
+        try {
+            return objectMapper.readValue(sectionsJson, new TypeReference<List<Level2ChapterSection>>() {});
+        } catch (Exception e) {
+            log.error("解析二级大纲sections失败: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
      * 转换节点为响应DTO
      */
     private OutlineResponse.OutlineNodeResponse convertNodeToResponse(com.yuxin.finrep.domain.outline.OutlineNode node) {
@@ -401,82 +166,13 @@ public class OutlineController {
                 .sectionId(section.getSectionId())
                 .sectionNumber(section.getSectionNumber())
                 .title(section.getTitle())
-                .sectionType(section.getType().name())
+                .sectionType(section.getType() != null ? section.getType().name() : null)
                 .knowledgeUnitId(section.getKnowledgeUnitId())
                 .order(section.getSortOrder())
                 .attributes(section.getAttributes())
                 .build();
     }
 
-    /**
-     * 转换为一级大纲响应DTO
-     */
-    private Level1OutlineResponse convertToLevel1Response(Level1OutlineRequest request, Outline outline) {
-        // 获取实际保存的章节列表,用于获取selected状态
-        Map<String, Boolean> selectedMap = new java.util.HashMap<>();
-        if (outline.getSections() != null) {
-            outline.getSectionList().forEach(section -> {
-                if (section.getAttributes() != null && section.getAttributes().containsKey("selected")) {
-                    selectedMap.put(section.getSectionId(), (Boolean) section.getAttributes().get("selected"));
-                }
-            });
-        }
 
-        List<Level1OutlineResponse.ChapterCandidateResponse> chapterCandidateResponses =
-                request.getChapterCandidates().stream()
-                        .map(candidate -> Level1OutlineResponse.ChapterCandidateResponse.builder()
-                                .chapterId(candidate.getChapterId())
-                                .chapterName(candidate.getChapterName())
-                                .importanceNoReport(candidate.getImportanceNoReport())
-                                .importanceWithReport(candidate.getImportanceWithReport())
-                                .applicableCondition(candidate.getApplicableCondition())
-                                .knowledgeBoundaryDescription(candidate.getKnowledgeBoundaryDescription())
-                                .selected(selectedMap.getOrDefault(candidate.getChapterId(), true))
-                                .build())
-                        .collect(Collectors.toList());
 
-        return Level1OutlineResponse.builder()
-                .taskId(request.getTaskId())
-                .tenantId(request.getTenantId())
-                .reportType(request.getReportType())
-                .agreementAmount(request.getAgreementAmount())
-                .enterpriseType(request.getEnterpriseType())
-                .groupBusinessSegments(request.getGroupBusinessSegments())
-                .industryType(request.getIndustryType())
-                .hasIndependentReport(request.getHasIndependentReport())
-                .independentReportTypes(request.getIndependentReportTypes())
-                .candidateFinancingTools(request.getCandidateFinancingTools())
-                .recommendedFinancingTools(request.getRecommendedFinancingTools())
-                .otherRequirements(request.getOtherRequirements())
-                .chapterCandidates(chapterCandidateResponses)
-                .outlineId(outline.getOutlineId())
-                .confirmed(outline.isConfirmed())
-                .confirmerId(outline.getConfirmerId())
-                .confirmerName(outline.getConfirmerName())
-                .confirmTime(outline.getConfirmTime())
-                .version(outline.getVersion())
-                .createTime(outline.getCreateTime())
-                .updateTime(outline.getUpdateTime())
-                .build();
-    }
-
-    /**
-     * 转换章节候选项为DTO
-     */
-    private List<ChapterCandidateDto> convertToChapterCandidateDtos(
-            List<Level1OutlineRequest.ChapterCandidate> candidates) {
-        if (candidates == null) {
-            return null;
-        }
-        return candidates.stream()
-                .map(candidate -> ChapterCandidateDto.builder()
-                        .chapterId(candidate.getChapterId())
-                        .chapterName(candidate.getChapterName())
-                        .importanceNoReport(candidate.getImportanceNoReport())
-                        .importanceWithReport(candidate.getImportanceWithReport())
-                        .applicableCondition(candidate.getApplicableCondition())
-                        .knowledgeBoundaryDescription(candidate.getKnowledgeBoundaryDescription())
-                        .build())
-                .collect(Collectors.toList());
-    }
 }

+ 1 - 4
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/ReportController.java

@@ -12,7 +12,6 @@ import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
-
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -42,7 +41,7 @@ public class ReportController {
         log.info("创建报告: tenantId={}, taskId={}, title={}", tenantId, request.getTaskId(), request.getTitle());
 
         Report report = reportApplicationService.createReport(
-                request.getTaskId(), tenantId, request.getTitle(), request.getSummary(),
+                request.getTaskId(), tenantId, request.getTitle(),
                 convertToSections(request.getSections()), creatorId, creatorName
         );
 
@@ -61,7 +60,6 @@ public class ReportController {
         Report report = reportApplicationService.getReport(reportId);
         return CommonResponse.success(convertToResponse(report));
     }
-
     /**
      * 获取任务的报告
      */
@@ -200,7 +198,6 @@ public class ReportController {
                 .taskId(report.getTaskId())
                 .tenantId(report.getTenantId())
                 .title(report.getTitle())
-                .summary(report.getSummary())
                 .status(report.getStatus().name())
                 .sections(sectionResponses)
                 .fullText(report.getFullText())

+ 0 - 1
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/controller/TaskController.java

@@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
-
 /**
  * 任务控制器
  */

+ 0 - 12
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/DataPackageRequest.java

@@ -26,12 +26,6 @@ public class DataPackageRequest {
     private String knowledgeUnitId;
 
     /**
-     * 知识单元名称
-     */
-    @Size(max = 200, message = "知识单元名称长度不能超过200")
-    private String knowledgeUnitName;
-
-    /**
      * 自动获取的数据
      */
     private Map<String, Object> autoData;
@@ -40,10 +34,4 @@ public class DataPackageRequest {
      * 手动补充的数据
      */
     private Map<String, Object> manualData;
-
-    /**
-     * 备注信息
-     */
-    @Size(max = 1000, message = "备注信息长度不能超过1000")
-    private String remark;
 }

+ 0 - 10
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/DataPackageResponse.java

@@ -38,11 +38,6 @@ public class DataPackageResponse {
     private String knowledgeUnitId;
 
     /**
-     * 知识单元名称
-     */
-    private String knowledgeUnitName;
-
-    /**
      * 自动获取的数据
      */
     private Map<String, Object> autoData;
@@ -83,11 +78,6 @@ public class DataPackageResponse {
     private LocalDateTime confirmTime;
 
     /**
-     * 备注信息
-     */
-    private String remark;
-
-    /**
      * 版本号
      */
     private Integer version;

+ 141 - 0
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/Level2ChapterSection.java

@@ -0,0 +1,141 @@
+package com.yuxin.finrep.web.rest.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 二级大纲章节DTO
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Level2ChapterSection {
+
+    /**
+     * 章节ID
+     */
+    @JsonProperty("chapter_id")
+    private String chapterId;
+
+    /**
+     * 章节名称
+     */
+    @JsonProperty("chapter_name")
+    private String chapterName;
+
+    /**
+     * 呈现方式枚举 (S1-呈现, S2-不呈现)
+     */
+    @JsonProperty("presentation_enum")
+    private String presentationEnum;
+
+    /**
+     * 段落数量枚举 (P0-无段落, P1-少量, P2-中等, P3-多)
+     */
+    @JsonProperty("paragraph_count_enum")
+    private String paragraphCountEnum;
+
+    /**
+     * 原因说明
+     */
+    @JsonProperty("reason")
+    private String reason;
+
+    /**
+     * 二级章节详情
+     */
+    @JsonProperty("l2")
+    private Level2ChapterDetail l2;
+
+    /**
+     * 二级章节详情DTO
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Level2ChapterDetail {
+
+        /**
+         * 章节名称
+         */
+        @JsonProperty("chapter_name")
+        private String chapterName;
+
+        /**
+         * 章节编号
+         */
+        @JsonProperty("chapter_no")
+        private String chapterNo;
+
+        /**
+         * 章节结构列表
+         */
+        @JsonProperty("chapter_structure")
+        private List<ChapterStructureItem> chapterStructure;
+
+        /**
+         * 结构逻辑说明
+         */
+        @JsonProperty("structure_logic")
+        private String structureLogic;
+    }
+
+    /**
+     * 章节结构项DTO
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ChapterStructureItem {
+
+        /**
+         * 节点ID
+         */
+        @JsonProperty("node_id")
+        private String nodeId;
+
+        /**
+         * 节点名称
+         */
+        @JsonProperty("node_name")
+        private String nodeName;
+
+        /**
+         * 节点编号
+         */
+        @JsonProperty("node_no")
+        private String nodeNo;
+
+        /**
+         * 节点层级
+         */
+        @JsonProperty("node_level")
+        private Integer nodeLevel;
+
+        /**
+         * 来源类型 (single/multi)
+         */
+        @JsonProperty("source_type")
+        private String sourceType;
+
+        /**
+         * 候选名称
+         */
+        @JsonProperty("source_candidate_name")
+        private String sourceCandidateName;
+
+        /**
+         * 是否选中
+         */
+        @JsonProperty("is_selected")
+        private Boolean isSelected;
+    }
+}

+ 5 - 0
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/OutlineResponse.java

@@ -54,6 +54,11 @@ public class OutlineResponse {
     private List<OutlineSectionResponse> sections;
 
     /**
+     * 二级大纲章节列表(解析后的结构化数据)
+     */
+    private List<Level2ChapterSection> level2Sections;
+
+    /**
      * 使用的知识单元ID
      */
     private List<String> knowledgeUnitIds;

+ 0 - 6
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/ReportRequest.java

@@ -28,12 +28,6 @@ public class ReportRequest {
     private String title;
 
     /**
-     * 报告摘要
-     */
-    @Size(max = 1000, message = "报告摘要长度不能超过1000")
-    private String summary;
-
-    /**
      * 段落列表
      */
     private List<SectionRequest> sections;

+ 0 - 5
backend/finrep-web/src/main/java/com/yuxin/finrep/web/rest/dto/ReportResponse.java

@@ -39,11 +39,6 @@ public class ReportResponse {
     private String title;
 
     /**
-     * 报告摘要
-     */
-    private String summary;
-
-    /**
      * 报告状态
      */
     private String status;

BIN
logs/finrep-web-all.2026-04-02.0.gz


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 4737
logs/finrep-web-all.log


BIN
logs/finrep-web-error.2026-04-02.0.gz


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 3060
logs/finrep-web-error.log


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels