增加更多的历史记录,修正部份API数据获取错误,增加备用API (#5)

* 增加更多的历史记录,修正部份数据获取错误

* 增加更多的历史记录,修正部份API数据获取错误,增加备用API

---------

Co-authored-by: bathfire <>
This commit is contained in:
Eikwang
2025-10-29 16:22:18 +08:00
committed by GitHub
parent 16071f81e8
commit 91d32c6ffa
39 changed files with 2377 additions and 824 deletions
+148 -47
View File
@@ -146,10 +146,10 @@ def display_analysis_tab():
col1, col2, col3 = st.columns([2, 2, 2])
with col1:
analyze_button = st.button("🚀 开始分析", type="primary", use_container_width=True)
analyze_button = st.button("🚀 开始分析", type="primary", width='stretch')
with col2:
if st.button("🔄 清除结果", use_container_width=True):
if st.button("🔄 清除结果", width='stretch'):
if 'longhubang_result' in st.session_state:
del st.session_state.longhubang_result
st.success("已清除分析结果")
@@ -335,13 +335,29 @@ def display_scoring_ranking(result):
# 显示TOP10评分表格
st.markdown("### 🥇 TOP10 综合评分排名")
# 兼容历史数据与类型统一,避免 Arrow 序列化错误
if isinstance(scoring_df, list):
scoring_df = pd.DataFrame(scoring_df)
numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入']
for col in numeric_cols:
if col in scoring_df.columns:
scoring_df[col] = pd.to_numeric(scoring_df[col], errors='coerce')
text_cols = ['股票名称','股票代码','机构参与']
for col in text_cols:
if col in scoring_df.columns:
scoring_df[col] = scoring_df[col].astype(str)
top10_df = scoring_df.head(10).copy()
if '排名' in top10_df.columns:
top10_df['排名'] = pd.to_numeric(top10_df['排名'], errors='coerce').fillna(0).astype(int)
# 格式化显示
st.dataframe(
top10_df,
column_config={
"排名": st.column_config.TextColumn("排名", width="small"),
"排名": st.column_config.NumberColumn("排名", format="%d", width="small"),
"股票名称": st.column_config.TextColumn("股票名称", width="medium"),
"股票代码": st.column_config.TextColumn("代码", width="small"),
"综合评分": st.column_config.NumberColumn(
@@ -385,7 +401,7 @@ def display_scoring_ranking(result):
"净流入": st.column_config.NumberColumn("净流入(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
# 一键批量分析功能
@@ -401,12 +417,15 @@ def display_scoring_ranking(result):
"分析数量",
options=[3, 5, 10],
index=0,
help="选择分析前N只股票"
help="选择分析前N只股票",
key="batch_count_selector"
)
# 同步更新session_state中的batch_count
st.session_state.batch_count = batch_count
with col_batch3:
st.write("") # 占位
if st.button("🚀 开始批量分析", type="primary", use_container_width=True):
if st.button("🚀 开始批量分析", type="primary", width='stretch'):
# 提取股票代码
stock_codes = top10_df.head(batch_count)['股票代码'].tolist()
@@ -439,25 +458,35 @@ def display_scoring_ranking(result):
showlegend=False,
height=400
)
st.plotly_chart(fig1, use_container_width=True)
st.plotly_chart(fig1, config={'displayModeBar': False}, use_container_width=True)
with col2:
# 五维评分雷达图(显示第一名
# 五维评分雷达图(显示批量分析数量的股票
if len(top10_df) > 0:
first_place = top10_df.iloc[0]
display_count = min(5, len(top10_df))
fig2 = go.Figure(data=go.Scatterpolar(
r=[
first_place['资金含金量'] / 30 * 100,
first_place['净买入额'] / 25 * 100,
first_place['卖出压力'] / 20 * 100,
first_place['机构共振'] / 15 * 100,
first_place['加分项'] / 10 * 100
],
theta=['资金含金量', '净买入额', '卖出压力', '机构共振', '加分项'],
fill='toself',
name=first_place['股票名称']
))
fig2 = go.Figure()
# 为每只股票添加雷达图
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']
for i in range(display_count):
stock = top10_df.iloc[i]
fig2.add_trace(go.Scatterpolar(
r=[
stock['资金含金量'] / 30 * 100,
stock['净买入额'] / 25 * 100,
stock['卖出压力'] / 20 * 100,
stock['机构共振'] / 15 * 100,
stock['加分项'] / 10 * 100
],
theta=['资金含金量', '净买入额', '卖出压力', '机构共振', '加分项'],
fill='toself',
name=f"{stock['股票名称']}",
line_color=colors[i % len(colors)],
fillcolor=colors[i % len(colors)],
opacity=0.6
))
fig2.update_layout(
polar=dict(
@@ -467,10 +496,17 @@ def display_scoring_ranking(result):
)
),
showlegend=True,
title=f"🥇 {first_place['股票名称']} 五维评分",
height=400
title=f"🏆 TOP{display_count} 五维评分对比",
height=400,
legend=dict(
orientation="h",
yanchor="auto",
y=-0.2,
xanchor="center",
x=0.5
)
)
st.plotly_chart(fig2, use_container_width=True)
st.plotly_chart(fig2, config={'displayModeBar': False}, use_container_width=True)
st.markdown("---")
@@ -480,7 +516,7 @@ def display_scoring_ranking(result):
st.dataframe(
scoring_df,
column_config={
"排名": st.column_config.TextColumn("排名", width="small"),
"排名": st.column_config.NumberColumn("排名", format="%d", width="small"),
"股票名称": st.column_config.TextColumn("股票名称"),
"股票代码": st.column_config.TextColumn("代码"),
"综合评分": st.column_config.NumberColumn("综合评分", format="%.1f"),
@@ -490,7 +526,7 @@ def display_scoring_ranking(result):
"净流入": st.column_config.NumberColumn("净流入(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
@@ -523,7 +559,7 @@ def display_recommended_stocks(result):
"reason": st.column_config.TextColumn("推荐理由")
},
hide_index=True,
use_container_width=True
width='stretch'
)
# 详细推荐理由
@@ -599,7 +635,7 @@ def display_data_details(result):
"净流入金额": st.column_config.NumberColumn("净流入金额(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
# TOP股票
@@ -616,7 +652,7 @@ def display_data_details(result):
"net_inflow": st.column_config.NumberColumn("净流入金额(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
# 热门概念
@@ -637,7 +673,7 @@ def display_data_details(result):
"出现次数": st.column_config.NumberColumn("出现次数", format="%d")
},
hide_index=True,
use_container_width=True
width='stretch'
)
@@ -664,7 +700,7 @@ def display_visualizations(result):
labels={'name': '股票名称', 'net_inflow': '净流入金额(元)'}
)
fig.update_layout(xaxis_tickangle=-45)
st.plotly_chart(fig, use_container_width=True)
st.plotly_chart(fig, config={'displayModeBar': False}, use_container_width=True)
# 热门概念图表
if summary.get('hot_concepts'):
@@ -679,7 +715,7 @@ def display_visualizations(result):
names='概念',
title='热门概念出现次数分布'
)
st.plotly_chart(fig, use_container_width=True)
st.plotly_chart(fig, config={'displayModeBar': False}, use_container_width=True)
def display_pdf_export_section(result):
@@ -693,7 +729,7 @@ def display_pdf_export_section(result):
st.info("💡 点击按钮生成并下载专业的PDF分析报告")
with col2:
if st.button("📥 生成PDF", type="primary", use_container_width=True):
if st.button("📥 生成PDF", type="primary", width='stretch'):
with st.spinner("正在生成PDF报告..."):
try:
generator = LonghubangPDFGenerator()
@@ -709,7 +745,7 @@ def display_pdf_export_section(result):
data=pdf_bytes,
file_name=f"智瞰龙虎报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
mime="application/pdf",
use_container_width=True
width='stretch'
)
st.success("✅ PDF报告生成成功!")
@@ -780,7 +816,7 @@ def display_history_tab():
"hold_period": st.column_config.TextColumn("持有周期")
},
hide_index=True,
use_container_width=True
width='stretch'
)
st.markdown("---")
@@ -818,6 +854,17 @@ def display_history_tab():
st.markdown("#### 🏆 AI智能评分排名 (TOP10)")
df_scoring = pd.DataFrame(scoring_ranking[:10])
# 类型统一,避免Arrow序列化错误
numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入']
for col in numeric_cols:
if col in df_scoring.columns:
df_scoring[col] = pd.to_numeric(df_scoring[col], errors='coerce')
text_cols = ['股票名称','股票代码','机构参与']
for col in text_cols:
if col in df_scoring.columns:
df_scoring[col] = df_scoring[col].astype(str)
if '排名' in df_scoring.columns:
df_scoring['排名'] = pd.to_numeric(df_scoring['排名'], errors='coerce').fillna(0).astype(int)
# 显示完整的评分表格
st.dataframe(
@@ -867,7 +914,7 @@ def display_history_tab():
"净流入": st.column_config.NumberColumn("净流入(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
# 显示评分说明
@@ -903,34 +950,88 @@ def display_history_tab():
st.markdown("#### 📄 原始分析内容")
analysis_content = report_detail.get('analysis_content', '')
if analysis_content:
st.text_area("", value=analysis_content[:2000], height=200, disabled=True)
st.text_area("原始分析内容", value=analysis_content[:2000], height=200, disabled=True)
if len(analysis_content) > 2000:
st.caption("(内容过长,仅显示前2000字符)")
# 导出按钮
# 操作按钮
st.markdown("---")
col_export1, col_export2 = st.columns(2)
col_export1, col_export2, col_export3 = st.columns(3)
with col_export1:
if st.button(f"📥 导出为PDF", key=f"export_pdf_{report_id}"):
st.info("PDF导出功能开发中...")
with col_export2:
if st.button(f"📋 加载到分析页", key=f"load_report_{report_id}"):
# 使用session_state来管理按钮状态,避免需要点击两次的问题
load_key = f"load_report_{report_id}"
if st.button(f"📋 加载到分析页", key=load_key):
# 将历史报告加载到当前分析结果中
if analysis_content_parsed:
# 重建完整的result结构
scoring_data = analysis_content_parsed.get('scoring_ranking', [])
if scoring_data:
df_scoring = pd.DataFrame(scoring_data)
# 类型统一,避免Arrow序列化错误
numeric_cols = ['排名','综合评分','资金含金量','净买入额','卖出压力','机构共振','加分项','顶级游资','买方数','净流入']
for col in numeric_cols:
if col in df_scoring.columns:
df_scoring[col] = pd.to_numeric(df_scoring[col], errors='coerce')
text_cols = ['股票名称','股票代码','机构参与']
for col in text_cols:
if col in df_scoring.columns:
df_scoring[col] = df_scoring[col].astype(str)
if '排名' in df_scoring.columns:
df_scoring['排名'] = pd.to_numeric(df_scoring['排名'], errors='coerce').fillna(0).astype(int)
else:
df_scoring = None
loaded_result = {
"success": True,
"timestamp": report_detail.get('analysis_date', ''),
"data_info": analysis_content_parsed.get('data_info', {}),
"agents_analysis": analysis_content_parsed.get('agents_analysis', {}),
"scoring_ranking": pd.DataFrame(analysis_content_parsed.get('scoring_ranking', [])) if analysis_content_parsed.get('scoring_ranking') else None,
"scoring_ranking": df_scoring,
"final_report": analysis_content_parsed.get('final_report', {}),
"recommended_stocks": report_detail.get('recommended_stocks', [])
}
st.session_state.longhubang_result = loaded_result
# 使用rerun来立即刷新页面状态
st.success('✅ 报告已加载到分析页面,请切换到"龙虎榜分析"标签查看')
st.rerun()
with col_export3:
# 删除按钮
delete_key = f"delete_report_{report_id}"
if st.button(f"🗑️ 删除报告", key=delete_key, type="secondary"):
# 使用session_state来管理删除确认状态
st.session_state[f"confirm_delete_{report_id}"] = True
st.rerun()
# 删除确认对话框
if st.session_state.get(f"confirm_delete_{report_id}", False):
st.warning(f"⚠️ 确认删除报告 #{report_id}?此操作不可撤销!")
col_confirm1, col_confirm2 = st.columns(2)
with col_confirm1:
if st.button(f"✅ 确认删除", key=f"confirm_delete_yes_{report_id}", type="primary"):
try:
# 调用数据库删除方法 - 修复属性名
engine.database.delete_analysis_report(report_id)
st.success(f"✅ 报告 #{report_id} 已成功删除")
# 清除确认状态并刷新页面
if f"confirm_delete_{report_id}" in st.session_state:
del st.session_state[f"confirm_delete_{report_id}"]
st.rerun()
except Exception as e:
st.error(f"❌ 删除失败: {str(e)}")
with col_confirm2:
if st.button(f"❌ 取消", key=f"confirm_delete_no_{report_id}"):
# 清除确认状态
if f"confirm_delete_{report_id}" in st.session_state:
del st.session_state[f"confirm_delete_{report_id}"]
st.rerun()
except Exception as e:
st.error(f"❌ 加载历史报告失败: {str(e)}")
@@ -986,7 +1087,7 @@ def display_statistics_tab():
"total_net_inflow": st.column_config.NumberColumn("总净流入(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
st.markdown("---")
@@ -1006,7 +1107,7 @@ def display_statistics_tab():
"total_net_inflow": st.column_config.NumberColumn("总净流入(元)", format="%.2f")
},
hide_index=True,
use_container_width=True
width='stretch'
)
except Exception as e:
@@ -1026,7 +1127,7 @@ def run_longhubang_batch_analysis():
# 返回按钮
col_back, col_clear = st.columns(2)
with col_back:
if st.button("🔙 返回龙虎榜分析", use_container_width=True):
if st.button("🔙 返回龙虎榜分析", width='stretch'):
# 清除所有批量分析相关状态
if 'longhubang_batch_trigger' in st.session_state:
del st.session_state.longhubang_batch_trigger
@@ -1037,7 +1138,7 @@ def run_longhubang_batch_analysis():
st.rerun()
with col_clear:
if st.button("🔄 重新分析", use_container_width=True):
if st.button("🔄 重新分析", width='stretch'):
# 清除结果,保留触发标志和代码
if 'longhubang_batch_results' in st.session_state:
del st.session_state.longhubang_batch_results
@@ -1098,11 +1199,11 @@ def run_longhubang_batch_analysis():
start_analysis = False
with col_confirm:
if st.button("🚀 确认开始分析", type="primary", use_container_width=True):
if st.button("🚀 确认开始分析", type="primary", width='stretch'):
start_analysis = True
with col_cancel:
if st.button("❌ 取消", type="secondary", use_container_width=True):
if st.button("❌ 取消", type="secondary", width='stretch'):
# 清除所有批量分析相关状态
if 'longhubang_batch_trigger' in st.session_state:
del st.session_state.longhubang_batch_trigger