launch_review.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. """
  2. 施工方案审查启动接口
  3. 接收审查配置参数,启动AI审查工作流
  4. """
  5. import uuid
  6. import time
  7. from datetime import datetime
  8. from typing import List, Optional, Dict, Any
  9. from pydantic import BaseModel, Field
  10. from fastapi import APIRouter, HTTPException
  11. from core.base.redis_duplicate_checker import RedisDuplicateChecker
  12. from foundation.logger.loggering import server_logger as logger
  13. from foundation.trace.trace_context import TraceContext, auto_trace
  14. from foundation.utils.redis_utils import get_file_info, delete_file_info
  15. from core.base.workflow_manager import WorkflowManager
  16. from views.construction_review.file_upload import validate_upload_parameters
  17. from .schemas.error_schemas import LaunchReviewErrors
  18. launch_review_router = APIRouter(prefix="/sgsc", tags=["审查启动"])
  19. duplicatechecker = RedisDuplicateChecker()
  20. # 初始化工作流管理器
  21. workflow_manager = WorkflowManager(
  22. max_concurrent_docs=3,
  23. max_concurrent_reviews=5
  24. )
  25. class LaunchReviewRequest(BaseModel):
  26. """启动审查请求模型"""
  27. callback_task_id: str = Field(..., description="回调任务ID,从文件上传接口获取")
  28. review_config: List[str] = Field(
  29. ...,
  30. description="审查配置列表,包含的项为启用状态"
  31. )
  32. project_plan_type: str = Field(
  33. "bridge_up_part",
  34. description="工程方案类型,当前仅支持 bridge_up_part"
  35. )
  36. class Config:
  37. extra = "forbid" # 禁止额外的字段
  38. class LaunchReviewResponse(BaseModel):
  39. """启动审查响应模型"""
  40. code: int
  41. data: dict
  42. def validate_review_config(review_config: List[str]) -> None:
  43. """验证审查配置参数"""
  44. # 检查review_config是否为空
  45. if not review_config or len(review_config) == 0:
  46. raise LaunchReviewErrors.enum_type_cannot_be_null()
  47. # 支持的审查项枚举值
  48. supported_review_items = {
  49. 'sensitive_word_check', # 词句语法检查
  50. 'semantic_logic_check', # 语义逻辑审查
  51. 'completeness_check', # 条文完整性审查
  52. 'timeliness_check', # 时效性审查
  53. 'reference_check', # 规范性审查
  54. 'sensitive_words_check', # 敏感词审查
  55. 'mandatory_standards_check', # 强制性标准检查
  56. 'technical_parameters_check', # 技术参数精确检查
  57. 'design_values_check' # 设计值符合性检查
  58. }
  59. # 检查是否包含不支持的审查项
  60. unsupported_items = set(review_config) - supported_review_items
  61. if unsupported_items:
  62. raise LaunchReviewErrors.enum_type_invalid()
  63. def validate_project_plan_type(project_plan_type: str) -> None:
  64. """验证工程方案类型"""
  65. # 当前支持的工程方案类型
  66. supported_types = {'bridge_up_part'} # 桥梁上部结构
  67. if project_plan_type not in supported_types:
  68. raise LaunchReviewErrors.project_plan_type_invalid()
  69. @launch_review_router.post("/sse/launch_review", response_model=LaunchReviewResponse)
  70. @auto_trace(generate_if_missing=True)
  71. async def launch_review(request_data: LaunchReviewRequest):
  72. """
  73. 启动施工方案审查
  74. Args:
  75. request_data: 启动审查请求参数
  76. Returns:
  77. LaunchReviewResponse: 包含任务ID的响应
  78. """
  79. try:
  80. callback_task_id = request_data.callback_task_id
  81. review_config = request_data.review_config
  82. project_plan_type = request_data.project_plan_type
  83. logger.info(f"收到审查启动请求: callback_task_id={callback_task_id}")
  84. # 验证审查配置
  85. validate_review_config(review_config)
  86. # 验证工程方案类型
  87. validate_project_plan_type(project_plan_type)
  88. try:
  89. # 从callback_task_id中提取file_id (格式: file_id-timestamp)
  90. file_id = callback_task_id.rsplit('-', 1)[0] if '-' in callback_task_id else callback_task_id
  91. # 检查重复任务
  92. if await duplicatechecker.is_duplicate_task(file_id):
  93. raise LaunchReviewErrors.task_already_exists()
  94. # 获取文件信息(确保包含文件内容)
  95. file_info = await get_file_info(file_id, include_content=True)
  96. if not file_info:
  97. raise LaunchReviewErrors.task_not_found()
  98. # 验证必要的字段是否存在
  99. if 'file_content' not in file_info:
  100. logger.error(f"文件信息中缺少file_content字段,可用字段: {list(file_info.keys())}")
  101. raise LaunchReviewErrors.task_not_found()
  102. # 添加审查配置到文件信息
  103. file_info.update({
  104. 'review_config': review_config,
  105. 'project_plan_type': project_plan_type,
  106. 'launched_at': int(time.time())
  107. })
  108. logger.info(f"获取到文件信息: file_id={file_id}, 包含字段: {list(file_info.keys())}")
  109. logger.info(f"文件内容大小: {len(file_info.get('file_content', b''))} bytes")
  110. # 注意:暂不删除Redis缓存,让工作流处理完成后再清理
  111. # await delete_file_info(file_id)
  112. logger.info(f"保留Redis缓存供工作流使用: file_info:{file_id}")
  113. except Exception as e:
  114. logger.error(f"获取文件信息失败: {str(e)}")
  115. raise LaunchReviewErrors.file_info_not_found(e)
  116. # 提交处理任务到工作流管理器
  117. task_id = await workflow_manager.submit_task_processing(file_info)
  118. except HTTPException:
  119. # 重新抛出HTTP异常
  120. raise
  121. except Exception as e:
  122. logger.error(f"启动审查失败: {str(e)}")
  123. raise LaunchReviewErrors.internal_error(e)