""" Reports Blueprint Provides report listing and management endpoints. """ import time from pathlib import Path from flask import Blueprint, request, current_app from ..shared import _get_file_type, _format_response, log_performance, logger, task_status from ..app import Config # Create blueprint reports_bp = Blueprint('reports', __name__, url_prefix='/reports') @reports_bp.route('', methods=['GET']) @log_performance def list_reports(): """List all generated reports with pagination and filtering.""" logger.debug("Reports list requested") try: # Parse query parameters try: page = int(request.args.get('page', 1)) if page < 1: return _format_response(400, "Invalid parameter: page must be >= 1") except (ValueError, TypeError): return _format_response(400, "Invalid parameter: page must be a valid integer") try: per_page = int(request.args.get('per_page', 20)) if per_page < 1 or per_page > 100: return _format_response(400, "Invalid parameter: per_page must be between 1 and 100") except (ValueError, TypeError): return _format_response(400, "Invalid parameter: per_page must be a valid integer") sort_by = request.args.get('sort_by', 'created_at') sort_order = request.args.get('sort_order', 'desc') status_filter = request.args.get('status', None) # 'completed', 'failed', or None for all # Validate sort parameters valid_sort_fields = ['created_at', 'task_id', 'file_size', 'processing_time'] if sort_by not in valid_sort_fields: return _format_response(400, f"Invalid parameter: sort_by must be one of {valid_sort_fields}") if sort_order not in ['asc', 'desc']: return _format_response(400, "Invalid parameter: sort_order must be 'asc' or 'desc'") # Validate status filter valid_statuses = ['completed', 'failed', None] if status_filter is not None and status_filter not in ['completed', 'failed']: return _format_response(400, "Invalid parameter: status must be 'completed', 'failed', or not specified") # 兼容缺省:优先 app.config,其次 Config.OUTPUT_FOLDER output_root = current_app.config.get('OUTPUT_FOLDER') or getattr(Config, 'OUTPUT_FOLDER', None) if not output_root: return _format_response(200, "报告列表获取成功", { 'reports': [], 'pagination': {'page': page, 'per_page': per_page, 'total_reports': 0, 'total_pages': 0, 'has_next': False, 'has_prev': False}, 'filters': {'sort_by': sort_by, 'sort_order': sort_order, 'status': status_filter} }) output_folder = Path(output_root) reports = [] # Scan all task directories if output_folder.exists(): for task_dir in output_folder.iterdir(): if not task_dir.is_dir(): continue task_id = task_dir.name # Get task information from global task_status task_info = task_status.get(task_id, {}) task_status_value = task_info.get('status') # Log task status for debugging logger.debug(f"Task {task_id}: status from memory={task_status_value}, info={task_info}") # 直接扫描平铺文件 files = [p for p in task_dir.iterdir() if p.is_file()] if not files: # 按需应用状态过滤 if status_filter: continue reports.append({ 'task_id': task_id, 'report_name': "N/A", 'status': 'failed', 'created_at': task_dir.stat().st_mtime, 'file_count': 0, 'total_size': 0, 'processing_time_seconds': None, 'main_report': None, 'all_files': [], 'run_directory': f'{task_id}' }) continue # 识别主报告与统计 total_size = sum(f.stat().st_size for f in files) created_at = max(f.stat().st_mtime for f in files) if files else task_dir.stat().st_mtime def file_entry(p): return { 'name': p.name, 'size': p.stat().st_size, 'type': _get_file_type(p.name), # 使用相对路径下载,清晰且安全 'download_url': f"/download/{task_id}/{p.name}" } all_files = [file_entry(f) for f in files] # 优先 CO2_report,其次任意 *_report_*.html report_html = None for f in files: if f.name.endswith('_report_') and f.suffix == '.html': report_html = file_entry(f) break if not report_html: for f in files: if f.name.endswith('.html'): report_html = file_entry(f) break # 任务状态:若有报告或关键产物则视为 completed has_outputs = any(f.name.startswith(('config_', 'output_vars_', 'processed_data_', 'CO2_report_')) for f in files) task_status_value = 'completed' if has_outputs else 'unknown' if status_filter and task_status_value != status_filter: continue # Create report entry report_entry = { 'task_id': task_id, 'report_name': task_id, 'status': task_status_value, 'created_at': created_at, 'file_count': len(files), 'total_size': total_size, 'processing_time_seconds': None, 'main_report': report_html, 'all_files': all_files, 'run_directory': f'{task_id}' } reports.append(report_entry) # Sort reports reverse_order = sort_order == 'desc' if sort_by == 'created_at': reports.sort(key=lambda x: x['created_at'], reverse=reverse_order) elif sort_by == 'task_id': reports.sort(key=lambda x: x['task_id'], reverse=reverse_order) elif sort_by == 'file_size': reports.sort(key=lambda x: x['total_size'], reverse=reverse_order) elif sort_by == 'processing_time': reports.sort(key=lambda x: x['processing_time_seconds'] or 0, reverse=reverse_order) # Paginate results total_reports = len(reports) start_idx = (page - 1) * per_page end_idx = start_idx + per_page paginated_reports = reports[start_idx:end_idx] # Calculate pagination metadata total_pages = (total_reports + per_page - 1) // per_page response_data = { 'reports': paginated_reports, 'pagination': { 'page': page, 'per_page': per_page, 'total_reports': total_reports, 'total_pages': total_pages, 'has_next': page < total_pages, 'has_prev': page > 1 }, 'filters': { 'sort_by': sort_by, 'sort_order': sort_order, 'status': status_filter } } logger.info(f"Returning {len(paginated_reports)} reports (page {page}/{total_pages})") return _format_response(200, "报告列表获取成功", response_data) except Exception as e: logger.error(f"Error listing reports: {str(e)}", exc_info=True) return _format_response(500, "内部服务器错误")