modify: cleanup project structure and docs
This commit is contained in:
189
ui_sections/hotspot.py
Normal file
189
ui_sections/hotspot.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import plotly.express as px
|
||||
import streamlit as st
|
||||
|
||||
from services.hotspot import (
|
||||
analyze_hotspot_frequency,
|
||||
calculate_hotspot_risk_score,
|
||||
generate_hotspot_strategies,
|
||||
prepare_hotspot_dataset,
|
||||
serialise_datetime_columns,
|
||||
)
|
||||
|
||||
|
||||
@st.cache_data(show_spinner=False)
|
||||
def _prepare_hotspot_data(df):
|
||||
return prepare_hotspot_dataset(df)
|
||||
|
||||
|
||||
def render_hotspot(accident_records, accident_source_name: str | None) -> None:
|
||||
st.header("📍 事故多发路口分析")
|
||||
st.markdown("独立分析事故数据,识别高风险路口并生成针对性策略。")
|
||||
|
||||
if accident_records is None:
|
||||
st.info("请在左侧上传事故数据并点击“应用数据与筛选”后再执行热点分析。")
|
||||
st.markdown(
|
||||
"""
|
||||
### 📝 支持的数据格式要求:
|
||||
- **文件格式**:Excel (.xlsx)
|
||||
- **必要字段**:
|
||||
- `事故时间`
|
||||
- `事故类型`
|
||||
- `事故具体地点`
|
||||
- `所在街道`
|
||||
- `道路类型`
|
||||
- `路口路段类型`
|
||||
"""
|
||||
)
|
||||
return
|
||||
|
||||
with st.spinner("正在准备事故热点数据…"):
|
||||
hotspot_data = _prepare_hotspot_data(accident_records)
|
||||
|
||||
st.success(f"✅ 成功加载数据:{len(hotspot_data)} 条事故记录")
|
||||
|
||||
metric_cols = st.columns(3)
|
||||
with metric_cols[0]:
|
||||
st.metric(
|
||||
"数据时间范围",
|
||||
f"{hotspot_data['事故时间'].min().strftime('%Y-%m-%d')} 至 {hotspot_data['事故时间'].max().strftime('%Y-%m-%d')}",
|
||||
)
|
||||
with metric_cols[1]:
|
||||
st.metric(
|
||||
"事故类型分布",
|
||||
f"财损: {len(hotspot_data[hotspot_data['事故类型'] == '财损'])}起",
|
||||
)
|
||||
with metric_cols[2]:
|
||||
st.metric("涉及区域", f"{hotspot_data['所在街道'].nunique()}个街道")
|
||||
|
||||
st.subheader("🔧 分析参数设置")
|
||||
settings_cols = st.columns(3)
|
||||
with settings_cols[0]:
|
||||
time_window = st.selectbox(
|
||||
"统计时间窗口",
|
||||
options=["7D", "15D", "30D"],
|
||||
index=0,
|
||||
key="hotspot_window",
|
||||
)
|
||||
with settings_cols[1]:
|
||||
min_accidents = st.number_input(
|
||||
"最小事故数", min_value=1, max_value=50, value=3, key="hotspot_min_accidents"
|
||||
)
|
||||
with settings_cols[2]:
|
||||
top_n = st.slider("显示热点数量", min_value=3, max_value=20, value=8, key="hotspot_top_n")
|
||||
|
||||
if not st.button("🚀 开始热点分析", type="primary"):
|
||||
return
|
||||
|
||||
with st.spinner("正在分析事故热点分布…"):
|
||||
hotspots = analyze_hotspot_frequency(hotspot_data, time_window=time_window)
|
||||
hotspots = hotspots[hotspots["accident_count"] >= min_accidents]
|
||||
|
||||
if hotspots.empty:
|
||||
st.warning("⚠️ 未找到符合条件的事故热点数据,请调整筛选参数。")
|
||||
return
|
||||
|
||||
hotspots_with_risk = calculate_hotspot_risk_score(hotspots.head(top_n * 3))
|
||||
top_hotspots = hotspots_with_risk.head(top_n)
|
||||
|
||||
st.subheader("📊 事故多发路口排名(前{0}个)".format(top_n))
|
||||
display_columns = {
|
||||
"accident_count": "累计事故数",
|
||||
"recent_count": "近期事故数",
|
||||
"trend_ratio": "趋势比例",
|
||||
"main_accident_type": "主要类型",
|
||||
"main_intersection_type": "路口类型",
|
||||
"risk_score": "风险评分",
|
||||
"risk_level": "风险等级",
|
||||
}
|
||||
display_df = top_hotspots[list(display_columns.keys())].rename(columns=display_columns)
|
||||
styled_df = display_df.style.format({"趋势比例": "{:.2f}", "风险评分": "{:.1f}"}).background_gradient(
|
||||
subset=["风险评分"], cmap="Reds"
|
||||
)
|
||||
st.dataframe(styled_df, use_container_width=True)
|
||||
|
||||
st.subheader("🎯 针对性策略建议")
|
||||
strategies = generate_hotspot_strategies(top_hotspots, time_period="本周")
|
||||
for index, strategy_info in enumerate(strategies, start=1):
|
||||
message = f"**{index}. {strategy_info['strategy']}**"
|
||||
risk_level = strategy_info["risk_level"]
|
||||
if risk_level == "高风险":
|
||||
st.error(f"🚨 {message}")
|
||||
elif risk_level == "中风险":
|
||||
st.warning(f"⚠️ {message}")
|
||||
else:
|
||||
st.info(f"✅ {message}")
|
||||
|
||||
st.subheader("📈 数据分析可视化")
|
||||
chart_cols = st.columns(2)
|
||||
with chart_cols[0]:
|
||||
fig_hotspots = px.bar(
|
||||
top_hotspots.head(10),
|
||||
x=top_hotspots.head(10).index,
|
||||
y=["accident_count", "recent_count"],
|
||||
title="事故频次TOP10分布",
|
||||
labels={"value": "事故数量", "variable": "类型", "index": "路口名称"},
|
||||
barmode="group",
|
||||
)
|
||||
fig_hotspots.update_layout(xaxis_tickangle=-45)
|
||||
st.plotly_chart(fig_hotspots, use_container_width=True)
|
||||
|
||||
with chart_cols[1]:
|
||||
risk_distribution = top_hotspots["risk_level"].value_counts()
|
||||
fig_risk = px.pie(
|
||||
values=risk_distribution.values,
|
||||
names=risk_distribution.index,
|
||||
title="风险等级分布",
|
||||
color_discrete_map={"高风险": "red", "中风险": "orange", "低风险": "green"},
|
||||
)
|
||||
st.plotly_chart(fig_risk, use_container_width=True)
|
||||
|
||||
st.subheader("💾 数据导出")
|
||||
download_cols = st.columns(2)
|
||||
with download_cols[0]:
|
||||
hotspot_csv = top_hotspots.to_csv().encode("utf-8-sig")
|
||||
st.download_button(
|
||||
"📥 下载热点数据CSV",
|
||||
data=hotspot_csv,
|
||||
file_name=f"accident_hotspots_{datetime.now().strftime('%Y%m%d')}.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
|
||||
with download_cols[1]:
|
||||
serializable = serialise_datetime_columns(
|
||||
top_hotspots.reset_index(),
|
||||
columns=[col for col in top_hotspots.columns if "time" in col or "date" in col],
|
||||
)
|
||||
report_payload = {
|
||||
"analysis_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"time_window": time_window,
|
||||
"data_source": accident_source_name or "事故数据",
|
||||
"total_records": int(len(hotspot_data)),
|
||||
"analysis_parameters": {"min_accidents": int(min_accidents), "top_n": int(top_n)},
|
||||
"top_hotspots": serializable.to_dict("records"),
|
||||
"recommended_strategies": strategies,
|
||||
"summary": {
|
||||
"high_risk_count": int((top_hotspots["risk_level"] == "高风险").sum()),
|
||||
"medium_risk_count": int((top_hotspots["risk_level"] == "中风险").sum()),
|
||||
"total_analyzed_locations": int(len(hotspots)),
|
||||
"most_dangerous_location": top_hotspots.index[0]
|
||||
if len(top_hotspots)
|
||||
else "无",
|
||||
},
|
||||
}
|
||||
st.download_button(
|
||||
"📄 下载完整分析报告",
|
||||
data=json.dumps(report_payload, ensure_ascii=False, indent=2),
|
||||
file_name=f"hotspot_analysis_report_{datetime.now().strftime('%Y%m%d_%H%M')}.json",
|
||||
mime="application/json",
|
||||
)
|
||||
|
||||
with st.expander("📋 查看原始数据预览"):
|
||||
preview_cols = ["事故时间", "所在街道", "事故类型", "事故具体地点", "道路类型"]
|
||||
preview_df = hotspot_data[preview_cols].copy()
|
||||
st.dataframe(preview_df.head(10), use_container_width=True)
|
||||
|
||||
Reference in New Issue
Block a user