Data_Storage / app.py
linxinhua's picture
Update app.py
ca2d3ff verified
Raw
History Blame Contribute Delete
14 kB
import gradio as gr
import pandas as pd
from datetime import datetime, timedelta
import io
import os
from huggingface_hub import HfApi, hf_hub_download, login
from typing import List, Tuple, Optional
import tempfile
import zipfile
class DataManager:
def __init__(self, token=None):
self.repo_id = "CIV3283/Data_Storage"
self.branch = "data_branch"
# 优先使用传入的token,否则从环境变量获取
self.token = token or os.getenv('CIV3283_access')
# 如果有token,进行认证
if self.token:
try:
login(token=self.token)
print("✅ 认证成功")
except Exception as e:
print(f"❌ 认证失败: {e}")
self.api = HfApi(token=self.token)
def get_available_files(self) -> Tuple[List[str], List[str]]:
"""获取所有可用的feedback和query文件"""
try:
# 尝试获取仓库中的所有文件
repo_files = self.api.list_repo_files(
repo_id=self.repo_id,
revision=self.branch,
token=self.token,
repo_type="space" # 明确指定这是一个space
)
feedback_files = []
query_files = []
for file in repo_files:
if file.endswith('_feedback_log.csv') and 'CIV3283_Student_' in file:
feedback_files.append(file)
elif file.endswith('_query_log.csv') and 'CIV3283_Student_' in file:
query_files.append(file)
return sorted(feedback_files), sorted(query_files)
except Exception as e:
print(f"❌ 获取文件列表错误: {e}")
# 如果API调用失败,尝试手动生成可能的文件名列表
return self._generate_possible_filenames()
def _generate_possible_filenames(self) -> Tuple[List[str], List[str]]:
"""生成可能的文件名列表作为后备方案"""
feedback_files = []
query_files = []
# 生成01到95的所有可能文件名
for i in range(1, 96):
student_id = f"{i:02d}" # 格式化为两位数
feedback_file = f"CIV3283_Student_{student_id}_feedback_log.csv"
query_file = f"CIV3283_Student_{student_id}_query_log.csv"
feedback_files.append(feedback_file)
query_files.append(query_file)
return feedback_files, query_files
def download_and_read_csv(self, filename: str) -> pd.DataFrame:
"""下载并读取CSV文件"""
try:
# 下载文件到临时位置
local_file = hf_hub_download(
repo_id=self.repo_id,
filename=filename,
revision=self.branch,
repo_type="space",
token=self.token,
cache_dir=tempfile.gettempdir()
)
# 读取CSV
df = pd.read_csv(local_file)
# 确保timestamp列存在并转换为datetime
if 'timestamp' in df.columns:
df['timestamp'] = pd.to_datetime(df['timestamp'])
return df
except Exception as e:
print(f"❌ 读取文件 {filename} 失败: {e}")
# 返回空DataFrame而不是完全失败
return pd.DataFrame()
def filter_data_by_date(self, df: pd.DataFrame, start_date: str, end_date: str) -> pd.DataFrame:
"""根据日期范围筛选数据"""
if df.empty or 'timestamp' not in df.columns:
return df
try:
# 转换输入的日期字符串
start_dt = pd.to_datetime(start_date)
if end_date == "现在":
end_dt = pd.Timestamp.now()
else:
end_dt = pd.to_datetime(end_date) + timedelta(days=1) - timedelta(seconds=1) # 包含整天
# 筛选数据
mask = (df['timestamp'] >= start_dt) & (df['timestamp'] <= end_dt)
return df[mask]
except Exception as e:
print(f"Error filtering data: {e}")
return df
def process_files(self, file_type: str, start_date: str, end_date: str) -> Tuple[str, str, str]:
"""处理文件并生成下载内容"""
feedback_files, query_files = self.get_available_files()
result_data = []
file_info = []
total_records = 0
# 根据选择的文件类型确定要处理的文件
files_to_process = []
if file_type in ["反馈文件", "两者都要"]:
files_to_process.extend([(f, "feedback") for f in feedback_files])
if file_type in ["查询文件", "两者都要"]:
files_to_process.extend([(f, "query") for f in query_files])
# 处理每个文件
for filename, ftype in files_to_process:
df = self.download_and_read_csv(filename)
if not df.empty:
original_count = len(df)
filtered_df = self.filter_data_by_date(df, start_date, end_date)
filtered_count = len(filtered_df)
if filtered_count > 0:
result_data.append(filtered_df)
total_records += filtered_count
file_info.append(f"{filename}: {original_count}条记录 → {filtered_count}条符合条件")
# 合并所有数据
if result_data:
combined_df = pd.concat(result_data, ignore_index=True)
# 生成CSV内容
csv_buffer = io.StringIO()
combined_df.to_csv(csv_buffer, index=False, encoding='utf-8')
csv_content = csv_buffer.getvalue()
# 生成文件名
end_date_str = "现在" if end_date == "现在" else end_date
filename = f"CIV3283_数据导出_{file_type}_{start_date}_to_{end_date_str}.csv"
# 生成状态信息
status = f"✅ 成功处理 {len(files_to_process)} 个文件\n"
status += f"📊 总共找到 {total_records} 条符合条件的记录\n"
status += f"📅 时间范围: {start_date}{end_date_str}\n\n"
status += "文件详情:\n" + "\n".join(file_info)
return csv_content, filename, status
else:
return "", "", "❌ 没有找到符合条件的数据"
# 全局变量存储数据管理器
data_manager = None
def initialize_data_manager(hf_token=None):
"""初始化数据管理器"""
global data_manager
data_manager = DataManager(token=hf_token)
return data_manager
def process_and_download(file_type, start_date, end_date, hf_token):
"""处理请求并返回下载文件"""
try:
# 确保数据管理器已初始化
global data_manager
env_token = os.getenv('CIV3283_access')
current_token = hf_token or env_token
if data_manager is None or data_manager.token != current_token:
data_manager = initialize_data_manager(current_token)
csv_content, filename, status = data_manager.process_files(file_type, start_date, end_date)
if csv_content:
# 创建临时文件
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False, encoding='utf-8')
temp_file.write(csv_content)
temp_file.close()
return status, temp_file.name, filename
else:
return status, None, None
except Exception as e:
return f"❌ 处理过程中出现错误: {str(e)}", None, None
def get_file_summary(hf_token=None):
"""获取文件概览"""
try:
# 如果还没有初始化或token发生变化,重新初始化
global data_manager
env_token = os.getenv('CIV3283_access')
current_token = hf_token or env_token
if data_manager is None or data_manager.token != current_token:
data_manager = initialize_data_manager(current_token)
feedback_files, query_files = data_manager.get_available_files()
summary = f"📁 当前可用文件:\n"
summary += f" 反馈文件: {len(feedback_files)} 个\n"
summary += f" 查询文件: {len(query_files)} 个\n"
summary += f" 总计: {len(feedback_files) + len(query_files)} 个文件"
if env_token:
summary += f"\n✅ 使用环境变量 CIV3283_access 认证"
elif hf_token:
summary += f"\n✅ 使用手动输入的token认证"
else:
summary += f"\n⚠️ 未找到认证token(可能无法访问私有仓库)"
return summary
except Exception as e:
return f"❌ 获取文件列表失败: {str(e)}"
# 创建Gradio界面
def create_interface():
with gr.Blocks(title="CIV3283 数据管理器", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🗃️ CIV3283 数据存储管理器")
gr.Markdown("从 Hugging Face Space 的 data_branch 分支整理和下载学生数据")
# 认证部分
with gr.Row():
with gr.Column(scale=3):
hf_token = gr.Textbox(
label="🔐 Hugging Face Token (可选)",
placeholder="hf_xxxxxxxxxxxx",
type="password",
info="可选:手动输入token,留空将使用环境变量 CIV3283_access"
)
with gr.Column(scale=1):
gr.Markdown(f"""
**环境变量状态:**
{'✅ CIV3283_access 已设置' if os.getenv('CIV3283_access') else '❌ CIV3283_access 未找到'}
""")
# 文件概览
with gr.Row():
file_summary = gr.Textbox(
label="📊 文件概览",
value="点击刷新按钮获取文件列表",
interactive=False,
max_lines=6
)
refresh_btn = gr.Button("🔄 刷新文件列表", variant="secondary")
# 自动刷新一次(使用环境变量)
demo.load(
fn=lambda: get_file_summary(None),
outputs=file_summary
)
refresh_btn.click(
fn=get_file_summary,
inputs=[hf_token],
outputs=file_summary
)
# 主要控制面板
with gr.Row():
with gr.Column(scale=1):
file_type = gr.Radio(
choices=["查询文件", "反馈文件", "两者都要"],
value="查询文件",
label="📂 选择文件类型"
)
start_date = gr.Textbox(
label="📅 开始日期",
placeholder="2025-06-16",
value="2025-06-16",
info="格式: YYYY-MM-DD"
)
end_date = gr.Textbox(
label="📅 结束日期",
placeholder="2025-06-20 或 现在",
value="现在",
info="格式: YYYY-MM-DD 或输入'现在'"
)
process_btn = gr.Button("🚀 处理并生成下载文件", variant="primary", size="lg")
with gr.Column(scale=2):
status_box = gr.Textbox(
label="📋 处理状态",
max_lines=15,
interactive=False
)
download_file = gr.File(
label="📥 下载文件",
interactive=False
)
# 使用说明
with gr.Accordion("📖 使用说明", open=False):
gr.Markdown("""
### 认证说明
- **环境变量优先**: 系统会自动使用环境变量 `CIV3283_access` 中的token
- **手动覆盖**: 如果需要使用不同的token,可以在上方手动输入
- **获取token**: 如果需要新token,访问 https://huggingface.co/settings/tokens
### 功能说明
1. **文件类型选择**: 选择要导出的数据类型
- 查询文件: 包含搜索信息和查询响应
- 反馈文件: 包含学生反馈评论
- 两者都要: 合并所有数据
2. **日期范围**:
- 开始日期: 必须填写,格式 YYYY-MM-DD
- 结束日期: 可以是具体日期或"现在"
3. **数据格式**:
- Query文件列: student_space, student_id, timestamp, search_info, query_and_response, thumb_feedback
- Feedback文件列: student_space, student_id, timestamp, comment
### 注意事项
- 系统会自动发现所有可用的学生文件(01-95,跳过缺失的编号)
- 时间戳格式: 2025-06-16 08:55:33
- 导出的数据保持原始顺序,不进行时间排序
- 每次处理会显示详细的文件统计信息
- 如果无法访问仓库API,系统会尝试预设的文件名列表
""")
# 绑定处理函数
process_btn.click(
fn=process_and_download,
inputs=[file_type, start_date, end_date, hf_token],
outputs=[status_box, download_file, gr.State()]
)
return demo
# 启动应用
if __name__ == "__main__":
demo = create_interface()
demo.launch(
)