diff --git a/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py b/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py index 7794eeb..d67c1b0 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py @@ -2,7 +2,7 @@ from typing import Any -from fastapi import Depends, File, Query, UploadFile +from fastapi import Depends, File, Form, Query, UploadFile from fastapi.responses import JSONResponse from fastapi_common.fastapi_common_security.security import verify_access_token @@ -139,6 +139,8 @@ class CrossReviewController(BaseController): async def UploadTaskDocument( TaskId: int, file: UploadFile = File(..., description="上传文档"), + typeId: int | None = Form(None, description="文档类型ID"), + groupId: int | None = Form(None, description="文档子类型ID"), payload: dict[str, Any] = Depends(verify_access_token), ): """向交叉评查任务补传文档。""" @@ -151,6 +153,8 @@ class CrossReviewController(BaseController): FileName=file.filename or "upload.bin", FileContent=content, ContentType=file.content_type, + TypeId=typeId, + GroupId=groupId, ) return Result.success(data=Data, message="交叉评查任务文档上传成功") diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py index 9163bed..e656dc1 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py @@ -93,6 +93,8 @@ class DocumentListItemVO(BaseModel): currentRunId: int | None = Field(None, description="当前运行ID") runStatus: str | None = Field(None, description="当前运行状态") resultStatus: str | None = Field(None, description="当前结果状态") + latestErrorCode: str | None = Field(None, description="最新失败错误码") + latestErrorMessage: str | None = Field(None, description="最新失败错误信息") totalScore: float | None = Field(None, description="总分") passedCount: int | None = Field(None, description="通过数") failedCount: int | None = Field(None, description="失败数") diff --git a/fastapi_modules/fastapi_leaudit/leaudit_bridge/storage_adapter.py b/fastapi_modules/fastapi_leaudit/leaudit_bridge/storage_adapter.py index 35e17d7..34e855d 100644 --- a/fastapi_modules/fastapi_leaudit/leaudit_bridge/storage_adapter.py +++ b/fastapi_modules/fastapi_leaudit/leaudit_bridge/storage_adapter.py @@ -486,6 +486,7 @@ class StorageAdapter: run_id: int | None, phase: str | None, message: str, + error_code: str = "AUDIT_RUN_FAILED", detail_json: dict[str, Any] | None = None, ) -> None: """记录运行失败并更新主表。""" @@ -496,7 +497,7 @@ class StorageAdapter: stage=phase or "persist", messages=[message], level="fatal", - error_code="AUDIT_RUN_FAILED", + error_code=error_code, detail_json=detail_json, ) async with GetAsyncSession() as session: diff --git a/fastapi_modules/fastapi_leaudit/leaudit_bridge/tasks.py b/fastapi_modules/fastapi_leaudit/leaudit_bridge/tasks.py index 415dbb4..fdce2ea 100644 --- a/fastapi_modules/fastapi_leaudit/leaudit_bridge/tasks.py +++ b/fastapi_modules/fastapi_leaudit/leaudit_bridge/tasks.py @@ -12,6 +12,7 @@ from typing import Any, Dict, Optional import fitz from fastapi_common.fastapi_common_logger import logger from leaudit.converters import doc2pdf +from leaudit.ocr.chandra_client import ChandraOCRError from sqlalchemy import select from fastapi_admin.celery_app import celery_app @@ -45,6 +46,48 @@ from fastapi_modules.fastapi_leaudit.models import ( log = logger +def _classify_run_failure(exc: Exception) -> tuple[str, str, dict[str, Any]]: + """将底层异常归一化为可落库、可展示的失败信息。""" + raw_message = str(exc).strip() or exc.__class__.__name__ + detail: dict[str, Any] = { + "rawMessage": raw_message, + "errorType": type(exc).__name__, + } + + if isinstance(exc, ChandraOCRError): + lower_message = raw_message.lower() + detail["service"] = "ocr" + if "all connection attempts failed" in lower_message or "connect" in lower_message: + return ( + "OCR_SERVICE_UNAVAILABLE", + "OCR服务暂时不可用,文档未完成识别,请稍后重试。", + {**detail, "reason": "connection_failed"}, + ) + if "timeout" in lower_message: + return ( + "OCR_SERVICE_TIMEOUT", + "OCR服务处理超时,文档未完成识别,请稍后重试。", + {**detail, "reason": "timeout"}, + ) + if "ocr api returned" in lower_message: + return ( + "OCR_SERVICE_BAD_RESPONSE", + "OCR服务响应异常,文档未完成识别,请稍后重试。", + {**detail, "reason": "bad_status"}, + ) + return ( + "OCR_PROCESSING_FAILED", + "OCR处理失败,文档未完成识别,请稍后重试。", + {**detail, "reason": "ocr_failed"}, + ) + + return ( + "AUDIT_RUN_FAILED", + raw_message, + detail, + ) + + def leaudit_process_document( document_id: int, file_content: bytes, @@ -164,6 +207,7 @@ def leaudit_process_document( except Exception as e: log.error(f"[任务ID: {task_id}] leaudit管线失败: {e}", exc_info=True) + error_code, user_message, error_detail = _classify_run_failure(e) try: loop.run_until_complete(_update_status_safe(document_id, "failed")) if 'run_id' in locals(): @@ -175,11 +219,12 @@ def leaudit_process_document( document_id, run_id=run_id, phase=failed_phase, - message=str(e), + message=user_message, + error_code=error_code, detail_json={ "taskId": task_id, "filename": filename, - "errorType": type(e).__name__, + **error_detail, }, ) ) diff --git a/fastapi_modules/fastapi_leaudit/services/crossReviewService.py b/fastapi_modules/fastapi_leaudit/services/crossReviewService.py index e6b806f..59a93fa 100644 --- a/fastapi_modules/fastapi_leaudit/services/crossReviewService.py +++ b/fastapi_modules/fastapi_leaudit/services/crossReviewService.py @@ -107,6 +107,8 @@ class ICrossReviewService(ABC): FileName: str, FileContent: bytes, ContentType: str | None, + TypeId: int | None = None, + GroupId: int | None = None, ) -> CrossReviewTaskDocumentUploadVO: """向交叉评查任务补传文档。""" ... diff --git a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py index 2e75229..8b4f325 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py @@ -832,6 +832,8 @@ class CrossReviewServiceImpl(ICrossReviewService): FileName: str, FileContent: bytes, ContentType: str | None, + TypeId: int | None = None, + GroupId: int | None = None, ) -> CrossReviewTaskDocumentUploadVO: """向交叉评查任务补传文档。""" async with GetAsyncSession() as session: @@ -857,12 +859,16 @@ class CrossReviewServiceImpl(ICrossReviewService): if not taskMeta: raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "交叉评查任务不存在") + resolvedTypeId = int(TypeId) if TypeId is not None else self._to_int(taskMeta.get("doc_type_id")) + resolvedGroupId = int(GroupId) if GroupId is not None else None + uploadResult = await self.DocumentService.Upload( FileName=FileName, FileContent=FileContent, ContentType=ContentType, - TypeId=self._to_int(taskMeta.get("doc_type_id")), - TypeCode=taskMeta.get("doc_type_code"), + TypeId=resolvedTypeId, + TypeCode=None if resolvedTypeId is not None else taskMeta.get("doc_type_code"), + GroupId=resolvedGroupId, CreatedBy=CurrentUserId, AutoRun=True, )