modify: update apps

This commit is contained in:
2025-11-02 21:56:35 +08:00
parent 5825cf81b7
commit d8eea8e3a9
6 changed files with 111 additions and 86 deletions

View File

@@ -3,7 +3,7 @@
## [1.1.0] - 2025-08-28 ## [1.1.0] - 2025-08-28
### Added ### Added
- Integrated GPT-based analysis for comprehensive traffic safety insights - Integrated AI-based analysis for comprehensive traffic safety insights
- Added automated report generation with AI-powered recommendations - Added automated report generation with AI-powered recommendations
- Implemented natural language query processing for data exploration - Implemented natural language query processing for data exploration
- Added export functionality for analysis reports (PDF/CSV formats) - Added export functionality for analysis reports (PDF/CSV formats)
@@ -22,7 +22,7 @@
- Addressed memory leaks in large dataset processing - Addressed memory leaks in large dataset processing
### Documentation ### Documentation
- Updated README with new GPT analysis features and usage examples - Updated README with new AI analysis features and usage examples
- Added API documentation for extended functionality - Added API documentation for extended functionality
- Included sample datasets and tutorial guides - Included sample datasets and tutorial guides
@@ -44,4 +44,4 @@
### Fixed ### Fixed
- Resolved session state KeyError. - Resolved session state KeyError.

158
app.py
View File

@@ -294,9 +294,9 @@ def run_streamlit_app():
# Add OpenAI API key input in sidebar # Add OpenAI API key input in sidebar
st.sidebar.markdown("---") st.sidebar.markdown("---")
st.sidebar.subheader("GPT API 配置") st.sidebar.subheader("AI API 配置")
openai_api_key = st.sidebar.text_input("GPT API Key", value='sk-sXY934yPqjh7YKKC08380b198fEb47308cDa09BeE23d9c8a', type="password", help="用于GPT分析结果的API密钥") openai_api_key = st.sidebar.text_input("AI API Key", value='sk-sXY934yPqjh7YKKC08380b198fEb47308cDa09BeE23d9c8a', type="password", help="用于 AI 分析结果的 API 密钥")
open_ai_base_url = st.sidebar.text_input("GPT Base Url", value='https://aihubmix.com/v1', type='default') open_ai_base_url = st.sidebar.text_input("AI Base Url", value='https://aihubmix.com/v1', type='default')
# Process data only when Apply button is clicked # Process data only when Apply button is clicked
if apply_button and accident_file and strategy_file: if apply_button and accident_file and strategy_file:
@@ -404,14 +404,14 @@ def run_streamlit_app():
tab_labels = [ tab_labels = [
"🏠 总览", "🏠 总览",
"📍 事故热点",
"🔍 AI 分析",
"📈 预测模型", "📈 预测模型",
"📊 模型评估", "📊 模型评估",
"⚠️ 异常检测", "⚠️ 异常检测",
"📝 策略评估", "📝 策略评估",
"⚖️ 策略对比", "⚖️ 策略对比",
"🧪 情景模拟", "🧪 情景模拟",
"🔍 GPT 分析",
"📍 事故热点",
] ]
default_tab = st.session_state.get("active_tab", tab_labels[0]) default_tab = st.session_state.get("active_tab", tab_labels[0])
if default_tab not in tab_labels: if default_tab not in tab_labels:
@@ -426,17 +426,94 @@ def run_streamlit_app():
st.session_state["active_tab"] = selected_tab st.session_state["active_tab"] = selected_tab
if selected_tab == "📍 事故热点": if selected_tab == "🏠 总览":
if render_overview is not None:
render_overview(base, region_sel, start_dt, end_dt, strat_filter)
else:
st.warning("概览模块未能加载,请检查 `ui_sections/overview.py`。")
elif selected_tab == "📍 事故热点":
if render_hotspot is not None: if render_hotspot is not None:
render_hotspot(accident_records, accident_source_name) render_hotspot(accident_records, accident_source_name)
else: else:
st.warning("事故热点模块未能加载,请检查 `ui_sections/hotspot.py`。") st.warning("事故热点模块未能加载,请检查 `ui_sections/hotspot.py`。")
elif selected_tab == "🏠 总览": elif selected_tab == "🔍 AI 分析":
if render_overview is not None: from openai import OpenAI
render_overview(base, region_sel, start_dt, end_dt, strat_filter) st.subheader("AI 数据分析与改进建议")
if not HAS_OPENAI:
st.warning("未安装 `openai` 库。请安装后重试。")
elif not openai_api_key:
st.info("请在左侧边栏输入 OpenAI API Key 以启用 AI 分析。")
else: else:
st.warning("概览模块未能加载,请检查 `ui_sections/overview.py`。") if all_strategy_types:
# Generate results if not already
results, recommendation = generate_output_and_recommendations(base, all_strategy_types,
region=region_sel if region_sel != '全市' else '全市')
df_res = pd.DataFrame(results).T
kpi_json = json.dumps(kpi, ensure_ascii=False, indent=2)
results_json = df_res.to_json(orient="records", force_ascii=False)
recommendation_text = recommendation
# Prepare data to send
data_to_analyze = {
"kpis": kpi_json,
"strategy_results": results_json,
"recommendation": recommendation_text
}
data_str = json.dumps(data_to_analyze, ensure_ascii=False)
prompt = (
"你是一名资深交通安全数据分析顾问。请基于以下结构化数据输出一份专业报告,需包含:\n"
"1. 核心指标洞察:按要点总结事故趋势、显著波动及可能原因。\n"
"2. 策略绩效评估:对比主要策略的优势、短板与适用场景。\n"
"3. 优化建议为短期0-3个月、中期3-12个月与长期12个月以上分别给出2-3条可操作措施。\n"
"请保持正式语气,引用关键数值支撑结论,并用清晰的小节或列表呈现。\n"
f"数据摘要:{data_str}\n"
)
if st.button("上传数据至 AI 并获取分析"):
if not openai_api_key.strip():
st.info("请提供有效的 AI API Key。")
elif not open_ai_base_url.strip():
st.info("请提供可访问的 AI Base Url。")
else:
try:
client = OpenAI(
base_url=open_ai_base_url,
# sk-xxx替换为自己的key
api_key=openai_api_key
)
st.markdown("### AI 分析结果与改进思路")
placeholder = st.empty()
accumulated_response: list[str] = []
with st.spinner("AI 正在生成专业报告,请稍候…"):
stream = client.chat.completions.create(
model="gpt-5-mini",
messages=[
{
"role": "system",
"content": "You are a professional traffic safety analyst who writes concise, well-structured Chinese reports."
},
{"role": "user", "content": prompt},
],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta if chunk.choices else None
piece = getattr(delta, "content", None) if delta else None
if piece:
accumulated_response.append(piece)
placeholder.markdown("".join(accumulated_response), unsafe_allow_html=True)
final_text = "".join(accumulated_response)
if not final_text:
placeholder.info("AI 未返回可用内容,请稍后重试或检查凭据配置。")
except Exception as e:
st.error(f"调用 OpenAI API 失败:{str(e)}")
else:
st.warning("没有策略数据可供分析。")
# Update refresh time
st.session_state['last_refresh'] = datetime.now()
elif selected_tab == "📈 预测模型": elif selected_tab == "📈 预测模型":
if render_forecast is not None: if render_forecast is not None:
@@ -652,67 +729,6 @@ def run_streamlit_app():
else: else:
st.info("请设置模拟参数并点击“应用模拟参数”按钮。") st.info("请设置模拟参数并点击“应用模拟参数”按钮。")
# --- New Tab 8: GPT 分析
elif selected_tab == "🔍 GPT 分析":
from openai import OpenAI
st.subheader("GPT 数据分析与改进建议")
# open_ai_key = f"sk-dQhKOOG48iVEfgJfAb14458dA4474fB09aBbE8153d4aB3Fc"
if not HAS_OPENAI:
st.warning("未安装 `openai` 库。请安装后重试。")
elif not openai_api_key:
st.info("请在左侧边栏输入 OpenAI API Key 以启用 GPT 分析。")
else:
if all_strategy_types:
# Generate results if not already
results, recommendation = generate_output_and_recommendations(base, all_strategy_types,
region=region_sel if region_sel != '全市' else '全市')
df_res = pd.DataFrame(results).T
kpi_json = json.dumps(kpi, ensure_ascii=False, indent=2)
results_json = df_res.to_json(orient="records", force_ascii=False)
recommendation_text = recommendation
# Prepare data to send
data_to_analyze = {
"kpis": kpi_json,
"strategy_results": results_json,
"recommendation": recommendation_text
}
data_str = json.dumps(data_to_analyze, ensure_ascii=False)
prompt = str(f"""
请分析以下交通安全分析结果包括KPI指标、策略评估结果和推荐。
提供数据结果的详细分析,以及改进思路和建议。
数据:{str(data_str)}
""")
if st.button("上传数据至 GPT 并获取分析"):
if False:
st.info("请将 GPT Base Url 更新为实际可访问的接口地址。")
else:
try:
client = OpenAI(
base_url=open_ai_base_url,
# sk-xxx替换为自己的key
api_key=openai_api_key
)
response = client.chat.completions.create(
model="gpt-5-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant that analyzes traffic safety data."},
{"role": "user", "content": prompt}
],
stream=False
)
gpt_response = response.choices[0].message.content
st.markdown("### GPT 分析结果与改进思路")
st.markdown(gpt_response, unsafe_allow_html=True)
except Exception as e:
st.error(f"调用 OpenAI API 失败:{str(e)}")
else:
st.warning("没有策略数据可供分析。")
# Update refresh time
st.session_state['last_refresh'] = datetime.now()
else: else:
st.info("请先在左侧上传事故数据与策略数据,并点击“应用数据与筛选”按钮。") st.info("请先在左侧上传事故数据与策略数据,并点击“应用数据与筛选”按钮。")

View File

@@ -41,7 +41,7 @@ Use the sidebar form labelled “数据与筛选”.
- **📝 策略评估 (Strategy evaluation)** — Aggregates metrics per strategy type, recommends the best option, writes `strategy_evaluation_results.csv`, and updates `recommendation.txt`. - **📝 策略评估 (Strategy evaluation)** — Aggregates metrics per strategy type, recommends the best option, writes `strategy_evaluation_results.csv`, and updates `recommendation.txt`.
- **⚖️ 策略对比 (Strategy comparison)** — side-by-side metrics for selected strategies, useful for “what worked best last month” reviews. - **⚖️ 策略对比 (Strategy comparison)** — side-by-side metrics for selected strategies, useful for “what worked best last month” reviews.
- **🧪 情景模拟 (Scenario simulation)** — apply intervention models (persistent/decay, lagged effects) to test potential roll-outs. - **🧪 情景模拟 (Scenario simulation)** — apply intervention models (persistent/decay, lagged effects) to test potential roll-outs.
- **🔍 GPT 分析** — enter your own OpenAI-compatible API key and base URL in the sidebar to generate narrative insights. Keys are read at runtime only. - **🔍 AI 分析** — 默认示例 API Key/Base URL 已预填,可直接体验;如需切换自有凭据,可在侧边栏更新后生成洞察(运行时读取,不会写入磁盘)。
- **📍 事故热点 (Hotspot)** — reuse the already uploaded accident data to identify high-risk intersections and produce targeted mitigation ideas; no separate hotspot upload is required. - **📍 事故热点 (Hotspot)** — reuse the already uploaded accident data to identify high-risk intersections and produce targeted mitigation ideas; no separate hotspot upload is required.
Each tab remembers the active filters from the sidebar so results stay consistent. Each tab remembers the active filters from the sidebar so results stay consistent.

View File

@@ -9,7 +9,7 @@
- 检测异常事故点 - 检测异常事故点
- 评估交通策略效果并提供推荐 - 评估交通策略效果并提供推荐
- 识别事故热点路口并生成风险分级与整治建议 - 识别事故热点路口并生成风险分级与整治建议
- 支持 GPT 分析生成自然语言洞察 - 支持 AI 分析生成自然语言洞察
## 安装步骤 ## 安装步骤
@@ -91,6 +91,7 @@ openai>=2.0.0
- **环境变量**(可选): - **环境变量**(可选):
- `LOG_LEVEL=DEBUG`:启用详细日志 - `LOG_LEVEL=DEBUG`:启用详细日志
- 示例:`export LOG_LEVEL=DEBUG`Linux/macOS或 `set LOG_LEVEL=DEBUG`Windows - 示例:`export LOG_LEVEL=DEBUG`Linux/macOS或 `set LOG_LEVEL=DEBUG`Windows
- **AI 分析凭据**:应用内已预填可用的示例 API Key 与 Base URL可直接体验如需使用自有服务可在侧边栏替换后即时生效。
## 示例数据 ## 示例数据

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import Iterable from typing import Iterable, Optional
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -211,11 +211,24 @@ def generate_hotspot_strategies(
return strategies return strategies
def serialise_datetime_columns(df: pd.DataFrame, columns: Iterable[str]) -> pd.DataFrame: def serialise_datetime_columns(df: pd.DataFrame, columns: Optional[Iterable[str]] = None) -> pd.DataFrame:
result = df.copy() result = df.copy()
if columns is None:
columns = result.columns
for column in columns: for column in columns:
if column in result.columns and pd.api.types.is_datetime64_any_dtype(result[column]): if column not in result.columns:
result[column] = result[column].dt.strftime("%Y-%m-%d %H:%M:%S") continue
series = result[column]
if pd.api.types.is_datetime64_any_dtype(series):
result[column] = series.dt.strftime("%Y-%m-%d %H:%M:%S")
else:
has_timestamp = series.map(lambda value: isinstance(value, (datetime, pd.Timestamp))).any()
if has_timestamp:
result[column] = series.map(
lambda value: value.strftime("%Y-%m-%d %H:%M:%S")
if isinstance(value, (datetime, pd.Timestamp))
else value
)
return result return result
@@ -224,4 +237,3 @@ def _mode_fallback(series: pd.Series) -> str:
return "" return ""
mode = series.mode() mode = series.mode()
return str(mode.iloc[0]) if not mode.empty else str(series.iloc[0]) return str(mode.iloc[0]) if not mode.empty else str(series.iloc[0])

View File

@@ -154,10 +154,7 @@ def render_hotspot(accident_records, accident_source_name: str | None) -> None:
) )
with download_cols[1]: with download_cols[1]:
serializable = serialise_datetime_columns( serializable = serialise_datetime_columns(top_hotspots.reset_index())
top_hotspots.reset_index(),
columns=[col for col in top_hotspots.columns if "time" in col or "date" in col],
)
report_payload = { report_payload = {
"analysis_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "analysis_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"time_window": time_window, "time_window": time_window,
@@ -186,4 +183,3 @@ def render_hotspot(accident_records, accident_source_name: str | None) -> None:
preview_cols = ["事故时间", "所在街道", "事故类型", "事故具体地点", "道路类型"] preview_cols = ["事故时间", "所在街道", "事故类型", "事故具体地点", "道路类型"]
preview_df = hotspot_data[preview_cols].copy() preview_df = hotspot_data[preview_cols].copy()
st.dataframe(preview_df.head(10), use_container_width=True) st.dataframe(preview_df.head(10), use_container_width=True)