404 lines
18 KiB
Python
404 lines
18 KiB
Python
from datetime import datetime
|
|
import json
|
|
import asyncio
|
|
from typing import Dict
|
|
from fastapi import HTTPException
|
|
from openai import OpenAI
|
|
from .config import (
|
|
CAMERA_DB_MAPPING,
|
|
BEHAVIOR_CATEGORIES,
|
|
ABNORMAL_BEHAVIORS,
|
|
REDIS_CACHE_CONFIG,
|
|
ai_client
|
|
)
|
|
|
|
async def get_camera_data_by_date(camera_id: str, date: str, redis_client, is_face: bool = False):
|
|
"""获取摄像头某天的所有数据"""
|
|
try:
|
|
if camera_id not in CAMERA_DB_MAPPING:
|
|
raise HTTPException(status_code=400, detail="Invalid camera ID")
|
|
|
|
# 使用新的键格式进行模式匹配
|
|
pattern = f"{'face_' if is_face else ''}{camera_id}_{date}_*"
|
|
all_keys = redis_client.keys(pattern)
|
|
|
|
if not all_keys:
|
|
return {"message": "No data found", "data": None}
|
|
|
|
# 获取所有键的数据并解析
|
|
all_data = {}
|
|
for key in all_keys:
|
|
data = redis_client.get(key)
|
|
if data:
|
|
all_data[key] = json.loads(data)
|
|
|
|
return {
|
|
"message": "success",
|
|
"data": all_data,
|
|
"total_records": len(all_data)
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
def background_generate_report(date_no_hyphen: str, redis_connections):
|
|
"""后台生成报告的函数"""
|
|
try:
|
|
print(f"\n=== 后台任务开始生成报告 {date_no_hyphen} ===")
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
report_redis = redis_connections["report"]
|
|
task_key = f"task_status_{date_no_hyphen}"
|
|
report_redis.setex(
|
|
task_key,
|
|
REDIS_CACHE_CONFIG["task_status_expiry"],
|
|
json.dumps({"status": "running"})
|
|
)
|
|
|
|
print("开始调用 generate_daily_report...")
|
|
report = loop.run_until_complete(generate_daily_report(date_no_hyphen, redis_connections))
|
|
print(f"报告生成结果: {report.get('message', 'unknown')}")
|
|
|
|
if report.get("message") != "no_data":
|
|
print("报告生成成功,准备保存到Redis...")
|
|
report_key = f"report_{date_no_hyphen}"
|
|
report_redis.setex(
|
|
report_key,
|
|
REDIS_CACHE_CONFIG["report_expiry"],
|
|
json.dumps(report)
|
|
)
|
|
print(f"报告已保存到Redis,键名: {report_key}")
|
|
report_redis.setex(
|
|
task_key,
|
|
REDIS_CACHE_CONFIG["task_status_expiry"],
|
|
json.dumps({"status": "completed"})
|
|
)
|
|
else:
|
|
report_redis.setex(
|
|
task_key,
|
|
REDIS_CACHE_CONFIG["task_status_expiry"],
|
|
json.dumps({"status": "completed"})
|
|
)
|
|
|
|
print("任务状态已更新为completed")
|
|
|
|
except Exception as e:
|
|
print(f"报告生成失败: {str(e)}")
|
|
report_redis.setex(
|
|
task_key,
|
|
REDIS_CACHE_CONFIG["task_status_expiry"],
|
|
json.dumps({
|
|
"status": "failed",
|
|
"error": str(e)
|
|
})
|
|
)
|
|
print("任务状态已更新为failed")
|
|
finally:
|
|
loop.close()
|
|
print(f"=== 后台任务结束 {date_no_hyphen} ===\n")
|
|
|
|
async def generate_daily_report(date: str, redis_connections) -> Dict:
|
|
"""生成每日分析报告"""
|
|
print(f"\n=== 开始生成日报 {date} ===")
|
|
|
|
# 初始化数据收集结构
|
|
data_collection = {
|
|
"date": date,
|
|
"total_events": 0,
|
|
"abnormal_events": 0,
|
|
"camera_num": set(),
|
|
"activity_areas": {}, # 活动区域统计
|
|
"behavior_distribution": {}, # 行为分布
|
|
"hourly_stats": {}, # 每小时统计
|
|
"category_stats": { # 各类别行为统计
|
|
category: {
|
|
"count": 0,
|
|
"behaviors": {}
|
|
} for category in BEHAVIOR_CATEGORIES.keys()
|
|
},
|
|
"abnormal_stats": {
|
|
"behaviors": [],
|
|
"times": [],
|
|
"locations": []
|
|
}
|
|
}
|
|
|
|
# 初始化摄像头小时统计
|
|
camera_hourly_counts = {
|
|
camera_id: {f"{hour:02d}": 0 for hour in range(24)}
|
|
for camera_id in CAMERA_DB_MAPPING.keys()
|
|
if camera_id != "report"
|
|
}
|
|
|
|
# 遍历所有摄像头数据
|
|
has_any_data = False
|
|
total_cameras = len([cam for cam in CAMERA_DB_MAPPING.keys() if cam != "report"])
|
|
processed_cameras = 0
|
|
|
|
print(f"开始处理 {total_cameras} 个摄像头的数据")
|
|
|
|
# 数据收集和预处理
|
|
for camera_id, redis_client in redis_connections.items():
|
|
if camera_id == "report":
|
|
continue
|
|
|
|
processed_cameras += 1
|
|
print(f"\n处理摄像头 {camera_id} ({processed_cameras}/{total_cameras})")
|
|
|
|
camera_event_count = 0
|
|
|
|
for hour in range(24):
|
|
hour_str = f"{hour:02d}"
|
|
pattern = f"{camera_id}_{date}_{hour_str}*"
|
|
hour_keys = redis_client.keys(pattern)
|
|
|
|
for key in hour_keys:
|
|
hour_data = redis_client.get(key)
|
|
if hour_data:
|
|
has_any_data = True
|
|
hour_json = json.loads(hour_data)
|
|
|
|
for video_file, video_data in hour_json.items():
|
|
if "video_analysis" in video_data:
|
|
analysis = video_data["video_analysis"]["qwen-7B"]["extracted_info"]
|
|
|
|
behaviors = analysis.get("actions", [])
|
|
camera_event_count += len(behaviors)
|
|
data_collection["total_events"] += len(behaviors)
|
|
|
|
environment = analysis.get("environment", "")
|
|
if environment:
|
|
if isinstance(environment, list):
|
|
for env in environment:
|
|
if isinstance(env, str):
|
|
data_collection["activity_areas"][env] = \
|
|
data_collection["activity_areas"].get(env, 0) + 1
|
|
elif isinstance(environment, str):
|
|
data_collection["activity_areas"][environment] = \
|
|
data_collection["activity_areas"].get(environment, 0) + 1
|
|
|
|
if hour_str not in data_collection["hourly_stats"]:
|
|
data_collection["hourly_stats"][hour_str] = {
|
|
"event_count": 0,
|
|
"categories": {cat: 0 for cat in BEHAVIOR_CATEGORIES.keys()}
|
|
}
|
|
|
|
data_collection["hourly_stats"][hour_str]["event_count"] += len(behaviors)
|
|
camera_hourly_counts[camera_id][hour_str] += len(behaviors)
|
|
|
|
for behavior in behaviors:
|
|
data_collection["behavior_distribution"][behavior] = \
|
|
data_collection["behavior_distribution"].get(behavior, 0) + 1
|
|
|
|
behavior_categorized = False
|
|
for category, keywords in BEHAVIOR_CATEGORIES.items():
|
|
if any(keyword in behavior for keyword in keywords):
|
|
data_collection["category_stats"][category]["count"] += 1
|
|
|
|
if behavior not in data_collection["category_stats"][category]["behaviors"]:
|
|
data_collection["category_stats"][category]["behaviors"][behavior] = {
|
|
"count": 0,
|
|
"occurrences": {}
|
|
}
|
|
|
|
occurrence_key = f"{camera_id}_{hour_str}"
|
|
if occurrence_key not in data_collection["category_stats"][category]["behaviors"][behavior]["occurrences"]:
|
|
data_collection["category_stats"][category]["behaviors"][behavior]["count"] += 1
|
|
data_collection["category_stats"][category]["behaviors"][behavior]["occurrences"][occurrence_key] = {
|
|
"time": f"{hour_str}:00",
|
|
"camera": camera_id
|
|
}
|
|
|
|
data_collection["hourly_stats"][hour_str]["categories"][category] += 1
|
|
behavior_categorized = True
|
|
break
|
|
|
|
if not behavior_categorized:
|
|
data_collection["category_stats"]["其他"]["count"] += 1
|
|
if behavior not in data_collection["category_stats"]["其他"]["behaviors"]:
|
|
data_collection["category_stats"]["其他"]["behaviors"][behavior] = {
|
|
"count": 0,
|
|
"occurrences": {}
|
|
}
|
|
|
|
occurrence_key = f"{camera_id}_{hour_str}"
|
|
if occurrence_key not in data_collection["category_stats"]["其他"]["behaviors"][behavior]["occurrences"]:
|
|
data_collection["category_stats"]["其他"]["behaviors"][behavior]["count"] += 1
|
|
data_collection["category_stats"]["其他"]["behaviors"][behavior]["occurrences"][occurrence_key] = {
|
|
"time": f"{hour_str}:00",
|
|
"camera": camera_id
|
|
}
|
|
|
|
data_collection["hourly_stats"][hour_str]["categories"]["其他"] += 1
|
|
|
|
if any(abnormal in behavior for abnormal in ABNORMAL_BEHAVIORS):
|
|
occurrence_key = f"{camera_id}_{hour_str}"
|
|
abnormal_key = f"{behavior}_{occurrence_key}"
|
|
if abnormal_key not in data_collection["abnormal_stats"]["behaviors"]:
|
|
data_collection["abnormal_events"] += 1
|
|
data_collection["abnormal_stats"]["behaviors"].append({
|
|
"behavior": behavior,
|
|
"time": f"{hour_str}:00",
|
|
"camera": camera_id
|
|
})
|
|
else:
|
|
print(f" - {hour_str}时 无数据")
|
|
|
|
print(f"摄像头 {camera_id} 总计: {camera_event_count} 个事件")
|
|
if camera_event_count > 0:
|
|
data_collection["camera_num"].add(camera_id)
|
|
|
|
if len(data_collection["camera_num"]) == 0:
|
|
print("判定为无数据,返回")
|
|
return {
|
|
"message": "no_data",
|
|
"data": None,
|
|
"detail": "暂无数据"
|
|
}
|
|
|
|
data_collection["camera_num"] = list(data_collection["camera_num"])
|
|
|
|
sorted_hours = sorted(
|
|
data_collection["hourly_stats"].items(),
|
|
key=lambda x: x[1]["event_count"],
|
|
reverse=True
|
|
)
|
|
data_collection["peak_hours"] = [hour for hour, _ in sorted_hours[:3]]
|
|
|
|
preprocessed_data = {
|
|
"日期": data_collection["date"],
|
|
"摄像头数量": len(data_collection["camera_num"]),
|
|
"行为总数": data_collection["total_events"],
|
|
"异常行为数": data_collection["abnormal_events"],
|
|
"行为高峰时段": data_collection["peak_hours"],
|
|
"主要活动区域": data_collection["activity_areas"],
|
|
"行为类别统计": data_collection["category_stats"],
|
|
"异常行为统计": data_collection["abnormal_stats"],
|
|
"每小时行为统计": data_collection["hourly_stats"]
|
|
}
|
|
|
|
ai_analysis = await analyze_experiment_data(preprocessed_data)
|
|
|
|
final_report = {
|
|
"整体活动趋势": ai_analysis["整体活动趋势"],
|
|
"高峰时段分析": ai_analysis["高峰时段分析"],
|
|
"异常行为分析": ai_analysis["异常行为分析"],
|
|
"行为分析": ai_analysis["行为分析"],
|
|
"建议": ai_analysis["建议"]
|
|
}
|
|
|
|
final_report["hourly_distribution"] = []
|
|
for camera_id in camera_hourly_counts:
|
|
total_events = sum(camera_hourly_counts[camera_id].values())
|
|
if total_events > 0:
|
|
camera_data = {
|
|
"camera_id": camera_id,
|
|
"data": []
|
|
}
|
|
for hour in range(24):
|
|
hour_str = f"{hour:02d}"
|
|
hour_data = {
|
|
"hour": f"{hour_str}:00",
|
|
"count": camera_hourly_counts[camera_id][hour_str],
|
|
"categories": data_collection["hourly_stats"].get(hour_str, {}).get("categories", {})
|
|
}
|
|
camera_data["data"].append(hour_data)
|
|
final_report["hourly_distribution"].append(camera_data)
|
|
|
|
return final_report
|
|
|
|
async def analyze_experiment_data(report_info):
|
|
"""使用AI分析实验数据"""
|
|
system_prompt = """
|
|
You are an AI assistant tasked with analyzing data.
|
|
Generate a comprehensive analysis report in JSON format.
|
|
The JSON structure must strictly follow the provided template.
|
|
"""
|
|
|
|
user_prompt = f"""Analyze the preprocessed data based on the following information:
|
|
Preprocessed data: {json.dumps(report_info, ensure_ascii=False)}
|
|
|
|
Generate a JSON response with the following structure:
|
|
|
|
{{
|
|
"整体活动趋势": {{
|
|
"日期": "{report_info['日期']}",
|
|
"摄像头数量": {report_info['摄像头数量']},
|
|
"行为总数": {report_info['行为总数']},
|
|
"异常行为数": {report_info['异常行为数']},
|
|
"行为高峰时段": {json.dumps(report_info['行为高峰时段'], ensure_ascii=False)},
|
|
"主要活动区域": {json.dumps(report_info['主要活动区域'], ensure_ascii=False)}
|
|
}},
|
|
"高峰时段分析": {{
|
|
"高峰时段": "分析行为高峰时段",
|
|
"高峰时段行为": "分析高峰时段主要行为",
|
|
"活动规律": "分析活动规律"
|
|
}},
|
|
"异常行为分析": {{
|
|
"异常行为": "分析异常行为类型",
|
|
"异常行为次数": "分析异常行为频率",
|
|
"异常行为出现时间": "分析异常行为时间分布",
|
|
"异常行为地点": "分析监测到异常行为的摄像头"
|
|
}},
|
|
"行为分析": {{
|
|
"基础动作": {{
|
|
"站立行为": "分析站立相关行为",
|
|
"行走行为": "分析行走相关行为",
|
|
"坐卧行为": "分析坐卧相关行为",
|
|
"其他基础动作": "分析其他基础动作"
|
|
}},
|
|
"日常生活": {{
|
|
"饮食情况": "分析饮食相关行为",
|
|
"休息情况": "分析休息相关行为",
|
|
"医疗情况": "分析医疗相关行为"
|
|
}},
|
|
"社交活动": {{
|
|
"交际情况": "分析交际相关行为",
|
|
"娱乐情况": "分析娱乐相关行为",
|
|
"情感表达": "分析情感表达相关行为"
|
|
}},
|
|
"工作学习": {{
|
|
"学习情况": "分析学习相关行为",
|
|
"工作情况": "分析学习相关行为",
|
|
"创作活动": "分析创作相关行为"
|
|
}},
|
|
"运动娱乐": {{
|
|
"运动情况": "分析运动相关行为",
|
|
"运动时长": "分析运动持续时间",
|
|
"运动强度": "分析运动强度"
|
|
}},
|
|
"其他行为": {{
|
|
"出现时间":"分析其他行为出现时间",
|
|
"出现次数":"分析其他行为出现次数"
|
|
}}
|
|
}},
|
|
"建议": {{
|
|
"生活作息": ["建议1", "建议2"],
|
|
"活动安排": ["建议1", "建议2"],
|
|
"安全防护": ["建议1", "建议2"],
|
|
"健康建议": ["建议1", "建议2"]
|
|
}}
|
|
}}
|
|
"""
|
|
|
|
try:
|
|
response = ai_client.chat.completions.create(
|
|
model="deepseek-chat",
|
|
messages=[
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": user_prompt}
|
|
],
|
|
max_tokens=4096,
|
|
temperature=0.7,
|
|
response_format={'type': 'json_object'}
|
|
)
|
|
|
|
return json.loads(response.choices[0].message.content)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="AI分析服务暂时不可用,请稍后重试"
|
|
)
|