Spaces:
Runtime error
Runtime error
| import os, tempfile, pathlib | |
| import gradio as gr | |
| import pandas as pd | |
| import zipfile, time | |
| from core_numeric import ( | |
| SUFFIX, CATEGORY_TITLES, | |
| classify_row, _is_blank, | |
| is_category_header_row, is_allowed_category_name, | |
| find_header_row_by_probe | |
| ) | |
| # ====== .xls 专用依赖(只在 .xls 分支用) ====== | |
| import xlrd # 读 .xls | |
| from xlutils.copy import copy as xls_copy # 将 xlrd.Book -> xlwt.Workbook | |
| import xlwt # 写回 .xls(BIFF8) | |
| # ====== .xlsx 读写(现代路径) ====== | |
| import openpyxl | |
| def _safe_basename(path: str) -> str: | |
| return os.path.splitext(os.path.basename(path))[0] | |
| def process_xls(in_path: str) -> str: | |
| """ | |
| 在原工作表末尾新增 4 列: | |
| IsTrueNumericMismatch / SrcNumbers / TgtNumbers / Note | |
| 并写回为 *_numeric_tagged.xls | |
| """ | |
| # 读取并保留样式信息 | |
| # 注意:formatting_info=True 仅对 .xls 生效,不能用于 .xlsx。:contentReference[oaicite:5]{index=5} | |
| book = xlrd.open_workbook(in_path, formatting_info=True) | |
| wbook = xls_copy(book) | |
| sh_in = book.sheet_by_index(0) | |
| sh_out = wbook.get_sheet(0) | |
| nrows, ncols = sh_in.nrows, sh_in.ncols | |
| # 找“Source/Target”表头行 | |
| def _cell(r, c): | |
| try: | |
| return sh_in.cell_value(r, c) | |
| except Exception: | |
| return "" | |
| hdr = find_header_row_by_probe(_cell, max_scan=min(80, nrows)) | |
| # 追加 4 列写结果 | |
| base_col = ncols | |
| headers = ["IsTrueNumericMismatch", "SrcNumbers", "TgtNumbers", "Note"] | |
| if hdr >= 0: | |
| for i, h in enumerate(headers): | |
| sh_out.write(hdr, base_col + i, h) | |
| # 遍历行并写入结果(保留原结构;不改变/删除任何行) | |
| in_allowed = False | |
| for r in range(hdr + 1 if hdr >= 0 else 0, nrows): | |
| a = _cell(r, 0) # A: Category or "FileName (SegmentID)" | |
| c = _cell(r, 2) # C: Source | |
| d = _cell(r, 3) # D: Target | |
| # 识别“类别标题行” | |
| if is_category_header_row(a, c, d): | |
| in_allowed = is_allowed_category_name(a) | |
| continue | |
| # 非允许类别(不是 Numeric mismatch)→ 不写结果 | |
| if not in_allowed: | |
| continue | |
| # 忽略纯空白行 | |
| if _is_blank(c) and _is_blank(d): | |
| continue | |
| # 计算并写入 | |
| tflag, src_s, tgt_s, note = classify_row(str(c or ""), str(d or "")) | |
| sh_out.write(r, base_col + 0, "TRUE" if tflag else "FALSE") | |
| sh_out.write(r, base_col + 1, src_s) | |
| sh_out.write(r, base_col + 2, tgt_s) | |
| sh_out.write(r, base_col + 3, note) | |
| # 保存为 *_numeric_tagged.xls | |
| out_path = os.path.join( | |
| tempfile.gettempdir(), | |
| f"{_safe_basename(in_path)}{SUFFIX}.xls" | |
| ) | |
| wbook.save(out_path) | |
| return out_path | |
| def _read_table_generic(path: str) -> pd.DataFrame: | |
| ext = pathlib.Path(path).suffix.lower() | |
| if ext == ".csv": | |
| return pd.read_csv(path, dtype=str, encoding="utf-8", keep_default_na=False) | |
| elif ext == ".xls": | |
| # pandas 读 .xls 也会用 xlrd 引擎;此路径仅用于 .xlsx 逻辑,.xls 我们已经直接“写回”。:contentReference[oaicite:6]{index=6} | |
| return pd.read_excel(path, dtype=str, engine="xlrd") | |
| else: | |
| return pd.read_excel(path, dtype=str, engine="openpyxl") | |
| def _reform_and_tag_df(df_raw: pd.DataFrame) -> pd.DataFrame: | |
| """面向 .xlsx/.csv:保留 'Numeric mismatch' 相关行,生成结构化输出表""" | |
| # 探测表头行 | |
| def _probe(r, c): | |
| try: | |
| v = df_raw.iat[r, c] | |
| return str(v) if pd.notna(v) else "" | |
| except Exception: | |
| return "" | |
| hdr = find_header_row_by_probe(_probe, max_scan=min(80, len(df_raw))) | |
| df = df_raw.iloc[hdr+1:].copy() if hdr >= 0 else df_raw.copy() | |
| while df.shape[1] < 5: | |
| df[df.shape[1]] = "" | |
| colA, colB, colC, colD, colE = df.columns[0], df.columns[1], df.columns[2], df.columns[3], df.columns[4] | |
| # 仅处理 Numeric mismatch 类别 | |
| keep, in_allowed = [], False | |
| for _, row in df.iterrows(): | |
| a, c, d = row.get(colA, ""), row.get(colC, None), row.get(colD, None) | |
| if is_category_header_row(a, c, d): | |
| in_allowed = is_allowed_category_name(a); keep.append(False) | |
| else: | |
| keep.append(in_allowed) | |
| df = df[pd.Series(keep, index=df.index)] | |
| def is_data_row(row): | |
| a_norm = str(row.get(colA, "")).strip().lower() | |
| if a_norm in CATEGORY_TITLES: return False | |
| return not (_is_blank(row.get(colC, None)) and _is_blank(row.get(colD, None))) | |
| df = df[df.apply(is_data_row, axis=1)].copy() | |
| # 计算 4 列结果 | |
| out_cols = ["IsTrueNumericMismatch", "SrcNumbers", "TgtNumbers", "Note"] | |
| df_res = pd.DataFrame(columns=out_cols, index=df.index) | |
| for i, row in df.iterrows(): | |
| tflag, src_s, tgt_s, note = classify_row(str(row.get(colC, "") or ""), str(row.get(colD, "") or "")) | |
| df_res.at[i, "IsTrueNumericMismatch"] = bool(tflag) | |
| df_res.at[i, "SrcNumbers"] = src_s | |
| df_res.at[i, "TgtNumbers"] = tgt_s | |
| df_res.at[i, "Note"] = note | |
| # 拼出导出表(保留关注列 + 4 列结论) | |
| out = pd.DataFrame({ | |
| "A": df[colA], | |
| "Percent": df[colB], | |
| "Source": df[colC], | |
| "Target": df[colD], | |
| "Comments": df[colE], | |
| }).join(df_res) | |
| return out | |
| def process_xlsx_or_csv(in_path: str) -> str: | |
| df_raw = _read_table_generic(in_path) | |
| out_df = _reform_and_tag_df(df_raw) | |
| out_path = os.path.join( | |
| tempfile.gettempdir(), | |
| f"{_safe_basename(in_path)}{SUFFIX}.xlsx" | |
| ) | |
| out_df.to_excel(out_path, index=False) | |
| # 设置筛选/冻结等属性(打开时生效) | |
| wb = openpyxl.load_workbook(out_path); ws = wb.active | |
| from openpyxl.utils import get_column_letter | |
| ws.auto_filter.ref = f"A1:{get_column_letter(ws.max_column)}{ws.max_row}" | |
| ws.freeze_panes = "A2" | |
| wb.save(out_path) | |
| return out_path | |
| def process_files(files, pack_zip): | |
| outputs = [] | |
| for f in files: | |
| path = f.name | |
| ext = pathlib.Path(path).suffix.lower() | |
| if ext == ".xls": | |
| outputs.append(process_xls(path)) | |
| elif ext in [".xlsx", ".csv"]: | |
| outputs.append(process_xlsx_or_csv(path)) | |
| else: | |
| raise gr.Error(f"不支持的文件类型:{ext}") | |
| zip_path = None | |
| if pack_zip and outputs: | |
| # 以时间戳避免同名覆盖 | |
| stamp = time.strftime("%Y%m%d-%H%M%S") | |
| zip_path = os.path.join( | |
| tempfile.gettempdir(), f"XbenchAssistant_{stamp}.zip" | |
| ) | |
| with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf: | |
| for p in outputs: | |
| zf.write(p, arcname=os.path.basename(p)) | |
| return outputs, (zip_path or "") | |
| with gr.Blocks(title="XbenchAssistant") as demo: | |
| gr.Image(value="logo.png", show_label=False, height=80) | |
| gr.Markdown("### XbenchAssistant|Xbench QA 报告 · 数字一致性去噪与打标\n" | |
| "上传 `.xls/.xlsx/.csv`;\n" | |
| "- `.xls`:在原工作表**追加 4 列**后,导出为 `*_numeric_tagged.xls`;\n" | |
| "- `.xlsx/.csv`:产出结构化 `*_numeric_tagged.xlsx`。") | |
| in_files = gr.File(label="上传 Xbench 报告(可多选)", file_count="multiple", | |
| file_types=[".xls", ".xlsx", ".csv"]) | |
| with gr.Row(): | |
| pack_zip = gr.Checkbox(value=True, label="打包为 ZIP(推荐)") | |
| btn = gr.Button("开始处理", variant="primary") | |
| out_files = gr.File(label="逐个下载(可多选点击)", interactive=False) | |
| zip_dl = gr.DownloadButton(label="⬇️ 一键下载 ZIP(处理后打包)", visible=True) | |
| # 现在返回两个输出:1) 文件列表 2) zip 路径 | |
| btn.click(process_files, inputs=[in_files, pack_zip], outputs=[out_files, zip_dl]) | |
| if __name__ == "__main__": | |
| import os | |
| # 从 HF 的 Secrets 里读取(Settings → Secrets,新建 APP_USER_1/APP_PASS_1/APP_USER_2/APP_PASS_2) | |
| USER1 = os.getenv("APP_USER_1", "") | |
| PASS1 = os.getenv("APP_PASS_1", "") | |
| USER2 = os.getenv("APP_USER_2", "") | |
| PASS2 = os.getenv("APP_PASS_2", "") | |
| AUTH_PAIRS = [] | |
| if USER1 and PASS1: | |
| AUTH_PAIRS.append((USER1, PASS1)) | |
| if USER2 and PASS2: | |
| AUTH_PAIRS.append((USER2, PASS2)) | |
| # 登录页提示可自定义 | |
| AUTH_MSG = "ECI 内部工具|请输入用户名与密码" | |
| # 多账户认证:Gradio 的 launch(auth=...) 支持元组或列表形式 | |
| demo.launch( | |
| auth=AUTH_PAIRS if AUTH_PAIRS else None, | |
| auth_message=AUTH_MSG, | |
| ssr_mode=False, | |
| show_error=True | |
| ) | |