|
@@ -2,15 +2,16 @@
|
|
|
Hazard detection routes.
|
|
Hazard detection routes.
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
-from typing import Any, Dict, List
|
|
|
|
|
|
|
+from typing import Any, Dict, List, Optional
|
|
|
import io
|
|
import io
|
|
|
import json
|
|
import json
|
|
|
import time
|
|
import time
|
|
|
|
|
+import traceback
|
|
|
from urllib.parse import urlparse, parse_qs
|
|
from urllib.parse import urlparse, parse_qs
|
|
|
|
|
|
|
|
import httpx
|
|
import httpx
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from fastapi import APIRouter, Depends, Request
|
|
|
-from pydantic import BaseModel
|
|
|
|
|
|
|
+from pydantic import BaseModel, Field
|
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy.orm import Session
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
|
|
|
@@ -32,14 +33,26 @@ class HazardRequest(BaseModel):
|
|
|
image: str = ""
|
|
image: str = ""
|
|
|
image_url: str = ""
|
|
image_url: str = ""
|
|
|
date: str = ""
|
|
date: str = ""
|
|
|
|
|
+ title: str = ""
|
|
|
|
|
+ description: str = ""
|
|
|
|
|
+ labels: str = ""
|
|
|
|
|
+ current_step: Optional[int] = None
|
|
|
|
|
+ hazard_count: Optional[int] = None
|
|
|
|
|
+ third_scene: str = ""
|
|
|
|
|
+ second_scene: str = ""
|
|
|
|
|
+ recognition_image_url: str = ""
|
|
|
|
|
+ original_image_url: str = ""
|
|
|
|
|
+ tag_type: str = ""
|
|
|
|
|
+ hazard_details: Any = Field(default_factory=dict)
|
|
|
|
|
+ save_history: bool = True
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def source_image(self) -> str:
|
|
def source_image(self) -> str:
|
|
|
- return self.image or self.image_url
|
|
|
|
|
|
|
+ return self.image or self.image_url or self.original_image_url or ""
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def source_scene(self) -> str:
|
|
def source_scene(self) -> str:
|
|
|
- return self.scene_name or self.scene_type
|
|
|
|
|
|
|
+ return self.scene_name or self.scene_type or self.tag_type
|
|
|
|
|
|
|
|
|
|
|
|
|
class SaveStepRequest(BaseModel):
|
|
class SaveStepRequest(BaseModel):
|
|
@@ -192,7 +205,7 @@ def _unique_ordered(items: List[str]) -> List[str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_scene_relations(
|
|
def _build_scene_relations(
|
|
|
- db: Session, normalized_labels: List[str], scene_key: str
|
|
|
|
|
|
|
+ db: Session, normalized_labels: List[str]
|
|
|
) -> tuple[list[str], dict[str, list[str]]]:
|
|
) -> tuple[list[str], dict[str, list[str]]]:
|
|
|
third_scene_names: List[str] = []
|
|
third_scene_names: List[str] = []
|
|
|
element_hazards: Dict[str, List[str]] = {}
|
|
element_hazards: Dict[str, List[str]] = {}
|
|
@@ -243,31 +256,56 @@ async def hazard(
|
|
|
logger.warning("[hazard] request.state.user is empty")
|
|
logger.warning("[hazard] request.state.user is empty")
|
|
|
return {"statusCode": 401, "msg": "获取用户信息失败"}
|
|
return {"statusCode": 401, "msg": "获取用户信息失败"}
|
|
|
|
|
|
|
|
|
|
+ user_id = getattr(user, "id", None) or getattr(user, "user_id", None) or 1
|
|
|
|
|
+ user_code = _get_user_code(user)
|
|
|
|
|
+ request_id = getattr(request.state, "request_id", None) or f"hazard-{int(time.time() * 1000)}"
|
|
|
|
|
+
|
|
|
try:
|
|
try:
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "[hazard] incoming payload: scene_name=%r, image_present=%s, image_len=%s, date=%r",
|
|
|
|
|
|
|
+ "[hazard][%s] incoming payload: user_id=%r, user_code=%r, scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, image_len=%s, image_url_len=%s, date=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ user_id,
|
|
|
|
|
+ user_code,
|
|
|
data.scene_name,
|
|
data.scene_name,
|
|
|
|
|
+ data.scene_type,
|
|
|
bool(data.image),
|
|
bool(data.image),
|
|
|
|
|
+ bool(data.image_url),
|
|
|
len(data.image or ""),
|
|
len(data.image or ""),
|
|
|
|
|
+ len(data.image_url or ""),
|
|
|
data.date,
|
|
data.date,
|
|
|
)
|
|
)
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "[hazard] request headers: content_type=%r, content_length=%r, user_agent=%r, path=%r",
|
|
|
|
|
|
|
+ "[hazard][%s] request headers: content_type=%r, content_length=%r, user_agent=%r, path=%r, method=%r",
|
|
|
|
|
+ request_id,
|
|
|
request.headers.get("content-type"),
|
|
request.headers.get("content-type"),
|
|
|
request.headers.get("content-length"),
|
|
request.headers.get("content-length"),
|
|
|
request.headers.get("user-agent"),
|
|
request.headers.get("user-agent"),
|
|
|
request.url.path,
|
|
request.url.path,
|
|
|
|
|
+ request.method,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
source_image_url = _resolve_image_url(data.source_image)
|
|
source_image_url = _resolve_image_url(data.source_image)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] resolved image url: raw=%r, resolved=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ data.source_image,
|
|
|
|
|
+ source_image_url,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
scene_key = _resolve_scene_key(data.source_scene)
|
|
scene_key = _resolve_scene_key(data.source_scene)
|
|
|
if not scene_key:
|
|
if not scene_key:
|
|
|
scene_key = DEFAULT_SCENE_KEY
|
|
scene_key = DEFAULT_SCENE_KEY
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] resolved scene: source_scene=%r, scene_key=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ data.source_scene,
|
|
|
|
|
+ scene_key,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
if not source_image_url:
|
|
if not source_image_url:
|
|
|
logger.warning(
|
|
logger.warning(
|
|
|
- "[hazard] validation failed: scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, raw_image_len=%s, raw_image_url_len=%s",
|
|
|
|
|
|
|
+ "[hazard][%s] validation failed: scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, raw_image_len=%s, raw_image_url_len=%s",
|
|
|
|
|
+ request_id,
|
|
|
data.scene_name,
|
|
data.scene_name,
|
|
|
data.scene_type,
|
|
data.scene_type,
|
|
|
bool(data.image),
|
|
bool(data.image),
|
|
@@ -277,35 +315,70 @@ async def hazard(
|
|
|
)
|
|
)
|
|
|
return {"statusCode": 400, "msg": "图片链接不能为空"}
|
|
return {"statusCode": 400, "msg": "图片链接不能为空"}
|
|
|
|
|
|
|
|
- if not _scene_exists(db, scene_key):
|
|
|
|
|
- logger.warning("[hazard] scene not found: %r", scene_key)
|
|
|
|
|
- return {"statusCode": 500, "msg": "场景名称不存在"}
|
|
|
|
|
-
|
|
|
|
|
|
|
+ scene_exists = _scene_exists(db, scene_key)
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "[hazard] normalized payload: scene_key=%r, image_len=%s, date=%r",
|
|
|
|
|
|
|
+ "[hazard][%s] scene lookup result: scene_key=%r, exists=%s",
|
|
|
|
|
+ request_id,
|
|
|
scene_key,
|
|
scene_key,
|
|
|
- len(source_image_url),
|
|
|
|
|
- data.date,
|
|
|
|
|
|
|
+ scene_exists,
|
|
|
)
|
|
)
|
|
|
|
|
+ if not scene_exists:
|
|
|
|
|
+ logger.warning("[hazard][%s] scene not found: %r", request_id, scene_key)
|
|
|
|
|
+ return {"statusCode": 500, "msg": "场景名称不存在"}
|
|
|
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] downloading image: url=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ source_image_url,
|
|
|
|
|
+ )
|
|
|
try:
|
|
try:
|
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
|
img_response = await client.get(source_image_url)
|
|
img_response = await client.get(source_image_url)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] image download response: status_code=%s, content_type=%r, content_length=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ img_response.status_code,
|
|
|
|
|
+ img_response.headers.get("content-type"),
|
|
|
|
|
+ img_response.headers.get("content-length"),
|
|
|
|
|
+ )
|
|
|
img_response.raise_for_status()
|
|
img_response.raise_for_status()
|
|
|
image_bytes = img_response.content
|
|
image_bytes = img_response.content
|
|
|
except httpx.HTTPError as e:
|
|
except httpx.HTTPError as e:
|
|
|
logger.error(
|
|
logger.error(
|
|
|
- f"下载图片失败: {e}",
|
|
|
|
|
- exc_info=True,
|
|
|
|
|
|
|
+ "[hazard][%s] 下载图片失败: url=%r, error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ source_image_url,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
)
|
|
)
|
|
|
return {"statusCode": 500, "msg": f"下载图片失败: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"下载图片失败: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] image downloaded: bytes=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ len(image_bytes),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
try:
|
|
try:
|
|
|
|
|
+ logger.info("[hazard][%s] calling yolo_service.detect_hazards: scene_key=%r", request_id, scene_key)
|
|
|
yolo_result = await yolo_service.detect_hazards(image_bytes, scene_key)
|
|
yolo_result = await yolo_service.detect_hazards(image_bytes, scene_key)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] yolo result summary: keys=%s, labels_count=%s, boxes_count=%s, scores_count=%s, model_type=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ list(yolo_result.keys()) if isinstance(yolo_result, dict) else type(yolo_result).__name__,
|
|
|
|
|
+ len((yolo_result or {}).get("labels", []) or []) if isinstance(yolo_result, dict) else -1,
|
|
|
|
|
+ len((yolo_result or {}).get("boxes", []) or []) if isinstance(yolo_result, dict) else -1,
|
|
|
|
|
+ len((yolo_result or {}).get("scores", []) or []) if isinstance(yolo_result, dict) else -1,
|
|
|
|
|
+ (yolo_result or {}).get("model_type", "") if isinstance(yolo_result, dict) else "",
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.debug("[hazard][%s] yolo result raw: %s", request_id, json.dumps(yolo_result, ensure_ascii=False, default=str))
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(
|
|
logger.error(
|
|
|
- f"YOLO检测失败: sceneName={scene_key}, error={e}",
|
|
|
|
|
- exc_info=True,
|
|
|
|
|
|
|
+ "[hazard][%s] YOLO检测失败: scene_key=%r, error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ scene_key,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
)
|
|
)
|
|
|
return {"statusCode": 500, "msg": f"YOLO API返回错误: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"YOLO API返回错误: {str(e)}"}
|
|
|
|
|
|
|
@@ -316,7 +389,8 @@ async def hazard(
|
|
|
|
|
|
|
|
if not labels:
|
|
if not labels:
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "[hazard] YOLO返回空结果: scene_key=%r, model_type=%r, image_len=%s",
|
|
|
|
|
|
|
+ "[hazard][%s] YOLO返回空结果: scene_key=%r, model_type=%r, image_bytes=%s",
|
|
|
|
|
+ request_id,
|
|
|
scene_key,
|
|
scene_key,
|
|
|
model_type,
|
|
model_type,
|
|
|
len(image_bytes),
|
|
len(image_bytes),
|
|
@@ -344,6 +418,13 @@ async def hazard(
|
|
|
normalized_labels = [
|
|
normalized_labels = [
|
|
|
_normalize_label_for_scene(str(label), scene_key) for label in labels
|
|
_normalize_label_for_scene(str(label), scene_key) for label in labels
|
|
|
]
|
|
]
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] normalized labels: raw_labels=%s, normalized_labels=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ labels,
|
|
|
|
|
+ normalized_labels,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
detection_results = []
|
|
detection_results = []
|
|
|
for i, label in enumerate(normalized_labels):
|
|
for i, label in enumerate(normalized_labels):
|
|
|
if i < len(scores) and i < len(boxes):
|
|
if i < len(scores) and i < len(boxes):
|
|
@@ -355,17 +436,37 @@ async def hazard(
|
|
|
"percent": f"{scores[i] * 100:.2f}%",
|
|
"percent": f"{scores[i] * 100:.2f}%",
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] detection results built: total=%s, detail=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ len(detection_results),
|
|
|
|
|
+ detection_results,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
third_scene_names, element_hazards = _build_scene_relations(
|
|
third_scene_names, element_hazards = _build_scene_relations(
|
|
|
- db, normalized_labels, scene_key
|
|
|
|
|
|
|
+ db, normalized_labels
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] scene relations built: third_scene_names=%s, element_hazards=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ third_scene_names,
|
|
|
|
|
+ element_hazards,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- user_id = getattr(user, "id", None) or getattr(user, "user_id", None) or 1
|
|
|
|
|
scene_name = scene_key
|
|
scene_name = scene_key
|
|
|
scene_display_name = SCENE_DISPLAY_NAMES.get(scene_key, scene_name)
|
|
scene_display_name = SCENE_DISPLAY_NAMES.get(scene_key, scene_name)
|
|
|
tag_type = scene_key
|
|
tag_type = scene_key
|
|
|
- title = f"{scene_display_name}隐患提示"
|
|
|
|
|
|
|
+ title = scene_display_name
|
|
|
|
|
+ description = " ".join(third_scene_names)
|
|
|
|
|
+ current_step = 1
|
|
|
|
|
+ hazard_count = len(detection_results)
|
|
|
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] drawing boxes and watermark: user_name=%r, user_account=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ getattr(user, "name", "") or getattr(user, "username", "") or "",
|
|
|
|
|
+ getattr(user, "contactNumber", "") or getattr(user, "account", "") or "",
|
|
|
|
|
+ )
|
|
|
result_image_bytes = await _draw_boxes_and_watermark(
|
|
result_image_bytes = await _draw_boxes_and_watermark(
|
|
|
image_bytes,
|
|
image_bytes,
|
|
|
[
|
|
[
|
|
@@ -379,10 +480,21 @@ async def hazard(
|
|
|
user_name=getattr(user, "name", "") or getattr(user, "username", "") or "",
|
|
user_name=getattr(user, "name", "") or getattr(user, "username", "") or "",
|
|
|
user_account=getattr(user, "contactNumber", "") or getattr(user, "account", "") or "",
|
|
user_account=getattr(user, "contactNumber", "") or getattr(user, "account", "") or "",
|
|
|
)
|
|
)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] annotated image generated: bytes=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ len(result_image_bytes),
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
utc_now = time.gmtime()
|
|
utc_now = time.gmtime()
|
|
|
timestamp = int(time.time())
|
|
timestamp = int(time.time())
|
|
|
file_name = f"hazard_annotated/{time.strftime('%Y', utc_now)}/{time.strftime('%m%d', utc_now)}_{timestamp}.jpg"
|
|
file_name = f"hazard_annotated/{time.strftime('%Y', utc_now)}/{time.strftime('%m%d', utc_now)}_{timestamp}.jpg"
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] uploading annotated image to oss: file_name=%r, folder=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ file_name,
|
|
|
|
|
+ "hazard_detection",
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
upload_fn = getattr(oss_service, "upload_bytes", None)
|
|
upload_fn = getattr(oss_service, "upload_bytes", None)
|
|
@@ -398,54 +510,156 @@ async def hazard(
|
|
|
file_name,
|
|
file_name,
|
|
|
folder="hazard_detection",
|
|
folder="hazard_detection",
|
|
|
)
|
|
)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] oss upload success: result_url=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ result_url,
|
|
|
|
|
+ )
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"上传标注图片到OSS失败: {e}", exc_info=True)
|
|
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "[hazard][%s] 上传标注图片到OSS失败: error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
|
|
+ )
|
|
|
return {"statusCode": 500, "msg": f"上传标注图片到OSS失败: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"上传标注图片到OSS失败: {str(e)}"}
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- record = RecognitionRecord(
|
|
|
|
|
- original_image_url=source_image_url,
|
|
|
|
|
- recognition_image_url=result_url,
|
|
|
|
|
- user_id=user_id,
|
|
|
|
|
- labels=_remove_duplicate_labels([item["label"] for item in detection_results]),
|
|
|
|
|
- title=title,
|
|
|
|
|
- tag_type=tag_type,
|
|
|
|
|
- second_scene="",
|
|
|
|
|
- third_scene=" ".join(third_scene_names),
|
|
|
|
|
|
|
+ labels_text = _remove_duplicate_labels([item["label"] for item in detection_results])
|
|
|
|
|
+ if data.labels.strip():
|
|
|
|
|
+ labels_text = _remove_duplicate_labels(
|
|
|
|
|
+ [item.strip() for item in data.labels.split(",") if item.strip()]
|
|
|
|
|
+ ) or labels_text
|
|
|
|
|
+
|
|
|
|
|
+ record_title = data.title.strip() or title
|
|
|
|
|
+ record_description = data.description.strip() or description
|
|
|
|
|
+ record_third_scene = data.third_scene.strip() or " ".join(third_scene_names)
|
|
|
|
|
+ record_second_scene = data.second_scene.strip()
|
|
|
|
|
+ record_tag_type = data.tag_type.strip() or tag_type
|
|
|
|
|
+ record_current_step = data.current_step if data.current_step is not None else current_step
|
|
|
|
|
+ record_hazard_count = (
|
|
|
|
|
+ data.hazard_count if data.hazard_count is not None else hazard_count
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
|
|
+ raw_hazard_details = data.hazard_details
|
|
|
|
|
+ if isinstance(raw_hazard_details, str):
|
|
|
|
|
+ try:
|
|
|
|
|
+ record_hazard_details = json.loads(raw_hazard_details)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ record_hazard_details = raw_hazard_details
|
|
|
|
|
+ elif raw_hazard_details:
|
|
|
|
|
+ record_hazard_details = raw_hazard_details
|
|
|
|
|
+ else:
|
|
|
|
|
+ record_hazard_details = detection_results
|
|
|
|
|
+
|
|
|
|
|
+ recognition_image_url = (
|
|
|
|
|
+ data.recognition_image_url.strip() or result_url
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ now_ts = int(time.time())
|
|
|
|
|
+ record_payload = {
|
|
|
|
|
+ "original_image_url": source_image_url,
|
|
|
|
|
+ "recognition_image_url": recognition_image_url,
|
|
|
|
|
+ "user_id": user_id,
|
|
|
|
|
+ "scene_type": scene_name,
|
|
|
|
|
+ "hazard_count": record_hazard_count,
|
|
|
|
|
+ "current_step": record_current_step,
|
|
|
|
|
+ "hazard_details": json.dumps(record_hazard_details, ensure_ascii=False)
|
|
|
|
|
+ if not isinstance(record_hazard_details, str)
|
|
|
|
|
+ else record_hazard_details,
|
|
|
|
|
+ "title": record_title,
|
|
|
|
|
+ "description": record_description,
|
|
|
|
|
+ "labels": labels_text,
|
|
|
|
|
+ "tag_type": record_tag_type,
|
|
|
|
|
+ "second_scene": record_second_scene,
|
|
|
|
|
+ "third_scene": record_third_scene,
|
|
|
|
|
+ "created_at": now_ts,
|
|
|
|
|
+ "updated_at": now_ts,
|
|
|
|
|
+ "is_deleted": 0,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] ===== HISTORY SAVE START ===== user_id=%r, scene_key=%r, labels=%r, title=%r, save_history=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ user_id,
|
|
|
|
|
+ scene_key,
|
|
|
|
|
+ labels_text,
|
|
|
|
|
+ record_title,
|
|
|
|
|
+ data.save_history,
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] history record payload: %s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ json.dumps(record_payload, ensure_ascii=False, default=str),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ record = RecognitionRecord(**record_payload)
|
|
|
db.add(record)
|
|
db.add(record)
|
|
|
|
|
+ logger.info("[hazard][%s] record added to session, committing db transaction", request_id)
|
|
|
db.commit()
|
|
db.commit()
|
|
|
|
|
+ logger.info("[hazard][%s] db commit success, refreshing record", request_id)
|
|
|
db.refresh(record)
|
|
db.refresh(record)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] record saved successfully: id=%r, current_step=%r, created_at=%r, updated_at=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ getattr(record, "id", None),
|
|
|
|
|
+ getattr(record, "current_step", None),
|
|
|
|
|
+ getattr(record, "created_at", None),
|
|
|
|
|
+ getattr(record, "updated_at", None),
|
|
|
|
|
+ )
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"识别失败: {e}", exc_info=True)
|
|
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "[hazard][%s] 识别失败(保存记录阶段): error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
|
|
+ )
|
|
|
db.rollback()
|
|
db.rollback()
|
|
|
|
|
+ logger.info("[hazard][%s] db rollback executed after record save failure", request_id)
|
|
|
return {"statusCode": 500, "msg": f"识别失败: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"识别失败: {str(e)}"}
|
|
|
|
|
|
|
|
display_labels = _dedupe_list([item["label"] for item in detection_results])
|
|
display_labels = _dedupe_list([item["label"] for item in detection_results])
|
|
|
|
|
|
|
|
|
|
+ response_data = {
|
|
|
|
|
+ "scene_name": scene_name,
|
|
|
|
|
+ "scene_display_name": scene_display_name,
|
|
|
|
|
+ "tag_type": tag_type,
|
|
|
|
|
+ "title": record_title,
|
|
|
|
|
+ "description": record_description,
|
|
|
|
|
+ "current_step": record_current_step,
|
|
|
|
|
+ "hazard_count": record_hazard_count,
|
|
|
|
|
+ "total_detections": len(detection_results),
|
|
|
|
|
+ "detections": detection_results,
|
|
|
|
|
+ "model_type": model_type,
|
|
|
|
|
+ "original_image": source_image_url,
|
|
|
|
|
+ "annotated_image": recognition_image_url,
|
|
|
|
|
+ "recognition_image_url": recognition_image_url,
|
|
|
|
|
+ "labels": labels_text,
|
|
|
|
|
+ "display_labels": display_labels,
|
|
|
|
|
+ "third_scenes": third_scene_names,
|
|
|
|
|
+ "element_hazards": element_hazards,
|
|
|
|
|
+ "record_id": getattr(record, "id", None),
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[hazard][%s] response prepared successfully: %s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ json.dumps(response_data, ensure_ascii=False, default=str),
|
|
|
|
|
+ )
|
|
|
return {
|
|
return {
|
|
|
"statusCode": 200,
|
|
"statusCode": 200,
|
|
|
"msg": "识别成功",
|
|
"msg": "识别成功",
|
|
|
- "data": {
|
|
|
|
|
- "scene_name": scene_name,
|
|
|
|
|
- "scene_display_name": scene_display_name,
|
|
|
|
|
- "tag_type": tag_type,
|
|
|
|
|
- "title": title,
|
|
|
|
|
- "total_detections": len(detection_results),
|
|
|
|
|
- "detections": detection_results,
|
|
|
|
|
- "model_type": model_type,
|
|
|
|
|
- "original_image": source_image_url,
|
|
|
|
|
- "annotated_image": result_url,
|
|
|
|
|
- "labels": _remove_duplicate_labels([item["label"] for item in detection_results]),
|
|
|
|
|
- "display_labels": display_labels,
|
|
|
|
|
- "third_scenes": third_scene_names,
|
|
|
|
|
- "element_hazards": element_hazards,
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ "data": response_data,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"处理失败: {e}", exc_info=True)
|
|
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "[hazard][%s] 处理失败: error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
|
|
+ )
|
|
|
db.rollback()
|
|
db.rollback()
|
|
|
|
|
+ logger.info("[hazard][%s] db rollback executed in outer exception handler", request_id)
|
|
|
return {"statusCode": 500, "msg": f"处理失败: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"处理失败: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -459,14 +673,34 @@ async def save_step(
|
|
|
|
|
|
|
|
user = request.state.user
|
|
user = request.state.user
|
|
|
if not user:
|
|
if not user:
|
|
|
|
|
+ logger.warning("[save_step] unauthorized request: request.state.user is empty")
|
|
|
return {"statusCode": 401, "msg": "未授权"}
|
|
return {"statusCode": 401, "msg": "未授权"}
|
|
|
|
|
|
|
|
|
|
+ user_id = getattr(user, "user_id", None) or getattr(user, "id", None)
|
|
|
|
|
+ request_id = getattr(request.state, "request_id", None) or f"save-step-{int(time.time() * 1000)}"
|
|
|
|
|
+
|
|
|
try:
|
|
try:
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[save_step][%s] incoming payload: user_id=%r, record_id=%r, current_step=%r, path=%r, method=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ user_id,
|
|
|
|
|
+ data.record_id,
|
|
|
|
|
+ data.current_step,
|
|
|
|
|
+ request.url.path,
|
|
|
|
|
+ request.method,
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[save_step][%s] updating RecognitionRecord: record_id=%r, user_id=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ data.record_id,
|
|
|
|
|
+ user_id,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
affected = (
|
|
affected = (
|
|
|
db.query(RecognitionRecord)
|
|
db.query(RecognitionRecord)
|
|
|
.filter(
|
|
.filter(
|
|
|
RecognitionRecord.id == data.record_id,
|
|
RecognitionRecord.id == data.record_id,
|
|
|
- RecognitionRecord.user_id == getattr(user, "user_id", None) or getattr(user, "id", None),
|
|
|
|
|
|
|
+ RecognitionRecord.user_id == user_id,
|
|
|
)
|
|
)
|
|
|
.update(
|
|
.update(
|
|
|
{
|
|
{
|
|
@@ -475,12 +709,26 @@ async def save_step(
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
|
|
+ logger.info("[save_step][%s] update result: affected_rows=%s", request_id, affected)
|
|
|
|
|
|
|
|
if affected == 0:
|
|
if affected == 0:
|
|
|
|
|
+ logger.warning(
|
|
|
|
|
+ "[save_step][%s] record not found or no permission: record_id=%r, user_id=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ data.record_id,
|
|
|
|
|
+ user_id,
|
|
|
|
|
+ )
|
|
|
return {"statusCode": 404, "msg": "记录不存在"}
|
|
return {"statusCode": 404, "msg": "记录不存在"}
|
|
|
|
|
|
|
|
|
|
+ logger.info("[save_step][%s] committing db transaction", request_id)
|
|
|
db.commit()
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "[save_step][%s] save success: record_id=%r, current_step=%r",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ data.record_id,
|
|
|
|
|
+ data.current_step,
|
|
|
|
|
+ )
|
|
|
return {
|
|
return {
|
|
|
"statusCode": 200,
|
|
"statusCode": 200,
|
|
|
"msg": "保存成功",
|
|
"msg": "保存成功",
|
|
@@ -491,8 +739,14 @@ async def save_step(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"[save_step] 异常: {e}")
|
|
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "[save_step][%s] 异常: error=%s, traceback=%s",
|
|
|
|
|
+ request_id,
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
|
|
+ )
|
|
|
db.rollback()
|
|
db.rollback()
|
|
|
|
|
+ logger.info("[save_step][%s] db rollback executed after save_step failure", request_id)
|
|
|
return {"statusCode": 500, "msg": f"保存失败: {str(e)}"}
|
|
return {"statusCode": 500, "msg": f"保存失败: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -586,5 +840,9 @@ async def _draw_boxes_and_watermark(
|
|
|
return output.getvalue()
|
|
return output.getvalue()
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"[_draw_boxes_and_watermark] 图片处理失败: {e}")
|
|
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "[_draw_boxes_and_watermark] 图片处理失败: %s, traceback=%s",
|
|
|
|
|
+ e,
|
|
|
|
|
+ traceback.format_exc(),
|
|
|
|
|
+ )
|
|
|
return image_bytes
|
|
return image_bytes
|