#!/usr/bin/env python3 """ 完整报告生成脚本:Excel → 赋分 → 统计 → LLM并行生成 → HTML报告 """ import sys import time import argparse import logging from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "backend")) from app.engines.data_engine import DataEngine from app.engines.scoring_engine import ScoringEngine from app.engines.stats_engine import StatsEngine from app.engines.llm_engine import LLMEngine from app.engines.report_renderer import ReportRenderer logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S", ) logger = logging.getLogger(__name__) def progress_callback(completed, total, segment_id): """进度回调""" pct = completed / total * 100 bar = "█" * int(pct / 5) + "░" * (20 - int(pct / 5)) print(f"\r [{bar}] {pct:.0f}% ({completed}/{total}) {segment_id:<40}", end="", flush=True) def main(): parser = argparse.ArgumentParser(description="生成学校课程实施监测报告") parser.add_argument("--school", type=str, default="延安中学", help="学校名称") parser.add_argument("--no-cache", action="store_true", help="不使用LLM缓存") parser.add_argument("--no-llm", action="store_true", help="跳过LLM生成(仅图表+数据)") parser.add_argument("--list-schools", action="store_true", help="列出所有可用学校") args = parser.parse_args() start_time = time.time() # ===== Step 1: 加载数据 ===== print("=" * 70) print("📊 课程实施监测报告生成系统") print("=" * 70) logger.info("[1/5] 加载Excel数据...") data_engine = DataEngine() data_engine.load_all() if args.list_schools: print("\n可用学校:") for s in data_engine.schools: from app.config import SCHOOL_TYPE_MAP info = SCHOOL_TYPE_MAP.get(s, {}) print(f" {s} ({info.get('type', '')})") return school = args.school if school not in data_engine.schools: print(f"\n❌ 学校 '{school}' 不在数据中。可用学校:") for s in data_engine.schools: print(f" - {s}") return print(f"\n🏫 目标学校: {school}") # ===== Step 2: 赋分 ===== logger.info("[2/5] 全区赋分计算...") scoring_engine = ScoringEngine(data_engine) raw_scores = scoring_engine.score_all_schools() # ===== Step 3: 统计分析 ===== logger.info("[3/5] PCA合成 + 标准化 + 统计分析...") stats_engine = StatsEngine() sub_scores = stats_engine.compute_dimension_scores(raw_scores) dim_scores = stats_engine.compute_dimension_aggregates(sub_scores) report_data = stats_engine.compute_school_report_data(school, sub_scores, dim_scores) # ===== Step 4: LLM生成 ===== if args.no_llm: logger.info("[4/5] 跳过LLM生成(--no-llm模式)") llm_sections = {} else: logger.info("[4/5] LLM并行生成报告文字...") llm_engine = LLMEngine() llm_engine.set_progress_callback(progress_callback) llm_sections = llm_engine.generate_report_segments( report_data, use_cache=not args.no_cache, ) print() # 换行 # ===== Step 5: 渲染HTML ===== logger.info("[5/5] 渲染HTML报告...") renderer = ReportRenderer() output_dir = Path(__file__).parent.parent / "output" output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / f"{school}_报告.html" renderer.render_to_file(report_data, llm_sections, output_path) elapsed = time.time() - start_time print(f"\n{'=' * 70}") print(f"✅ 报告生成完成!") print(f" 学校: {school}") print(f" 总体得分: {report_data['overall']['score']}分 (区内第{report_data['overall']['rank_in_district']}名)") print(f" LLM段落: {len(llm_sections)}个") print(f" 耗时: {elapsed:.1f}秒") print(f" 输出: {output_path}") print(f"{'=' * 70}") if __name__ == "__main__": main()