update pay ui

This commit is contained in:
2025-12-02 12:22:49 +08:00
parent bd86ccce85
commit d1a222d9e9
2 changed files with 331 additions and 169 deletions

Binary file not shown.

242
app_vx.py
View File

@@ -5381,6 +5381,114 @@ def get_comment_replies(comment_id):
}), 500 }), 500
# 工具函数解析JSON字段
def parse_json_field(field_value):
"""解析JSON字段"""
if not field_value:
return []
try:
if isinstance(field_value, str):
if field_value.startswith('['):
return json.loads(field_value)
else:
return field_value.split(',')
else:
return field_value
except:
return []
# 工具函数:获取 future_events 表字段值,支持新旧字段回退
def get_future_event_field(row, new_field, old_field):
"""
获取 future_events 表字段值,支持新旧字段回退
如果新字段存在且不为空,使用新字段;否则使用旧字段
"""
new_value = getattr(row, new_field, None) if hasattr(row, new_field) else None
old_value = getattr(row, old_field, None) if hasattr(row, old_field) else None
# 如果新字段有值(不为空字符串),使用新字段
if new_value is not None and str(new_value).strip():
return new_value
return old_value
# 工具函数:解析新的 best_matches 数据结构(含研报引用信息)
def parse_best_matches(best_matches_value):
"""
解析新的 best_matches 数据结构(含研报引用信息)
新结构示例:
[
{
"stock_code": "300451.SZ",
"company_name": "创业慧康",
"original_description": "核心标的,医疗信息化...",
"best_report_title": "报告标题",
"best_report_author": "作者",
"best_report_sentences": "相关内容",
"best_report_match_score": "",
"best_report_match_ratio": 0.9285714285714286,
"best_report_declare_date": "2023-04-25T00:00:00",
"total_reports": 9,
"high_score_reports": 6
},
...
]
返回统一格式的股票列表,兼容旧格式
"""
if not best_matches_value:
return []
try:
# 解析 JSON
if isinstance(best_matches_value, str):
data = json.loads(best_matches_value)
else:
data = best_matches_value
if not isinstance(data, list):
return []
result = []
for item in data:
if isinstance(item, dict):
# 新结构:包含研报信息的字典
stock_info = {
'code': item.get('stock_code', ''),
'name': item.get('company_name', ''),
'description': item.get('original_description', ''),
'score': item.get('best_report_match_ratio', 0),
# 研报引用信息
'report': {
'title': item.get('best_report_title', ''),
'author': item.get('best_report_author', ''),
'sentences': item.get('best_report_sentences', ''),
'match_score': item.get('best_report_match_score', ''),
'match_ratio': item.get('best_report_match_ratio', 0),
'declare_date': item.get('best_report_declare_date', ''),
'total_reports': item.get('total_reports', 0),
'high_score_reports': item.get('high_score_reports', 0)
} if item.get('best_report_title') else None
}
result.append(stock_info)
elif isinstance(item, (list, tuple)) and len(item) >= 2:
# 旧结构:[code, name, description, score]
result.append({
'code': item[0],
'name': item[1],
'description': item[2] if len(item) > 2 else '',
'score': item[3] if len(item) > 3 else 0,
'report': None
})
return result
except Exception as e:
print(f"parse_best_matches error: {e}")
return []
# 工具函数:处理转义字符,保留 Markdown 格式 # 工具函数:处理转义字符,保留 Markdown 格式
def unescape_markdown_text(text): def unescape_markdown_text(text):
""" """
@@ -5470,6 +5578,7 @@ def api_calendar_events():
offset = (page - 1) * per_page offset = (page - 1) * per_page
# 构建基础查询 - 使用 future_events 表 # 构建基础查询 - 使用 future_events 表
# 添加新字段 second_modified_text, `second_modified_text.1`, best_matches 支持新旧回退
query = """ query = """
SELECT data_id, \ SELECT data_id, \
calendar_time, \ calendar_time, \
@@ -5481,7 +5590,10 @@ def api_calendar_events():
fact, \ fact, \
related_stocks, \ related_stocks, \
concepts, \ concepts, \
inferred_tag inferred_tag, \
second_modified_text, \
`second_modified_text.1` as second_modified_text_1, \
best_matches
FROM future_events FROM future_events
WHERE 1 = 1 \ WHERE 1 = 1 \
""" """
@@ -5552,19 +5664,30 @@ def api_calendar_events():
events_data = [] events_data = []
for event in events: for event in events:
# 解析相关股票 # 使用新字段回退机制获取 former 和 forecast
# second_modified_text -> former
former_value = get_future_event_field(event, 'second_modified_text', 'former')
# second_modified_text.1 -> forecast
forecast_new = getattr(event, 'second_modified_text_1', None)
forecast_value = forecast_new if (forecast_new and str(forecast_new).strip()) else getattr(event, 'forecast', None)
# 解析相关股票 - 优先使用 best_matches回退到 related_stocks
related_stocks_list = [] related_stocks_list = []
related_avg_chg = 0 related_avg_chg = 0
related_max_chg = 0 related_max_chg = 0
related_week_chg = 0 related_week_chg = 0
# 处理相关股票数据 # 优先使用 best_matches新结构含研报引用
best_matches = getattr(event, 'best_matches', None)
if best_matches and str(best_matches).strip():
# 使用新的 parse_best_matches 函数解析
parsed_stocks = parse_best_matches(best_matches)
else:
# 回退到旧的 related_stocks 处理
parsed_stocks = []
if event.related_stocks: if event.related_stocks:
try: try:
import json
import ast import ast
# 使用与detail接口相同的解析逻辑
if isinstance(event.related_stocks, str): if isinstance(event.related_stocks, str):
try: try:
stock_data = json.loads(event.related_stocks) stock_data = json.loads(event.related_stocks)
@@ -5574,18 +5697,30 @@ def api_calendar_events():
stock_data = event.related_stocks stock_data = event.related_stocks
if stock_data: if stock_data:
for stock_info in stock_data:
if isinstance(stock_info, list) and len(stock_info) >= 2:
parsed_stocks.append({
'code': stock_info[0],
'name': stock_info[1],
'description': stock_info[2] if len(stock_info) > 2 else '',
'score': stock_info[3] if len(stock_info) > 3 else 0,
'report': None
})
except Exception as e:
print(f"Error parsing related_stocks for event {event.data_id}: {e}")
# 处理解析后的股票数据,获取交易信息
if parsed_stocks:
try:
daily_changes = [] daily_changes = []
week_changes = [] week_changes = []
# 处理正确的数据格式 [股票代码, 股票名称, 描述, 分数] for stock_info in parsed_stocks:
for stock_info in stock_data: stock_code = stock_info.get('code', '')
if isinstance(stock_info, list) and len(stock_info) >= 2: stock_name = stock_info.get('name', '')
stock_code = stock_info[0] # 股票代码 description = stock_info.get('description', '')
stock_name = stock_info[1] # 股票名称 score = stock_info.get('score', 0)
description = stock_info[2] if len(stock_info) > 2 else '' report = stock_info.get('report', None)
score = stock_info[3] if len(stock_info) > 3 else 0
else:
continue
if stock_code: if stock_code:
# 规范化股票代码,移除后缀 # 规范化股票代码,移除后缀
@@ -5626,7 +5761,8 @@ def api_calendar_events():
'description': description, 'description': description,
'score': score, 'score': score,
'daily_chg': daily_chg, 'daily_chg': daily_chg,
'week_chg': week_chg 'week_chg': week_chg,
'report': report # 添加研报引用信息
}) })
# 计算平均收益率 # 计算平均收益率
@@ -5660,8 +5796,9 @@ def api_calendar_events():
highlight_match = 'concepts' highlight_match = 'concepts'
# 将转义的换行符转换为真正的换行符,保留 Markdown 格式 # 将转义的换行符转换为真正的换行符,保留 Markdown 格式
cleaned_former = unescape_markdown_text(event.former) # 使用新字段回退后的值former_value, forecast_value
cleaned_forecast = unescape_markdown_text(event.forecast) cleaned_former = unescape_markdown_text(former_value)
cleaned_forecast = unescape_markdown_text(forecast_value)
cleaned_fact = unescape_markdown_text(event.fact) cleaned_fact = unescape_markdown_text(event.fact)
event_dict = { event_dict = {
@@ -5907,6 +6044,7 @@ def api_future_event_detail(item_id):
"""未来事件详情接口 - 连接 future_events 表 (修正数据解析) - 仅限 Pro/Max 会员""" """未来事件详情接口 - 连接 future_events 表 (修正数据解析) - 仅限 Pro/Max 会员"""
try: try:
# 从 future_events 表查询事件详情 # 从 future_events 表查询事件详情
# 添加新字段 second_modified_text, `second_modified_text.1`, best_matches 支持新旧回退
query = """ query = """
SELECT data_id, \ SELECT data_id, \
calendar_time, \ calendar_time, \
@@ -5917,7 +6055,10 @@ def api_future_event_detail(item_id):
forecast, \ forecast, \
fact, \ fact, \
related_stocks, \ related_stocks, \
concepts concepts, \
second_modified_text, \
`second_modified_text.1` as second_modified_text_1, \
best_matches
FROM future_events FROM future_events
WHERE data_id = :item_id \ WHERE data_id = :item_id \
""" """
@@ -5932,6 +6073,13 @@ def api_future_event_detail(item_id):
'data': None 'data': None
}), 404 }), 404
# 使用新字段回退机制获取 former 和 forecast
# second_modified_text -> former
former_value = get_future_event_field(event, 'second_modified_text', 'former')
# second_modified_text.1 -> forecast
forecast_new = getattr(event, 'second_modified_text_1', None)
forecast_value = forecast_new if (forecast_new and str(forecast_new).strip()) else getattr(event, 'forecast', None)
extracted_concepts = extract_concepts_from_concepts_field(event.concepts) extracted_concepts = extract_concepts_from_concepts_field(event.concepts)
# 解析相关股票 # 解析相关股票
@@ -5975,42 +6123,55 @@ def api_future_event_detail(item_id):
'环保': '公共产业板块', '综合': '公共产业板块' '环保': '公共产业板块', '综合': '公共产业板块'
} }
# 处理相关股票 # 处理相关股票 - 优先使用 best_matches回退到 related_stocks
related_avg_chg = 0 related_avg_chg = 0
related_max_chg = 0 related_max_chg = 0
related_week_chg = 0 related_week_chg = 0
# 优先使用 best_matches新结构含研报引用
best_matches = getattr(event, 'best_matches', None)
if best_matches and str(best_matches).strip():
# 使用新的 parse_best_matches 函数解析
parsed_stocks = parse_best_matches(best_matches)
else:
# 回退到旧的 related_stocks 处理
parsed_stocks = []
if event.related_stocks: if event.related_stocks:
try: try:
import json
import ast import ast
# **修正正确解析related_stocks数据结构**
if isinstance(event.related_stocks, str): if isinstance(event.related_stocks, str):
try: try:
# 先尝试JSON解析
stock_data = json.loads(event.related_stocks) stock_data = json.loads(event.related_stocks)
except: except:
# 如果JSON解析失败尝试ast.literal_eval解析
stock_data = ast.literal_eval(event.related_stocks) stock_data = ast.literal_eval(event.related_stocks)
else: else:
stock_data = event.related_stocks stock_data = event.related_stocks
print(f"Parsed stock_data: {stock_data}") # 调试输出
if stock_data: if stock_data:
for stock_info in stock_data:
if isinstance(stock_info, list) and len(stock_info) >= 2:
parsed_stocks.append({
'code': stock_info[0],
'name': stock_info[1],
'description': stock_info[2] if len(stock_info) > 2 else '',
'score': stock_info[3] if len(stock_info) > 3 else 0,
'report': None
})
except Exception as e:
print(f"Error parsing related_stocks for event {event.data_id}: {e}")
# 处理解析后的股票数据
if parsed_stocks:
try:
daily_changes = [] daily_changes = []
week_changes = [] week_changes = []
# **修正:处理正确的数据格式 [股票代码, 股票名称, 描述, 分数]** for stock_info in parsed_stocks:
for stock_info in stock_data: stock_code = stock_info.get('code', '')
if isinstance(stock_info, list) and len(stock_info) >= 2: stock_name = stock_info.get('name', '')
stock_code = stock_info[0] # 第一个元素是股票代码 description = stock_info.get('description', '')
stock_name = stock_info[1] # 第二个元素是股票名称 score = stock_info.get('score', 0)
description = stock_info[2] if len(stock_info) > 2 else '' report = stock_info.get('report', None)
score = stock_info[3] if len(stock_info) > 3 else 0
else:
continue # 跳过格式不正确的数据
if stock_code: if stock_code:
# 规范化股票代码,移除后缀 # 规范化股票代码,移除后缀
@@ -6080,7 +6241,8 @@ def api_future_event_detail(item_id):
'sw_primary_sector': sw_primary_sector, # 申万一级行业F004V 'sw_primary_sector': sw_primary_sector, # 申万一级行业F004V
'primary_sector': primary_sector, # 主板块分类 'primary_sector': primary_sector, # 主板块分类
'daily_change': daily_chg, # 真实的日涨跌幅 'daily_change': daily_chg, # 真实的日涨跌幅
'week_change': week_chg # 真实的周涨跌幅 'week_change': week_chg, # 真实的周涨跌幅
'report': report # 研报引用信息(新字段)
}) })
# 计算平均收益率 # 计算平均收益率
@@ -6096,15 +6258,15 @@ def api_future_event_detail(item_id):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
# 构建返回数据 # 构建返回数据,使用新字段回退后的值
detail_data = { detail_data = {
'id': event.data_id, 'id': event.data_id,
'title': event.title, 'title': event.title,
'type': event.type, 'type': event.type,
'star': event.star, 'star': event.star,
'calendar_time': event.calendar_time.isoformat() if event.calendar_time else None, 'calendar_time': event.calendar_time.isoformat() if event.calendar_time else None,
'former': event.former, 'former': former_value, # 使用回退后的值(优先 second_modified_text
'forecast': event.forecast, 'forecast': forecast_value, # 使用回退后的值(优先 second_modified_text.1
'fact': event.fact, 'fact': event.fact,
'concepts': event.concepts, 'concepts': event.concepts,
'extracted_concepts': extracted_concepts, 'extracted_concepts': extracted_concepts,