137 lines
5.1 KiB
Python
137 lines
5.1 KiB
Python
import json
|
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from backend.app.core.error_codes import ErrorCode
|
|
from backend.app.core.exceptions import AppException
|
|
from backend.app.repositories.ai_repository import AIRepository
|
|
from backend.app.services.audit_service import audit_service
|
|
|
|
|
|
class AIService:
|
|
def __init__(self) -> None:
|
|
self.repository = AIRepository()
|
|
|
|
def recognize_image(self, payload: dict, session: Session | None = None) -> dict:
|
|
image_url = (payload.get("image_url") or "").strip()
|
|
biz_type = (payload.get("biz_type") or "").strip()
|
|
biz_id = payload.get("biz_id")
|
|
|
|
if not image_url:
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="图片地址不能为空", status_code=400)
|
|
if not biz_type:
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="业务类型不能为空", status_code=400)
|
|
if image_url.startswith("mock://fail"):
|
|
raise AppException(code=ErrorCode.THIRD_PARTY_FAILED, message="AI 识别失败", status_code=400)
|
|
|
|
suggested_result = self._build_suggested_result(image_url, biz_type, biz_id)
|
|
raw_result = {
|
|
"provider": "mock_aliyun_adapter",
|
|
"image_url": image_url,
|
|
"fields": suggested_result,
|
|
}
|
|
confidence = 0.92
|
|
|
|
if session is not None:
|
|
try:
|
|
log = self.repository.create_log(
|
|
session,
|
|
{
|
|
"biz_type": biz_type,
|
|
"biz_id": biz_id,
|
|
"image_url": image_url,
|
|
"raw_result": json.dumps(raw_result, ensure_ascii=False),
|
|
"confidence": confidence,
|
|
"corrected_result": None,
|
|
"created_by": None,
|
|
},
|
|
)
|
|
audit_service.write_log(
|
|
session,
|
|
{
|
|
"operate_type": "ai_recognize",
|
|
"biz_type": biz_type,
|
|
"biz_id": biz_id,
|
|
"before_value": None,
|
|
"after_value": {
|
|
"log_id": log.id,
|
|
"confidence": confidence,
|
|
"suggested_result": suggested_result,
|
|
},
|
|
"remark": f"AI识别图片 {image_url}",
|
|
},
|
|
)
|
|
session.commit()
|
|
return {
|
|
"log_id": log.id,
|
|
"raw_result": raw_result,
|
|
"confidence": confidence,
|
|
"suggested_result": suggested_result,
|
|
}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {
|
|
"log_id": 7001,
|
|
"raw_result": raw_result,
|
|
"confidence": confidence,
|
|
"suggested_result": suggested_result,
|
|
}
|
|
|
|
def correct_result(self, log_id: int, payload: dict, session: Session | None = None) -> dict:
|
|
corrected_result = payload.get("corrected_result")
|
|
if not isinstance(corrected_result, dict) or not corrected_result:
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="修正结果不能为空", status_code=400)
|
|
|
|
if session is not None:
|
|
try:
|
|
log = self.repository.get_log(session, log_id)
|
|
if log is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="识别记录不存在", status_code=404)
|
|
before_value = self._safe_load_json(log.corrected_result)
|
|
log.corrected_result = json.dumps(corrected_result, ensure_ascii=False)
|
|
session.add(log)
|
|
audit_service.write_log(
|
|
session,
|
|
{
|
|
"operate_type": "ai_correct",
|
|
"biz_type": log.biz_type,
|
|
"biz_id": log.biz_id,
|
|
"before_value": before_value,
|
|
"after_value": corrected_result,
|
|
"remark": f"修正AI识别结果 {log_id}",
|
|
},
|
|
)
|
|
session.commit()
|
|
return {"log_id": log.id, "corrected": True}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {"log_id": log_id, "corrected": True}
|
|
|
|
def _build_suggested_result(self, image_url: str, biz_type: str, biz_id: int) -> dict:
|
|
tail = image_url.rsplit("/", 1)[-1]
|
|
return {
|
|
"image_name": tail,
|
|
"biz_type": biz_type,
|
|
"biz_id": biz_id,
|
|
}
|
|
|
|
def _safe_load_json(self, value: str | None):
|
|
if not value:
|
|
return None
|
|
try:
|
|
return json.loads(value)
|
|
except json.JSONDecodeError:
|
|
return value
|
|
|
|
|
|
ai_service = AIService()
|