Upload app.py
Browse files
app.py
CHANGED
|
@@ -99,6 +99,39 @@ div[class*=" gradio-container-"] {
|
|
| 99 |
text-align: left;
|
| 100 |
width: 100%;
|
| 101 |
box-sizing: border-box;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
/* 悬停效果 */
|
|
@@ -107,9 +140,9 @@ div[class*=" gradio-container-"] {
|
|
| 107 |
border-color: #d1d5db;
|
| 108 |
}
|
| 109 |
|
| 110 |
-
/* 选中效果 -
|
| 111 |
.company-list-container input[type="radio"]:checked + span {
|
| 112 |
-
background:
|
| 113 |
color: white !important;
|
| 114 |
font-weight: 600 !important;
|
| 115 |
display: block;
|
|
@@ -128,7 +161,7 @@ div[class*=" gradio-container-"] {
|
|
| 128 |
}
|
| 129 |
|
| 130 |
label.selected {
|
| 131 |
-
background:
|
| 132 |
color: white !important;
|
| 133 |
}
|
| 134 |
|
|
@@ -210,13 +243,15 @@ label.selected {
|
|
| 210 |
background: #f9fafb !important;
|
| 211 |
}
|
| 212 |
|
| 213 |
-
/* ✅ 缩小Easy Financial AI Assistant标题 */
|
| 214 |
.chatbot > .wrap > .head h1,
|
| 215 |
.chatbot > .wrap > .head h2,
|
| 216 |
.chatbot > .wrap > .head h3 {
|
| 217 |
-
font-size:
|
| 218 |
margin: 0 !important;
|
| 219 |
-
font-weight:
|
|
|
|
|
|
|
| 220 |
}
|
| 221 |
|
| 222 |
/* Tabs标签栏样式 */
|
|
@@ -700,7 +735,7 @@ def update_report_section(selected_company, report_data, stock_code):
|
|
| 700 |
<div class="report-item-content">
|
| 701 |
<span class="text-gray-800">{period}-{stock_code}-{source_form}</span>
|
| 702 |
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" class="text-blue-500" viewBox="0 0 20 20" fill="currentColor">
|
| 703 |
-
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 10-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l-1.5-1.
|
| 704 |
</svg>
|
| 705 |
</div>
|
| 706 |
</div>
|
|
@@ -1172,35 +1207,6 @@ def build_income_table(table_data):
|
|
| 1172 |
income_statement = table_data
|
| 1173 |
yoy_rates = []
|
| 1174 |
|
| 1175 |
-
# ✅ 转置表格:横轴显示Category,纵轴显示年份
|
| 1176 |
-
# 原始数据格式: [["Category", "2024/FY", "2023/FY", "2022/FY"], ["Total", "100", "90", "80"], ...]
|
| 1177 |
-
# 转置后格式: [["Year", "Total", "Net Income", ...], ["2024/FY", "100", "200", ...], ...]
|
| 1178 |
-
|
| 1179 |
-
if len(income_statement) == 0:
|
| 1180 |
-
return "<div>No data available</div>"
|
| 1181 |
-
|
| 1182 |
-
# 提取原始表头和数据
|
| 1183 |
-
original_headers = income_statement[0] # ["Category", "2024/FY", "2023/FY", "2022/FY"]
|
| 1184 |
-
original_data = income_statement[1:] # 数据行
|
| 1185 |
-
|
| 1186 |
-
# 构建转置后的表格
|
| 1187 |
-
transposed_table = []
|
| 1188 |
-
|
| 1189 |
-
# 新表头:第一列是"Year",其余是各个Category
|
| 1190 |
-
new_header = ["Year"] + [row[0] for row in original_data]
|
| 1191 |
-
transposed_table.append(new_header)
|
| 1192 |
-
|
| 1193 |
-
# 新数据行:每一行对应一个年份
|
| 1194 |
-
for col_idx in range(1, len(original_headers)):
|
| 1195 |
-
year = original_headers[col_idx]
|
| 1196 |
-
row_data = [year] # 第一列是年份
|
| 1197 |
-
for data_row in original_data:
|
| 1198 |
-
if col_idx < len(data_row):
|
| 1199 |
-
row_data.append(data_row[col_idx])
|
| 1200 |
-
else:
|
| 1201 |
-
row_data.append("N/A")
|
| 1202 |
-
transposed_table.append(row_data)
|
| 1203 |
-
|
| 1204 |
# 创建一个映射,将年份列索引映射到增长率
|
| 1205 |
yoy_map = {}
|
| 1206 |
if len(yoy_rates) > 1 and len(yoy_rates[0]) > 1:
|
|
@@ -1216,10 +1222,10 @@ def build_income_table(table_data):
|
|
| 1216 |
yoy_map[category][yoy_headers[j]] = rate
|
| 1217 |
|
| 1218 |
table_rows = ""
|
|
|
|
| 1219 |
|
| 1220 |
-
for i, row in enumerate(
|
| 1221 |
if i == 0:
|
| 1222 |
-
# 表头行
|
| 1223 |
row_style = "background-color: #f5f5f5; font-weight: 500;"
|
| 1224 |
else:
|
| 1225 |
row_style = "background-color: #f9f9f9;"
|
|
@@ -1227,17 +1233,16 @@ def build_income_table(table_data):
|
|
| 1227 |
|
| 1228 |
for j, cell in enumerate(row):
|
| 1229 |
if j == 0:
|
| 1230 |
-
#
|
| 1231 |
-
cells += f"<td style='padding: 8px; border: 1px solid #ddd; text-align: left; font-size: 13px; max-width:
|
| 1232 |
else:
|
| 1233 |
-
# 数据列居中对齐
|
| 1234 |
# 添加增长率箭头(如果有的话)
|
| 1235 |
growth = None
|
| 1236 |
-
|
| 1237 |
-
|
| 1238 |
-
|
| 1239 |
-
if
|
| 1240 |
-
growth = yoy_map[category][
|
| 1241 |
|
| 1242 |
if growth and growth != "N/A":
|
| 1243 |
arrow = "▲" if growth.startswith("+") else "▼"
|
|
@@ -1251,7 +1256,7 @@ def build_income_table(table_data):
|
|
| 1251 |
table_rows += f"<tr style='{row_style}'>{cells}</tr>"
|
| 1252 |
|
| 1253 |
html = f"""
|
| 1254 |
-
<div style="min-width: 400px;max-width: 600px;height:
|
| 1255 |
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 18px; padding-bottom: 12px; border-bottom: 2px solid #e5e7eb;">
|
| 1256 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 1257 |
<path d="M12 2L15.09 8.26L19 9.07L16 14L16 19L12 19L8 14L8 9.07L4.91 8.26L8 2L12 2Z" fill="#3b82f6"/>
|
|
@@ -1322,18 +1327,18 @@ def create_metrics_dashboard():
|
|
| 1322 |
prev_close_val = company_info.get("previous_close", "N/A")
|
| 1323 |
|
| 1324 |
html = f"""
|
| 1325 |
-
<div style="width: 250px; height: 300px !important; border: 1px solid #e5e7eb; border-radius: 12px; padding:
|
| 1326 |
-
<div style="font-size:
|
| 1327 |
-
<div style="font-size:
|
| 1328 |
-
<div style="display: flex; align-items:
|
| 1329 |
-
<div style="font-size:
|
| 1330 |
-
<div style="font-size:
|
| 1331 |
</div>
|
| 1332 |
-
<div style="
|
| 1333 |
-
<div style="
|
| 1334 |
-
<div style="
|
| 1335 |
-
<div style="
|
| 1336 |
-
<div style="
|
| 1337 |
</div>
|
| 1338 |
</div>
|
| 1339 |
"""
|
|
@@ -1541,9 +1546,9 @@ def update_metrics_dashboard(company_name):
|
|
| 1541 |
</div>
|
| 1542 |
"""
|
| 1543 |
# <div style="font-size: 14px; color: #555;">Vol</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{volume_display}</div>
|
| 1544 |
-
|
| 1545 |
return html
|
| 1546 |
-
|
| 1547 |
except Exception as e:
|
| 1548 |
print(f"Error building stock card: {e}")
|
| 1549 |
return '<div style="width:250px; padding:16px; color:red;">Error loading stock data</div>'
|
|
|
|
| 99 |
text-align: left;
|
| 100 |
width: 100%;
|
| 101 |
box-sizing: border-box;
|
| 102 |
+
position: relative;
|
| 103 |
+
padding-right: 2rem;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/* ✅ 为每个公司选项添加删除按钮 */
|
| 107 |
+
.company-list-container label::after {
|
| 108 |
+
content: '×';
|
| 109 |
+
position: absolute;
|
| 110 |
+
right: 8px;
|
| 111 |
+
top: 50%;
|
| 112 |
+
transform: translateY(-50%);
|
| 113 |
+
width: 18px;
|
| 114 |
+
height: 18px;
|
| 115 |
+
border-radius: 50%;
|
| 116 |
+
background: #d1d5db;
|
| 117 |
+
color: white;
|
| 118 |
+
font-size: 14px;
|
| 119 |
+
font-weight: bold;
|
| 120 |
+
display: flex;
|
| 121 |
+
align-items: center;
|
| 122 |
+
justify-content: center;
|
| 123 |
+
opacity: 0;
|
| 124 |
+
transition: opacity 0.2s;
|
| 125 |
+
cursor: pointer;
|
| 126 |
+
line-height: 1;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.company-list-container label:hover::after {
|
| 130 |
+
opacity: 1;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.company-list-container label::after:hover {
|
| 134 |
+
background: #9ca3af;
|
| 135 |
}
|
| 136 |
|
| 137 |
/* 悬停效果 */
|
|
|
|
| 140 |
border-color: #d1d5db;
|
| 141 |
}
|
| 142 |
|
| 143 |
+
/* 选中效果 - 使用蓝色 */
|
| 144 |
.company-list-container input[type="radio"]:checked + span {
|
| 145 |
+
background: #3b82f6 !important;
|
| 146 |
color: white !important;
|
| 147 |
font-weight: 600 !important;
|
| 148 |
display: block;
|
|
|
|
| 161 |
}
|
| 162 |
|
| 163 |
label.selected {
|
| 164 |
+
background: #3b82f6 !important;
|
| 165 |
color: white !important;
|
| 166 |
}
|
| 167 |
|
|
|
|
| 243 |
background: #f9fafb !important;
|
| 244 |
}
|
| 245 |
|
| 246 |
+
/* ✅ 缩小Easy Financial AI Assistant标题 - 更精致 */
|
| 247 |
.chatbot > .wrap > .head h1,
|
| 248 |
.chatbot > .wrap > .head h2,
|
| 249 |
.chatbot > .wrap > .head h3 {
|
| 250 |
+
font-size: 13px !important;
|
| 251 |
margin: 0 !important;
|
| 252 |
+
font-weight: 500 !important;
|
| 253 |
+
color: #6b7280 !important;
|
| 254 |
+
letter-spacing: 0.3px !important;
|
| 255 |
}
|
| 256 |
|
| 257 |
/* Tabs标签栏样式 */
|
|
|
|
| 735 |
<div class="report-item-content">
|
| 736 |
<span class="text-gray-800">{period}-{stock_code}-{source_form}</span>
|
| 737 |
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" class="text-blue-500" viewBox="0 0 20 20" fill="currentColor">
|
| 738 |
+
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 10-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l-1.5-1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd" />
|
| 739 |
</svg>
|
| 740 |
</div>
|
| 741 |
</div>
|
|
|
|
| 1207 |
income_statement = table_data
|
| 1208 |
yoy_rates = []
|
| 1209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1210 |
# 创建一个映射,将年份列索引映射到增长率
|
| 1211 |
yoy_map = {}
|
| 1212 |
if len(yoy_rates) > 1 and len(yoy_rates[0]) > 1:
|
|
|
|
| 1222 |
yoy_map[category][yoy_headers[j]] = rate
|
| 1223 |
|
| 1224 |
table_rows = ""
|
| 1225 |
+
header_row = income_statement[0]
|
| 1226 |
|
| 1227 |
+
for i, row in enumerate(income_statement):
|
| 1228 |
if i == 0:
|
|
|
|
| 1229 |
row_style = "background-color: #f5f5f5; font-weight: 500;"
|
| 1230 |
else:
|
| 1231 |
row_style = "background-color: #f9f9f9;"
|
|
|
|
| 1233 |
|
| 1234 |
for j, cell in enumerate(row):
|
| 1235 |
if j == 0:
|
| 1236 |
+
# Category列设置最大宽度,避免过宽
|
| 1237 |
+
cells += f"<td style='padding: 8px; border: 1px solid #ddd; text-align: left; font-size: 13px; max-width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;'>{cell}</td>"
|
| 1238 |
else:
|
|
|
|
| 1239 |
# 添加增长率箭头(如果有的话)
|
| 1240 |
growth = None
|
| 1241 |
+
category = row[0]
|
| 1242 |
+
if i > 0 and category in yoy_map and j > 0 and j < len(header_row):
|
| 1243 |
+
year_header = header_row[j]
|
| 1244 |
+
if year_header in yoy_map[category]:
|
| 1245 |
+
growth = yoy_map[category][year_header]
|
| 1246 |
|
| 1247 |
if growth and growth != "N/A":
|
| 1248 |
arrow = "▲" if growth.startswith("+") else "▼"
|
|
|
|
| 1256 |
table_rows += f"<tr style='{row_style}'>{cells}</tr>"
|
| 1257 |
|
| 1258 |
html = f"""
|
| 1259 |
+
<div style="min-width: 400px;max-width: 600px;height: 300px !important;border: 1px solid #e5e7eb; border-radius: 12px; padding: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.08); font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%);">
|
| 1260 |
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 18px; padding-bottom: 12px; border-bottom: 2px solid #e5e7eb;">
|
| 1261 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 1262 |
<path d="M12 2L15.09 8.26L19 9.07L16 14L16 19L12 19L8 14L8 9.07L4.91 8.26L8 2L12 2Z" fill="#3b82f6"/>
|
|
|
|
| 1327 |
prev_close_val = company_info.get("previous_close", "N/A")
|
| 1328 |
|
| 1329 |
html = f"""
|
| 1330 |
+
<div style="width: 250px; height: 300px !important; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; box-shadow: 0 4px 6px rgba(0,0,0,0.08); font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%); display: flex; flex-direction: column;">
|
| 1331 |
+
<div style="font-size: 15px; color: #374151; font-weight: 600; margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{company_name}</div>
|
| 1332 |
+
<div style="font-size: 12px; color: #6b7280; margin-bottom: 10px;">NYSE:{symbol}</div>
|
| 1333 |
+
<div style="display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; padding: 10px; background: #f9fafb; border-radius: 8px; flex-wrap: wrap;">
|
| 1334 |
+
<div style="font-size: 28px; font-weight: bold; color: #111827;">{price}</div>
|
| 1335 |
+
<div style="font-size: 13px;">{change_html}</div>
|
| 1336 |
</div>
|
| 1337 |
+
<div style="flex: 1; display: grid; grid-template-columns: auto 1fr; gap: 6px 8px; padding: 10px; background: white; border-radius: 8px; border: 1px solid #e5e7eb; font-size: 12px; align-content: start;">
|
| 1338 |
+
<div style="color: #6b7280;">Open</div><div style="font-weight: 600; text-align: right; color: #111827; white-space: nowrap;">{open_val}</div>
|
| 1339 |
+
<div style="color: #6b7280;">High</div><div style="font-weight: 600; text-align: right; color: #111827; white-space: nowrap;">{high_val}</div>
|
| 1340 |
+
<div style="color: #6b7280;">Low</div><div style="font-weight: 600; text-align: right; color: #111827; white-space: nowrap;">{low_val}</div>
|
| 1341 |
+
<div style="color: #6b7280;">Prev Close</div><div style="font-weight: 600; text-align: right; color: #111827; white-space: nowrap;">{prev_close_val}</div>
|
| 1342 |
</div>
|
| 1343 |
</div>
|
| 1344 |
"""
|
|
|
|
| 1546 |
</div>
|
| 1547 |
"""
|
| 1548 |
# <div style="font-size: 14px; color: #555;">Vol</div><div style="font-size: 14px; font-weight: 500; text-align: center;">{volume_display}</div>
|
| 1549 |
+
|
| 1550 |
return html
|
| 1551 |
+
|
| 1552 |
except Exception as e:
|
| 1553 |
print(f"Error building stock card: {e}")
|
| 1554 |
return '<div style="width:250px; padding:16px; color:red;">Error loading stock data</div>'
|