Spaces:
Runtime error
Runtime error
| 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: | |
| # 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("数据读取失败:无法在《积分赋分参考》中读取到有效的数字,请检查文件是否传错框位。") | |
| 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}") |