File size: 11,310 Bytes
88af790 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | import streamlit as st
import pandas as pd
import io
import re
import difflib
# ==========================================
# 页面基础设置
# ==========================================
# 使用 centered 布局可以让页面更集中,两列宽度的拖拽框面积更大、更美观
st.set_page_config(page_title="竞赛积分全自动赋分系统", page_icon="🏆", layout="centered")
st.title("🏆 竞赛积分全自动赋分系统")
st.markdown("请在下方依次上传对应的四个数据文件(**支持点击上传,或直接将文件拖拽至虚线框内**)。")
st.divider() # 添加一条分割线,提升视觉层次
# ==========================================
# 文件上传区 (2x2 网格布局,增大拖拽面积)
# ==========================================
col1, col2 = st.columns(2)
with col1:
f_comp = st.file_uploader("请上传《需要录入的竞赛名录.xlsx》", type=["xlsx", "xls", "csv"])
with col2:
f_score = st.file_uploader("请上传《积分赋分参考.xlsx》", type=["xlsx", "xls", "csv"])
col3, col4 = st.columns(2)
with col3:
f_student = st.file_uploader("请上传《学生获奖统计.xlsx》", type=["xlsx", "xls", "csv"])
with col4:
f_template = st.file_uploader("请上传《系统批量导入模板.xlsx》", type=["xlsx", "xls", "csv"])
# 辅助函数:文件读取
def load_data(file, as_raw=False):
file.seek(0)
if file.name.endswith('.csv'):
return pd.read_csv(file, header=None if as_raw else 'infer')
else:
return pd.read_excel(file, header=None if as_raw else 0)
# ==========================================
# 数据处理区
# ==========================================
if f_comp and f_score and f_student and f_template:
st.success("✅ 文件已全部就绪,请点击下方按钮开始处理数据。")
if st.button("🚀 一键处理并生成导入文件", type="primary", use_container_width=True):
with st.spinner("系统正在处理数据,请稍候..."):
try:
# 1. 加载数据
df_comp = load_data(f_comp, as_raw=False)
df_score = load_data(f_score, as_raw=True)
df_student = load_data(f_student, as_raw=False)
df_template = load_data(f_template, as_raw=True)
valid_comps = df_comp['竞赛名称'].dropna().astype(str).unique().tolist()
# 名称清洗与匹配逻辑
def clean_name(name):
name = re.sub(r'(20\d{2}|第[一二三四五六七八九十百]+届|年度)', '', name)
name = re.sub(r'(总决赛|决赛|校内选拔赛|校内赛|选拔赛|系列赛)', '', name)
return re.sub(r'[^\w\u4e00-\u9fa5]', '', name).lower()
comp_clean_map = {c: clean_name(c) for c in valid_comps if len(clean_name(c)) >= 2}
student_comps_unique = df_student['竞赛项目名称'].dropna().astype(str).unique()
mapping_dict = {}
for s_name in student_comps_unique:
s_clean = clean_name(s_name)
best_match, best_match_score = None, 0
for c_orig, c_clean in comp_clean_map.items():
if c_clean in s_clean or s_clean in c_clean:
if len(c_clean) > best_match_score:
best_match_score = len(c_clean)
best_match = c_orig
if not best_match:
best_ratio = 0
for c_orig, c_clean in comp_clean_map.items():
ratio = difflib.SequenceMatcher(None, s_clean, c_clean).ratio()
if ratio > best_ratio:
best_ratio = ratio
best_match = c_orig
if best_ratio > 0.55: mapping_dict[s_name] = best_match
else:
mapping_dict[s_name] = best_match
df_student['最终匹配名录名称'] = df_student['竞赛项目名称'].map(mapping_dict)
df_matched_students = df_student.dropna(subset=['最终匹配名录名称']).copy()
# 2. 赋分规则解析
score_dict = {}
for index, row in df_score.iterrows():
row_str = "".join([str(v) for v in row.values])
level = None
if '院级' in row_str: level = '院级'
elif '校级' in row_str: level = '校级'
elif '省部级' in row_str or '市级' in row_str: level = '省部级(北京市级)'
elif '国家' in row_str: level = '国家级及以上'
if level:
nums = []
for v in row.values:
val_str = str(v).strip()
if re.match(r'^\d+(\.\d+)?$', val_str):
nums.append(float(val_str))
if len(nums) >= 10:
nums = nums[-10:]
score_dict[level] = {
'队长': {
'一等奖及以上': nums[0], '二等奖': nums[1],
'三等奖': nums[2], '优秀奖': nums[3], '参与但未获奖': nums[4]
},
'队员': {
'一等奖及以上': nums[5], '二等奖': nums[6],
'三等奖': nums[7], '优秀奖': nums[8], '参与但未获奖': nums[9]
}
}
if not score_dict:
st.error("数据读取失败:无法在《积分赋分参考.xlsx》中读取到有效的数字分数,请检查文件是否上传正确。")
st.stop()
# 3. 计算得分
def calculate_score(row):
try:
sort_val = float(row.get('获奖者排序', 0))
except:
sort_val = 0
role = '队长' if sort_val == 1.0 else '队员'
raw_level = str(row.get('获奖级别', '')).strip()
level = ''
if '国家' in raw_level: level = '国家级及以上'
elif '省' in raw_level or '市' in raw_level: level = '省部级(北京市级)'
elif '校' in raw_level: level = '校级'
elif '院' in raw_level: level = '院级'
raw_award = str(row.get('获奖等级', '')).strip()
award = '参与但未获奖'
if '特等' in raw_award or '一等' in raw_award: award = '一等奖及以上'
elif '二等' in raw_award: award = '二等奖'
elif '三等' in raw_award: award = '三等奖'
elif '优秀' in raw_award: award = '优秀奖'
if level in score_dict and role in score_dict[level] and award in score_dict[level][role]:
return score_dict[level][role][award]
return 0
df_matched_students['发放学分值'] = df_matched_students.apply(calculate_score, axis=1)
def format_award_for_export(award_str):
award_str = str(award_str).strip()
if '优秀' in award_str:
return '三等奖'
return award_str
df_matched_students['导出用奖项名称'] = df_matched_students['获奖等级'].apply(format_award_for_export)
# 4. 生成最终系统模板
head_rows = min(3, len(df_template))
template_head = df_template.iloc[0:head_rows].copy()
result_cols = None
for i in range(len(df_template)):
row_vals = [str(v) for v in df_template.iloc[i].values]
if '学号' in row_vals and '姓名' in row_vals:
result_cols = df_template.iloc[i].tolist()
break
if result_cols is None:
result_cols = ['学号', '姓名', '开始时间', '结束时间', '内容', '活动一级分类', '活动二级分类', '活动等级', '奖项内容', '学分类型', '发放学分值']
df_result = pd.DataFrame(columns=result_cols)
df_result['学号'] = df_matched_students['获奖者学号']
df_result['姓名'] = df_matched_students['获奖者姓名']
df_result['开始时间'] = df_matched_students['获奖时间']
df_result['内容'] = df_matched_students['最终匹配名录名称']
df_result['活动一级分类'] = '学科竞赛'
df_result['活动二级分类'] = '学科竞赛'
df_result['活动等级'] = df_matched_students['获奖级别']
df_result['奖项内容'] = df_matched_students['导出用奖项名称']
df_result['学分类型'] = '竞赛加分'
df_result['发放学分值'] = df_matched_students['发放学分值']
df_result.columns = template_head.columns
final_df = pd.concat([template_head, df_result], ignore_index=True)
output = io.BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
final_df.to_excel(writer, index=False, header=False)
processed_data = output.getvalue()
# ==========================================
# 处理完成及下载区
# ==========================================
st.divider()
st.success(f"数据处理完毕!共生成 {len(df_result)} 条积分记录。")
st.download_button(
label="📥 点击下载最终导入文件 (Excel格式)",
data=processed_data,
file_name="最终系统批量导入文件.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
use_container_width=True
)
st.markdown("### 数据预览 (节选前10条)")
df_result.columns = result_cols
st.dataframe(df_result.head(10), use_container_width=True)
except Exception as e:
st.error(f"处理数据时出现异常,请检查文件格式。错误详情:{e}") |