| import streamlit as st
|
| import pandas as pd
|
| import io
|
| import re
|
| import difflib
|
|
|
|
|
|
|
|
|
|
|
| st.set_page_config(page_title="竞赛积分全自动赋分系统", page_icon="🏆", layout="centered")
|
|
|
| st.title("🏆 竞赛积分全自动赋分系统")
|
| st.markdown("请在下方依次上传对应的四个数据文件(**支持点击上传,或直接将文件拖拽至虚线框内**)。")
|
| st.divider()
|
|
|
|
|
|
|
|
|
| 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:
|
|
|
| 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()
|
|
|
|
|
| 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()
|
|
|
|
|
| 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)
|
|
|
|
|
| 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}") |