feat:集成事件决策/规则/聚合管道

This commit is contained in:
2026-06-11 17:27:57 +08:00
parent 51279c00ab
commit 279bffbcde

View File

@@ -10,6 +10,10 @@ from PIL import Image, ImageDraw, ImageFont
import torch import torch
from .loitering_service import get_loitering_service from .loitering_service import get_loitering_service
from .adapters import DetectionAdapter
from .event import AlertRuleEngine, EventAggregator, EventDecisionEngine
from core.settings import get_settings
from models.event_schemas import DetectionSource
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,6 +30,18 @@ class DetectionService:
# 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时) # 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时)
self.loitering_service = get_loitering_service() self.loitering_service = get_loitering_service()
# 事件管道 (MVP-1 / P1-P2-P5)
settings = get_settings()
self.decision_engine = EventDecisionEngine(
min_confidence=settings.detection.min_confidence,
)
rules_dir = os.path.join(self.base_dir, settings.event_engine.rules_dir)
self.rule_engine = AlertRuleEngine.from_directory(rules_dir)
self.event_aggregator = EventAggregator(
dedup_window_seconds=settings.event_engine.dedup_window_seconds,
max_active_events=settings.event_engine.max_active_events,
)
async def detect_image( async def detect_image(
self, self,
image: np.ndarray, image: np.ndarray,
@@ -133,6 +149,9 @@ class DetectionService:
result_data, algorithm_config result_data, algorithm_config
) )
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
result_data = self._apply_event_pipeline(result_data, model_id)
return result_data return result_data
except Exception as e: except Exception as e:
logger.error(f"图片检测失败: {e}") logger.error(f"图片检测失败: {e}")
@@ -171,6 +190,7 @@ class DetectionService:
if draw: if draw:
frame = self.draw_detections(frame, detections, fps) frame = self.draw_detections(frame, detections, fps)
# detect_fire_composite 已自带事件管道,此处无需再次调用
return frame, result_data return frame, result_data
# 普通单模型检测 # 普通单模型检测
@@ -294,6 +314,9 @@ class DetectionService:
if draw: if draw:
frame = self.draw_detections(frame, detections, fps) frame = self.draw_detections(frame, detections, fps)
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
result_data = self._apply_event_pipeline(result_data, model_id=model_id)
return frame, result_data return frame, result_data
except Exception as e: except Exception as e:
logger.error(f"帧检测失败: {e}") logger.error(f"帧检测失败: {e}")
@@ -385,7 +408,7 @@ class DetectionService:
processing_time = time.time() - start_time processing_time = time.time() - start_time
avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0 avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0
return { result_data = {
'success': True, 'success': True,
'message': '复合火灾检测完成', 'message': '复合火灾检测完成',
'detections': all_detections, 'detections': all_detections,
@@ -401,6 +424,14 @@ class DetectionService:
} }
} }
# 事件管道 (MVP-1): 复合火灾检测产出 candidate/alert 事件
result_data = self._apply_event_pipeline(
result_data,
model_id='fire_composite',
source=DetectionSource.COMPOSITE,
)
return result_data
except Exception as e: except Exception as e:
logger.error(f"复合火灾检测失败: {e}") logger.error(f"复合火灾检测失败: {e}")
import traceback import traceback
@@ -470,6 +501,49 @@ class DetectionService:
return result_data return result_data
def _apply_event_pipeline(
self,
result_data: Dict,
model_id: Optional[str] = None,
source_id: Optional[str] = None,
source: DetectionSource = DetectionSource.YOLO,
) -> Dict:
"""对检测结果执行 决策 → 规则 → 聚合 三段管道。
在 ``result_data`` 中追加两个字段:
- ``candidate_events``: List[dict] 决策引擎产出的候选事件
- ``alert_events``: List[dict] 规则命中后经聚合的预警事件
"""
if not result_data.get('success') or not result_data.get('detections'):
result_data['candidate_events'] = []
result_data['alert_events'] = []
return result_data
try:
unified_result = DetectionAdapter.from_yolo(
result_data, model_id=model_id, source=source
)
candidates = self.decision_engine.decide(unified_result, source_id=source_id)
alerts = self.rule_engine.evaluate(candidates)
emitted = self.event_aggregator.aggregate(alerts)
result_data['candidate_events'] = [
event.model_dump(mode='json') for event in candidates
]
result_data['alert_events'] = [
event.model_dump(mode='json') for event in emitted
]
except Exception as e: # noqa: BLE001
logger.error(f"事件管道执行失败: {e}")
result_data['candidate_events'] = []
result_data['alert_events'] = []
result_data['event_pipeline_error'] = str(e)
return result_data
def draw_detections( def draw_detections(
self, self,
frame: np.ndarray, frame: np.ndarray,