Files
report-admin/backend/app/api/state.py
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 71db82393a Initial commit
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-13 15:38:41 +08:00

133 lines
4.6 KiB
Python

"""
应用全局状态:管理数据引擎、赋分引擎、统计引擎的单例
避免每次请求重新加载 Excel 数据
"""
import logging
import time
from datetime import datetime
from typing import Dict, List, Optional
import pandas as pd
from ..engines.data_engine import DataEngine
from ..engines.scoring_engine import ScoringEngine
from ..engines.stats_engine import StatsEngine
from ..config import SCHOOL_TYPE_MAP, DIMENSION_FRAMEWORK
logger = logging.getLogger(__name__)
class AppState:
"""应用全局状态"""
def __init__(self):
self.data_engine: Optional[DataEngine] = None
self.scoring_engine: Optional[ScoringEngine] = None
self.stats_engine: Optional[StatsEngine] = None
self.raw_scores: Optional[Dict] = None
self.sub_scores: Optional[pd.DataFrame] = None
self.dim_scores: Optional[pd.DataFrame] = None
self.schools: List[str] = []
self._initialized = False
def initialize(self):
"""初始化所有引擎(只在应用启动时调用一次)"""
if self._initialized:
return
start = time.time()
# 1. 加载数据
self.data_engine = DataEngine()
self.data_engine.load_all()
self.schools = self.data_engine.schools
# 2. 赋分
self.scoring_engine = ScoringEngine(self.data_engine)
self.raw_scores = self.scoring_engine.score_all_schools()
# 3. 统计
self.stats_engine = StatsEngine()
self.sub_scores = self.stats_engine.compute_dimension_scores(self.raw_scores)
self.dim_scores = self.stats_engine.compute_dimension_aggregates(self.sub_scores)
self._initialized = True
elapsed = time.time() - start
logger.info(f"全局状态初始化完成,耗时 {elapsed:.1f}s")
def get_report_data(self, school: str) -> Dict:
"""获取某学校的报告数据包"""
if not self._initialized:
self.initialize()
return self.stats_engine.compute_school_report_data(
school, self.sub_scores, self.dim_scores
)
def get_school_info(self, school: str) -> Dict:
"""获取学校基本信息"""
info = SCHOOL_TYPE_MAP.get(school, {})
if not self.dim_scores is None and school in self.dim_scores.index:
score = round(float(self.dim_scores.loc[school, "总体得分"]), 2)
rank = int((self.dim_scores["总体得分"] >= self.dim_scores.loc[school, "总体得分"]).sum())
else:
score = 0
rank = 0
return {
"name": school,
"type": info.get("type", ""),
"code": info.get("code", ""),
"nature": info.get("nature", ""),
"feature": info.get("feature", ""),
"score": score,
"rank": rank,
"total_schools": len(self.schools),
}
def get_all_schools_summary(self) -> List[Dict]:
"""获取所有学校的摘要信息"""
if not self._initialized:
self.initialize()
summaries = []
# 排名
sorted_schools = self.dim_scores["总体得分"].sort_values(ascending=False)
for rank, (school, score) in enumerate(sorted_schools.items(), 1):
if school == "总体得分":
continue
info = SCHOOL_TYPE_MAP.get(school, {})
# 聚类
dim_cols = [c for c in self.dim_scores.columns if c in DIMENSION_FRAMEWORK]
cluster_result = self.stats_engine.cluster_analysis(self.dim_scores[dim_cols])
cluster = cluster_result["school_clusters"].get(school, "")
# 检查已生成的报告
from ..config import OUTPUT_DIR
report_path = OUTPUT_DIR / f"{school}_报告.html"
has_report = report_path.exists()
report_size = report_path.stat().st_size if has_report else 0
report_generated_at = ""
if has_report:
mtime = report_path.stat().st_mtime
report_generated_at = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
summaries.append({
"name": school,
"type": info.get("type", ""),
"code": info.get("code", ""),
"nature": info.get("nature", ""),
"score": round(float(score), 2),
"rank": rank,
"cluster": cluster,
"has_report": has_report,
"report_size": report_size,
"report_generated_at": report_generated_at,
})
return summaries
# 全局单例
app_state = AppState()